Initial commit: VaultMesh Skills collection
Collection of operational skills for VaultMesh infrastructure including: - backup-sovereign: Backup and recovery operations - btc-anchor: Bitcoin anchoring - cloudflare-tunnel-manager: Cloudflare tunnel management - container-registry: Container registry operations - disaster-recovery: Disaster recovery procedures - dns-sovereign: DNS management - eth-anchor: Ethereum anchoring - gitea-bootstrap: Gitea setup and configuration - hetzner-bootstrap: Hetzner server provisioning - merkle-forest: Merkle tree operations - node-hardening: Node security hardening - operator-bootstrap: Operator initialization - proof-verifier: Cryptographic proof verification - rfc3161-anchor: RFC3161 timestamping - secrets-vault: Secrets management 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
155
operator-bootstrap/scripts/02_identity_apply.sh
Executable file
155
operator-bootstrap/scripts/02_identity_apply.sh
Executable file
@@ -0,0 +1,155 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# === METADATA ===
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
# === CONFIGURATION ===
|
||||
: "${OPERATOR_NAME:?OPERATOR_NAME required}"
|
||||
: "${OPERATOR_EMAIL:?OPERATOR_EMAIL required}"
|
||||
: "${NODE_NAME:=node-a}"
|
||||
: "${SSH_KEY_COMMENT:=$NODE_NAME-operator}"
|
||||
: "${GPG_KEY_SIZE:=4096}"
|
||||
: "${GPG_KEY_EXPIRE:=2y}"
|
||||
: "${OUTPUT_DIR:=$SKILL_ROOT/outputs}"
|
||||
|
||||
# === FUNCTIONS ===
|
||||
log_info() { echo "[INFO] $(date -Iseconds) $*"; }
|
||||
log_warn() { echo "[WARN] $(date -Iseconds) $*" >&2; }
|
||||
log_error() { echo "[ERROR] $(date -Iseconds) $*" >&2; }
|
||||
die() { log_error "$@"; exit 1; }
|
||||
|
||||
preflight() {
|
||||
[[ -d "$OUTPUT_DIR" ]] || mkdir -p "$OUTPUT_DIR"
|
||||
[[ -d "$HOME/.ssh" ]] || { mkdir -p "$HOME/.ssh" && chmod 700 "$HOME/.ssh"; }
|
||||
}
|
||||
|
||||
create_gpg_key() {
|
||||
if gpg --list-keys "$OPERATOR_EMAIL" &>/dev/null 2>&1; then
|
||||
log_info "GPG key for $OPERATOR_EMAIL already exists - skipping"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_info "Generating GPG key (you will be prompted for passphrase)..."
|
||||
|
||||
# Create key generation parameters
|
||||
local params_file
|
||||
params_file=$(mktemp)
|
||||
cat > "$params_file" <<EOF
|
||||
%echo Generating GPG key for $OPERATOR_NAME
|
||||
Key-Type: RSA
|
||||
Key-Length: $GPG_KEY_SIZE
|
||||
Subkey-Type: RSA
|
||||
Subkey-Length: $GPG_KEY_SIZE
|
||||
Name-Real: $OPERATOR_NAME
|
||||
Name-Email: $OPERATOR_EMAIL
|
||||
Expire-Date: $GPG_KEY_EXPIRE
|
||||
%ask-passphrase
|
||||
%commit
|
||||
%echo Done
|
||||
EOF
|
||||
|
||||
gpg --batch --gen-key "$params_file"
|
||||
rm -f "$params_file"
|
||||
|
||||
log_info "GPG key created for $OPERATOR_EMAIL"
|
||||
}
|
||||
|
||||
create_ssh_keys() {
|
||||
local ed_key="$HOME/.ssh/id_ed25519_${NODE_NAME}"
|
||||
local rsa_key="$HOME/.ssh/id_rsa_${NODE_NAME}"
|
||||
|
||||
# Ed25519 key
|
||||
if [[ -f "$ed_key" ]]; then
|
||||
log_info "Ed25519 key already exists at $ed_key - skipping"
|
||||
else
|
||||
log_info "Generating Ed25519 SSH key..."
|
||||
ssh-keygen -t ed25519 -f "$ed_key" -C "$SSH_KEY_COMMENT" -N ""
|
||||
log_info "Ed25519 key created: $ed_key"
|
||||
fi
|
||||
|
||||
# RSA fallback key
|
||||
if [[ -f "$rsa_key" ]]; then
|
||||
log_info "RSA key already exists at $rsa_key - skipping"
|
||||
else
|
||||
log_info "Generating RSA fallback SSH key..."
|
||||
ssh-keygen -t rsa -b 4096 -f "$rsa_key" -C "$SSH_KEY_COMMENT-rsa" -N ""
|
||||
log_info "RSA key created: $rsa_key"
|
||||
fi
|
||||
}
|
||||
|
||||
update_ssh_config() {
|
||||
local config="$HOME/.ssh/config"
|
||||
local backup="$HOME/.ssh/config.bak.$(date +%Y%m%d%H%M%S)"
|
||||
|
||||
# Create config if it doesn't exist
|
||||
if [[ ! -f "$config" ]]; then
|
||||
touch "$config"
|
||||
chmod 600 "$config"
|
||||
fi
|
||||
|
||||
# Backup existing config
|
||||
cp "$config" "$backup"
|
||||
log_info "SSH config backed up to $backup"
|
||||
|
||||
# Check if entry already exists
|
||||
if grep -q "Host $NODE_NAME\$" "$config" 2>/dev/null; then
|
||||
log_info "SSH config entry for $NODE_NAME already exists - skipping"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Append new entry
|
||||
cat >> "$config" <<EOF
|
||||
|
||||
# Added by operator-bootstrap $(date -Iseconds)
|
||||
Host $NODE_NAME
|
||||
IdentityFile ~/.ssh/id_ed25519_${NODE_NAME}
|
||||
IdentitiesOnly yes
|
||||
EOF
|
||||
|
||||
chmod 600 "$config"
|
||||
log_info "SSH config updated with $NODE_NAME entry"
|
||||
}
|
||||
|
||||
generate_manifest() {
|
||||
local manifest="$OUTPUT_DIR/identity_manifest.json"
|
||||
local gpg_fingerprint
|
||||
gpg_fingerprint=$(gpg --list-keys --with-colons "$OPERATOR_EMAIL" 2>/dev/null | grep fpr | head -1 | cut -d: -f10 || echo "unknown")
|
||||
|
||||
cat > "$manifest" <<EOF
|
||||
{
|
||||
"timestamp": "$(date -Iseconds)",
|
||||
"node": "$NODE_NAME",
|
||||
"operator": {
|
||||
"name": "$OPERATOR_NAME",
|
||||
"email": "$OPERATOR_EMAIL"
|
||||
},
|
||||
"gpg": {
|
||||
"fingerprint": "$gpg_fingerprint",
|
||||
"key_size": $GPG_KEY_SIZE,
|
||||
"expires": "$GPG_KEY_EXPIRE"
|
||||
},
|
||||
"ssh": {
|
||||
"ed25519": "$HOME/.ssh/id_ed25519_${NODE_NAME}",
|
||||
"rsa": "$HOME/.ssh/id_rsa_${NODE_NAME}"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
log_info "Identity manifest written to $manifest"
|
||||
}
|
||||
|
||||
main() {
|
||||
preflight
|
||||
log_info "Starting $SCRIPT_NAME..."
|
||||
|
||||
create_gpg_key
|
||||
create_ssh_keys
|
||||
update_ssh_config
|
||||
generate_manifest
|
||||
|
||||
log_info "Completed $SCRIPT_NAME"
|
||||
}
|
||||
|
||||
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"
|
||||
Reference in New Issue
Block a user