diff --git a/chart/SETUP.md b/chart/SETUP.md new file mode 100644 index 0000000..09e6e1a --- /dev/null +++ b/chart/SETUP.md @@ -0,0 +1,110 @@ +# Helm Deployment Setup + +## Prerequisites + +- helm v3 +- sops +- age (for key generation) +- kubectl with cluster access + +## 1. Install helm-secrets plugin + +The plugin must register as `getter/v1` type so it works via the `secrets://` protocol handler. + +```bash +# Install helm-secrets as a getter/downloader plugin +helm plugin install https://github.com/jkroepke/helm-secrets --verify=false +``` + +Verify it registered correctly: + +```bash +helm plugin list +``` + +Expected output: + +``` +NAME VERSION TYPE ... +secrets 4.x.x getter/v1 ... +``` + +The `getter/v1` type means helm-secrets acts as a protocol handler. +Use `secrets://` prefix on encrypted value files instead of `helm secrets `: + +```bash +# Correct usage with getter/v1 +helm install my-release ./chart -f values.yaml -f secrets://values-secret.yaml + +# NOT: helm secrets install ... (that requires wrapper type, not getter) +``` + +## 2. Generate an age key + +```bash +age-keygen -o ~/.config/sops/age/keys.txt +``` + +Note the public key from the output (starts with `age1...`). + +## 3. Configure sops + +Create `.sops.yaml` in the repo root: + +```yaml +creation_rules: + - path_regex: values-secret\.yaml$ + age: age1yourpublickeyhere +``` + +## 4. Create values files + +```bash +cp chart/values.yaml.example chart/values.yaml +cp chart/values-secret.yaml.example chart/values-secret.yaml +``` + +Edit `chart/values.yaml` with your environment config (non-sensitive). + +Edit `chart/values-secret.yaml` with sensitive values: + +```bash +# Generate a session secret +python -c "import secrets; print(secrets.token_urlsafe(32))" + +# Generate an admin password hash +python -c "from passlib.hash import argon2; print(argon2.hash('yourpassword'))" +``` + +## 5. Encrypt secrets + +```bash +sops -e -i chart/values-secret.yaml +``` + +To edit later: + +```bash +sops chart/values-secret.yaml # opens in $EDITOR, re-encrypts on save +``` + +Or decrypt/encrypt in place: + +```bash +sops -d -i chart/values-secret.yaml # decrypt +# edit the file +sops -e -i chart/values-secret.yaml # re-encrypt +``` + +## 6. Deploy + +```bash +./deploy.sh install # first time +./deploy.sh upgrade # subsequent deploys +./deploy.sh diff # preview changes (requires helm-diff plugin) +./deploy.sh template # render manifests locally +./deploy.sh status # show running pods/services +./deploy.sh logs # tail app logs +./deploy.sh restart # rolling restart +./deploy.sh destroy # uninstall +``` diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..c05bd16 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,97 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Deploy apcupsd-client via Helm with sops-encrypted secrets +# Usage: ./deploy.sh [install|upgrade|diff|destroy|status|logs|restart] + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +CHART_DIR="${SCRIPT_DIR}/chart" +RELEASE="apcupsd-client" +NAMESPACE="apcupsd" +VALUES="${CHART_DIR}/values.yaml" +SECRETS="secrets://${CHART_DIR}/values-secret.yaml" +ACTION="${1:-upgrade}" + +# Preflight checks +for cmd in helm sops; do + if ! command -v "$cmd" &>/dev/null; then + echo "ERROR: $cmd not found in PATH" >&2 + exit 1 + fi +done + +# Verify helm-secrets plugin is available +if ! helm plugin list | grep -q secrets; then + echo "ERROR: helm-secrets plugin not installed. Install with: helm plugin install https://github.com/jkroepke/helm-secrets" >&2 + exit 1 +fi + +# Check if secrets file exists +if [[ ! -f "${CHART_DIR}/values-secret.yaml" ]]; then + echo "ERROR: values-secret.yaml not found. Copy values-secret.yaml.example and encrypt with sops." >&2 + exit 1 +fi + +case "$ACTION" in + install) + echo "--- Installing ${RELEASE} ---" + helm install "$RELEASE" "$CHART_DIR" \ + -n "$NAMESPACE" --create-namespace \ + -f "$VALUES" \ + -f "$SECRETS" + echo "--- Install complete ---" + kubectl -n "$NAMESPACE" get pods + ;; + + upgrade) + echo "--- Upgrading ${RELEASE} ---" + helm upgrade "$RELEASE" "$CHART_DIR" \ + -n "$NAMESPACE" --create-namespace --install \ + -f "$VALUES" \ + -f "$SECRETS" + echo "--- Upgrade complete ---" + kubectl -n "$NAMESPACE" get pods + ;; + + diff) + if ! helm plugin list | grep -q diff; then + echo "ERROR: helm-diff plugin not installed. Install with: helm plugin install https://github.com/databus23/helm-diff" >&2 + exit 1 + fi + helm diff upgrade "$RELEASE" "$CHART_DIR" \ + -n "$NAMESPACE" \ + -f "$VALUES" \ + -f "$SECRETS" || true + ;; + + template) + helm template "$RELEASE" "$CHART_DIR" \ + -n "$NAMESPACE" \ + -f "$VALUES" \ + -f "$SECRETS" + ;; + + destroy) + echo "--- Uninstalling ${RELEASE} ---" + helm uninstall "$RELEASE" -n "$NAMESPACE" --ignore-not-found + echo "--- Destroyed ---" + ;; + + status) + kubectl -n "$NAMESPACE" get all + ;; + + logs) + kubectl -n "$NAMESPACE" logs -l app.kubernetes.io/name=apcupsd-client -f --tail=50 + ;; + + restart) + kubectl -n "$NAMESPACE" rollout restart deployment/apcupsd-client + kubectl -n "$NAMESPACE" rollout status deployment/apcupsd-client --timeout=120s + ;; + + *) + echo "Usage: $0 {install|upgrade|diff|template|destroy|status|logs|restart}" >&2 + exit 1 + ;; +esac