Skip to content

Latest commit

 

History

History
158 lines (126 loc) · 13 KB

File metadata and controls

158 lines (126 loc) · 13 KB

AGENT - Operator role

Goal: run famstack on a Mac safely. Install, lifecycle, troubleshoot, back up. No code changes from this role - that is the engineer role (see dev.md).

For full prose, see ../admin-guide.md. This file is the compact decision layer; the admin guide is the manual.

Mental model in 3 lines

  1. stack.toml (per-host config) + each stacklet's stacklet.toml → render .env on every stack up. .env is derived, never edit it.
  2. Every stacklet lives under stacklets/<id>/, or under ~/famstack-extensions/<id>/ if it does not ship with famstack. All persistent data lives under ~/famstack-data/<id>/. No exceptions.
  3. State is derived, not stored. Container existence == "running"; container absence + data dir absence == "available". There is no enabled-list.

Hardware preconditions

  • macOS on Apple Silicon (M1+).
  • 16 GB RAM minimum; 32 GB recommended (AI quality, especially non-English).
  • Homebrew installed. It is the only manual dep - it pulls everything else.
  • OrbStack (preferred) or Docker Desktop running.

If a precondition is missing, ./stack prints exactly what to do. Don't improvise.

Command map: idempotency & blast radius

Command Idempotent Destructive Touches
./stack yes no interactive installer (first-run path)
./stack up <id>... yes no starts containers, renders .env, runs hooks
./stack down <id>... yes no stops containers; data preserved. Several ids: spaces or commas
./stack down all yes no stops every running stacklet in reverse dep order
./stack restart [<id>...] yes no down + up; several ids as for down; with no argument, only the stacklets running stale code
./stack destroy <id>... yes YES removes containers + ~/famstack-data/<id>/ + secrets
./stack uninstall yes YES, EVERYTHING destroys every stacklet, network, all data, config
./stack update [<tag>] yes no moves the checkout to a release; restarts nothing, prints what to restart
./stack list yes no reports state
./stack status yes no runs health checks
./stack logs <id> yes no tail container logs
./stack errors yes no recent error logs (24h)
./stack host yes no disk / memory / uptime
./stack config [--secrets] yes no prints resolved config
./stack ai models yes no lists installed AI models
./stack ai switch <url> yes no points [ai] at an AI server the stack does not manage; checks it, installs nothing, restarts nothing. Warns when the address is outside the home network
./stack ai switch managed yes no moves [ai] to the engine the stack runs here: runs stack up ai, installing oMLX when missing; keeps the previous server if that fails
./stack setup ai yes no re-runs AI install (model swap path)
./stack infra dns-token yes no stores or replaces the DNS provider API token (HTTPS in domain mode); ./stack up infra applies it

Output contract: every command returns JSON when piped or when --json is passed. Force human output with --pretty. Exit code 0 == success.

Applying config/compose changes to a running stacklet: stack up <id> is the apply command — it re-renders .env and recreates that stacklet's containers. It force-recreates unconditionally (docker.py compose_up), because compose's config hash does not cover env_file contents, so a plain up -d would leave a container on stale env. That makes up and restart (= down + up) similar in cost; restart stops everything first and runs the stop hooks. Plain docker compose restart does neither: it won't re-render env or create new services.

Invariants

  • stack up is always safe to re-run. Failures leave the system in a valid intermediate state; re-running picks up where it failed.
  • .env is a derived artifact. Never edit by hand. Overwritten on every stack up.
  • .stack/secrets.toml is gitignored and contains generated passwords + tokens. Treat as a password export.
  • Data lives outside the repo at ~/famstack-data/<id>/. That's the only mandatory backup.
  • ~/.omlx/models/ holds LLM model files. Re-downloadable; not in data_dir.
  • Extension stacklets are source code, and nothing backs them up. ~/famstack-extensions/<id>/ holds stacklets famstack does not ship. Nothing creates it, nothing deletes it, and it is outside the data dir the backup rule covers. Push it to a remote. [core] extension_dirs (a list, first entry wins a clash and is the one mounted into the bot runner) adds or moves search paths.
  • A stage is a claim, not a fault. stack list and stack status group extensions in their own section and show a stage that is not stable. The warning on stack up means the stacklet can change or disappear between releases. Do not put anything irreplaceable in it.
  • Two famstack instances cannot run on the same Mac at the same time - container names collide (stack-<id>). stack down the active one first.

Danger zones

Refuse without explicit, scoped human approval:

Action Why
./stack destroy <id> Deletes ~/famstack-data/<id>/ permanently. No undo.
./stack uninstall Wipes every stacklet's data, secrets, runtime state, and config.
rm -rf ~/famstack-data/... Bypasses the CLI's safety prompts.
Editing .env directly Will be silently overwritten next stack up.
Editing .stack/secrets.toml Breaks every stacklet that depends on the changed secret.
Moving data_dir while stacklets are running Bind mounts break. stack down all first, then move, then stack up.
Changing stack.toml [core] language Re-seeds Paperless taxonomy. Orphan tags require manual cleanup.
Switching [core] domain empty ↔ non-empty Switches port mode ↔ domain mode. Needs *.<domain> and <domain> DNS records to the Mac, and the infra stacklet up.
Pointing the router's DNS at the Mac (infra) Every device on the LAN then resolves through AdGuard; the Mac or AdGuard going down takes name resolution for the whole house with it. The admin changes the router, never an agent; note the previous DNS setting first.
./stack down infra / ./stack destroy infra With the router pointing at the Mac, the house loses DNS until infra is back or the router is switched back.

Ports (42xxx range)

Port Stacklet Service
42000 core Link resolver for /go/* (tool endpoints are internal to the stack network)
42010 photos Immich web + API
42020 docs Paperless-ngx
42030 messages Element web
42031 messages Synapse (Matrix homeserver - phones connect here)
42040 code Forgejo web
222 code Forgejo SSH (macOS uses 22 itself)
42050 chatai Open WebUI
42060 ai oMLX
42062 ai Whisper
42070 memory Family wiki (Quartz)
42080 infra AdGuard admin (domain mode, Mac only)
42081 infra AdGuard setup wizard, first run (domain mode, Mac only)
53, 80, 443 infra DNS and the Caddy proxy (domain mode, whole LAN)

Port collisions: do not silently rebind. Surface them. The user's fix is "stop the offender" or switch to domain mode.

Troubleshooting decision table

Symptom First check Likely cause
"Docker is not running" OrbStack/Docker Desktop icon present? App not started.
"Port 42xxx already in use" lsof -nP -iTCP:<port> -sTCP:LISTEN Another service grabbed it.
Phone can't reach server Same Wi-Fi? Guest network isolating clients? Network isolation.
"Server not Matrix" on phone ./stack status → messages Synapse down. ./stack restart messages.
Mac IP keeps changing Router DHCP behaviour Set DHCP reservation on the router.
AI install fails at whisper.cpp build xcode-select -p Xcode CLT missing. xcode-select --install.
brew: command not found after install PATH on Apple Silicon Add eval "$(/opt/homebrew/bin/brew shellenv)" to ~/.zshrc.
LLM OOM / very slow RAM tier Edit [ai] default to smaller model, then ./stack setup ai.
stack list shows ai as remote or localhost [ai] provider in stack.toml The stack uses an AI server it does not manage (stack ai switch): another machine, or an app on this Mac (localhost). stack up ai keeps it; stack ai switch managed switches back to this Mac.
Voice messages fail on a remote AI setup ./stack ai switch <url> output, "Voice messages" line The AI server lists no speech-to-text model. Pass --whisper <url> for a speech server.
Disk full ./stack host Likely the photo library. Move data_dir to external SSD.
Element warns "browser not supported", then never loads Which address is open? Port mode over the LAN IP: plain HTTP is not a secure context. On the server Mac open http://localhost:42030; other computers use the Element desktop app with http://<ip>:42031.
Wiki pages stale after filings docker logs stack-memory-curator Curator debounces (~3 min quiet) before rebuilding; topic pages wait for the nightly sweep. Manual override: ./stack memory wiki update (what the last filings touched) or ./stack memory wiki update --all (everything, as at night).
Wiki shows old content entirely docker logs stack-memory-curator The curator owns the vault git pull (the wiki container is a read-only view). Curator down/stuck → vault and wiki go stale together.
Curator logs "waiting for vault" ./stack status → memory, code Vault not cloned yet — the memory install hooks own the initial clone.

For symptoms not on this table: ./stack logs <id> + ./stack errors, paste output to the user. Do not invent fixes.

Updating

A release is a git tag. ./stack update moves the checkout to one and names what that staled; ./stack restart with no argument then restarts exactly those. --dry-run shows the plan, --yes skips the confirmation, a tag argument picks a release other than the newest.

  • It never restarts anything. New code on disk is not new code running; recreating containers is the admin's decision. The command names what the release staled: stacklets it changed and that are running, or every running stacklet when anything under lib/ changed.
  • ./stack restart with no argument applies it. Every container is stamped at stack up with the commit it started from (stack.commit), so stack list and stack doctor report a stacklet running code the checkout has moved past, and a bare restart restarts those. A container with no stamp is reported as unknown, never guessed about.
  • Local edits are set aside and put back. If they collide with the release the whole update winds back: same release as before, edits in place, stash empty. Never leave a tree carrying conflict markers, a compose file with them in it does not parse.
  • Before v0.3.0-beta.4 there is no update. By hand: git fetch --tags && git checkout <tag> && ./stack doctor, then restart what git diff --name-only <old> <new> -- stacklets/ names.
  • Works from a branch or a fork. On main it moves to the tag and says the branch is left behind (git switch main returns). Tags are fetched from every remote, so a fork needs the project added as a remote or there is nothing to update to.
  • A tag checkout is a detached HEAD. git pull fails there. Never suggest git pull origin main as the fix: it succeeds and silently moves the instance onto unreleased code while ./stack version still reports the last release.
  • ./stack version now says what is running, not what the source constant claims: 0.3.0-beta.3-104-g9a5bdc9-dirty is the tag, the distance past it, the commit, and an unclean tree. version, status, list, doctor and update all print the same string. ./stack doctor adds a warning when a newer release exists, without fetching, so that half is as fresh as the last git fetch.
  • The instance survives a tag switch. Config, secrets, data and ~/<product>-extensions/ are all outside git.

Full prose: ../admin-guide.md § Updating.

Backups

  • ~/famstack-data/ - mandatory. Time Machine, restic, or rsync. Pick one.
  • stack.toml, users.toml, .stack/secrets.toml - small, irreplaceable, gitignored. One-liner:
    tar czf famstack-config-$(date +%F).tgz stack.toml users.toml .stack/
  • ~/famstack-extensions/ - only if the instance has one. Source code for stacklets famstack does not ship; a git remote counts.
  • ~/.omlx/models/ - re-downloadable; skip.

Boundaries

This role describes operating an existing famstack install. Do NOT, from the operator role:

  • Edit code in stacklets/, lib/, or tests/. That's the engineer role; see dev.md.
  • Create or merge PRs.
  • Run ./stack uninstall to "start fresh" without explicit user approval.
  • Suggest exact config strings (URLs, ports, IPs, field names) from memory. Verify against stack.toml, the user guide, or ./stack config.

When in doubt: ./stack status and ask.