install_extras

This commit is contained in:
2026-03-05 10:01:26 -08:00
parent 5446fbb950
commit 99e70f8543
5 changed files with 76 additions and 54 deletions

View File

@@ -1,42 +1,43 @@
gateway:
port: 9000
programs:
central-context:
description: Content storage API useful to get context into my data dir from anywhere
on the LAN
behavior: daemon
source: components/central-context
stack: python-fastapi
behavior: daemon
notification-bridge:
description: Desktop notification forwarder. This taps into your notifications
system on the desktop and will forward all notifications the the central context
server.
behavior: daemon
source: components/notification-bridge
stack: python-fastapi
behavior: daemon
install_extras:
- linux
castle-api:
description: Castle API
behavior: daemon
source: castle-api
stack: python-fastapi
behavior: daemon
protonmail:
description: ProtonMail email sync via Bridge
behavior: tool
source: components/protonmail
stack: python-cli
behavior: tool
backup-collect:
description: Collect files from various sources into backup directory
behavior: tool
source: components/backup-collect
stack: python-cli
behavior: tool
system_dependencies:
- rsync
castle-app:
description: Castle web app
behavior: frontend
source: app
stack: react-vite
behavior: frontend
build:
commands:
- - pnpm
@@ -45,92 +46,91 @@ programs:
- dist/
devbox-connect:
description: SSH tunnel manager with auto-reconnect
behavior: tool
source: components/devbox-connect
stack: python-cli
behavior: tool
mbox2eml:
description: MBOX to EML email converter
behavior: tool
source: components/mbox2eml
stack: python-cli
behavior: tool
android-backup:
description: Backup Android device using ADB
behavior: tool
source: components/android-backup
stack: python-cli
behavior: tool
system_dependencies:
- adb
browser:
description: Browse the web using natural language via browser-use
behavior: tool
source: components/browser
stack: python-cli
behavior: tool
docx-extractor:
description: Extract content and metadata from Word .docx files
behavior: tool
source: components/docx-extractor
stack: python-cli
behavior: tool
system_dependencies:
- pandoc
docx2md:
description: Convert Word .docx files to Markdown
behavior: tool
source: components/docx2md
stack: python-cli
behavior: tool
system_dependencies:
- pandoc
gpt:
description: OpenAI text generation utility
behavior: tool
source: components/gpt
stack: python-cli
behavior: tool
html2text:
description: Convert HTML content to plain text
behavior: tool
source: components/html2text
stack: python-cli
behavior: tool
md2pdf:
description: Convert Markdown files to PDF
behavior: tool
source: components/md2pdf
stack: python-cli
behavior: tool
system_dependencies:
- pandoc
- texlive-latex-base
mdscraper:
description: Combine text files into a single markdown document
behavior: tool
source: components/mdscraper
stack: python-cli
behavior: tool
pdf-extractor:
description: Extract content and metadata from PDF files
behavior: tool
source: components/pdf-extractor
stack: python-cli
behavior: tool
pdf2md:
description: Convert PDF files to Markdown
behavior: tool
source: components/pdf2md
stack: python-cli
behavior: tool
system_dependencies:
- pandoc
- poppler-utils
schedule:
description: Manage systemd user timers and scheduled tasks
behavior: tool
source: components/schedule
stack: python-cli
behavior: tool
search:
description: Manage self-contained searchable collections of files
behavior: tool
source: components/search
stack: python-cli
behavior: tool
text-extractor:
description: Extract content and metadata from text files
behavior: tool
source: components/text-extractor
stack: python-cli
behavior: tool
services:
castle-gateway:
description: Caddy reverse proxy gateway
@@ -143,22 +143,20 @@ services:
- /home/payne/.castle/generated/Caddyfile
- --adapter
- caddyfile
manage:
systemd:
exec_reload: caddy reload --config /home/payne/.castle/generated/Caddyfile
--adapter caddyfile
expose:
http:
internal:
port: 9000
health_path: /
manage:
systemd:
exec_reload: caddy reload --config /home/payne/.castle/generated/Caddyfile
--adapter caddyfile
central-context:
component: central-context
run:
runner: python
program: central-context
manage:
systemd: {}
expose:
http:
internal:
@@ -167,18 +165,13 @@ services:
proxy:
caddy:
path_prefix: /central-context
manage:
systemd: {}
notification-bridge:
component: notification-bridge
run:
runner: python
program: notification-bridge
defaults:
env:
CENTRAL_CONTEXT_URL: http://localhost:9001
BUCKET_NAME: notifications
PORT: '9002'
manage:
systemd: {}
expose:
http:
internal:
@@ -187,13 +180,18 @@ services:
proxy:
caddy:
path_prefix: /notifications
manage:
systemd: {}
defaults:
env:
CENTRAL_CONTEXT_URL: http://localhost:9001
BUCKET_NAME: notifications
PORT: '9002'
castle-api:
component: castle-api
run:
runner: python
program: castle-api
manage:
systemd: {}
expose:
http:
internal:
@@ -202,24 +200,24 @@ services:
proxy:
caddy:
path_prefix: /api
manage:
systemd: {}
castle-mqtt:
description: MQTT broker for inter-node mesh coordination
run:
runner: container
image: eclipse-mosquitto:2
ports:
1883: 1883
'1883': 1883
volumes:
- /data/castle/castle-mqtt/config:/mosquitto/config
- /data/castle/castle-mqtt/data:/mosquitto/data
manage:
systemd: {}
expose:
http:
internal:
port: 1883
manage:
systemd: {}
jobs:
protonmail-sync:
component: protonmail
@@ -230,12 +228,12 @@ jobs:
- protonmail
- sync
schedule: '*/5 * * * *'
manage:
systemd: {}
defaults:
env:
PROTONMAIL_USERNAME: paul@payne.io
PROTONMAIL_API_KEY: ${secret:PROTONMAIL_API_KEY}
manage:
systemd: {}
backup-collect:
component: backup-collect
description: Collect files from various sources into backup directory
@@ -244,11 +242,11 @@ jobs:
argv:
- backup-collect
schedule: 0 2 * * *
manage:
systemd: {}
defaults:
env:
DBACKUP: /data/backup
manage:
systemd: {}
backup-data:
description: Nightly restic backup of /data to /storage
run:
@@ -256,9 +254,9 @@ jobs:
argv:
- backup-data
schedule: 30 3 * * *
manage:
systemd: {}
defaults:
env:
RESTIC_REPOSITORY: /storage/restic-data
RESTIC_PASSWORD_FILE: /home/payne/.config/restic-password
manage:
systemd: {}

View File

@@ -86,8 +86,13 @@ def run_sync(args: argparse.Namespace) -> int:
continue
if (source_dir / "pyproject.toml").exists():
print(f"\nInstalling {name}...")
prog = config.programs.get(svc.component or "") if svc.component else None
extras = prog.install_extras if prog else []
pkg_spec = str(source_dir)
if extras:
pkg_spec += "[" + ",".join(extras) + "]"
result = subprocess.run(
[uv_path, "tool", "install", "--editable", str(source_dir), "--force"],
[uv_path, "tool", "install", "--editable", pkg_spec, "--force"],
capture_output=True,
text=True,
)
@@ -122,8 +127,12 @@ def _try_install(
if (source_dir / "pyproject.toml").exists():
print(f"\nInstalling {name}...")
extras = getattr(comp, "install_extras", [])
pkg_spec = str(source_dir)
if extras:
pkg_spec += "[" + ",".join(extras) + "]"
result = subprocess.run(
[uv_path, "tool", "install", "--editable", str(source_dir), "--force"],
[uv_path, "tool", "install", "--editable", pkg_spec, "--force"],
capture_output=True,
text=True,
)

View File

@@ -134,7 +134,11 @@ def load_config(root: Path | None = None) -> CastleConfig:
# Support both "programs:" and legacy "components:" key
programs_data = data.get("programs") or data.get("components") or {}
for name, comp_data in programs_data.items():
programs[name] = _parse_program(name, comp_data)
prog = _parse_program(name, comp_data)
# Resolve relative source paths to absolute
if prog.source and not Path(prog.source).is_absolute():
prog.source = str(root / prog.source)
programs[name] = prog
services: dict[str, ServiceSpec] = {}
for name, svc_data in data.get("services", {}).items():
@@ -230,7 +234,14 @@ def save_config(config: CastleConfig) -> None:
if config.programs:
data["programs"] = {}
for name, spec in config.programs.items():
data["programs"][name] = _spec_to_yaml_dict(spec)
d = _spec_to_yaml_dict(spec)
# Store relative source paths in YAML
if d.get("source") and Path(d["source"]).is_absolute():
try:
d["source"] = str(Path(d["source"]).relative_to(config.root))
except ValueError:
pass # not under root — keep absolute
data["programs"][name] = d
if config.services:
data["services"] = {}

View File

@@ -185,6 +185,7 @@ class ProgramSpec(BaseModel):
stack: str | None = None
system_dependencies: list[str] = Field(default_factory=list)
install_extras: list[str] = Field(default_factory=list)
version: str | None = None
build: BuildSpec | None = None

View File

@@ -139,8 +139,11 @@ class PythonHandler(StackHandler):
async def install(self, name: str, comp: ProgramSpec, root: Path) -> ActionResult:
src = _source_dir(comp, root)
pkg_spec = str(src)
if comp.install_extras:
pkg_spec += "[" + ",".join(comp.install_extras) + "]"
rc, output = await _run(
["uv", "tool", "install", "--editable", str(src), "--force"], src
["uv", "tool", "install", "--editable", pkg_spec, "--force"], src
)
return ActionResult(
component=name, action="install", status="ok" if rc == 0 else "error", output=output