diff --git a/BACKLOG.md b/BACKLOG.md index 357179c..bafcb1b 100644 --- a/BACKLOG.md +++ b/BACKLOG.md @@ -21,8 +21,8 @@ and vice versa. `db export|import`, `import-yaml` ([#108](https://github.com/CryptoJones/FlatlineRoundtable/issues/108)) — shipped in PR #123 - [x] Run from the store: `load_config` reads the DB, `fetch_keys` reads secrets, YAML retired ([#109](https://github.com/CryptoJones/FlatlineRoundtable/issues/109)) — shipped in PR #124 -- [ ] `db backup|restore` and the telesto restic job ([#110](https://github.com/CryptoJones/FlatlineRoundtable/issues/110)) — on feat/sqlite-store-backup -- [ ] Fleet rollout notes and the Python 3.11 floor ([#111](https://github.com/CryptoJones/FlatlineRoundtable/issues/111)) +- [x] `db backup|restore` and the telesto restic job ([#110](https://github.com/CryptoJones/FlatlineRoundtable/issues/110)) — shipped in PR #125 +- [ ] Fleet rollout notes and the Python 3.11 floor ([#111](https://github.com/CryptoJones/FlatlineRoundtable/issues/111)) — on docs/sqlite-store-fleet-rollout ### Epic: run the roundtable from inside Orca — local web UI, no fork ([#115](https://github.com/CryptoJones/FlatlineRoundtable/issues/115)) — DONE 2026-10-03 diff --git a/README.md b/README.md index b14aa27..1323245 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,61 @@ roundtable secrets check --config /tmp/drill/roundtable.db roundtable --config /tmp/drill/roundtable.db --list ``` +## Fleet + +Each host has its own store and its own DB key. makemake is the system of +record: its roster is exported and imported elsewhere, and each host sets its +own secrets under the same names (epic #105, decisions 2 and 6). Nothing is +shared live, so a host that is down or behind never blocks another. + +On makemake, after any roster change worth sharing: + +```sh +roundtable db export --out ~/roster.json # 0600, never holds a secret value +scp ~/roster.json pluto:roster.json +``` + +On the other host (pluto shown; its default `python3` is 3.9, with 3.11 beside it): + +```sh +git -C ~/source/repos/FlatlineRoundtable pull --ff-only +python3.11 -m pip install --user cryptography +cd ~/source/repos/FlatlineRoundtable && PYTHON=python3.11 ./install.sh +roundtable db init # this host's own DB key, in its pass +roundtable db import ~/roster.json --replace +# Secrets: same names as makemake, this host's values. pluto has one shared +# OpenRouter key, so every openrouter/agent/* name gets that one value. +for n in $(roundtable lanes list | cut -f7 | sed -n 's/^key=//p' | grep '^openrouter/agent/' | sort -u); do + pass show openrouter/api-key | roundtable secrets set "$n" --stdin +done +pass show nvidia/api-key | roundtable secrets set nvidia/api-key --stdin +roundtable secrets check # every name: present +roundtable --list +``` + +`PYTHON=python3.11 ./install.sh` installs `roundtable` as a two-line wrapper +that execs that interpreter, so the host's default `python3` is left alone. + +A host can keep its own value for a host-specific setting, such as a CLI path +or a wrapper. Set it with `lanes edit` after the import, with a `--reason`. +The next `db import --replace` from makemake puts makemake's value back, so +re-apply it after each import. On pluto that is GLaDOS, which reads its brief +through `~/.local/bin/grok-stdin`, because Linux caps one argument at 128 KB: + +```sh +roundtable lanes edit GLaDOS --set command=/home/akclark/.local/bin/grok-stdin --set stdin=true \ + --set 'args=["--no-auto-update","--disable-web-search","--no-subagents","--no-plan","--tools","","--max-turns","1","--output-format","plain","-m","grok-4.6"]' \ + --reason "pluto: Linux caps one argv at 128KB; grok-stdin passes --prompt-file" +roundtable globals set concurrency=16 # pluto's own capacity +``` + +A CLI lane also needs its CLI signed in on that host (`grok login --device-code`, +`codex login`, `claude`); the store holds only API-key secrets. + +After the move, a host's old YAML files are archived into `backups/` beside the +store (so `scripts/backup-telesto.sh` ships them) and removed, so nobody edits a +file that is no longer read. + ## Cost **`harness` decides cost, not `model`.** diff --git a/install.sh b/install.sh index aed4d75..7663873 100755 --- a/install.sh +++ b/install.sh @@ -10,6 +10,12 @@ TARGET="${HOME}/.claude/skills/flatline-roundtable" BIN_DIR="${HOME}/.local/bin" BIN_LINK="${BIN_DIR}/roundtable" STORE="${XDG_DATA_HOME:-${HOME}/.local/share}/flatline-roundtable/roundtable.db" +# PYTHON=python3.11 ./install.sh for a host whose default python3 is too old +# (pluto: 3.9 by default, 3.11 beside it). roundtable is then installed as a +# two-line wrapper that execs that interpreter, rather than a symlink that +# would run under the old default. +PYTHON="${PYTHON:-python3}" +WRAPPER_MARK="# flatline-roundtable install.sh wrapper" OLD_YAML="${HOME}/.config/flatline-roundtable/FlatlineRoundtable.yaml" die() { printf 'ERROR: %s\n' "$*" >&2; exit 1; } @@ -17,7 +23,7 @@ log() { printf '%s\n' "$*"; } uninstall() { for link in "${TARGET}" "${BIN_LINK}"; do - if [[ -L "${link}" ]]; then + if [[ -L "${link}" ]] || { [[ -f "${link}" ]] && grep -qF "${WRAPPER_MARK}" "${link}"; }; then rm "${link}"; log "Removed ${link}" elif [[ -e "${link}" ]]; then # Never delete something we did not create. @@ -35,10 +41,11 @@ uninstall() { [[ -x "${SCRIPT_DIR}/roundtable" ]] || die "Missing or non-executable ${SCRIPT_DIR}/roundtable." # The floor is 3.11 (the store code and CI assume it). pluto's default python3 # is 3.9 with 3.11 installed beside it, so name the fix rather than just failing. -python3 -c 'import sys; sys.exit(0 if sys.version_info >= (3, 11) else 1)' 2>/dev/null || \ - die "Python 3.11+ required; python3 is $(python3 -V 2>&1). Put python3.11 first on PATH." -python3 -c 'import cryptography' 2>/dev/null || \ - die "\`cryptography\` not installed — python3 -m pip install --user cryptography (the store needs it)" +command -v "${PYTHON}" >/dev/null || die "${PYTHON} not found" +"${PYTHON}" -c 'import sys; sys.exit(0 if sys.version_info >= (3, 11) else 1)' 2>/dev/null || \ + die "Python 3.11+ required; ${PYTHON} is $("${PYTHON}" -V 2>&1). Rerun with PYTHON=python3.11 ./install.sh" +"${PYTHON}" -c 'import cryptography' 2>/dev/null || \ + die "\`cryptography\` not installed — ${PYTHON} -m pip install --user cryptography (the store needs it)" # `pass` holds the store's DB key, which unlocks every lane secret. This is a # warning rather than a hard failure because a roster of only `cli` / `acp` @@ -87,7 +94,16 @@ check_pass mkdir -p "${HOME}/.claude/skills" "${BIN_DIR}" ln -sfn "${SKILL_SRC}" "${TARGET}"; log "Skill -> ${TARGET}" -ln -sfn "${SCRIPT_DIR}/roundtable" "${BIN_LINK}"; log "Binary -> ${BIN_LINK}" +if [[ "${PYTHON}" == python3 ]]; then + ln -sfn "${SCRIPT_DIR}/roundtable" "${BIN_LINK}"; log "Binary -> ${BIN_LINK}" +else + [[ -e "${BIN_LINK}" && ! -L "${BIN_LINK}" ]] && ! grep -qF "${WRAPPER_MARK}" "${BIN_LINK}" && \ + die "${BIN_LINK} exists and is not ours — refusing to overwrite it." + rm -f "${BIN_LINK}" + printf '#!/bin/sh\n%s\nexec '"'"'%s'"'"' '"'"'%s'"'"' "$@"\n' "${WRAPPER_MARK}" \ + "$(command -v "${PYTHON}")" "${SCRIPT_DIR}/roundtable" > "${BIN_LINK}" + chmod 755 "${BIN_LINK}"; log "Binary -> ${BIN_LINK} (wrapper: $(command -v "${PYTHON}"))" +fi if [[ -f "${STORE}" ]]; then log "Store found at ${STORE} — left untouched." @@ -98,6 +114,8 @@ else if [[ -f "${OLD_YAML}" ]]; then log " roundtable import-yaml ${OLD_YAML} --dry-run # needs PyYAML, once" log " roundtable import-yaml ${OLD_YAML} --pull-secrets" + log " (or, on a host other than makemake: db import makemake's export —" + log " see the README's Fleet section)" else log " roundtable db import ${SCRIPT_DIR}/examples/roster.example.json" fi