feat(provision): fill card config from env, git, keychain, or prompts
typoena.conf no longer requires a hand-written firmware/.env. _write-conf now resolves each value through a ladder: .env if set, else derived from tools already on the machine (git config, gh, the active Wi-Fi network + its System keychain password), else an interactive prompt with the derived value as the default. The PAT is never derived (a broad gh token on a plaintext card would defeat the scoped-token model) and is always typed by hand. Missing required values now abort before touching the card instead of ejecting a blank config.
This commit is contained in:
@@ -123,7 +123,14 @@ ports:
|
||||
#
|
||||
# just init ~/code/notes # full prep of a fresh card: repo + config
|
||||
# just load ~/code/notes # (re)copy just the notes repo → /sd/repo
|
||||
# just provision # (re)write just the config from firmware/.env
|
||||
# just provision # (re)write just the config (derive + prompt)
|
||||
#
|
||||
# Config (Wi-Fi + PAT + git identity) needs no firmware/.env: each value is
|
||||
# resolved from firmware/.env if present, else derived from tools the machine
|
||||
# already has (git config, gh, the active Wi-Fi network + its Keychain
|
||||
# password), else asked for at an interactive prompt with the derived value as
|
||||
# the default. The PAT is always typed by hand — never derived (a broad
|
||||
# `gh auth token` on a plaintext card would defeat the scoped-token model).
|
||||
#
|
||||
# Add a /Volumes/<name> as the last arg if more than one card is mounted.
|
||||
# The `_`-prefixed recipes are shared internals (hidden from `just --list`).
|
||||
@@ -136,7 +143,7 @@ init repo_src sd_volume="":
|
||||
set -euo pipefail
|
||||
vol="$(just _card "{{sd_volume}}" | tail -n1)"
|
||||
just _load-repo "{{repo_src}}" "$vol"
|
||||
just _write-conf "$vol"
|
||||
just _write-conf "$vol" "{{repo_src}}"
|
||||
just _eject "$vol"
|
||||
|
||||
# (Re)copy just the notes repo to /sd/repo (full clone, gitignored paths
|
||||
@@ -148,8 +155,10 @@ load repo_src sd_volume="":
|
||||
just _load-repo "{{repo_src}}" "$vol"
|
||||
just _eject "$vol"
|
||||
|
||||
# (Re)write just the config (Wi-Fi + PAT + git identity) from firmware/.env,
|
||||
# then eject — e.g. to rotate the PAT or switch networks without touching repo/.
|
||||
# (Re)write just the config (Wi-Fi + PAT + git identity), then eject — e.g. to
|
||||
# rotate the PAT or switch networks without touching repo/. Values are derived +
|
||||
# prompted (see the section header); firmware/.env is an optional override. With
|
||||
# no repo arg, the git remote is derived from the card's existing clone.
|
||||
provision sd_volume="":
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
@@ -237,38 +246,106 @@ _load-repo repo_src vol:
|
||||
# -P for progress on the ~700 MB copy. Scoped to repo/, never the card root.
|
||||
rsync -rtP --delete --modify-window=1 --exclude-from="$ignore_list" "$src/" "$dest/"
|
||||
|
||||
# Write <vol>/typoena.conf (Wi-Fi + PAT + git identity) from firmware/.env
|
||||
# (already loaded via `set dotenv-load`), the same file the build uses — so no
|
||||
# re-typing creds, no prompts, no network. The PAT is written to the file but
|
||||
# only ever reported as set/MISSING, never echoed. FAT has no file permissions,
|
||||
# so physical custody of the card is the control: use a fine-grained PAT
|
||||
# (contents:write on just the notes repo) so a lost card is a one-token revoke.
|
||||
_write-conf vol:
|
||||
# Resolve + write <vol>/typoena.conf (Wi-Fi + PAT + git identity). Each value
|
||||
# runs a ladder: firmware/.env (loaded via `set dotenv-load`) → derived from
|
||||
# tools already on the machine (git config, gh, the active Wi-Fi network + its
|
||||
# System-keychain password) → interactive prompt, with the derived value as the
|
||||
# default. So a newcomer needs no .env: they Enter through the defaults and paste
|
||||
# a PAT once. The PAT is never derived (a broad `gh auth token` on plaintext
|
||||
# removable media would defeat the scoped-token model) and never echoed. FAT has
|
||||
# no file permissions, so physical custody of the card is the control: use a
|
||||
# fine-grained PAT (contents:write on just the notes repo) so a lost card is a
|
||||
# one-token revoke. <repo_src> (optional) seeds the git remote; without it the
|
||||
# remote is derived from the card's existing clone.
|
||||
_write-conf vol repo_src="":
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
conf="{{vol}}/typoena.conf"
|
||||
if [ -n "${TW_WIFI_SSID:-}${TW_REMOTE_URL:-}${TW_PAT:-}" ]; then
|
||||
{
|
||||
printf '# Typoena runtime config — generated by `just provision` from firmware/.env.\n'
|
||||
printf '# Plaintext secrets on removable media: keep the card safe; scope TW_PAT\n'
|
||||
printf '# to contents:write on just the notes repo. `key=value`, `#` = comment.\n'
|
||||
printf 'TW_WIFI_SSID=%s\n' "${TW_WIFI_SSID:-}"
|
||||
printf 'TW_WIFI_PASS=%s\n' "${TW_WIFI_PASS:-}"
|
||||
printf 'TW_REMOTE_URL=%s\n' "${TW_REMOTE_URL:-}"
|
||||
printf 'TW_GH_USER=%s\n' "${TW_GH_USER:-}"
|
||||
printf 'TW_PAT=%s\n' "${TW_PAT:-}"
|
||||
printf 'TW_AUTHOR_NAME=%s\n' "${TW_AUTHOR_NAME:-}"
|
||||
printf 'TW_AUTHOR_EMAIL=%s\n' "${TW_AUTHOR_EMAIL:-}"
|
||||
} > "$conf"
|
||||
mask() { if [ -n "${1:-}" ]; then printf 'set'; else printf 'MISSING'; fi; }
|
||||
echo "wrote $conf (values reused from firmware/.env, never printed):"
|
||||
echo " wifi: ssid=$(mask "${TW_WIFI_SSID:-}") pass=$(mask "${TW_WIFI_PASS:-}")"
|
||||
echo " git: remote=$(mask "${TW_REMOTE_URL:-}") gh_user=$(mask "${TW_GH_USER:-}") pat=$(mask "${TW_PAT:-}")"
|
||||
echo " author: name=$(mask "${TW_AUTHOR_NAME:-}") email=$(mask "${TW_AUTHOR_EMAIL:-}")"
|
||||
else
|
||||
echo "note: no TW_* values in firmware/.env — leaving any existing $conf untouched"
|
||||
echo " (copy .env.example → firmware/.env and fill in Wi-Fi + PAT to provision)"
|
||||
repo_src="{{repo_src}}"; repo_src="${repo_src%/}"
|
||||
interactive=1; [ -t 0 ] || interactive=0
|
||||
|
||||
# ask "label" "default" → chosen value on stdout; prompt shown on stderr (so
|
||||
# it survives the $(...) capture). Non-interactive → returns the default.
|
||||
ask() {
|
||||
local label="$1" def="${2:-}" ans
|
||||
if [ "$interactive" = 0 ]; then printf '%s' "$def"; return; fi
|
||||
read -r -p " $label${def:+ [$def]}: " ans || ans=""
|
||||
printf '%s' "${ans:-$def}"
|
||||
}
|
||||
# like ask but hides input (secrets). A present default reads as "keep current".
|
||||
ask_secret() {
|
||||
local label="$1" def="${2:-}" ans
|
||||
if [ "$interactive" = 0 ]; then printf '%s' "$def"; return; fi
|
||||
read -r -s -p " $label${def:+ [keep current]}: " ans || ans=""; printf '\n' >&2
|
||||
printf '%s' "${ans:-$def}"
|
||||
}
|
||||
|
||||
# ── derive defaults (silent; empty when a tool is missing/unauthed) ──────────
|
||||
# remote: .env → source repo's origin → the card's existing clone.
|
||||
remote="${TW_REMOTE_URL:-}"
|
||||
if [ -z "$remote" ] && [ -n "$repo_src" ] && [ -d "$repo_src/.git" ]; then
|
||||
remote="$(git -C "$repo_src" remote get-url origin 2>/dev/null || true)"
|
||||
fi
|
||||
if [ -z "$remote" ] && [ -d "{{vol}}/{{sd_repo_dir}}/.git" ]; then
|
||||
remote="$(git -C "{{vol}}/{{sd_repo_dir}}" remote get-url origin 2>/dev/null || true)"
|
||||
fi
|
||||
a_name="${TW_AUTHOR_NAME:-$(git config user.name 2>/dev/null || true)}"
|
||||
a_email="${TW_AUTHOR_EMAIL:-$(git config user.email 2>/dev/null || true)}"
|
||||
gh_user="${TW_GH_USER:-}"
|
||||
[ -z "$gh_user" ] && gh_user="$(gh api user --jq .login 2>/dev/null || true)"
|
||||
# ssid: .env → the Mac's active Wi-Fi network.
|
||||
wifi_if="$(networksetup -listallhardwareports 2>/dev/null | awk '/Wi-Fi/{getline; print $2; exit}')"
|
||||
ssid="${TW_WIFI_SSID:-}"
|
||||
[ -z "$ssid" ] && ssid="$(networksetup -getairportnetwork "${wifi_if:-en0}" 2>/dev/null | sed -n 's/^Current Wi-Fi Network: //p')"
|
||||
wifi_pass="${TW_WIFI_PASS:-}"
|
||||
pat="${TW_PAT:-}"
|
||||
|
||||
# ── confirm / fill via prompts ──────────────────────────────────────────────
|
||||
[ "$interactive" = 1 ] && echo "configuring $conf — press Enter to accept each [default]:" >&2
|
||||
ssid="$(ask "Wi-Fi SSID" "$ssid")"
|
||||
# Keychain read happens after the SSID is final (the user may have edited it).
|
||||
# Reading a System-keychain Wi-Fi password can pop a macOS auth dialog — only
|
||||
# attempt it interactively so scripted runs never trigger a surprise prompt.
|
||||
if [ -z "$wifi_pass" ] && [ -n "$ssid" ] && [ "$interactive" = 1 ]; then
|
||||
echo " looking up Wi-Fi password for '$ssid' in Keychain (approve the macOS dialog if it appears)…" >&2
|
||||
wifi_pass="$(security find-generic-password -wa "$ssid" 2>/dev/null || true)"
|
||||
fi
|
||||
wifi_pass="$(ask_secret "Wi-Fi password" "$wifi_pass")"
|
||||
remote="$(ask "Git remote URL" "$remote")"
|
||||
gh_user="$(ask "GitHub username" "$gh_user")"
|
||||
pat="$(ask_secret "GitHub PAT (fine-grained, contents:write)" "$pat")"
|
||||
a_name="$(ask "Commit author name" "$a_name")"
|
||||
a_email="$(ask "Commit author email" "$a_email")"
|
||||
|
||||
# ── require the essentials (a blank config ships a dead device) ──────────────
|
||||
missing=""
|
||||
[ -n "$ssid" ] || missing="$missing Wi-Fi-SSID"
|
||||
[ -n "$remote" ] || missing="$missing git-remote-URL"
|
||||
[ -n "$pat" ] || missing="$missing GitHub-PAT"
|
||||
if [ -n "$missing" ]; then
|
||||
echo "error: missing required config:$missing" >&2
|
||||
[ "$interactive" = 0 ] && echo " (no TTY — set these in firmware/.env or run interactively)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── write ───────────────────────────────────────────────────────────────────
|
||||
{
|
||||
printf '# Typoena runtime config — generated by `just init`/`provision`.\n'
|
||||
printf '# Plaintext secrets on removable media: keep the card safe; scope TW_PAT\n'
|
||||
printf '# to contents:write on just the notes repo. `key=value`, `#` = comment.\n'
|
||||
printf 'TW_WIFI_SSID=%s\n' "$ssid"
|
||||
printf 'TW_WIFI_PASS=%s\n' "$wifi_pass"
|
||||
printf 'TW_REMOTE_URL=%s\n' "$remote"
|
||||
printf 'TW_GH_USER=%s\n' "$gh_user"
|
||||
printf 'TW_PAT=%s\n' "$pat"
|
||||
printf 'TW_AUTHOR_NAME=%s\n' "$a_name"
|
||||
printf 'TW_AUTHOR_EMAIL=%s\n' "$a_email"
|
||||
} > "$conf"
|
||||
mask() { if [ -n "${1:-}" ]; then printf 'set'; else printf 'MISSING'; fi; }
|
||||
echo "wrote $conf (secrets never printed):"
|
||||
echo " wifi: ssid=$(mask "$ssid") pass=$(mask "$wifi_pass")"
|
||||
echo " git: remote=$(mask "$remote") gh_user=$(mask "$gh_user") pat=$(mask "$pat")"
|
||||
echo " author: name=$(mask "$a_name") email=$(mask "$a_email")"
|
||||
|
||||
# Flush + eject so the card can go straight into Typoena.
|
||||
_eject vol:
|
||||
|
||||
Reference in New Issue
Block a user