Playbook: deploying gh-app-token to /usr/local/bin (fleet)
Status: ACTIVE (created 2026-08-11, #6103 — follow-up from #6096)
What this covers
Keeping the deployed on-PATH helper /usr/local/bin/gh-app-token in sync with the
canonical repo implementation scripts/gh-app-token (208-line Python, argparse-based,
built for #4068). This is the operator-facing binary invoked as a bare gh-app-token
(e.g. gh-app-token --check) on a fleet host.
Why it matters — the footgun this fixes (#6103)
There are two copies of this helper on each host and they drift independently:
| Path | Kept current by | Notes |
|---|---|---|
/opt/p24-infra/scripts/gh-app-token | secrets-sync.yml / the repo checkout on that host | canonical; tracks origin/main |
/usr/local/bin/gh-app-token | nothing — manual deploy only | the drift source |
bin/gh-app-token.sh (wrapper) | repo checkout | resolves repo root, always current |
No CI job or Ansible role deploys /usr/local/bin/gh-app-token. It was placed manually and
never refreshed, so it lags the repo. The stale copy (104-line, sha256 5031655d…) predates
argparse entirely: it has no argument parsing, so --check (and --repos / --permissions)
is silently ignored — the script always mints a fresh 1h installation token and prints it to
stdout unconditionally. Running the documented safe verification gh-app-token --check
against the stale copy therefore does the opposite of what it promises: it leaks a live token
to the terminal. #6096 hit exactly this on dev-laptop.
The current 208-line version (sha256 ca015e61…, at repo HEAD) is a strict superset and
backward compatible: a bare gh-app-token still prints the default cached token, so existing
$(gh-app-token) callers are unaffected. --check prints only a status line to stderr,
never the token, and exits 0.
Preconditions
- Root on the target host (the deployed file is
root:root 0755). - The current
scripts/gh-app-tokenreachable on/for the host:- bms-4 / vps-i1: already present at
/opt/p24-infra/scripts/gh-app-token(repo checkout). - dev-laptop: has no current checkout (its
/opt/p24-infrais on the olddevbranch, 85-line copy) — transport the file from bms-4 via the vps-i1 reverse tunnel.
- bms-4 / vps-i1: already present at
Deploy procedure (per host)
Always back up, install with explicit mode, then verify with --check:
# 1. Back up the existing deployed copy (timestamped).
cp -a /usr/local/bin/gh-app-token "/usr/local/bin/gh-app-token.bak-$(date +%Y%m%d%H%M%S)"
# 2. Install the current repo version (root:root 0755). `install` sets mode atomically.
install -m 0755 -o root -g root /path/to/current/scripts/gh-app-token /usr/local/bin/gh-app-token
# 3. Verify byte-for-byte against the source.
sha256sum /usr/local/bin/gh-app-token # must equal the repo-HEAD sha (ca015e61… as of #6103)
# 4. Verify --check is SAFE: status line to stderr only, token NEVER on stdout, exit 0.
# Capture stdout separately so a regression (token on stdout) is caught, not printed.
out=$(gh-app-token --check 2>/tmp/ghapp.err); rc=$?
echo "exit=$rc"; echo "stdout_bytes=$(printf '%s' "$out" | wc -c)" # expect exit=0, stdout_bytes=0
grep -q '^gh-app-token: OK' /tmp/ghapp.err && echo "stderr status OK"; rm -f /tmp/ghapp.errstdout_bytes=0 + exit=0 + an OK status on stderr = the deployed copy is the current,
safe version. Any non-zero stdout byte count means a token was printed — stop, the wrong
binary is still in place.
Reaching dev-laptop (no public IP)
dev-laptop (Ubuntu, radieu-Aspire-F5-573G) is reachable only via the autossh reverse tunnel
through vps-i1 (vps-i1:127.0.0.1:2222 → dev-laptop:22). From bms-4:
# vps-i1's ~/.ssh/config defines a `dev-laptop` alias (IdentityFile /root/.ssh/p24-dispatcher).
ssh root@217.154.82.162 'ssh dev-laptop "<command>"'
# Transport a file bms-4 → vps-i1 → dev-laptop:
scp /opt/p24-infra/scripts/gh-app-token root@217.154.82.162:/tmp/gh-app-token.new
ssh root@217.154.82.162 'scp /tmp/gh-app-token.new dev-laptop:/tmp/gh-app-token.new && \
ssh dev-laptop "install -m0755 /tmp/gh-app-token.new /usr/local/bin/gh-app-token"'The worker’s bms-4 key has no root on dev-laptop directly; the p24-dispatcher key lives on vps-i1, so all dev-laptop hops route through vps-i1.
Known gap — no automated sync (root cause of the drift)
/usr/local/bin/gh-app-token has no owning deploy automation. Until one exists, every bump of
scripts/gh-app-token must be manually re-deployed to each host’s /usr/local/bin via this
playbook. The durable fix is a secrets-sync.yml (or Ansible) step that installs the repo
copy into /usr/local/bin/gh-app-token on each dispatch host on every sync — tracked as
follow-up to #6103. Prefer pointing operators at bin/gh-app-token.sh (always current) over
the standalone deployed binary in the meantime.