generated from coulomb/repo-seed
Fixed and improved token tracking
This commit is contained in:
369
scripts/bootstrap-env.sh
Executable file
369
scripts/bootstrap-env.sh
Executable file
@@ -0,0 +1,369 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
STATE_HUB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
GITEA_CONF="${GITEA_CONF:-$HOME/.railiance_gitea.conf}"
|
||||
GITEA_URL="${GITEA_URL:-http://92.205.130.254:32166}"
|
||||
GITEA_USER="${GITEA_USER:-}"
|
||||
GITEA_TOKEN="${GITEA_TOKEN:-}"
|
||||
GIT_HELPER="${GIT_HELPER:-auto}"
|
||||
INSTALL_MISSING=0
|
||||
NON_INTERACTIVE=0
|
||||
DRY_RUN=0
|
||||
AUTHORIZE_SSH=0
|
||||
ALLOW_PLAINTEXT_STORE=0
|
||||
SKIP_GITEA=0
|
||||
SKIP_MCP=0
|
||||
SSH_KEY="${SSH_KEY:-$HOME/.ssh/id_ed25519}"
|
||||
SSH_TARGETS=(
|
||||
"tegwick@92.205.62.239"
|
||||
"tegwick@92.205.130.254"
|
||||
)
|
||||
|
||||
usage() {
|
||||
cat <<'USAGE'
|
||||
Usage: scripts/bootstrap-env.sh [options]
|
||||
|
||||
Idempotently prepares a State Hub operator or collaborator environment.
|
||||
|
||||
Options:
|
||||
--install-missing Install missing apt packages when possible.
|
||||
--non-interactive Do not prompt; warn instead of asking for secrets.
|
||||
--dry-run Show intended actions without changing local config.
|
||||
--git-helper MODE auto, libsecret, cache, or store. Default: auto.
|
||||
--allow-plaintext-store Allow git credential.helper=store in auto mode.
|
||||
--authorize-ssh Run ssh-copy-id for configured SSH targets.
|
||||
--ssh-target USER@HOST Add an SSH authorization target. May repeat.
|
||||
--gitea-url URL Gitea base URL for ~/.railiance_gitea.conf.
|
||||
--gitea-user USER Gitea user for ~/.railiance_gitea.conf.
|
||||
--gitea-token TOKEN Gitea token; otherwise prompted when interactive.
|
||||
--skip-gitea Do not create or update ~/.railiance_gitea.conf.
|
||||
--skip-mcp Do not run make register-mcp.
|
||||
-h, --help Show this help.
|
||||
USAGE
|
||||
}
|
||||
|
||||
ok() { printf '[OK] %s\n' "$*"; }
|
||||
warn() { printf '[WARN] %s\n' "$*"; }
|
||||
err() { printf '[ERR] %s\n' "$*" >&2; }
|
||||
step() { printf '\n==> %s\n' "$*"; }
|
||||
|
||||
run() {
|
||||
if [ "$DRY_RUN" -eq 1 ]; then
|
||||
printf 'DRY-RUN: %s\n' "$*"
|
||||
else
|
||||
"$@"
|
||||
fi
|
||||
}
|
||||
|
||||
need_arg() {
|
||||
if [ -z "${2:-}" ]; then
|
||||
err "$1 requires a value"
|
||||
exit 2
|
||||
fi
|
||||
}
|
||||
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--install-missing)
|
||||
INSTALL_MISSING=1
|
||||
shift
|
||||
;;
|
||||
--non-interactive)
|
||||
NON_INTERACTIVE=1
|
||||
shift
|
||||
;;
|
||||
--dry-run)
|
||||
DRY_RUN=1
|
||||
shift
|
||||
;;
|
||||
--git-helper)
|
||||
need_arg "$1" "${2:-}"
|
||||
GIT_HELPER="$2"
|
||||
shift 2
|
||||
;;
|
||||
--allow-plaintext-store)
|
||||
ALLOW_PLAINTEXT_STORE=1
|
||||
shift
|
||||
;;
|
||||
--authorize-ssh)
|
||||
AUTHORIZE_SSH=1
|
||||
shift
|
||||
;;
|
||||
--ssh-target)
|
||||
need_arg "$1" "${2:-}"
|
||||
SSH_TARGETS+=("$2")
|
||||
shift 2
|
||||
;;
|
||||
--gitea-url)
|
||||
need_arg "$1" "${2:-}"
|
||||
GITEA_URL="$2"
|
||||
shift 2
|
||||
;;
|
||||
--gitea-user)
|
||||
need_arg "$1" "${2:-}"
|
||||
GITEA_USER="$2"
|
||||
shift 2
|
||||
;;
|
||||
--gitea-token)
|
||||
need_arg "$1" "${2:-}"
|
||||
GITEA_TOKEN="$2"
|
||||
shift 2
|
||||
;;
|
||||
--skip-gitea)
|
||||
SKIP_GITEA=1
|
||||
shift
|
||||
;;
|
||||
--skip-mcp)
|
||||
SKIP_MCP=1
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
err "unknown argument: $1"
|
||||
usage >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
case "$GIT_HELPER" in
|
||||
auto|libsecret|cache|store) ;;
|
||||
*)
|
||||
err "--git-helper must be auto, libsecret, cache, or store"
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
apt_install() {
|
||||
local packages=("$@")
|
||||
if [ "$INSTALL_MISSING" -ne 1 ]; then
|
||||
warn "Missing packages: ${packages[*]}"
|
||||
warn "Rerun with --install-missing or install them manually."
|
||||
return
|
||||
fi
|
||||
if ! command -v sudo >/dev/null 2>&1; then
|
||||
warn "sudo is not available; cannot install: ${packages[*]}"
|
||||
return
|
||||
fi
|
||||
run sudo apt-get update
|
||||
run sudo apt-get install -y "${packages[@]}"
|
||||
}
|
||||
|
||||
check_commands() {
|
||||
step "Checking prerequisites"
|
||||
local missing=()
|
||||
local commands=(git curl ssh-keygen ssh-copy-id python3 make)
|
||||
local optional=(sops age helm kubectl uv claude)
|
||||
|
||||
for cmd in "${commands[@]}"; do
|
||||
if command -v "$cmd" >/dev/null 2>&1; then
|
||||
ok "$cmd found"
|
||||
else
|
||||
missing+=("$cmd")
|
||||
warn "$cmd missing"
|
||||
fi
|
||||
done
|
||||
|
||||
for cmd in "${optional[@]}"; do
|
||||
if command -v "$cmd" >/dev/null 2>&1; then
|
||||
ok "$cmd found"
|
||||
else
|
||||
warn "$cmd missing"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "${#missing[@]}" -gt 0 ]; then
|
||||
apt_install "${missing[@]}"
|
||||
fi
|
||||
}
|
||||
|
||||
libsecret_helper_path() {
|
||||
local candidates=(
|
||||
"/usr/share/doc/git/contrib/credential/libsecret/git-credential-libsecret"
|
||||
"/usr/lib/git-core/git-credential-libsecret"
|
||||
"/usr/libexec/git-core/git-credential-libsecret"
|
||||
)
|
||||
local candidate
|
||||
for candidate in "${candidates[@]}"; do
|
||||
if [ -x "$candidate" ]; then
|
||||
printf '%s\n' "$candidate"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
build_libsecret_helper() {
|
||||
local source_dir="/usr/share/doc/git/contrib/credential/libsecret"
|
||||
if [ ! -d "$source_dir" ]; then
|
||||
apt_install libsecret-1-0 libsecret-1-dev make gcc
|
||||
fi
|
||||
if [ -d "$source_dir" ]; then
|
||||
run sudo make -C "$source_dir"
|
||||
fi
|
||||
}
|
||||
|
||||
configure_git_helper() {
|
||||
step "Configuring Git credential helper"
|
||||
|
||||
local current
|
||||
current="$(git config --global --get credential.helper || true)"
|
||||
if [ -n "$current" ]; then
|
||||
ok "credential.helper already set: $current"
|
||||
return
|
||||
fi
|
||||
|
||||
local helper="$GIT_HELPER"
|
||||
if [ "$helper" = "auto" ]; then
|
||||
if libsecret_helper_path >/dev/null 2>&1; then
|
||||
helper="libsecret"
|
||||
elif [ "$ALLOW_PLAINTEXT_STORE" -eq 1 ]; then
|
||||
helper="store"
|
||||
else
|
||||
helper="cache"
|
||||
fi
|
||||
fi
|
||||
|
||||
case "$helper" in
|
||||
libsecret)
|
||||
local path
|
||||
path="$(libsecret_helper_path || true)"
|
||||
if [ -z "$path" ]; then
|
||||
build_libsecret_helper
|
||||
path="$(libsecret_helper_path || true)"
|
||||
fi
|
||||
if [ -z "$path" ]; then
|
||||
warn "libsecret helper is not available; using cache helper for this machine."
|
||||
run git config --global credential.helper "cache --timeout=3600"
|
||||
else
|
||||
run git config --global credential.helper "$path"
|
||||
fi
|
||||
;;
|
||||
cache)
|
||||
run git config --global credential.helper "cache --timeout=3600"
|
||||
;;
|
||||
store)
|
||||
if [ "$ALLOW_PLAINTEXT_STORE" -ne 1 ]; then
|
||||
err "credential.helper=store writes plaintext credentials."
|
||||
err "Rerun with --allow-plaintext-store if that is intended for this host."
|
||||
exit 1
|
||||
fi
|
||||
run git config --global credential.helper store
|
||||
;;
|
||||
esac
|
||||
|
||||
ok "credential.helper configured"
|
||||
}
|
||||
|
||||
setup_ssh_key() {
|
||||
step "Checking SSH key"
|
||||
mkdir -p "$HOME/.ssh"
|
||||
chmod 700 "$HOME/.ssh"
|
||||
|
||||
if [ -f "$SSH_KEY" ]; then
|
||||
ok "SSH key exists: $SSH_KEY"
|
||||
else
|
||||
run ssh-keygen -t ed25519 -f "$SSH_KEY" -N "" -C "$USER@$(hostname)-state-hub"
|
||||
ok "SSH key generated: $SSH_KEY"
|
||||
fi
|
||||
|
||||
if [ -f "${SSH_KEY}.pub" ]; then
|
||||
printf '\nPublic key to authorize on managed hosts:\n\n'
|
||||
sed 's/^/ /' "${SSH_KEY}.pub"
|
||||
printf '\n'
|
||||
fi
|
||||
|
||||
if [ "$AUTHORIZE_SSH" -eq 1 ]; then
|
||||
local target
|
||||
for target in "${SSH_TARGETS[@]}"; do
|
||||
run ssh-copy-id -i "${SSH_KEY}.pub" "$target"
|
||||
done
|
||||
else
|
||||
warn "SSH authorization not attempted. Use --authorize-ssh after confirming host access."
|
||||
fi
|
||||
}
|
||||
|
||||
write_gitea_conf() {
|
||||
step "Checking Gitea config"
|
||||
if [ "$SKIP_GITEA" -eq 1 ]; then
|
||||
warn "Skipping Gitea config by request."
|
||||
return
|
||||
fi
|
||||
|
||||
if [ -f "$GITEA_CONF" ]; then
|
||||
chmod 600 "$GITEA_CONF"
|
||||
ok "$GITEA_CONF already exists"
|
||||
return
|
||||
fi
|
||||
|
||||
if [ -z "$GITEA_USER" ] && [ "$NON_INTERACTIVE" -eq 0 ]; then
|
||||
read -r -p "Gitea username: " GITEA_USER
|
||||
fi
|
||||
|
||||
if [ -z "$GITEA_TOKEN" ] && [ "$NON_INTERACTIVE" -eq 0 ]; then
|
||||
read -r -s -p "Gitea token (requires read:user and repository write scopes): " GITEA_TOKEN
|
||||
printf '\n'
|
||||
fi
|
||||
|
||||
if [ -z "$GITEA_USER" ] || [ -z "$GITEA_TOKEN" ]; then
|
||||
warn "Gitea config not written. Set GITEA_USER/GITEA_TOKEN or rerun interactively."
|
||||
return
|
||||
fi
|
||||
|
||||
if [ "$DRY_RUN" -eq 1 ]; then
|
||||
printf 'DRY-RUN: would write %s with GITEA_URL and GITEA_USER; token hidden\n' "$GITEA_CONF"
|
||||
return
|
||||
fi
|
||||
|
||||
umask 077
|
||||
{
|
||||
printf 'GITEA_URL="%s"\n' "$GITEA_URL"
|
||||
printf 'GITEA_USER="%s"\n' "$GITEA_USER"
|
||||
printf 'GITEA_TOKEN="%s"\n' "$GITEA_TOKEN"
|
||||
} >"$GITEA_CONF"
|
||||
chmod 600 "$GITEA_CONF"
|
||||
ok "Wrote $GITEA_CONF"
|
||||
}
|
||||
|
||||
register_mcp() {
|
||||
step "Registering State Hub MCP"
|
||||
if [ "$SKIP_MCP" -eq 1 ]; then
|
||||
warn "Skipping MCP registration by request."
|
||||
return
|
||||
fi
|
||||
if [ "$DRY_RUN" -eq 1 ]; then
|
||||
run make -C "$STATE_HUB_DIR" register-mcp DRY_RUN=1
|
||||
else
|
||||
make -C "$STATE_HUB_DIR" register-mcp
|
||||
fi
|
||||
}
|
||||
|
||||
health_check() {
|
||||
step "Checking State Hub reachability"
|
||||
if curl -fsS --max-time 2 "http://127.0.0.1:8000/state/health" >/dev/null 2>&1; then
|
||||
ok "State Hub API reachable at http://127.0.0.1:8000"
|
||||
elif curl -fsS --max-time 2 "http://127.0.0.1:18000/state/health" >/dev/null 2>&1; then
|
||||
ok "State Hub API reachable through tunnel at http://127.0.0.1:18000"
|
||||
else
|
||||
warn "State Hub API is not reachable locally or through the default tunnel."
|
||||
warn "Start it with 'make api' or run 'make bridges' if this machine uses ops-bridge."
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
step "State Hub environment bootstrap"
|
||||
printf 'Repository: %s\n' "$STATE_HUB_DIR"
|
||||
check_commands
|
||||
configure_git_helper
|
||||
setup_ssh_key
|
||||
write_gitea_conf
|
||||
register_mcp
|
||||
health_check
|
||||
ok "Bootstrap checks complete."
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user