Skip to content

Instantly share code, notes, and snippets.

@fxstein
Last active February 16, 2026 00:34
Show Gist options
  • Select an option

  • Save fxstein/50ab414add783f4a67afa78d92ca5b47 to your computer and use it in GitHub Desktop.

Select an option

Save fxstein/50ab414add783f4a67afa78d92ca5b47 to your computer and use it in GitHub Desktop.
pion-remote: Remote secret rotation CLI
#!/usr/bin/env bash
# pion-remote β€” Remote infrastructure CLI for pionizer.ai
# Manage your Pi instance from any machine over SSH + 1Password
#
# Install (requires gh CLI authenticated to pionizer org):
# gh api repos/pionizer/pi-infra/contents/scripts/pion-remote.sh --jq .content | base64 -d \
# | sudo tee /usr/local/bin/pion-remote > /dev/null && sudo chmod +x /usr/local/bin/pion-remote
# shellcheck disable=SC2310,SC2029,SC2015,SC2312 # Functions in conditions and SSH command expansions are intentional
#
# First run:
# pion-remote setup
#
# Requires: bash 4+, op (1Password CLI), ssh, curl
# Works on: macOS, Linux, WSL, Chromebook (Crostini), Raspberry Pi
{
set -euo pipefail
VERSION="1.0.0"
# --- Config ---
CONFIG_DIR="${XDG_CONFIG_HOME:-${HOME}/.config}/pion"
CONFIG_FILE="${CONFIG_DIR}/remote.env"
# shellcheck source=/dev/null # user config
[[ -f "${CONFIG_FILE}" ]] && source "${CONFIG_FILE}"
SSH_HOST="${PION_SSH_HOST:-}"
INSTANCE="${PION_INSTANCE:?PION_INSTANCE not set β€” run setup.sh or set in ~/.zshenv}"
OP_VAULT="${PION_OP_VAULT:-pi-${INSTANCE}}"
REGISTRY_URL="${PION_REGISTRY_URL:-https://raw.githubusercontent.com/pionizer/pi-infra/main/secrets-registry.yml}"
# --- Colors (disabled if not a terminal) ---
if [[ -t 1 ]]; then
RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m'
BLUE='\033[0;34m' BOLD='\033[1m' NC='\033[0m'
else
RED='' GREEN='' YELLOW='' BLUE='' BOLD='' NC=''
fi
ok() { echo -e "${GREEN}βœ…${NC} ${1}"; }
warn() { echo -e "${YELLOW}⚠️${NC} ${1}"; }
fail() { echo -e "${RED}❌${NC} ${1}" >&2; }
info() { echo -e "${BLUE}ℹ️${NC} ${1}"; }
die() { fail "${1}"; exit 1; }
# --- Dependency checks ---
_check_dep() {
local cmd="${1}" install_hint="${2}"
if ! command -v "${cmd}" &>/dev/null; then
fail "${cmd} not found"
echo " Install: ${install_hint}" >&2
return 1
fi
}
_check_deps() {
local missing=0
_check_dep "ssh" "included with your OS (OpenSSH)" || ((missing++))
_check_dep "op" "https://developer.1password.com/docs/cli/get-started/" || ((missing++))
_check_dep "curl" "apt install curl / brew install curl" || ((missing++))
_check_dep "yq" "brew install yq / pip install yq / snap install yq" || ((missing++))
[[ ${missing} -eq 0 ]] || die "Missing ${missing} required tool(s). Install them and retry."
}
_check_ssh_host() {
if [[ -z "${SSH_HOST}" ]]; then
die "SSH host not configured. Run: pion-remote setup"
fi
}
_check_op_auth() {
if ! op account list &>/dev/null 2>&1; then
die "1Password CLI not authenticated. Run: op signin"
fi
}
# --- Registry lookup (fetched from GitHub, cached per invocation) ---
_REGISTRY_CACHE=""
_reg() {
local name="${1}" field="${2}"
if [[ -z "${_REGISTRY_CACHE}" ]]; then
# Try gh api first (works for private repos), fall back to curl (public)
if command -v gh &>/dev/null; then
_REGISTRY_CACHE=$(gh api repos/pionizer/pi-infra/contents/secrets-registry.yml --jq .content 2>/dev/null | base64 -d 2>/dev/null)
fi
if [[ -z "${_REGISTRY_CACHE}" ]]; then
_REGISTRY_CACHE=$(curl -fsSL "${REGISTRY_URL}" 2>/dev/null) || die "Cannot fetch secrets registry β€” ensure 'gh' is authenticated or registry URL is accessible"
fi
fi
# Compatible with both Go yq (mikefarah) and Python yq (kislyuk)
local val
val=$(echo "${_REGISTRY_CACHE}" | yq -r ".secrets.${name}.${field}" 2>/dev/null) || val=""
[[ "${val}" == "null" ]] && val=""
echo "${val}"
}
# --- Commands ---
cmd_rotate() {
local name="${1:-}"
[[ -n "${name}" ]] || die "Usage: pion-remote rotate <secret-name>"
_check_deps
_check_ssh_host
_check_op_auth
local title
title=$(_reg "${name}" "title")
local rotation_url
rotation_url=$(_reg "${name}" "rotation_url")
local rotation_mode
rotation_mode=$(_reg "${name}" "rotation_mode")
local type
type=$(_reg "${name}" "type")
local provider_key_name
provider_key_name=$(_reg "${name}" "provider_key_name")
local notes
notes=$(_reg "${name}" "notes")
[[ -n "${title}" ]] || die "Secret '${name}' not found in registry"
echo ""
echo -e "${BOLD}πŸ” Remote Secret Rotation: ${name}${NC}"
echo -e " 1Password item: ${title}"
echo -e " SSH host: ${SSH_HOST}"
[[ -n "${notes}" ]] && echo -e " ${YELLOW}${notes}${NC}"
echo ""
# --- Phase 1: Provider + 1Password (local, with biometrics) ---
local new_value="" file_path=""
if [[ "${rotation_url}" == "self-generated" ]]; then
if [[ "${name}" == "openclaw_gateway_token" ]]; then
new_value=$(openssl rand -hex 24)
info "Generated new gateway token"
else
die "Don't know how to self-generate '${name}'"
fi
else
if [[ "${rotation_mode}" == "reissue" ]]; then
echo -e " ${YELLOW}This key must be REISSUED (cannot update in place).${NC}"
echo ""
echo -e " ${BOLD}URL:${NC} ${GREEN}${rotation_url}${NC}"
echo ""
[[ -n "${provider_key_name}" ]] && echo -e " ${BOLD}Key name:${NC} ${GREEN}${provider_key_name}${NC}"
echo ""
echo " Steps:"
echo " 1. Open the URL above"
echo " 2. Revoke/delete the old key"
echo " 3. Create a new key with the name above"
echo " 4. Paste the new value below"
else
echo -e " ${BOLD}URL:${NC} ${GREEN}${rotation_url}${NC}"
echo ""
echo " Steps:"
echo " 1. Open the URL above"
echo " 2. Rotate or regenerate the key"
echo " 3. Paste the new value below"
fi
echo ""
if [[ "${type}" == "file" ]]; then
echo -n " Path to new credential file: "
read -r file_path
[[ -f "${file_path}" ]] || die "File not found: ${file_path}"
new_value=$(cat "${file_path}")
else
echo -n " New secret value: "
read -rs new_value
echo ""
fi
fi
[[ -n "${new_value}" ]] || die "No value provided"
# Expiration
echo ""
echo -n " Expiration date (YYYY-MM-DD or 'never') [never]: "
read -r expires_input
local expires="${expires_input:-never}"
# Update 1Password (local β€” uses biometrics)
info "Updating 1Password: ${title}..."
local full_notes
full_notes="${notes}
Rotation URL: ${rotation_url}
Rotated: $(date -u +%Y-%m-%dT%H:%M:%SZ) via pion-remote rotate
Expires: ${expires}"
op item edit "${title}" \
--vault="${OP_VAULT}" \
"credential[password]=${new_value}" \
"notesPlain=${full_notes}" \
"expires[text]=${expires}" 2>&1 | grep -v "^$" || die "Failed to update 1Password"
ok "1Password updated"
# Clean up source file if used
[[ -n "${file_path}" && -f "${file_path}" ]] && rm -f "${file_path}" && ok "Deleted source file"
# --- Phase 2: Deploy + Verify (remote, via SSH) ---
echo ""
info "Deploying to ${SSH_HOST} via SSH..."
echo ""
ssh "${SSH_HOST}" "pion secret reload ${name}"
local exit_code
exit_code=$?
echo ""
if [[ ${exit_code} -eq 0 ]]; then
ok "Remote rotation complete for ${BOLD}${name}${NC} β€” verified end-to-end"
else
die "Remote deployment failed (exit ${exit_code}) β€” check server logs"
fi
}
cmd_verify() {
local name="${1:-}"
_check_dep "ssh" "included with your OS" || exit 1
_check_ssh_host
info "Running remote verification on ${SSH_HOST}..."
if [[ -n "${name}" ]]; then
ssh "${SSH_HOST}" "pion secret verify ${name}"
else
ssh "${SSH_HOST}" "pion secret verify"
fi
}
cmd_status() {
_check_dep "ssh" "included with your OS" || exit 1
_check_ssh_host
info "Checking remote system on ${SSH_HOST}..."
ssh "${SSH_HOST}" "pion status"
}
cmd_setup() {
echo ""
echo -e "${BOLD}πŸ”§ pion-remote setup${NC}"
echo ""
# Check and install dependencies
echo "Checking dependencies..."
local missing=()
for cmd_info in "ssh:included with your OS:" "op:https://developer.1password.com/docs/cli/get-started/:brew install --cask 1password-cli" "curl:apt install curl / brew install curl:" "yq:brew install yq:brew install yq"; do
local cmd="${cmd_info%%:*}"
local rest="${cmd_info#*:}"
local hint="${rest%%:*}"
local brew_cmd="${rest#*:}"
if command -v "${cmd}" &>/dev/null; then
ok "${cmd}"
else
fail "${cmd} β€” ${hint}"
# Offer to install via brew if available
if [[ -n "${brew_cmd}" ]] && command -v brew &>/dev/null; then
echo -n " Install ${cmd} now? [Y/n] "
read -r yn
if [[ "${yn:-Y}" =~ ^[Yy]$ ]]; then
${brew_cmd} && ok "${cmd} installed" || { fail "${cmd} install failed"; missing+=("${cmd}"); }
else
missing+=("${cmd}")
fi
else
missing+=("${cmd}")
fi
fi
done
if [[ ${#missing[@]} -gt 0 ]]; then
echo ""
die "Missing required tools: ${missing[*]}. Install them and re-run: pion-remote setup"
fi
# Create/update config
mkdir -p "${CONFIG_DIR}"
local current_host="${PION_SSH_HOST:-}"
echo ""
echo -n "SSH host for your Pi server [${current_host:-e.g. o-gluon}]: "
read -r input_host
local ssh_host="${input_host:-${current_host}}"
if [[ -z "${ssh_host}" ]]; then
die "SSH host is required. Configure it in ~/.ssh/config first."
fi
# Test SSH connection β€” try bare name, then .pionizer.ai suffix
echo ""
info "Testing SSH connection to ${ssh_host}..."
if ssh -o ConnectTimeout=10 "${ssh_host}" "echo ok" &>/dev/null; then
ok "SSH connection to ${ssh_host} works"
elif [[ "${ssh_host}" != *.* ]]; then
# Bare hostname β€” try with .pionizer.ai suffix
local fqdn="${ssh_host}.pionizer.ai"
info "Bare name failed, trying ${fqdn}..."
if ssh -o ConnectTimeout=10 "${fqdn}" "echo ok" &>/dev/null; then
ok "SSH connection to ${fqdn} works"
ssh_host="${fqdn}"
else
warn "SSH connection failed for both ${input_host} and ${fqdn}"
echo -n " Save ${ssh_host} anyway and fix SSH config later? [Y/n] "
read -r yn
[[ "${yn:-Y}" =~ ^[Yy]$ ]] || die "Setup cancelled. Configure SSH and re-run: pion-remote setup"
fi
else
warn "SSH connection to ${ssh_host} failed"
echo -n " Save anyway and fix SSH config later? [Y/n] "
read -r yn
[[ "${yn:-Y}" =~ ^[Yy]$ ]] || die "Setup cancelled. Configure SSH and re-run: pion-remote setup"
fi
cat > "${CONFIG_FILE}" <<EOF
# pion-remote configuration
# Generated: $(date -u +%Y-%m-%dT%H:%M:%SZ)
# SSH host for Pi server (must be configured in ~/.ssh/config)
PION_SSH_HOST=${ssh_host}
# 1Password vault name
PION_OP_VAULT=${PION_OP_VAULT:-pi-${PION_INSTANCE}}
# Secrets registry URL
PION_REGISTRY_URL=${PION_REGISTRY_URL:-https://raw.githubusercontent.com/pionizer/pi-infra/main/secrets-registry.yml}
EOF
ok "Config saved: ${CONFIG_FILE}"
echo ""
echo "You're all set. Try: pion-remote status"
}
cmd_update() {
info "Updating pion-remote..."
local tmp
tmp=$(mktemp)
# Primary: gh api (private repo, always fresh)
# Fallback: gist (public, may be CDN-cached)
if command -v gh &>/dev/null && gh api repos/pionizer/pi-infra/contents/scripts/pion-remote.sh --jq .content 2>/dev/null | base64 -d > "${tmp}" 2>/dev/null && [[ -s "${tmp}" ]]; then
info "Updated from pi-infra repo"
else
curl -fsSL "https://gist.githubusercontent.com/fxstein/50ab414add783f4a67afa78d92ca5b47/raw/pion-remote.sh" -o "${tmp}" || die "Download failed"
info "Updated from gist (may be cached β€” if stale, run: gh api repos/pionizer/pi-infra/contents/scripts/pion-remote.sh --jq .content | base64 -d | sudo tee /usr/local/bin/pion-remote > /dev/null)"
fi
sudo cp "${tmp}" /usr/local/bin/pion-remote
sudo chmod +x /usr/local/bin/pion-remote
rm -f "${tmp}"
ok "Updated to latest version"
}
# --- Main ---
case "${1:-}" in
rotate)
shift; cmd_rotate "$@" ;;
verify)
shift; cmd_verify "$@" ;;
status)
cmd_status ;;
setup)
cmd_setup ;;
update)
cmd_update ;;
version|--version|-v)
echo "pion-remote ${VERSION}" ;;
help|--help|-h)
echo "pion-remote ${VERSION} β€” Remote infrastructure CLI for pionizer.ai"
echo ""
echo -e "${BOLD}COMMANDS${NC}"
echo " rotate <name> Rotate a secret end-to-end"
echo " Phase 1: Update provider + 1Password (local, biometrics)"
echo " Phase 2: Deploy + verify on server (remote, via SSH)"
echo " verify [<name>] Verify secret chain integrity on server"
echo " status Check server health"
echo " setup Configure pion-remote (SSH host, dependencies)"
echo " update Self-update to latest version from GitHub"
echo " version Show version"
echo " help Show this help"
echo ""
echo -e "${BOLD}HOW IT WORKS${NC}"
echo " Secrets never travel over SSH. The flow is:"
echo " You (local) β†’ 1Password Cloud β†’ Server (via service account)"
echo ""
echo " Your machine handles writes (1Password, biometrics)."
echo " The server handles reads (chezmoi, deploy, verify, liveness)."
echo ""
echo -e "${BOLD}FIRST TIME SETUP${NC}"
echo " 1. Install (requires gh CLI):"
echo " gh api repos/pionizer/pi-infra/contents/scripts/pion-remote.sh --jq .content | base64 -d \\"
echo " | sudo tee /usr/local/bin/pion-remote > /dev/null && sudo chmod +x /usr/local/bin/pion-remote"
echo " 2. Configure: pion-remote setup"
echo " 3. Use: pion-remote rotate <secret-name>"
echo ""
echo -e "${BOLD}REQUIREMENTS${NC}"
echo " bash 4+, op (1Password CLI), ssh, curl, yq"
echo ""
echo -e "${BOLD}CONFIG${NC}"
echo " ${CONFIG_FILE}"
if [[ -f "${CONFIG_FILE}" ]]; then
echo " SSH host: ${PION_SSH_HOST:-not set}"
echo " Vault: ${PION_OP_VAULT:-pi-${PION_INSTANCE}}"
else
echo " (not configured β€” run: pion-remote setup)"
fi
;;
"")
echo "pion-remote ${VERSION} β€” run 'pion-remote help' for usage"
exit 1
;;
*)
die "Unknown command: ${1} β€” run 'pion-remote help' for usage"
;;
esac
exit
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment