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>
This commit is contained in:
146
deploy-k3s-dev/scripts/01-setup-k3s.sh
Executable file
146
deploy-k3s-dev/scripts/01-setup-k3s.sh
Executable file
@@ -0,0 +1,146 @@
|
||||
#!/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 '[setup] %s\n' "$*"; }
|
||||
die() { printf '[setup][error] %s\n' "$*" >&2; exit 1; }
|
||||
|
||||
# --- Local prerequisites ---
|
||||
|
||||
command -v kubectl >/dev/null 2>&1 || die "Missing locally: kubectl (https://kubernetes.io/docs/tasks/tools/)"
|
||||
|
||||
# --- Server connection ---
|
||||
|
||||
SERVER_HOST="$(cfg_require server.host "Server IP or SSH alias")"
|
||||
SERVER_USER="$(cfg server.user)"
|
||||
SERVER_USER="${SERVER_USER:-root}"
|
||||
SSH_KEY="$(cfg server.ssh_key | sed "s|~|${HOME}|g")"
|
||||
|
||||
SSH_OPTS=()
|
||||
if [[ -n "${SSH_KEY}" && -f "${SSH_KEY}" ]]; then
|
||||
SSH_OPTS+=(-i "${SSH_KEY}")
|
||||
fi
|
||||
SSH_OPTS+=(-o StrictHostKeyChecking=accept-new)
|
||||
|
||||
ssh_cmd() {
|
||||
ssh "${SSH_OPTS[@]}" "${SERVER_USER}@${SERVER_HOST}" "$@"
|
||||
}
|
||||
|
||||
log "Testing SSH connection to ${SERVER_USER}@${SERVER_HOST}..."
|
||||
ssh_cmd "echo 'SSH connection OK'" || die "Cannot SSH into ${SERVER_HOST}"
|
||||
|
||||
# --- Server prerequisites ---
|
||||
|
||||
log "Setting up server prerequisites..."
|
||||
|
||||
ssh_cmd 'bash -s' <<'REMOTE_SETUP'
|
||||
set -euo pipefail
|
||||
|
||||
log() { printf '[setup][remote] %s\n' "$*"; }
|
||||
|
||||
# --- System updates ---
|
||||
log "Updating system packages..."
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update -qq
|
||||
apt-get upgrade -y -qq
|
||||
|
||||
# --- SSH hardening ---
|
||||
log "Hardening SSH..."
|
||||
sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
|
||||
sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config
|
||||
systemctl reload sshd 2>/dev/null || systemctl reload ssh 2>/dev/null || true
|
||||
|
||||
# --- fail2ban ---
|
||||
if ! command -v fail2ban-client >/dev/null 2>&1; then
|
||||
log "Installing fail2ban..."
|
||||
apt-get install -y -qq fail2ban
|
||||
systemctl enable --now fail2ban
|
||||
else
|
||||
log "fail2ban already installed"
|
||||
fi
|
||||
|
||||
# --- Unattended security upgrades ---
|
||||
if ! dpkg -l | grep -q unattended-upgrades; then
|
||||
log "Installing unattended-upgrades..."
|
||||
apt-get install -y -qq unattended-upgrades
|
||||
dpkg-reconfigure -plow unattended-upgrades
|
||||
else
|
||||
log "unattended-upgrades already installed"
|
||||
fi
|
||||
|
||||
# --- Firewall (ufw) ---
|
||||
if command -v ufw >/dev/null 2>&1; then
|
||||
log "Configuring firewall..."
|
||||
ufw default deny incoming
|
||||
ufw default allow outgoing
|
||||
ufw allow 22/tcp # SSH
|
||||
ufw allow 443/tcp # HTTPS (Traefik)
|
||||
ufw allow 6443/tcp # K3s API
|
||||
ufw allow 80/tcp # HTTP (Let's Encrypt ACME challenge)
|
||||
ufw --force enable
|
||||
else
|
||||
log "Installing ufw..."
|
||||
apt-get install -y -qq ufw
|
||||
ufw default deny incoming
|
||||
ufw default allow outgoing
|
||||
ufw allow 22/tcp
|
||||
ufw allow 443/tcp
|
||||
ufw allow 6443/tcp
|
||||
ufw allow 80/tcp
|
||||
ufw --force enable
|
||||
fi
|
||||
|
||||
log "Server prerequisites complete."
|
||||
REMOTE_SETUP
|
||||
|
||||
# --- Install K3s ---
|
||||
|
||||
log "Installing K3s on ${SERVER_HOST}..."
|
||||
log " This takes about 1-2 minutes."
|
||||
|
||||
ssh_cmd "curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC='server --secrets-encryption' sh -"
|
||||
|
||||
# --- Wait for K3s to be ready ---
|
||||
|
||||
log "Waiting for K3s to be ready..."
|
||||
ssh_cmd "until kubectl get nodes >/dev/null 2>&1; do sleep 2; done"
|
||||
|
||||
# --- Copy kubeconfig ---
|
||||
|
||||
KUBECONFIG_PATH="${DEPLOY_DIR}/kubeconfig"
|
||||
|
||||
log "Copying kubeconfig..."
|
||||
ssh_cmd "sudo cat /etc/rancher/k3s/k3s.yaml" > "${KUBECONFIG_PATH}"
|
||||
|
||||
# Replace 127.0.0.1 with the server's actual IP/hostname
|
||||
# If SERVER_HOST is an SSH alias, resolve the actual IP
|
||||
ACTUAL_HOST="${SERVER_HOST}"
|
||||
if ! echo "${SERVER_HOST}" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'; then
|
||||
# Try to resolve from SSH config
|
||||
RESOLVED="$(ssh -G "${SERVER_HOST}" 2>/dev/null | awk '/^hostname / {print $2}')"
|
||||
if [[ -n "${RESOLVED}" && "${RESOLVED}" != "${SERVER_HOST}" ]]; then
|
||||
ACTUAL_HOST="${RESOLVED}"
|
||||
fi
|
||||
fi
|
||||
|
||||
sed -i.bak "s|https://127.0.0.1:6443|https://${ACTUAL_HOST}:6443|g" "${KUBECONFIG_PATH}"
|
||||
rm -f "${KUBECONFIG_PATH}.bak"
|
||||
chmod 600 "${KUBECONFIG_PATH}"
|
||||
|
||||
# --- Verify ---
|
||||
|
||||
export KUBECONFIG="${KUBECONFIG_PATH}"
|
||||
log "Verifying cluster..."
|
||||
kubectl get nodes
|
||||
|
||||
log ""
|
||||
log "K3s installed successfully on ${SERVER_HOST}."
|
||||
log "Server hardened: SSH key-only, fail2ban, ufw firewall, unattended-upgrades."
|
||||
log ""
|
||||
log "Next steps:"
|
||||
log " export KUBECONFIG=${KUBECONFIG_PATH}"
|
||||
log " kubectl get nodes"
|
||||
log " ./scripts/02-setup-secrets.sh"
|
||||
Reference in New Issue
Block a user