app/codehosting: forgejo deployment
Change-Id: Icfe6e0b17932a3248e1bdb807f431c59c48430de
Reviewed-on: https://gerrit.hackerspace.pl/c/hscloud/+/1685
Reviewed-by: q3k <q3k@hackerspace.pl>
diff --git a/app/codehosting/forgejo.libsonnet b/app/codehosting/forgejo.libsonnet
new file mode 100644
index 0000000..9abbcf1
--- /dev/null
+++ b/app/codehosting/forgejo.libsonnet
@@ -0,0 +1,253 @@
+/*
+
+ Deploy a Forgejo instance with PostgreSQL database and additional PV for git data.
+ Pre-provision the secrets with:
+
+ kubectl -n $KUBE_NAMESPACE create secret generic forgejo \
+ --from-literal=postgres_password=$(pwgen -s 24 1) \
+ --from-literal=secret_key=$(pwgen -s 128 1) \
+ --from-literal=admin_password=$(pwgen -s 128 1) \
+ --from-literal=oauth2_client_id=$SSO_CLIENT_ID \
+ --from-literal=oauth2_client_secret=$SSO_CLIENT_SECRET \
+ --from-literal=ldap_bind_dn=$LDAP_BIND_DN \
+ --from-literal=ldap_bind_password=$LDAP_BIND_PASSWORD \
+ --from-literal=smtp_password=$SMTP_PASSWORD
+
+ Import objectstore secret:
+
+ ceph_ns=ceph-waw3; ceph_pool=waw-hdd-redundant-3
+ kubectl -n $ceph_ns get secrets rook-ceph-object-user-${ceph_pool}-object-codehosting -o json | jq 'del(.metadata.namespace,.metadata.resourceVersion,.metadata.uid) | .metadata.creationTimestamp=null' | kubectl apply -f - -n $KUBE_NAMESPACE
+
+ Import oidc auth trigger:
+
+ kubectl -n $KUBE_NAMESPACE exec deploy/postgres -i -- psql -U forgejo forgejo < create-oidc-binding.sql
+
+*/
+
+local kube = import "../../kube/kube.libsonnet";
+local postgres = import "../../kube/postgres.libsonnet";
+
+{
+ local forgejo = self,
+ local cfg = forgejo.cfg,
+ cfg:: {
+ namespace: error "namespace must be set",
+ prefix: "",
+
+ image: "codeberg.org/forgejo/forgejo:1.20.5-0",
+ storageClassName: "waw-hdd-redundant-3",
+ storageSize: { git: "200Gi" },
+
+ admin_username: error "admin_username must be set",
+ admin_email: error "admin_email must be set",
+
+ # Forgejo configuration, roughly representing the structure of app.ini
+ instanceName: error "instanceName (e.g. 'Warsaw Hackerspace Forgejo') must be set",
+ runMode: "prod",
+ server: {
+ domain: error "domain (e.g. git.hackerspace.pl) must be set",
+ sshDomain: cfg.server.domain,
+ rootURL: "https://" + cfg.server.domain + "/",
+ offlineMode: "true",
+ },
+ security: {
+ installLock: "true",
+ },
+ service: {
+ disableRegistration: "false",
+ allowOnlyExternalRegistration: "true",
+ },
+
+ s3: {
+ endpoint: "rook-ceph-rgw-waw-hdd-redundant-3-object.ceph-waw3.svc:80", #{ secretKeyRef: {name: "rook-ceph-object-user-waw-hdd-redundant-3-object-codehosting", key: "Endpoint" } },
+ accessKey: { secretKeyRef: {name: "rook-ceph-object-user-waw-hdd-redundant-3-object-codehosting", key: "AccessKey" } },
+ secretKey: { secretKeyRef: {name: "rook-ceph-object-user-waw-hdd-redundant-3-object-codehosting", key: "SecretKey" } },
+ bucket: "codehosting",
+ },
+
+ mailer: {
+ from: "forgejo@hackerspace.pl",
+ host: "mail.hackerspace.pl",
+ port: 465,
+ user: "forgejo",
+ password: { secretKeyRef: { name: "forgejo", key: "smtp_password" } },
+ },
+ },
+
+ name(suffix):: cfg.prefix + suffix,
+ ns: kube.Namespace(cfg.namespace),
+
+ postgres: postgres {
+ cfg+: {
+ namespace: cfg.namespace,
+ appName: "forgejo",
+ database: "forgejo",
+ username: "forgejo",
+ password: { secretKeyRef: { name: "forgejo", key: "postgres_password" } },
+ storageClassName: cfg.storageClassName,
+ },
+ },
+
+ configMap: forgejo.ns.Contain(kube.ConfigMap(forgejo.name("forgejo"))) {
+ data: {
+ "app.ini.template": importstr 'app.ini.template',
+ "entrypoint.sh": importstr 'entrypoint.sh',
+ "bootstrap-auth.sh": importstr 'bootstrap-auth.sh',
+ },
+ },
+
+ dataVolume: forgejo.ns.Contain(kube.PersistentVolumeClaim(forgejo.name("forgejo"))) {
+ spec+: {
+ storageClassName: cfg.storageClassName,
+ accessModes: [ "ReadWriteOnce" ],
+ resources: {
+ requests: {
+ storage: cfg.storageSize.git,
+ },
+ },
+ },
+ },
+
+ forgejoCustom: forgejo.ns.Contain(kube.ConfigMap(forgejo.name("forgejo-custom"))) {
+ data: {
+ "signin_inner.tmpl": importstr 'signin_inner.tmpl',
+ },
+ },
+
+ statefulSet: forgejo.ns.Contain(kube.StatefulSet(forgejo.name("forgejo"))) {
+ spec+: {
+ replicas: 1,
+ template+: {
+ spec+: {
+ securityContext: {
+ runAsUser: 1000,
+ runAsGroup: 1000,
+ fsGroup: 1000,
+ },
+ volumes_: {
+ configmap: kube.ConfigMapVolume(forgejo.configMap),
+ custom: kube.ConfigMapVolume(forgejo.forgejoCustom),
+ data: kube.PersistentVolumeClaimVolume(forgejo.dataVolume),
+ empty: kube.EmptyDirVolume(),
+ },
+ containers_: {
+ server: kube.Container(forgejo.name("forgejo")) {
+ image: cfg.image,
+ command: [ "bash", "/usr/bin/entrypoint" ],
+ ports_: {
+ server: { containerPort: 3000 },
+ ssh: { containerPort: 22 },
+ },
+ readinessProbe: {
+ tcpSocket: {
+ port: "server",
+ },
+ initialDelaySeconds: 5,
+ periodSeconds: 5,
+ successThreshold: 1,
+ failureThreshold: 3
+ },
+ env_: {
+ APP_NAME: cfg.instanceName,
+ RUN_MODE: cfg.runMode,
+ INSTALL_LOCK: cfg.security.installLock,
+ SECRET_KEY: { secretKeyRef: { name: "forgejo", key: "secret_key" } },
+ DB_TYPE: "postgres",
+ DB_HOST: "postgres:5432",
+ DB_USER: forgejo.postgres.cfg.username,
+ DB_PASSWD: forgejo.postgres.cfg.password,
+ DB_NAME: forgejo.postgres.cfg.appName,
+ DOMAIN: cfg.server.domain,
+ SSH_DOMAIN: cfg.server.sshDomain,
+ SSH_LISTEN_PORT: "2222",
+ ROOT_URL: forgejo.cfg.server.rootURL,
+ DISABLE_REGISTRATION: cfg.service.disableRegistration,
+ ALLOW_ONLY_EXTERNAL_REGISTRATION: cfg.service.allowOnlyExternalRegistration,
+ OFFLINE_MODE: cfg.server.offlineMode,
+ USER_UID: "1000",
+ USER_GID: "1000",
+ GITEA_CUSTOM: "/custom",
+ MINIO_ENDPOINT: cfg.s3.endpoint,
+ MINIO_BUCKET: cfg.s3.bucket,
+ MINIO_ACCESS_KEY_ID: cfg.s3.accessKey,
+ MINIO_SECRET_ACCESS_KEY: cfg.s3.secretKey,
+ MAILER_FROM: cfg.mailer.from,
+ MAILER_HOST: cfg.mailer.host,
+ MAILER_PORT: cfg.mailer.port,
+ MAILER_USER: cfg.mailer.user,
+ MAILER_PASSWORD: cfg.mailer.password,
+ },
+ volumeMounts: [
+ { name: "configmap", subPath: "entrypoint.sh", mountPath: "/usr/bin/entrypoint" },
+ { name: "configmap", subPath: "app.ini.template", mountPath: "/etc/templates/app.ini" },
+ { name: "data", mountPath: "/data" },
+ { name: "empty", mountPath: "/custom" },
+ { name: "custom", subPath: "signin_inner.tmpl", mountPath: "/custom/templates/user/auth/signin_inner.tmpl" },
+ ],
+ },
+ },
+ initContainers: [
+ kube.Container(forgejo.name("forgejo-dbmigrate")) {
+ image: forgejo.statefulSet.spec.template.spec.containers_.server.image,
+ command: [ "bash", "/usr/bin/entrypoint", "/app/gitea/gitea", "migrate" ],
+ env_: forgejo.statefulSet.spec.template.spec.containers_.server.env_,
+ volumeMounts: forgejo.statefulSet.spec.template.spec.containers_.server.volumeMounts,
+ },
+ kube.Container(forgejo.name("forgejo-bootstrap-auth")) {
+ image: forgejo.statefulSet.spec.template.spec.containers_.server.image,
+ command: [
+ "bash", "/bootstrap-auth.sh"
+ ],
+ env_: forgejo.statefulSet.spec.template.spec.containers_.server.env_ + {
+ ADMIN_PASSWORD: { secretKeyRef: { name: "forgejo", key: "admin_password" } },
+ SSO_CLIENT_ID: { secretKeyRef: { name: "forgejo", key: "oauth2_client_id" } },
+ SSO_CLIENT_SECRET: { secretKeyRef: { name: "forgejo", key: "oauth2_client_secret" } },
+ LDAP_BIND_DN: { secretKeyRef: { name: "forgejo", key: "ldap_bind_dn" } },
+ LDAP_BIND_PASSWORD: { secretKeyRef: { name: "forgejo", key: "ldap_bind_password" } },
+ },
+ volumeMounts: forgejo.statefulSet.spec.template.spec.containers_.server.volumeMounts + [
+ { name: "configmap", subPath: "bootstrap-auth.sh", mountPath: "/bootstrap-auth.sh" },
+ ]
+ },
+ ],
+ },
+ },
+ },
+ },
+
+ svc: forgejo.ns.Contain(kube.Service(forgejo.name("forgejo"))) {
+ target_pod:: forgejo.statefulSet.spec.template,
+ spec+: {
+ ports: [
+ { name: "server", port: 80, targetPort: 3000, protocol: "TCP" },
+ { name: "ssh", port: 22, targetPort: 2222, protocol: "TCP" },
+ ],
+ },
+ },
+
+ ingress: forgejo.ns.Contain(kube.Ingress(forgejo.name("forgejo"))) {
+ metadata+: {
+ annotations+: {
+ "kubernetes.io/tls-acme": "true",
+ "cert-manager.io/cluster-issuer": "letsencrypt-prod",
+ "nginx.ingress.kubernetes.io/proxy-body-size": "0",
+ },
+ },
+ spec+: {
+ tls: [
+ { hosts: [cfg.server.domain], secretName: forgejo.name("acme") },
+ ],
+ rules: [
+ {
+ host: cfg.server.domain,
+ http: {
+ paths: [
+ { path: "/", backend: forgejo.svc.name_port },
+ ],
+ },
+ }
+ ],
+ },
+ },
+
+}