refactor: Restructure ~/.castle/ as the instance directory

Move all instance state into ~/.castle/ with clear separation:
- code/        — user project source (was components/)
- artifacts/   — generated specs, built content (was generated/, static/)
- data/        — service runtime data (was /data/castle/)
- secrets/     — credentials

castle.yaml moves to ~/.castle/castle.yaml as the canonical location.
Source paths use repo: prefix for git repo programs (castle-api, app)
and relative paths for user projects (code/central-context).

Add idempotent install.sh for bootstrapping infrastructure (Docker,
Caddy, MQTT, Postgres, Neo4j) with interactive service detection.

Remove components/ from repo (now in ~/.castle/code/), deinit
submodules, remove .gitmodules.
This commit is contained in:
2026-03-09 22:03:37 -07:00
parent b7628c590b
commit 8be129259c
86 changed files with 1222 additions and 13989 deletions

View File

@@ -8,8 +8,9 @@ import subprocess
from pathlib import Path
from castle_core.config import (
GENERATED_DIR,
STATIC_DIR,
CONTENT_DIR,
DATA_DIR,
SPECS_DIR,
CastleConfig,
ensure_dirs,
load_config,
@@ -31,8 +32,6 @@ from castle_core.registry import (
load_registry,
save_registry,
)
DATA_ROOT = Path("/data/castle")
SYSTEMD_USER_DIR = Path.home() / ".config" / "systemd" / "user"
@@ -88,7 +87,7 @@ def run_deploy(args: argparse.Namespace) -> int:
_generate_systemd_units(config, registry)
# Generate Caddyfile from registry
caddyfile_path = GENERATED_DIR / "Caddyfile"
caddyfile_path = SPECS_DIR / "Caddyfile"
caddyfile_content = generate_caddyfile_from_registry(registry)
caddyfile_path.write_text(caddyfile_content)
print(f"Caddyfile written: {caddyfile_path}")
@@ -130,7 +129,7 @@ def _build_deployed_service(
if svc.manage and svc.manage.systemd and not svc.manage.systemd.enable:
managed = False
if managed:
env[f"{prefix}_DATA_DIR"] = str(DATA_ROOT / name)
env[f"{prefix}_DATA_DIR"] = str(DATA_DIR / name)
# Port convention (if exposed)
port = None
@@ -183,7 +182,7 @@ def _build_deployed_job(
env: dict[str, str] = {}
# Data dir convention
env[f"{prefix}_DATA_DIR"] = str(DATA_ROOT / name)
env[f"{prefix}_DATA_DIR"] = str(DATA_DIR / name)
# Merge defaults.env (overrides conventions)
if job.defaults and job.defaults.env:
@@ -274,23 +273,22 @@ def _print_deployed(name: str, deployed: DeployedComponent) -> None:
def _copy_app_static(config: CastleConfig) -> None:
"""Copy castle-app build output to ~/.castle/static/castle-app/."""
if "castle-app" not in config.programs:
return
"""Copy frontend build outputs to ~/.castle/static/<name>/."""
for name, comp in config.programs.items():
if comp.behavior != "frontend":
continue
if not (comp.build and comp.build.outputs):
continue
comp = config.programs["castle-app"]
if not (comp.build and comp.build.outputs):
return
source_dir = comp.source_dir or "app"
for output in comp.build.outputs:
src = config.root / source_dir / output
if src.exists():
dest = STATIC_DIR / "castle-app"
if dest.exists():
shutil.rmtree(dest)
shutil.copytree(src, dest)
print(f" Static: {src}{dest}")
source_dir = comp.source_dir or name
for output in comp.build.outputs:
src = config.root / source_dir / output
if src.exists():
dest = CONTENT_DIR / name
if dest.exists():
shutil.rmtree(dest)
shutil.copytree(src, dest)
print(f" Static: {src}{dest}")
def _generate_systemd_units(config: CastleConfig, registry: NodeRegistry) -> None:

View File

@@ -5,7 +5,7 @@ from __future__ import annotations
import argparse
import subprocess
from castle_core.config import GENERATED_DIR
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
@@ -24,7 +24,7 @@ def _write_generated_files() -> None:
return
registry = load_registry()
caddyfile_path = GENERATED_DIR / "Caddyfile"
caddyfile_path = SPECS_DIR / "Caddyfile"
caddyfile_path.write_text(generate_caddyfile_from_registry(registry))
print(f" Generated {caddyfile_path}")

View File

@@ -271,11 +271,11 @@ def _services_start(config: CastleConfig) -> int:
ensure_dirs()
from castle_core.config import GENERATED_DIR
from castle_core.config import SPECS_DIR
from castle_core.generators.caddyfile import generate_caddyfile_from_registry
registry = load_registry()
caddyfile_path = GENERATED_DIR / "Caddyfile"
caddyfile_path = SPECS_DIR / "Caddyfile"
caddyfile_path.write_text(generate_caddyfile_from_registry(registry))
print(f"Generated {caddyfile_path}")