diff --git a/helm/ezscale-website/README.md b/helm/ezscale-website/README.md new file mode 100644 index 0000000..46d9b2a --- /dev/null +++ b/helm/ezscale-website/README.md @@ -0,0 +1,134 @@ +# ezscale-website Helm Chart + +Production deployment for the EZSCALE billing platform Laravel app. + +## Environments + +| Env | Values file | Notes | +|---|---|---| +| Local k3d | `values-local.yaml` | Self-contained: in-cluster MariaDB + Valkey. No ingress (use port-forward). | +| US prod | `values-us-prod.yaml` | Connects to existing `mariadb` CR in `ezscale` ns; per-app Valkey on Longhorn. | + +## Prerequisites (us-prod) + +The `ezscale` namespace must already have: +- mariadb-operator installed cluster-wide (provides `MariaDB`/`Database`/`User`/`Grant` CRDs) +- A `MariaDB` CR named `mariadb` +- cert-manager `ClusterIssuer` named `letsencrypt` +- Traefik with `cloudflarewarp` and `http-to-https` middlewares in `kube-system` +- Image-pull `Secret` named `gitea-registry` + +These are all already provisioned per `infrastructure/kubernetes/`. + +## First-time bootstrap (us-prod) + +This step is run **once** per environment, never repeated. The Secret it creates lives forever — re-running it would invalidate every encrypted DB column and every issued OAuth token. + +```bash +# 1. Generate APP_KEY locally (any Laravel checkout works) +APP_KEY=$(cd /tmp && composer create-project laravel/laravel _key_gen --quiet --no-install \ + && cd _key_gen && composer install --quiet \ + && php artisan key:generate --show) + +# 2. Generate Passport keys +TMP_KEYS=$(mktemp -d) +openssl genrsa -out "$TMP_KEYS/oauth-private.key" 4096 +openssl rsa -in "$TMP_KEYS/oauth-private.key" -pubout -out "$TMP_KEYS/oauth-public.key" + +# 3. Gather production secrets. Read each one and store in shell vars. +DB_PASS=$(openssl rand -base64 32 | tr -d '/=+' | cut -c1-32) +read -sp "STRIPE_KEY (pk_live_...): " STRIPE_KEY; echo +read -sp "STRIPE_SECRET (sk_live_...): " STRIPE_SECRET; echo +read -sp "STRIPE_WEBHOOK_SECRET (whsec_...): " STRIPE_WEBHOOK; echo +read -sp "PAYPAL_CLIENT_ID: " PAYPAL_ID; echo +read -sp "PAYPAL_CLIENT_SECRET: " PAYPAL_SECRET; echo +read -sp "AWS_ACCESS_KEY_ID (Storj): " AWS_KEY; echo +read -sp "AWS_SECRET_ACCESS_KEY (Storj): " AWS_SECRET; echo +read -sp "MAIL_USERNAME (SMTP): " MAIL_USER; echo +read -sp "MAIL_PASSWORD (SMTP): " MAIL_PASS; echo +read -sp "VIRTFUSION_API_KEY: " VF_KEY; echo +read -sp "PTERODACTYL_APP_KEY: " PTERO_KEY; echo + +# 4. Create the Secret in the ezscale namespace +kubectl create secret generic ezscale-website-secrets \ + --namespace ezscale \ + --from-literal=APP_KEY="$APP_KEY" \ + --from-literal=DB_PASSWORD="$DB_PASS" \ + --from-literal=STRIPE_KEY="$STRIPE_KEY" \ + --from-literal=STRIPE_SECRET="$STRIPE_SECRET" \ + --from-literal=STRIPE_WEBHOOK_SECRET="$STRIPE_WEBHOOK" \ + --from-literal=PAYPAL_CLIENT_ID="$PAYPAL_ID" \ + --from-literal=PAYPAL_CLIENT_SECRET="$PAYPAL_SECRET" \ + --from-literal=AWS_ACCESS_KEY_ID="$AWS_KEY" \ + --from-literal=AWS_SECRET_ACCESS_KEY="$AWS_SECRET" \ + --from-literal=MAIL_USERNAME="$MAIL_USER" \ + --from-literal=MAIL_PASSWORD="$MAIL_PASS" \ + --from-literal=VIRTFUSION_API_KEY="$VF_KEY" \ + --from-literal=PTERODACTYL_APP_KEY="$PTERO_KEY" \ + --from-file=oauth-private.key="$TMP_KEYS/oauth-private.key" \ + --from-file=oauth-public.key="$TMP_KEYS/oauth-public.key" + +# 5. Wipe the local key copies +rm -rf /tmp/_key_gen "$TMP_KEYS" +``` + +After this, `helm install` and every subsequent `helm upgrade` reference the existing Secret without ever rewriting it. + +## Deploy + +```bash +helm upgrade --install ezscale-website ./helm/ezscale-website \ + --namespace ezscale \ + -f helm/ezscale-website/values-us-prod.yaml \ + --set image.tag=v0.1.0 +``` + +## Local dev cluster + +```bash +# 1. Create cluster with Longhorn (or local-path) and the operator +k3d cluster create ezscale-local +helm repo add mariadb-operator https://helm.mariadb.com/mariadb-operator +helm install mariadb-operator -n mariadb-operator --create-namespace mariadb-operator/mariadb-operator + +# 2. Build the image into the cluster +docker build --target app -t ezscale-website:app-local . +docker build --target horizon -t ezscale-website:horizon-local . +docker build --target scheduler -t ezscale-website:scheduler-local . +k3d image import ezscale-website:app-local ezscale-website:horizon-local ezscale-website:scheduler-local -c ezscale-local + +# 3. Edit values-local.yaml — replace APP_KEY with `php artisan key:generate --show` output + +# 4. Install +helm upgrade --install ezscale-website ./helm/ezscale-website \ + --namespace ezscale --create-namespace \ + -f helm/ezscale-website/values-local.yaml \ + --set image.registry=docker.io \ + --set image.repository=library/ezscale-website \ + --set image.tag=local + +# 5. Port-forward and visit http://localhost:8080 +kubectl port-forward -n ezscale svc/ezscale-website 8080:80 +``` + +## Operations + +```bash +# Tail Horizon logs +kubectl logs -n ezscale -l app.kubernetes.io/component=horizon -f + +# Run an artisan command in a one-off pod +kubectl run -n ezscale --rm -it --image=git.ezscale.cloud/ezscale/website:app-v0.1.0 \ + --env-from='[{"configMapRef":{"name":"ezscale-website-env"}},{"secretRef":{"name":"ezscale-website-secrets"}}]' \ + artisan-shell -- bash + +# Force a Horizon rolling restart +kubectl rollout restart deployment -n ezscale ezscale-website-horizon +``` + +## What's NOT in this chart (intentionally) + +- Cloudflare Zero Trust for the admin panel — layered on later. +- ExternalDNS records — DNS managed manually in Cloudflare Terraform. +- Backup CronJob for the application database — covered by the existing `mariadb` instance's backup CronJob in `infrastructure/kubernetes/ezscale/mysql/`. +- Backups for Storj uploads — Storj provides its own replication; per-bucket lifecycle policies live in the Storj console.