4
0
Fork 2
mirror of https://gerrit.hackerspace.pl/hscloud synced 2025-01-21 21:13:53 +00:00

app/codehosting: forgejo deployment

Change-Id: Icfe6e0b17932a3248e1bdb807f431c59c48430de
Reviewed-on: https://gerrit.hackerspace.pl/c/hscloud/+/1685
Reviewed-by: q3k <q3k@hackerspace.pl>
This commit is contained in:
informatic 2023-09-24 14:12:59 +02:00 committed by informatic
parent f1dbac29a1
commit 3a3b425ddf
9 changed files with 587 additions and 1 deletions

26
app/codehosting/README.md Normal file
View file

@ -0,0 +1,26 @@
# Hackerspace Code Hosting deployment
"Code Hosting service" below means Forgejo.
Due to certain specific requirements our deployment is a little customized.
While we prefer users to use SSO/OpenID Connect for authentication, we also
want code hosting service to be aware of all active users to correctly
synchronize account access and SSH keys. When running with both LDAP and OpenID
Connect integration enabled users are automatically created in a local database
based on LDAP source, however OpenID Connect identity is not automatically bound
to LDAP users. This causes code hosting service to still show a password-based
authentication form in order to join the two identities.
Workaround for this in our case is a SQL trigger function that automatically
creates an OpenID Connect -> LDAP identity binding injected directly into code
hosting service's PostgreSQL database. This trigger can be reviewed in
`create-oidc-binding.sql` file here. For this to work correctly
auto-registration needs to be disabled for OpenID Connect integration, in case
some new user attempts to log in before code hosting service runs external
users synchronization job.
LDAP users synchronization job has been adjusted to run every 10 minutes. (in
contrast to default 24h, see `app.ini.template`)
Explore page has users listing disabled. Email and name display is disabled.

View file

@ -0,0 +1,98 @@
APP_NAME = $APP_NAME
RUN_MODE = $RUN_MODE
[repository]
ROOT = /data/git/repositories
[repository.local]
LOCAL_COPY_PATH = /data/gitea/tmp/local-repo
[repository.upload]
TEMP_PATH = /data/gitea/uploads
[server]
APP_DATA_PATH = /data/gitea
DOMAIN = $DOMAIN
SSH_DOMAIN = $SSH_DOMAIN
HTTP_PORT = $HTTP_PORT
ROOT_URL = $ROOT_URL
START_SSH_SERVER = true
DISABLE_SSH = $DISABLE_SSH
SSH_PORT = $SSH_PORT
SSH_LISTEN_PORT = $SSH_LISTEN_PORT
LFS_START_SERVER = $LFS_START_SERVER
OFFLINE_MODE = $OFFLINE_MODE
LANDING_PAGE = explore
[lfs]
PATH = /data/git/lfs
[database]
PATH = /data/gitea/gitea.db
DB_TYPE = $DB_TYPE
HOST = $DB_HOST
NAME = $DB_NAME
USER = $DB_USER
PASSWD = $DB_PASSWD
[storage]
STORAGE_TYPE = minio
MINIO_ENDPOINT = $MINIO_ENDPOINT
MINIO_ACCESS_KEY_ID = $MINIO_ACCESS_KEY_ID
MINIO_SECRET_ACCESS_KEY = $MINIO_SECRET_ACCESS_KEY
MINIO_BUCKET = $MINIO_BUCKET
[indexer]
ISSUE_INDEXER_PATH = /data/gitea/indexers/issues.bleve
[session]
PROVIDER_CONFIG = /data/gitea/sessions
[picture]
AVATAR_UPLOAD_PATH = /data/gitea/avatars
REPOSITORY_AVATAR_UPLOAD_PATH = /data/gitea/repo-avatars
[attachment]
PATH = /data/gitea/attachments
[log]
ROOT_PATH = /data/gitea/log
[security]
INSTALL_LOCK = $INSTALL_LOCK
SECRET_KEY = $SECRET_KEY
[service]
DISABLE_REGISTRATION = $DISABLE_REGISTRATION
REQUIRE_SIGNIN_VIEW = $REQUIRE_SIGNIN_VIEW
ALLOW_ONLY_EXTERNAL_REGISTRATION = $ALLOW_ONLY_EXTERNAL_REGISTRATION
[service.explore]
DISABLE_USERS_PAGE = true
[ui]
SHOW_USER_EMAIL = false
[oauth2_client]
REGISTER_EMAIL_CONFIRM = false
ENABLE_AUTO_REGISTRATION = false
USERNAME = userid
ACCOUNT_LINKING = auto
[cron.sync_external_users]
SCHEDULE = @every 10m
RUN_AT_START = true
[i18n]
LANGS = en-US,pl-PL
NAMES = English,Polski
[mailer]
ENABLED = true
FROM = $MAILER_FROM
PROTOCOL = smtps
SMTP_ADDR = $MAILER_HOST
SMTP_PORT = $MAILER_PORT
USER = $MAILER_USER
PASSWD = $MAILER_PASSWORD

View file

@ -0,0 +1,19 @@
#!/bin/bash
# This script runs in an initContainer (once, using /data/.gitea_bootstrap_done
# as a witness file) and is responsible for setting up and configuring:
# * initial admin user
# * hswaw OpenID Connect provider
# * hswaw LDAP user database
set -e -o pipefail
if [[ -f '/data/.gitea_bootstrap_done' ]]; then
echo '/data/.gitea_bootstrap_done exists, not doing anything'
exit 0
fi
/app/gitea/gitea admin user create --username bofh --password ${ADMIN_PASSWORD} --email bofh@hackerspace.pl --admin --must-change-password=false
/app/gitea/gitea admin auth add-oauth --name hswaw-oidc --provider openidConnect --key ${SSO_CLIENT_ID} --secret ${SSO_CLIENT_SECRET} --auto-discover-url https://sso.hackerspace.pl/.well-known/openid-configuration
/app/gitea/gitea admin auth add-ldap --name hswaw-ldap --active --security-protocol ldaps --host ldap.hackerspace.pl --port 636 --bind-dn ${LDAP_BIND_DN} --bind-password ${LDAP_BIND_PASSWORD} --user-search-base "ou=People,dc=hackerspace,dc=pl" --user-filter "(&(objectclass=hsMember)(uid=%[1]s)(|(memberOf=cn=fatty,ou=Group,dc=hackerspace,dc=pl)(memberOf=cn=starving,ou=Group,dc=hackerspace,dc=pl)(memberOf=cn=potato,ou=Group,dc=hackerspace,dc=pl)))" --admin-filter "(memberOf=cn=staff,ou=Group,dc=hackerspace,dc=pl)" --username-attribute uid --email-attribute mail --public-ssh-key-attribute sshPublicKey --synchronize-users
touch /data/.gitea_bootstrap_done

View file

@ -0,0 +1,23 @@
-- This trigger automatically ensures an openid connect identity is bound to an
-- equivalent LDAP account created by Gitea/Forgejo.
BEGIN;
CREATE OR REPLACE FUNCTION upsert_oidc_external_users ()
RETURNS TRIGGER
AS $$
BEGIN
-- 1 is OpenID Connect login source ID
-- 2 is LDAP login source ID
INSERT INTO external_login_user (external_id, user_id, login_source_id) SELECT name, id, 1 FROM "user" WHERE login_source = 2 ON CONFLICT DO NOTHING;
RETURN NULL;
END;
$$ LANGUAGE PLPGSQL;
DROP TRIGGER IF EXISTS upsert_oidc_external_users ON "user";
CREATE TRIGGER upsert_oidc_external_users AFTER INSERT OR UPDATE ON "user" EXECUTE PROCEDURE upsert_oidc_external_users();
-- Force trigger run
UPDATE "user" SET name = name WHERE FALSE;
COMMIT;

View file

@ -0,0 +1,69 @@
#!/bin/bash
for FOLDER in /data/gitea/log /data/git /data/ssh; do
mkdir -p ${FOLDER}
done
if [ ! -d /data/git/.ssh ]; then
mkdir -p /data/git/.ssh
fi
if [ ! -f /data/git/.ssh/environment ]; then
echo "GITEA_CUSTOM=$GITEA_CUSTOM" >| /data/git/.ssh/environment
elif ! grep -q "^GITEA_CUSTOM=$GITEA_CUSTOM$" /data/git/.ssh/environment; then
sed -i /^GITEA_CUSTOM=/d /data/git/.ssh/environment
echo "GITEA_CUSTOM=$GITEA_CUSTOM" >> /data/git/.ssh/environment
fi
if [ ! -f ${GITEA_CUSTOM}/conf/app.ini ]; then
mkdir -p ${GITEA_CUSTOM}/conf
# Set INSTALL_LOCK to true only if SECRET_KEY is not empty and
# INSTALL_LOCK is empty
if [ -n "$SECRET_KEY" ] && [ -z "$INSTALL_LOCK" ]; then
INSTALL_LOCK=true
fi
# Substitude the environment variables in the template
env -i \
APP_NAME="${APP_NAME:-"Gitea: Git with a cup of tea"}" \
RUN_MODE="${RUN_MODE:-"dev"}" \
DOMAIN="${DOMAIN:-"localhost"}" \
SSH_DOMAIN="${SSH_DOMAIN:-"localhost"}" \
HTTP_PORT="${HTTP_PORT:-"3000"}" \
ROOT_URL="${ROOT_URL:-""}" \
DISABLE_SSH="${DISABLE_SSH:-"false"}" \
SSH_PORT="${SSH_PORT:-"22"}" \
SSH_LISTEN_PORT="${SSH_LISTEN_PORT:-"${SSH_PORT}"}" \
LFS_START_SERVER="${LFS_START_SERVER:-"false"}" \
DB_TYPE="${DB_TYPE:-"sqlite3"}" \
DB_HOST="${DB_HOST:-"localhost:3306"}" \
DB_NAME="${DB_NAME:-"gitea"}" \
DB_USER="${DB_USER:-"root"}" \
DB_PASSWD="${DB_PASSWD:-""}" \
INSTALL_LOCK="${INSTALL_LOCK:-"false"}" \
DISABLE_REGISTRATION="${DISABLE_REGISTRATION:-"false"}" \
REQUIRE_SIGNIN_VIEW="${REQUIRE_SIGNIN_VIEW:-"false"}" \
SECRET_KEY="${SECRET_KEY:-""}" \
ALLOW_ONLY_EXTERNAL_REGISTRATION="${ALLOW_ONLY_EXTERNAL_REGISTRATION:-"false"}" \
OFFLINE_MODE="${OFFLINE_MODE:-"true"}" \
MINIO_ENDPOINT="${MINIO_ENDPOINT:-""}" \
MINIO_ACCESS_KEY_ID="${MINIO_ACCESS_KEY_ID:-""}" \
MINIO_SECRET_ACCESS_KEY="${MINIO_SECRET_ACCESS_KEY:-""}" \
MINIO_BUCKET="${MINIO_BUCKET:-""}" \
MAILER_FROM="${MAILER_FROM:-""}" \
MAILER_HOST="${MAILER_HOST:-""}" \
MAILER_PORT="${MAILER_PORT:-""}" \
MAILER_USER="${MAILER_USER:-""}" \
MAILER_PASSWORD="${MAILER_PASSWORD:-""}" \
envsubst < /etc/templates/app.ini > ${GITEA_CUSTOM}/conf/app.ini
cat ${GITEA_CUSTOM}/conf/app.ini
fi
if [ $# -gt 0 ]; then
exec "$@"
else
exec /app/gitea/gitea web
fi

View file

@ -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 },
],
},
}
],
},
},
}

View file

@ -0,0 +1,20 @@
local kube = import "../../kube/kube.libsonnet";
local forgejo = import "forgejo.libsonnet";
{
#namespace: kube.Namespace("forgejo-prod"),
forgejo: forgejo {
cfg+: {
namespace: "codehosting-prod",
prefix: "",
admin_username: "bofh",
admin_email: "bofh@hackerspace.pl",
instanceName: "Warsaw Hackerspace Codehosting",
server+: {
domain: "git.hackerspace.pl",
},
},
},
}

View file

@ -0,0 +1,77 @@
<!-- Mirrored from https://codeberg.org/forgejo/forgejo/raw/tag/v1.20.5-0/templates/user/auth/signin_inner.tmpl with gt-hidden adjustment to hide login form -->
{{if or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn)}}
{{template "base/alert" .}}
{{end}}
<h4 class="ui top attached header center">
{{if .LinkAccountMode}}
{{.locale.Tr "auth.oauth_signin_title"}}
{{else}}
{{.locale.Tr "auth.login_userpass"}}
{{end}}
</h4>
<div class="ui attached segment">
<form class="ui form" action="{{.SignInLink}}" method="post">
{{.CsrfTokenHtml}}
<div class="gt-hidden">
<div class="required inline field {{if and (.Err_UserName) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn))}}error{{end}}">
<label for="user_name">{{.locale.Tr "home.uname_holder"}}</label>
<input id="user_name" type="text" name="user_name" value="{{.user_name}}" autofocus required>
</div>
{{if or (not .DisablePassword) .LinkAccountMode}}
<div class="required inline field {{if and (.Err_Password) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn))}}error{{end}}">
<label for="password">{{.locale.Tr "password"}}</label>
<input id="password" name="password" type="password" value="{{.password}}" autocomplete="current-password" required>
</div>
{{end}}
{{if not .LinkAccountMode}}
<div class="inline field">
<label></label>
<div class="ui checkbox">
<label>{{.locale.Tr "auth.remember_me"}}</label>
<input name="remember" type="checkbox">
</div>
</div>
{{end}}
{{template "user/auth/captcha" .}}
<div class="inline field">
<label></label>
<button class="ui green button">
{{if .LinkAccountMode}}
{{.locale.Tr "auth.oauth_signin_submit"}}
{{else}}
{{.locale.Tr "sign_in"}}
{{end}}
</button>
<a href="{{AppSubUrl}}/user/forgot_password">{{.locale.Tr "auth.forgot_password"}}</a>
</div>
{{if .ShowRegistrationButton}}
<div class="inline field">
<label></label>
<a href="{{AppSubUrl}}/user/sign_up">{{.locale.Tr "auth.sign_up_now" | Str2html}}</a>
</div>
{{end}}
</div>
{{if and .OrderedOAuth2Names .OAuth2Providers}}
<div class="ui horizontal divider gt-hidden">
{{.locale.Tr "sign_in_or"}}
</div>
<div id="oauth2-login-navigator" class="gt-py-2">
<div class="gt-df gt-fc gt-jc">
<div id="oauth2-login-navigator-inner" class="gt-df gt-fc gt-fw gt-ac gt-gap-3">
{{range $key := .OrderedOAuth2Names}}
{{$provider := index $.OAuth2Providers $key}}
<a class="{{$provider.Name}} ui button gt-df gt-ac gt-jc gt-py-3 oauth-login-link" href="{{AppSubUrl}}/user/oauth2/{{$key}}">
{{$provider.IconHTML}}
{{$.locale.Tr "sign_in_with_provider" $provider.DisplayName}}
</a>
{{end}}
</div>
</div>
</div>
{{end}}
</form>
</div>

View file

@ -84,6 +84,7 @@ local policies = import "../../../kube/policies.libsonnet";
tcp: env.maps.make("tcp-services") {
data: {
"22": "gerrit/gerrit:22",
"222": "codehosting-prod/forgejo:22",
}
},
udp: env.maps.make("udp-services"),
@ -235,7 +236,7 @@ local policies = import "../../../kube/policies.libsonnet";
},
},
serviceGitea: kube.Service("ingress-nginx-gitea") {
serviceCodehosting: kube.Service("ingress-nginx-codehosting") {
metadata+: env.metadata,
target:: env.deployment,
spec+: {