2026-10-08 10:36:33 +02:00
#!/usr/bin/env bash
set -Eeuo pipefail
NAMESPACE = go-insane-prd
REGISTRY = nexus.urpi.be
CERT_MANAGER_VERSION = v1.19.1
LOCAL_PATH_VERSION = v0.0.37
LOCK_NAME = forgejo-prd-deploy-lock
if [ [ $# -lt 4 ] ] ; then echo " Usage: $0 <chart> <release> <image-name> <image-tag> [helm args...] " >& 2; exit 2; fi
CHART = $1 ; RELEASE = $2 ; IMAGE_NAME = $3 ; IMAGE_TAG = $4 ; shift 4
: " ${ KUBECONFIG_PRD : ?Set the Forgejo secret KUBECONFIG_PRD to the PRD kubeconfig contents } "
: " ${ NEXUS_USERNAME : ?Set the Forgejo secret NEXUS_USERNAME } "
: " ${ NEXUS_PASSWORD : ?Set the Forgejo secret NEXUS_PASSWORD } "
KUBECONFIG = " ${ RUNNER_TEMP :- /tmp } /kubeconfig-prd- ${ GITHUB_RUN_ID :- $$ } "
umask 077; printf '%s\n' " $KUBECONFIG_PRD " > " $KUBECONFIG " ; export KUBECONFIG
LOCK_ACQUIRED = false
cleanup( ) { if [ [ " $LOCK_ACQUIRED " = = true ] ] ; then kubectl -n " $NAMESPACE " delete configmap " $LOCK_NAME " --ignore-not-found --wait= false >/dev/null 2>& 1 || true; fi ; rm -f " $KUBECONFIG " ; }
trap cleanup EXIT
secret_value( ) {
local name = $1 key = $2 encoded
encoded = " $( kubectl -n " $NAMESPACE " get secret " $name " -o go-template= " {{index .data \" ${ key } \"}} " 2>/dev/null || true ) "
[ [ -z " $encoded " || " $encoded " = = "<no value>" ] ] && return 0
printf '%s' " $encoded " | base64 -d 2>/dev/null || true
}
kubectl get --raw= /readyz >/dev/null
2026-10-10 18:58:32 +02:00
if [ [ " $RELEASE " = = go-loose ] ] && ! kubectl -n " $NAMESPACE " get secret go-loose-wildcard-tls >/dev/null 2>& 1; then
echo "Required go-loose-wildcard-tls Secret is missing; provision the tenant wildcard certificate before bootstrap/deploy." >& 2
exit 1
fi
2026-10-08 10:36:33 +02:00
kubectl create namespace " $NAMESPACE " --dry-run= client -o yaml | kubectl apply -f -
for attempt in { 1..360} ; do if kubectl -n " $NAMESPACE " create configmap " $LOCK_NAME " --from-literal= owner = " ${ GITHUB_REPOSITORY :- local } : ${ GITHUB_RUN_ID :- $$ } " >/dev/null 2>& 1; then LOCK_ACQUIRED = true; break; fi ; sleep 5; done
if [ [ " $LOCK_ACQUIRED " != true ] ] ; then echo "Timed out waiting for another go-insane-prd deploy to release its lock." >& 2; exit 1; fi
kubectl -n " $NAMESPACE " create secret docker-registry nexus-registry --docker-server= " $REGISTRY " --docker-username= " $NEXUS_USERNAME " --docker-password= " $NEXUS_PASSWORD " --dry-run= client -o yaml | kubectl apply -f -
DEFAULT_STORAGE_CLASS = " $( kubectl get storageclass -o= jsonpath = '{range .items[*]}{.metadata.name}{" "}{.metadata.annotations.storageclass\.kubernetes\.io/is-default-class}{"\n"}{end}' | awk '$2 == "true" { print $1; exit }' ) "
if [ [ -z " $DEFAULT_STORAGE_CLASS " ] ] ; then
if ! kubectl get storageclass local-path >/dev/null 2>& 1 || ! kubectl -n local-path-storage get deployment local-path-provisioner >/dev/null 2>& 1; then kubectl apply -f " https://raw.githubusercontent.com/rancher/local-path-provisioner/ ${ LOCAL_PATH_VERSION } /deploy/local-path-storage.yaml " ; kubectl -n local-path-storage rollout status deployment/local-path-provisioner --timeout= 5m; fi
kubectl annotate storageclass local-path storageclass.kubernetes.io/is-default-class= true --overwrite
fi
if ! kubectl get ingressclass nginx >/dev/null 2>& 1; then
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx --force-update
helm repo update ingress-nginx
helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx --namespace ingress-nginx --create-namespace --set controller.service.type= NodePort --set controller.hostPort.enabled= true --set controller.kind= DaemonSet --set controller.admissionWebhooks.enabled= false --wait --timeout 10m
fi
if ! kubectl get crd clusterissuers.cert-manager.io >/dev/null 2>& 1 || ! kubectl -n cert-manager get deployment cert-manager >/dev/null 2>& 1; then
kubectl apply --validate= false -f " https://github.com/cert-manager/cert-manager/releases/download/ ${ CERT_MANAGER_VERSION } /cert-manager.yaml "
kubectl -n cert-manager rollout status deployment/cert-manager --timeout= 5m
kubectl -n cert-manager rollout status deployment/cert-manager-cainjector --timeout= 5m
kubectl -n cert-manager rollout status deployment/cert-manager-webhook --timeout= 5m
fi
if ! kubectl get clusterissuer letsencrypt-prod >/dev/null 2>& 1; then
kubectl apply -f - <<'ISSUER'
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@urpi.be
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
class: nginx
ISSUER
fi
kubectl wait --for= condition = Ready clusterissuer/letsencrypt-prod --timeout= 2m
[ [ -d " $CHART " ] ] || { echo " Helm chart not found: $CHART " >& 2; exit 1; }
if [ [ -f " $CHART /Chart.lock " ] ] ; then helm dependency build " $CHART " ; fi
HELM_ARGS = ( --namespace " $NAMESPACE " --create-namespace)
if [ [ " $RELEASE " != go-guess ] ] ; then HELM_ARGS += ( --set-string " image.repository= ${ REGISTRY } /docker-hosted/ ${ IMAGE_NAME } " --set-string " image.tag= ${ IMAGE_TAG } " ) ; fi
[ [ ! -f " $CHART /values-prd.yaml " ] ] || HELM_ARGS += ( --values " $CHART /values-prd.yaml " )
case " $RELEASE " in
go-loose)
db_password = " $( secret_value go-loose-runtime postgresql-password) "
2026-10-10 18:58:32 +02:00
[ [ -n " $db_password " ] ] || db_password = " $( secret_value go-loose-postgresql postgres-password) "
[ [ -n " $db_password " ] ] || db_password = " $( secret_value go-loose-postgresql password) "
if [ [ -z " $db_password " ] ] && kubectl -n " $NAMESPACE " get pvc -o name | grep -Eq 'go-loose.*postgresql|data-go-loose' ; then
echo "Existing go-loose database storage has no recoverable password secret; refusing to rotate its credentials." >& 2
exit 1
fi
[ [ -n " $db_password " ] ] || db_password = " $( openssl rand -hex 24) "
2026-10-08 10:36:33 +02:00
session_secret = " $( secret_value go-loose-runtime session-secret) "
2026-10-10 18:58:32 +02:00
[ [ -n " $session_secret " ] ] || session_secret = " $( secret_value go-loose-env GO_LOOSE_SESSION_SECRET) "
[ [ -n " $session_secret " ] ] || session_secret = " $( openssl rand -hex 32) "
2026-10-08 10:36:33 +02:00
database_url = " postgres://goloose: ${ db_password } @go-loose-postgresql:5432/goloose?sslmode=disable "
2026-10-10 18:58:32 +02:00
kubectl -n " $NAMESPACE " create secret generic go-loose-runtime \
--from-literal= postgresql-password= " $db_password " \
--from-literal= postgres-password= " $db_password " \
--from-literal= database-url= " $database_url " \
--from-literal= session-secret= " $session_secret " \
--dry-run= client -o yaml | kubectl apply -f -
2026-10-08 10:36:33 +02:00
; ;
go-mad)
if ! kubectl -n " $NAMESPACE " get secret go-mad-runtime >/dev/null 2>& 1; then
db_password = " $( secret_value go-mad-postgresql password) " ; [ [ -n " $db_password " ] ] || db_password = " $( secret_value go-mad-postgresql postgres-password) "
if [ [ -z " $db_password " ] ] && kubectl -n " $NAMESPACE " get pvc -o name | grep -Eq 'go-mad.*postgresql|data-go-mad' ; then echo "Existing go-mad database storage has no recoverable password secret; refusing to rotate its credentials." >& 2; exit 1; fi
[ [ -n " $db_password " ] ] || db_password = " $( openssl rand -hex 24) "
kubectl -n " $NAMESPACE " create secret generic go-mad-runtime --from-literal= postgresql-password= " $db_password " --from-literal= jwt-secret= " $( openssl rand -hex 32) "
fi
db_password = " $( secret_value go-mad-runtime postgresql-password) " ; jwt_secret = " $( secret_value go-mad-runtime jwt-secret) "
HELM_ARGS += ( --set-string " postgresql.auth.password= ${ db_password } " --set-string " go-mad.jwtSecret= ${ jwt_secret } " )
; ;
go-first)
if ! kubectl -n " $NAMESPACE " get secret go-first-runtime >/dev/null 2>& 1; then
kubectl -n " $NAMESPACE " create secret generic go-first-runtime --from-literal= jwt-secret= " $( openssl rand -hex 32) "
fi
; ;
go-places)
: " ${ GO_LOOSE_JWT_SECRET : ?Set Forgejo secret GO_LOOSE_JWT_SECRET to the same signing key used by go-loose } "
kubectl -n " $NAMESPACE " create secret generic go-loose-jwt --from-literal= secret = " $GO_LOOSE_JWT_SECRET " --dry-run= client -o yaml | kubectl apply -f -
; ;
go-bananas)
: " ${ GO_BANANAS_OIDC_CLIENT_SECRET : ?Set Forgejo secret GO_BANANAS_OIDC_CLIENT_SECRET } "
session_secret = " $( secret_value go-bananas-auth session-secret) "
if [ [ -z " $session_secret " ] ] ; then session_secret = " $( openssl rand -hex 32) " ; fi
kubectl -n " $NAMESPACE " create secret generic go-bananas-auth --from-literal= session-secret= " $session_secret " --from-literal= oidc-client-secret= " $GO_BANANAS_OIDC_CLIENT_SECRET " --dry-run= client -o yaml | kubectl apply -f -
; ;
esac
helm upgrade --install " $RELEASE " " $CHART " " ${ HELM_ARGS [@] } " " $@ " --wait --timeout 15m
kubectl -n " $NAMESPACE " get deployments,statefulsets,pods,services,ingresses