diff --git a/.env.example b/.env.example index 737fbeb..e074db9 100644 --- a/.env.example +++ b/.env.example @@ -52,11 +52,18 @@ JIRA_STORE_CUSTOM_FIELD_ID=customfield_10261 # This PAT is used ONLY for reading the Stores schema; nothing that mutates # Jira state uses it. # -# ASSETS_SYNC_TOKEN should be exported at runtime from macOS Keychain via -# bin/load-assets-sync-secret.sh, NOT hardcoded here. It's listed only for -# completeness / documentation. +# The .env file is the primary supported storage for the PAT (the bot runs on +# a Linux host; macOS Keychain isn't available there). Because .env stays out +# of source control (.gitignore) and app.log no longer echoes auth headers, +# the cleartext token here is scoped to whoever has filesystem access on the +# deploy host — lock the file down with `chmod 600 .env` and rotate the token +# if that trust changes. +# +# For local dev on macOS you can instead source the token from Keychain via +# bin/load-assets-sync-secret.sh (see README) and leave ASSETS_SYNC_TOKEN out +# of .env entirely. ASSETS_SYNC_EMAIL=you@ae.com -# ASSETS_SYNC_TOKEN= # loaded from Keychain by bin/load-assets-sync-secret.sh +ASSETS_SYNC_TOKEN=REPLACE_ME # Where the local cache lives on disk (JSON). Gitignored. Regenerable via # POST /api/wxccai/admin/storesCache/refresh. diff --git a/README.md b/README.md index e325e40..64d1562 100644 --- a/README.md +++ b/README.md @@ -68,27 +68,49 @@ Base path: `/api/wxccai`. `createSSRequest` needs to translate a store number into an Assets object id. The shared service account is silently filtered out of the Store object type, so the app maintains a local `storeNumber → objectId` cache that's populated from a **personal Atlassian PAT** (a real human account with the right Assets role). The service account is still used for everything else (creating tickets, comments, attachments). -**One-time setup:** +Both storage patterns end up in the same place — `process.env.ASSETS_SYNC_TOKEN` — so the runtime code path is identical. Pick whichever fits the host. + +### Setup A — production / Linux host (`.env`) + +Put the values directly in `.env` (which is gitignored) and lock the file down: + +```bash +cat >> .env <<'EOF' +ASSETS_SYNC_EMAIL=you@ae.com +ASSETS_SYNC_TOKEN= +EOF + +chmod 600 .env # only the bot user can read it +``` + +Then start normally: + +```bash +npm start +``` + +Rotate the token in Atlassian → Account → Security → API tokens whenever the trust boundary on the host changes (new operator, offboarding, suspected leak). The app reloads it on the next process start. + +### Setup B — local dev on macOS (Keychain) + +If you're running the app on a Mac and would rather not keep the PAT in `.env`, use the wrapper script — it pulls the token from Keychain into `ASSETS_SYNC_TOKEN` before exec'ing the process: ```bash -# Store your PAT in macOS Keychain (never touches disk in cleartext) security add-generic-password \ -s jira-assets-sync \ -a you@ae.com \ -w '' \ -U -# Export the account name via .env / your shell -echo 'ASSETS_SYNC_EMAIL=you@ae.com' >> .env -``` +echo 'ASSETS_SYNC_EMAIL=you@ae.com' >> .env # email in .env, token stays in Keychain -**Run the server:** - -```bash -# Wrapper loads the PAT from Keychain into ASSETS_SYNC_TOKEN before exec ./bin/load-assets-sync-secret.sh npm start ``` +If `ASSETS_SYNC_TOKEN` is already in the process env (Setup A above), the wrapper is a no-op and skips the Keychain lookup. + +### Runtime behavior + On boot the app loads the on-disk cache at `data/stores.json`, kicks off a background refresh if the snapshot is missing or older than `STORES_CACHE_STALE_AFTER_HOURS`, and schedules a periodic full resync every `STORES_CACHE_REFRESH_HOURS`. `resolveStoreAssetReference` then serves lookups from memory (sub-ms) with a live PAT lookup as fallback for brand-new stores. Force a refresh at any time: diff --git a/bin/load-assets-sync-secret.sh b/bin/load-assets-sync-secret.sh index 224d43d..6bf1468 100755 --- a/bin/load-assets-sync-secret.sh +++ b/bin/load-assets-sync-secret.sh @@ -9,8 +9,11 @@ # Rationale: # The service account is silently filtered out of Object Type 109 (Store # Address / Hierarchy) — see Forgejo issue #1. The stores cache is populated -# using a personal PAT instead. This PAT authenticates as a real user and so -# should NEVER sit in .env in cleartext; the Keychain is a safer store. +# using a personal PAT instead. On a shared dev Mac, Keychain is a nicer home +# for that PAT than .env (encrypted at rest, per-user access). On the prod +# Linux host where the bot actually runs, put the PAT in .env directly with +# `chmod 600 .env` — this script exits as a no-op if ASSETS_SYNC_TOKEN is +# already exported into the process env. # # Setup (one-time, per machine): #