gateway: converge, don't start/stop/reload
The gateway is a deployment (castle-gateway) — its lifecycle is the same convergence as everything else, so the bespoke start/stop/reload verbs (which still routed through the retired enable/disable path) are gone. - CLI: `castle gateway` is now inspection only — bare or `status` shows the route table. Start/stop/reload it via `castle apply` (render routes + reload) or `castle restart castle-gateway`. Deletes the last users of `_service_enable`/`_service_disable`, so those are removed too. - API: retire `POST /gateway/reload` (making routes live is `POST /apply`); `PUT /gateway/config` message + docstring now say apply, not deploy. - UI: GatewayPanel's "Reload" button → "Apply" (useApply); drop the useGatewayReload hook; GatewaySettings points at `castle apply`. - docs: `castle gateway reload|status` → `castle gateway` (status lens). core 129 / cli 31 / api 59 pass; dashboard builds clean; live `castle gateway` shows routes with no start/stop/reload; /gateway/reload removed from the API.
This commit is contained in:
@@ -81,7 +81,7 @@ castle list [--kind ...] [--stack ...] [--json] # all deployments
|
|||||||
castle status # unified health/status
|
castle status # unified health/status
|
||||||
castle doctor # diagnose setup + runtime, with fix hints
|
castle doctor # diagnose setup + runtime, with fix hints
|
||||||
castle restart [name] # imperative bounce (one or all)
|
castle restart [name] # imperative bounce (one or all)
|
||||||
castle gateway reload|status # the Caddy gateway
|
castle gateway # gateway status + route table (inspection)
|
||||||
```
|
```
|
||||||
|
|
||||||
`castle service`/`job`/`tool` are **views** over the one deployment set, filtered
|
`castle service`/`job`/`tool` are **views** over the one deployment set, filtered
|
||||||
@@ -184,8 +184,8 @@ public: true # ALSO expose to the internet via Cloudflare tunnel (requires pro
|
|||||||
- There are **no path-prefix routes** — a whole subdomain maps to the backend
|
- There are **no path-prefix routes** — a whole subdomain maps to the backend
|
||||||
root, so root-relative assets and `window.location` WebSocket URLs just work.
|
root, so root-relative assets and `window.location` WebSocket URLs just work.
|
||||||
|
|
||||||
Inspect routes: `castle gateway status` (or `GET /gateway`). Regenerate + reload:
|
Inspect routes: `castle gateway` / `GET /gateway`. Regenerate routes + reload the
|
||||||
`castle gateway reload`. → Field-level detail: **`docs/registry.md`**.
|
gateway: `castle apply` (converge). → Field-level detail: **`docs/registry.md`**.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ castle list [--kind K] [--stack S] [--json] # catalog + every deployment view
|
|||||||
castle status # unified runtime status
|
castle status # unified runtime status
|
||||||
castle doctor # diagnose setup + health, with fix hints
|
castle doctor # diagnose setup + health, with fix hints
|
||||||
castle restart [name] # imperative bounce (one or all)
|
castle restart [name] # imperative bounce (one or all)
|
||||||
castle gateway reload|status
|
castle gateway # status + route table (inspection)
|
||||||
```
|
```
|
||||||
|
|
||||||
To turn a deployment off, set `enabled: false` in its config and `castle apply` —
|
To turn a deployment off, set `enabled: false` in its config and `castle apply` —
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useMemo, useState } from "react"
|
|||||||
import { Link } from "react-router-dom"
|
import { Link } from "react-router-dom"
|
||||||
import { Globe, RefreshCw, FileText, ExternalLink, Cable } from "lucide-react"
|
import { Globe, RefreshCw, FileText, ExternalLink, Cable } from "lucide-react"
|
||||||
import type { GatewayInfo, HealthStatus } from "@/types"
|
import type { GatewayInfo, HealthStatus } from "@/types"
|
||||||
import { useGatewayReload, useCaddyfile } from "@/services/api/hooks"
|
import { useApply, useCaddyfile } from "@/services/api/hooks"
|
||||||
import { subdomainUrl } from "@/lib/labels"
|
import { subdomainUrl } from "@/lib/labels"
|
||||||
import { HealthBadge } from "./HealthBadge"
|
import { HealthBadge } from "./HealthBadge"
|
||||||
import { GatewaySettings } from "./GatewaySettings"
|
import { GatewaySettings } from "./GatewaySettings"
|
||||||
@@ -14,7 +14,7 @@ interface GatewayPanelProps {
|
|||||||
|
|
||||||
export function GatewayPanel({ gateway, statuses }: GatewayPanelProps) {
|
export function GatewayPanel({ gateway, statuses }: GatewayPanelProps) {
|
||||||
const statusMap = new Map(statuses.map((s) => [s.id, s]))
|
const statusMap = new Map(statuses.map((s) => [s.id, s]))
|
||||||
const { mutate: reload, isPending: reloading } = useGatewayReload()
|
const { mutate: apply, isPending: applying } = useApply()
|
||||||
const [showCaddyfile, setShowCaddyfile] = useState(false)
|
const [showCaddyfile, setShowCaddyfile] = useState(false)
|
||||||
const { data: caddyfileData } = useCaddyfile(showCaddyfile)
|
const { data: caddyfileData } = useCaddyfile(showCaddyfile)
|
||||||
|
|
||||||
@@ -36,13 +36,13 @@ export function GatewayPanel({ gateway, statuses }: GatewayPanelProps) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<button
|
<button
|
||||||
onClick={() => reload()}
|
onClick={() => apply({})}
|
||||||
disabled={reloading}
|
disabled={applying}
|
||||||
className="flex items-center gap-1 text-xs px-2.5 py-1 rounded bg-[var(--border)] hover:bg-[var(--border)]/80 text-[var(--muted)] hover:text-[var(--foreground)] transition-colors disabled:opacity-40"
|
className="flex items-center gap-1 text-xs px-2.5 py-1 rounded bg-[var(--border)] hover:bg-[var(--border)]/80 text-[var(--muted)] hover:text-[var(--foreground)] transition-colors disabled:opacity-40"
|
||||||
title="Regenerate Caddyfile and reload Caddy"
|
title="Converge: regenerate routes and reload the gateway"
|
||||||
>
|
>
|
||||||
<RefreshCw size={12} className={reloading ? "animate-spin" : ""} />
|
<RefreshCw size={12} className={applying ? "animate-spin" : ""} />
|
||||||
Reload
|
Apply
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowCaddyfile((v) => !v)}
|
onClick={() => setShowCaddyfile((v) => !v)}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type { GatewayInfo } from "@/types"
|
|||||||
import { useSaveGatewayConfig } from "@/services/api/hooks"
|
import { useSaveGatewayConfig } from "@/services/api/hooks"
|
||||||
|
|
||||||
/** Editable gateway routing + public-exposure settings (domain / public_domain /
|
/** Editable gateway routing + public-exposure settings (domain / public_domain /
|
||||||
* tunnel / tls). Saves to castle.yaml; changes take effect on the next deploy. */
|
* tunnel / tls). Saves to castle.yaml; changes take effect on the next apply. */
|
||||||
export function GatewaySettings({ gateway }: { gateway: GatewayInfo }) {
|
export function GatewaySettings({ gateway }: { gateway: GatewayInfo }) {
|
||||||
const { mutate: save, isPending, data: saved } = useSaveGatewayConfig()
|
const { mutate: save, isPending, data: saved } = useSaveGatewayConfig()
|
||||||
const [editing, setEditing] = useState(false)
|
const [editing, setEditing] = useState(false)
|
||||||
@@ -44,7 +44,7 @@ export function GatewaySettings({ gateway }: { gateway: GatewayInfo }) {
|
|||||||
<button onClick={() => setEditing(false)} className="flex items-center gap-1 text-xs px-2.5 py-1 rounded bg-[var(--border)] text-[var(--muted)]">
|
<button onClick={() => setEditing(false)} className="flex items-center gap-1 text-xs px-2.5 py-1 rounded bg-[var(--border)] text-[var(--muted)]">
|
||||||
<X size={12} /> Cancel
|
<X size={12} /> Cancel
|
||||||
</button>
|
</button>
|
||||||
<span className="text-xs text-[var(--muted)]">Run <span className="font-mono">castle deploy</span> to apply.</span>
|
<span className="text-xs text-[var(--muted)]">Run <span className="font-mono">castle apply</span> (or the Apply button) to converge.</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -226,16 +226,6 @@ export function useProgramAction() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useGatewayReload() {
|
|
||||||
const qc = useQueryClient()
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: () => apiClient.post<{ status: string }>("/gateway/reload"),
|
|
||||||
onSuccess: () => {
|
|
||||||
qc.invalidateQueries({ queryKey: ["gateway"] })
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useSaveGatewayConfig() {
|
export function useSaveGatewayConfig() {
|
||||||
const qc = useQueryClient()
|
const qc = useQueryClient()
|
||||||
return useMutation({
|
return useMutation({
|
||||||
|
|||||||
@@ -2,13 +2,11 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from fastapi import APIRouter, HTTPException, status
|
from fastapi import APIRouter, HTTPException, status
|
||||||
|
|
||||||
from castle_core.config import SPECS_DIR
|
|
||||||
from castle_core.generators.caddyfile import generate_caddyfile_from_registry
|
from castle_core.generators.caddyfile import generate_caddyfile_from_registry
|
||||||
from castle_core.manifest import (
|
from castle_core.manifest import (
|
||||||
ProgramSpec,
|
ProgramSpec,
|
||||||
@@ -937,7 +935,7 @@ def get_gateway() -> GatewayInfo:
|
|||||||
def save_gateway_config(request: GatewayConfigRequest) -> dict[str, str]:
|
def save_gateway_config(request: GatewayConfigRequest) -> dict[str, str]:
|
||||||
"""Update the gateway's routing/exposure settings in castle.yaml.
|
"""Update the gateway's routing/exposure settings in castle.yaml.
|
||||||
|
|
||||||
Saves only; the caller runs deploy to regenerate the Caddyfile + tunnel config.
|
Saves only; the caller runs apply to regenerate the Caddyfile + tunnel config.
|
||||||
An empty string clears a field.
|
An empty string clears a field.
|
||||||
"""
|
"""
|
||||||
root = get_castle_root()
|
root = get_castle_root()
|
||||||
@@ -952,7 +950,7 @@ def save_gateway_config(request: GatewayConfigRequest) -> dict[str, str]:
|
|||||||
config.gateway.public_domain = norm(request.public_domain)
|
config.gateway.public_domain = norm(request.public_domain)
|
||||||
config.gateway.tunnel_id = norm(request.tunnel_id)
|
config.gateway.tunnel_id = norm(request.tunnel_id)
|
||||||
save_config(config)
|
save_config(config)
|
||||||
return {"status": "saved", "message": "Saved. Run deploy to apply."}
|
return {"status": "saved", "message": "Saved. Run apply to converge."}
|
||||||
|
|
||||||
|
|
||||||
@router.get("/gateway/caddyfile")
|
@router.get("/gateway/caddyfile")
|
||||||
@@ -962,35 +960,5 @@ def get_caddyfile() -> dict[str, str]:
|
|||||||
return {"content": generate_caddyfile_from_registry(registry)}
|
return {"content": generate_caddyfile_from_registry(registry)}
|
||||||
|
|
||||||
|
|
||||||
@router.post("/gateway/reload")
|
# Note: gateway reload is not a standalone endpoint. Making routes/config live is
|
||||||
async def reload_gateway() -> dict[str, str]:
|
# convergence — POST /apply renders the Caddyfile (+ tunnel) and reloads Caddy.
|
||||||
"""Regenerate Caddyfile and reload Caddy."""
|
|
||||||
registry = get_registry()
|
|
||||||
SPECS_DIR.mkdir(parents=True, exist_ok=True)
|
|
||||||
caddyfile_path = SPECS_DIR / "Caddyfile"
|
|
||||||
|
|
||||||
# Include remote registries for cross-node routing
|
|
||||||
remote_regs = {h: n.registry for h, n in mesh_state.all_nodes().items()}
|
|
||||||
caddyfile_path.write_text(
|
|
||||||
generate_caddyfile_from_registry(
|
|
||||||
registry, remote_registries=remote_regs or None
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
proc = await asyncio.create_subprocess_exec(
|
|
||||||
"systemctl",
|
|
||||||
"--user",
|
|
||||||
"reload",
|
|
||||||
"castle-castle-gateway.service",
|
|
||||||
stdout=asyncio.subprocess.PIPE,
|
|
||||||
stderr=asyncio.subprocess.PIPE,
|
|
||||||
)
|
|
||||||
_, stderr = await proc.communicate()
|
|
||||||
|
|
||||||
if proc.returncode != 0:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=500,
|
|
||||||
detail=f"Reload failed: {(stderr or b'').decode().strip()}",
|
|
||||||
)
|
|
||||||
|
|
||||||
return {"status": "ok"}
|
|
||||||
|
|||||||
@@ -1,118 +1,24 @@
|
|||||||
"""castle gateway - manage the Caddy reverse proxy gateway."""
|
"""castle gateway — inspect the Caddy reverse proxy gateway.
|
||||||
|
|
||||||
|
The gateway is itself a deployment (`castle-gateway`): start/stop/reload it the
|
||||||
|
same way as anything else — `castle apply` (render routes + reload), `castle
|
||||||
|
restart castle-gateway` (bounce), or `enabled: false` + apply (stop). This command
|
||||||
|
is the read-only inspection lens: is it up, and what's the route table.
|
||||||
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from castle_core.config import SPECS_DIR
|
|
||||||
from castle_core.generators.caddyfile import generate_caddyfile_from_registry
|
|
||||||
from castle_core.registry import REGISTRY_PATH, load_registry
|
from castle_core.registry import REGISTRY_PATH, load_registry
|
||||||
|
|
||||||
from castle_cli.config import CastleConfig, ensure_dirs, load_config
|
|
||||||
|
|
||||||
GATEWAY_COMPONENT = "castle-gateway"
|
|
||||||
GATEWAY_UNIT = "castle-castle-gateway.service"
|
GATEWAY_UNIT = "castle-castle-gateway.service"
|
||||||
|
|
||||||
|
|
||||||
def _write_generated_files() -> None:
|
|
||||||
"""Write generated Caddyfile from registry."""
|
|
||||||
ensure_dirs()
|
|
||||||
|
|
||||||
if not REGISTRY_PATH.exists():
|
|
||||||
print("Error: no registry found. Run 'castle apply' first.")
|
|
||||||
return
|
|
||||||
|
|
||||||
registry = load_registry()
|
|
||||||
caddyfile_path = SPECS_DIR / "Caddyfile"
|
|
||||||
caddyfile_path.write_text(generate_caddyfile_from_registry(registry))
|
|
||||||
print(f" Generated {caddyfile_path}")
|
|
||||||
|
|
||||||
|
|
||||||
def run_gateway(args: argparse.Namespace) -> int:
|
def run_gateway(args: argparse.Namespace) -> int:
|
||||||
"""Manage the Caddy gateway."""
|
"""Show the gateway's status + route table (the only gateway verb)."""
|
||||||
if not args.gateway_command:
|
return _gateway_status()
|
||||||
print("Usage: castle gateway {start|stop|reload|status}")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
config = load_config()
|
|
||||||
|
|
||||||
if args.gateway_command == "start":
|
|
||||||
if getattr(args, "dry_run", False):
|
|
||||||
return _gateway_dry_run()
|
|
||||||
return _gateway_start(config)
|
|
||||||
elif args.gateway_command == "stop":
|
|
||||||
return _gateway_stop()
|
|
||||||
elif args.gateway_command == "reload":
|
|
||||||
if getattr(args, "dry_run", False):
|
|
||||||
return _gateway_dry_run()
|
|
||||||
return _gateway_reload()
|
|
||||||
elif args.gateway_command == "status":
|
|
||||||
return _gateway_status()
|
|
||||||
|
|
||||||
return 1
|
|
||||||
|
|
||||||
|
|
||||||
def _gateway_dry_run() -> int:
|
|
||||||
"""Print generated Caddyfile without applying."""
|
|
||||||
if not REGISTRY_PATH.exists():
|
|
||||||
print("Error: no registry found. Run 'castle apply' first.")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
registry = load_registry()
|
|
||||||
print("# Caddyfile")
|
|
||||||
print(generate_caddyfile_from_registry(registry))
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
def _gateway_start(config: CastleConfig) -> int:
|
|
||||||
"""Generate config and enable the gateway service."""
|
|
||||||
from castle_cli.commands.service import _service_enable
|
|
||||||
|
|
||||||
if GATEWAY_COMPONENT not in config.services:
|
|
||||||
print(f"Error: '{GATEWAY_COMPONENT}' not found in services section")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
print("Generating gateway configuration...")
|
|
||||||
_write_generated_files()
|
|
||||||
|
|
||||||
print(f"\nStarting gateway on port {config.gateway.port}...")
|
|
||||||
return _service_enable(config, GATEWAY_COMPONENT)
|
|
||||||
|
|
||||||
|
|
||||||
def _gateway_stop() -> int:
|
|
||||||
"""Stop the gateway service."""
|
|
||||||
from castle_cli.commands.service import _service_disable
|
|
||||||
|
|
||||||
return _service_disable(GATEWAY_COMPONENT)
|
|
||||||
|
|
||||||
|
|
||||||
def _gateway_reload() -> int:
|
|
||||||
"""Regenerate config and reload Caddy."""
|
|
||||||
print("Regenerating gateway configuration...")
|
|
||||||
_write_generated_files()
|
|
||||||
|
|
||||||
result = subprocess.run(
|
|
||||||
["systemctl", "--user", "reload", GATEWAY_UNIT],
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
if result.returncode == 0:
|
|
||||||
print("Gateway reloaded.")
|
|
||||||
else:
|
|
||||||
print("Reload signal sent. Verifying...")
|
|
||||||
result = subprocess.run(
|
|
||||||
["systemctl", "--user", "is-active", GATEWAY_UNIT],
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
)
|
|
||||||
if result.stdout.strip() == "active":
|
|
||||||
print("Gateway running.")
|
|
||||||
else:
|
|
||||||
print("Warning: gateway may not be running. Try: castle gateway start")
|
|
||||||
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
def _gateway_status() -> int:
|
def _gateway_status() -> int:
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ from castle_core.manifest import kind_for
|
|||||||
|
|
||||||
from castle_cli.config import (
|
from castle_cli.config import (
|
||||||
CastleConfig,
|
CastleConfig,
|
||||||
ensure_dirs,
|
|
||||||
load_config,
|
load_config,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -178,30 +177,6 @@ def run_status(args: argparse.Namespace) -> int:
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def _service_enable(config: CastleConfig, name: str) -> int:
|
|
||||||
"""Enable a service in its mode (systemd unit / gateway route / PATH install)."""
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
from castle_core.lifecycle import activate
|
|
||||||
|
|
||||||
ensure_dirs()
|
|
||||||
result = asyncio.run(activate(name, config, config.root))
|
|
||||||
print(result.output)
|
|
||||||
return 0 if result.status == "ok" else 1
|
|
||||||
|
|
||||||
|
|
||||||
def _service_disable(config: CastleConfig, name: str) -> int:
|
|
||||||
"""Disable a service in its mode (stop unit / drop route / uninstall)."""
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
from castle_core.lifecycle import deactivate
|
|
||||||
|
|
||||||
print(f"Disabling {name}...")
|
|
||||||
result = asyncio.run(deactivate(name, config, config.root))
|
|
||||||
print(f" {result.output}")
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
def _service_status(config: CastleConfig) -> int:
|
def _service_status(config: CastleConfig) -> int:
|
||||||
"""Show status of all services and jobs, dispatched by manager."""
|
"""Show status of all services and jobs, dispatched by manager."""
|
||||||
from castle_core.lifecycle import is_active
|
from castle_core.lifecycle import is_active
|
||||||
|
|||||||
@@ -159,15 +159,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|||||||
_build_deployment_group(subparsers, "job")
|
_build_deployment_group(subparsers, "job")
|
||||||
_build_tool_group(subparsers)
|
_build_tool_group(subparsers)
|
||||||
|
|
||||||
# Gateway (infrastructure)
|
# Gateway (inspection). The gateway is a deployment — start/stop/reload it via
|
||||||
gw = subparsers.add_parser("gateway", help="Manage the Caddy gateway")
|
# `castle apply` / `castle restart castle-gateway`; this lens just shows routes.
|
||||||
|
gw = subparsers.add_parser("gateway", help="Show the gateway's status + route table")
|
||||||
gw_sub = gw.add_subparsers(dest="gateway_command")
|
gw_sub = gw.add_subparsers(dest="gateway_command")
|
||||||
p = gw_sub.add_parser("start", help="Start the gateway")
|
gw_sub.add_parser("status", help="Show gateway status + routes (the default)")
|
||||||
p.add_argument("--dry-run", action="store_true")
|
|
||||||
gw_sub.add_parser("stop", help="Stop the gateway")
|
|
||||||
p = gw_sub.add_parser("reload", help="Reload gateway configuration")
|
|
||||||
p.add_argument("--dry-run", action="store_true")
|
|
||||||
gw_sub.add_parser("status", help="Show gateway status")
|
|
||||||
|
|
||||||
# Convergence — the one lifecycle verb. Renders units/Caddyfile/tunnel, then
|
# Convergence — the one lifecycle verb. Renders units/Caddyfile/tunnel, then
|
||||||
# reconciles the runtime to match config (activate/restart/deactivate).
|
# reconciles the runtime to match config (activate/restart/deactivate).
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ for tools/libraries.
|
|||||||
- Catalog + editing: `GET /programs[/{name}]`, `POST /programs/{name}/{action}`,
|
- Catalog + editing: `GET /programs[/{name}]`, `POST /programs/{name}/{action}`,
|
||||||
`PUT|DELETE /programs/{name}`; likewise `/services`, `/jobs`
|
`PUT|DELETE /programs/{name}`; likewise `/services`, `/jobs`
|
||||||
- Config: `GET|PUT /`, `POST /apply`, `POST /deploy`
|
- Config: `GET|PUT /`, `POST /apply`, `POST /deploy`
|
||||||
- Gateway: `GET /gateway`, `GET /gateway/caddyfile`, `POST /gateway/reload`
|
- Gateway: `GET /gateway`, `GET /gateway/caddyfile` (reload is convergence: `POST /apply`)
|
||||||
- Mesh: `GET /mesh/status`, `GET /nodes[/{hostname}]`
|
- Mesh: `GET /mesh/status`, `GET /nodes[/{hostname}]`
|
||||||
- Agents (dashboard terminal UX): `GET /agents`, `GET /agents/sessions`,
|
- Agents (dashboard terminal UX): `GET /agents`, `GET /agents/sessions`,
|
||||||
`GET /agents/history`, `DELETE /agents/sessions/{id}`, `WS /agents/{name}/session`
|
`GET /agents/history`, `DELETE /agents/sessions/{id}`, `WS /agents/{name}/session`
|
||||||
|
|||||||
@@ -454,4 +454,4 @@ castle program create my-service --stack python-fastapi --description "Does some
|
|||||||
```
|
```
|
||||||
|
|
||||||
See @docs/registry.md for manifest fields, castle.yaml structure,
|
See @docs/registry.md for manifest fields, castle.yaml structure,
|
||||||
and the full service lifecycle (enable, logs, gateway reload).
|
and the full service lifecycle (`castle apply`, logs).
|
||||||
|
|||||||
Reference in New Issue
Block a user