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

View File

@@ -86,8 +86,13 @@ def run_sync(args: argparse.Namespace) -> int:
continue continue
if (source_dir / "pyproject.toml").exists(): if (source_dir / "pyproject.toml").exists():
print(f"\nInstalling {name}...") 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( result = subprocess.run(
[uv_path, "tool", "install", "--editable", str(source_dir), "--force"], [uv_path, "tool", "install", "--editable", pkg_spec, "--force"],
capture_output=True, capture_output=True,
text=True, text=True,
) )
@@ -122,8 +127,12 @@ def _try_install(
if (source_dir / "pyproject.toml").exists(): if (source_dir / "pyproject.toml").exists():
print(f"\nInstalling {name}...") print(f"\nInstalling {name}...")
extras = getattr(comp, "install_extras", [])
pkg_spec = str(source_dir)
if extras:
pkg_spec += "[" + ",".join(extras) + "]"
result = subprocess.run( result = subprocess.run(
[uv_path, "tool", "install", "--editable", str(source_dir), "--force"], [uv_path, "tool", "install", "--editable", pkg_spec, "--force"],
capture_output=True, capture_output=True,
text=True, text=True,
) )

View File

@@ -134,7 +134,11 @@ def load_config(root: Path | None = None) -> CastleConfig:
# Support both "programs:" and legacy "components:" key # Support both "programs:" and legacy "components:" key
programs_data = data.get("programs") or data.get("components") or {} programs_data = data.get("programs") or data.get("components") or {}
for name, comp_data in programs_data.items(): 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] = {} services: dict[str, ServiceSpec] = {}
for name, svc_data in data.get("services", {}).items(): for name, svc_data in data.get("services", {}).items():
@@ -230,7 +234,14 @@ def save_config(config: CastleConfig) -> None:
if config.programs: if config.programs:
data["programs"] = {} data["programs"] = {}
for name, spec in config.programs.items(): 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: if config.services:
data["services"] = {} data["services"] = {}

View File

@@ -185,6 +185,7 @@ class ProgramSpec(BaseModel):
stack: str | None = None stack: str | None = None
system_dependencies: list[str] = Field(default_factory=list) system_dependencies: list[str] = Field(default_factory=list)
install_extras: list[str] = Field(default_factory=list)
version: str | None = None version: str | None = None
build: BuildSpec | 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: async def install(self, name: str, comp: ProgramSpec, root: Path) -> ActionResult:
src = _source_dir(comp, root) src = _source_dir(comp, root)
pkg_spec = str(src)
if comp.install_extras:
pkg_spec += "[" + ",".join(comp.install_extras) + "]"
rc, output = await _run( rc, output = await _run(
["uv", "tool", "install", "--editable", str(src), "--force"], src ["uv", "tool", "install", "--editable", pkg_spec, "--force"], src
) )
return ActionResult( return ActionResult(
component=name, action="install", status="ok" if rc == 0 else "error", output=output component=name, action="install", status="ok" if rc == 0 else "error", output=output