Files
honeyDueAPI/deploy-k3s/scripts/01-provision-cluster.sh
Trey t 34553f3bec Add K3s dev deployment setup for single-node VPS
Mirrors the prod deploy-k3s/ setup but runs all services in-cluster
on a single node: PostgreSQL (replaces Neon), MinIO S3-compatible
storage (replaces B2), Redis, API, worker, and admin.

Includes fully automated setup scripts (00-init through 04-verify),
server hardening (SSH, fail2ban, ufw), Let's Encrypt TLS via Traefik,
network policies, RBAC, and security contexts matching prod.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 21:30:39 -05:00

125 lines
3.8 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=_config.sh
source "${SCRIPT_DIR}/_config.sh"
log() { printf '[provision] %s\n' "$*"; }
die() { printf '[provision][error] %s\n' "$*" >&2; exit 1; }
# --- Prerequisites ---
command -v hetzner-k3s >/dev/null 2>&1 || die "Missing: hetzner-k3s CLI. Install: https://github.com/vitobotta/hetzner-k3s"
command -v kubectl >/dev/null 2>&1 || die "Missing: kubectl"
HCLOUD_TOKEN="$(cfg_require cluster.hcloud_token "Hetzner API token")"
export HCLOUD_TOKEN
# Validate SSH keys
SSH_PUB="$(cfg cluster.ssh_public_key | sed "s|~|${HOME}|g")"
SSH_PRIV="$(cfg cluster.ssh_private_key | sed "s|~|${HOME}|g")"
[[ -f "${SSH_PUB}" ]] || die "SSH public key not found: ${SSH_PUB}"
[[ -f "${SSH_PRIV}" ]] || die "SSH private key not found: ${SSH_PRIV}"
# --- Generate hetzner-k3s cluster config from config.yaml ---
CLUSTER_CONFIG="${DEPLOY_DIR}/cluster-config.yaml"
log "Generating cluster-config.yaml from config.yaml..."
generate_cluster_config > "${CLUSTER_CONFIG}"
# --- Provision ---
INSTANCE_TYPE="$(cfg cluster.instance_type)"
LOCATION="$(cfg cluster.location)"
NODE_COUNT="$(node_count)"
log "Provisioning K3s cluster on Hetzner Cloud..."
log " Nodes: ${NODE_COUNT}x ${INSTANCE_TYPE} in ${LOCATION}"
log " This takes about 5-10 minutes."
echo ""
hetzner-k3s create --config "${CLUSTER_CONFIG}"
KUBECONFIG_PATH="${DEPLOY_DIR}/kubeconfig"
if [[ ! -f "${KUBECONFIG_PATH}" ]]; then
die "Provisioning completed but kubeconfig not found. Check hetzner-k3s output."
fi
# --- Write node IPs back to config.yaml ---
log "Querying node IPs..."
export KUBECONFIG="${KUBECONFIG_PATH}"
python3 -c "
import yaml, subprocess, json
# Get node info from kubectl
result = subprocess.run(
['kubectl', 'get', 'nodes', '-o', 'json'],
capture_output=True, text=True
)
nodes_json = json.loads(result.stdout)
# Build name → IP map
ip_map = {}
for node in nodes_json.get('items', []):
name = node['metadata']['name']
for addr in node.get('status', {}).get('addresses', []):
if addr['type'] == 'ExternalIP':
ip_map[name] = addr['address']
break
else:
for addr in node.get('status', {}).get('addresses', []):
if addr['type'] == 'InternalIP':
ip_map[name] = addr['address']
break
# Update config.yaml with IPs
with open('${CONFIG_FILE}') as f:
config = yaml.safe_load(f)
updated = 0
for i, node in enumerate(config.get('nodes', [])):
for real_name, ip in ip_map.items():
if node['name'] in real_name or real_name in node['name']:
config['nodes'][i]['ip'] = ip
config['nodes'][i]['name'] = real_name
updated += 1
break
if updated == 0 and ip_map:
# Names didn't match — assign by index
for i, (name, ip) in enumerate(sorted(ip_map.items())):
if i < len(config['nodes']):
config['nodes'][i]['name'] = name
config['nodes'][i]['ip'] = ip
updated += 1
with open('${CONFIG_FILE}', 'w') as f:
yaml.dump(config, f, default_flow_style=False, sort_keys=False)
print(f'Updated {updated} node IPs in config.yaml')
for name, ip in sorted(ip_map.items()):
print(f' {name}: {ip}')
"
# --- Label Redis node ---
REDIS_NODE="$(nodes_with_role redis | head -1)"
if [[ -n "${REDIS_NODE}" ]]; then
# Find the actual K8s node name that matches
ACTUAL_NODE="$(kubectl get nodes -o jsonpath='{.items[*].metadata.name}' | tr ' ' '\n' | head -1)"
log "Labeling node ${ACTUAL_NODE} for Redis..."
kubectl label node "${ACTUAL_NODE}" honeydue/redis=true --overwrite
fi
log ""
log "Cluster provisioned successfully."
log ""
log "Next steps:"
log " export KUBECONFIG=${KUBECONFIG_PATH}"
log " kubectl get nodes"
log " ./scripts/02-setup-secrets.sh"