9217d1faaa8e9c58c3f5891ef157e3ea4d59b6ef
5
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2e7fb84454 |
fix three officer-setup bugs found on a general review, unrelated to macOS
env_get() never matched its own file. write_env() always writes OFFICERDEV_-prefixed keys, but env_get() compared against the bare name — verified against a real written .env: `env_get PORT` -> [], `env_get OFFICERDEV_PORT` -> [9000]. Every one of its 5 call sites uses the bare name, so this always came back empty. Two consequences: the "recover what earlier runs already decided" block never recovers anything — its own comment says it exists to fix "Build announced PUBLIC_URL not set on a machine whose .env had been written twenty minutes earlier", and that exact bug was still live — and re-running the Environment step showed the wrong default (9000, tailnet URL) instead of the machine's actual current config. Fixed to check the prefixed key first, falling back to the bare one for an old unprefixed file, matching the runtime's own `OFFICERDEV_X ?? X` priority. pg_password_from_env_file() returned success with an empty string when the compose .env existed but had no POSTGRES_PASSWORD= line. The caller (`pg_compose_exists && PG_EXISTING_PASSWORD="$(pg_password_from_env_file)"`) would then treat that as "reuse this password" and build POSTGRES_URL with a blank credential rather than falling through to provision fresh. Only reachable via a hand-edited or corrupted file — write_pg_compose always writes the line — but "silently reuse a blank password" was the wrong direction to fail in. Now returns 1 when the line is missing or empty. SETUP_DOCKER_NETWORK was read independently by both scripts, each defaulting to "services", but never actually persisted across the machine-setup -> officer-setup boundary — not one of the three keys load_machine_answers recognised. A customised bridge name from machine-setup would silently not carry over, and Proxy would land back on "services" regardless of what Docker was actually told to use. Now saved by machine-setup's save_answers(), recognised by officer-setup's load_machine_answers(), and PROXY_NET is re-derived from it right after that load — proxy.sh's own top-level assignment runs at source time, before the answers file could have populated the variable, same ordering issue ENV_PORT/ENV_PUBLIC_URL/POSTGRES_URL already had to solve. All three verified with isolated reproductions before and after: env_get against a real prefixed .env and a legacy bare one, pg_password_from_env_file against a normal and a corrupted compose .env, and the full save/load/re-derive chain for SETUP_DOCKER_NETWORK. bash -n clean on every touched file. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
681df1c8bc |
make machine-setup.sh and officer-setup.sh actually run on macOS
Both scripts had real macOS branches already — MACOS_SKIP, Xcode CLT, Docker Desktop
detection — but neither had ever run past its own library load on a real Mac, and
verifying that live is what this commit is. This machine is the Mac.
The root cause was the interpreter, not missing OS coverage: macOS's stock /bin/bash is
3.2.57 (Apple stopped shipping newer once bash went GPLv3) and this machine has no newer
one installed. Both scripts used bash 4+/4.3+ syntax with zero version guard, so a real
run died in seconds — declare -A (base.sh, associative array), local -n (packages.sh,
nameref), mapfile (5 sites), ${var,,} (both scripts' own step() — confirmed --only killed
the ENTIRE run), and machine-setup.sh's own pre-flight hard-refused brew outright ("this
script does not implement yet") even once the bash-4 crash was fixed. Each got rewritten
to its bash-3.2-safe equivalent rather than requiring a newer bash and re-executing under
it — nine call sites, a bounded fix, and one less thing that can go wrong before the
script can report what's wrong.
Past that gate, a live probe run (real account/path prompts, killed before Database/
Services could touch this machine's actual state) reproduced a second bug in the first
few seconds: resolve_user_home used getent (Linux/glibc, absent on macOS) with a
hardcoded /home/<user> fallback, so Layout tried `install -d /home/pastilhas` and failed
with "Operation not supported". Fixed with the portable `eval echo ~user` tilde-expansion
idiom, re-ran the same probe, and it correctly created /Users/pastilhas/officerdev/ and
started a real git clone.
Everything else follows the same shape — an unguarded Linux-only tool with a portable or
brew-based macOS equivalent:
- grep -oP (4 sites: both --list flags, env.sh's tailnet lookup, build.sh's schema
table count) — BSD grep has no -P at all. Portable ERE+sed where regex was enough;
jq where the input was already JSON (tailnet lookup) — jq is already an unconditional
dependency elsewhere in this script, so that's a correctness fix, not just a port.
- ss (postgres.sh, proxy.sh — is anything listening on this port, and is it bound wide
enough for NPM's container to reach) — both silently mis-reported on macOS, since the
"command not found" was swallowed by 2>/dev/null and read as "nothing's listening".
New shared `listening_addrs()` in officer-setup's base.sh, branching to lsof.
- getent hosts (proxy.sh's DNS-wait loop) — same silent-failure shape, would have spun
its full 30-minute timeout regardless of whether the record actually resolved. macOS
branch uses dscacheutil.
- stat -c (layout.sh's ownership check) — BSD stat has no -c, confirmed live ("illegal
option -- c"), and the failure was VISIBLE and made every layout directory report as
wrong-owner. Portable file_owner() tries -c then falls back to -f %Su.
- apt-only Postgres client, NodeSource's Debian-only setup script, lazydocker/lazygit's
installers (one literally named install_update_linux.sh, the other hardcoding
"_Linux_" into the release asset URL) — each gets a brew branch, formula names
verified against the live Homebrew catalog rather than assumed.
- sudo -u "$USERNAME" to become the SAME account the process already is — real on
Linux, where this whole script runs as root and dropping to the owner needs no
password; on macOS the process never escalates in the first place, so re-invoking
sudo for yourself asks for a real password on every call, or fails outright without
sudo rights. machine-setup's dev.sh had ~10 inline call sites (git, bun, deno, claude,
opencode, editor detection) rerouted through a new shared run_as_owner(); officer-
setup's own as_owner() (already the shared entry point there) got the same branch,
and six direct sudo call sites in build.sh/secrets.sh/services.sh that bypassed it
were rerouted through it too.
Three things stay Linux-only rather than getting an unverified macOS implementation,
each skipped with a stated reason at the point it would otherwise run: pm2's boot-
persistence hook (needs a launchd plist generated and loaded, not a systemd unit
printed and eval'd — real, separate work), the Tailscale exit-node NIC/sysctl tuning
(no macOS equivalent concept, and advertising a laptop as an exit node is already the
edge case MACOS_SKIP treats it as), and ufw's bridge-to-host firewall rule (pf is a
different rule language entirely, and Docker Desktop's own VM-based networking may not
even have the problem this rule exists to solve — guessing at a firewall rule felt like
the wrong place to be wrong).
Verified: both --list flags print step names instead of a grep usage error. A bounded
officer-setup.sh run (killed before Database) resolved the real macOS home directory,
created the layout with correct ownership, and began a real git clone — cleaned up
after (rm -rf the test directory, the gitignored .setup-progress file, the stray
install-report). machine-setup.sh's pre-flight now reports "Packages: brew" and reaches
the account/path prompts instead of refusing the OS outright. bash -n clean on all 16
files under bash 3.2, the actual interpreter these scripts run under on a Mac.
Not run further live: Database, Secrets, Services and Proxy would mutate this machine's
real state (a live Postgres, a live set of pm2 processes this session runs through) and
were left for a deliberate, supervised run rather than folded into validation.
|
||
|
|
b2349b5480 |
install the psql client, matching the server it talks to
there was no psql on this machine. the server runs in a container, so nothing ever put a client on the host, and `docker exec officer-postgres psql` is the owner's tool — a member has their own Postgres role and no access to the owner's Docker socket. the version is derived from PG_IMAGE rather than typed again, because the pairing is load-bearing: pg_dump refuses a server newer than itself, and Ubuntu 24.04 ships client 16 against this 18 server. so the archive package is not merely old, it is unusable for dumps. that is also why this sits beside the server definition instead of in machine-setup's package list — one constant, one place to bump. PGDG added the same way docker.sh adds Docker's: key in its own file, one sources.list.d entry, no add-apt-repository. non-fatal, and the exit status is not the gate — apt can succeed while holding an older client back, so the check is that psql is present AND is the major we asked for. installed by hand on this host already: psql/pg_dump 18.6, verified as green connecting with their own role. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
0bceace6f1 |
an officerdev docker network, and the reasoning next to the port binding
One network for everything Officer provisions, created before anything joins it and declared external in the compose file. Postgres needs nothing from it today — the platform is a host process reaching it over loopback — but a reverse proxy in front of the web UI does, and so does any app-store service that talks to another. Creating it now means the later ones do not have to be migrated onto it. Two things written next to the line they explain, rather than assumed: Why loopback. Publishing a port makes Docker write its own DNAT and ACCEPT rules into iptables, and those are evaluated BEFORE ufw sees the packet — so `ports: "5432:5432"` is reachable from the internet while `ufw status` reports everything denied. That is the same mechanism the machine-setup firewall section hooks DOCKER-USER to close. Binding to 127.0.0.1 sidesteps it: the DNAT rule only matches traffic arriving on loopback. Why the password is not decoration. Loopback means nothing off this machine, but every account ON it can open 127.0.0.1:5432 — including the per-user Linux accounts Officer gives its members. What stops them is that they cannot authenticate. The password is the boundary between the platform and anyone with a login here, which is why it stays random and why both files holding it are 0600. A unix socket would remove even that, and was ruled out for a specific reason: postgres.js only treats a host as a socket path when the host FIELD contains a slash (src/index.js:468), and officer_db/src/db.ts passes a bare URL string. It would take a change to db.ts, which is not a setup-script change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a64d5610e6 |
officer-setup section 5: Postgres, and only Postgres
The original offered five containers. Of those:
Postgres the only database Officer has — account, passkeys, settings,
dashboards, email accounts, the queue. Required.
Redis not referenced anywhere in the platform. No import of the client,
no environment variable, no mention; the only "redis" string in
src/ is the word "rediscover" in a comment. Dropped. (It is still
in package.json and comes out in the dependency pass.)
SearXNG zero references anywhere. Dropped. If it ever arrives it brings
its own compose file and its own Redis with it.
Mailhog a development convenience, offered separately rather than here.
Nginx PM a deployment choice — Caddy, Traefik, nginx or the tailnet — and
not something a setup script should pick.
Provisioned into $OFFICER_ROOT/dockers/postgres/, the same convention the app
store uses: one directory per service, the compose file in it, relative bind
mounts so the data sits beside the compose file.
Bound to 127.0.0.1, deliberately and with the reason in the compose file itself.
Docker publishes ports by writing iptables rules underneath ufw, so "5432:5432"
is reachable from the internet whatever the firewall reports — the same mechanism
the machine-setup firewall section exists to close. The platform runs on this
machine, so loopback is all it needs.
The password lives in a 0600 .env beside the compose file rather than inside it,
so the compose file can be read or copied without carrying a credential. A second
run reuses it rather than minting a new one, which would leave the container and
the URL disagreeing.
Readiness is waited for rather than assumed: Postgres initialises its data
directory on first start, and db:push against a database that is still starting
fails in a way that reads as a schema problem.
Choosing an existing database checks the URL but does not insist on it — the URL
may be right and the database not yet started, and refusing to continue over that
would be worse than saying so.
Also carries the whitespace fix for the comment removed in the previous commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|