Working NixOS port: all core services operational

- Fix Caddy cfProxy helper for cloudflared http:// vhosts (X-Forwarded-Proto)
- Fix Authelia LDAP bind (readonly user ACL + password sync)
- Add gitea-admin-setup oneshot service to survive rebuilds
- Update Authelia forward_auth with header_up X-Forwarded-Proto https
- Update TODO.org with completed tasks and LDAP config details
- Remove old Helm/k8s artifacts (Chart.yaml, templates/, values/, scripts)
- Add result to .gitignore

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Aner Zakobar
2026-04-23 14:46:21 +03:00
parent 05619d12fc
commit 0b73d493d8
22 changed files with 1410 additions and 355 deletions
+149 -118
View File
@@ -9,7 +9,15 @@
# Volume layout:
# <dataDir>/gitea/data/ → /data (repos, sqlite db, avatars, lfs, etc.)
#
# The app.ini is rendered by Nix and bind-mounted read-only.
# Configuration strategy: all settings are passed as GITEA__<SECTION>__<KEY>
# environment variables. Gitea writes its own app.ini into /data/gitea/conf/
# on first start; the env vars override every key at runtime without touching
# that file. This avoids the bind-mount / read-only-fs problem where Gitea
# needs to rewrite its own config file on startup.
#
# Non-secret settings go in the `environment` block (they are fine in the
# Nix store). Secret settings go into /run/gitea-secrets.env via ExecStartPre
# (never in the store).
#
# Secrets consumed from sops:
# gitea/admin_password
@@ -21,98 +29,6 @@ let
cfg = config.homey.gitea;
dataDir = config.homey.storage.mountPoint;
domain = homeyConfig.domain;
# Gitea app.ini — generated at build time.
# Secrets that Gitea reads from env vars are referenced as env var names here.
# The actual values are injected by the ExecStartPre wrapper below.
giteaAppIni = ''
APP_NAME = ${homeyConfig.organization}
RUN_MODE = prod
RUN_USER = git
WORK_PATH = /data/gitea
[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 = git.${domain}
HTTP_PORT = 3000
ROOT_URL = https://git.${domain}/
DISABLE_SSH = true
LFS_START_SERVER = true
; LFS_JWT_SECRET injected at container start via env var / startup script
LFS_JWT_SECRET = __GITEA_LFS_JWT_SECRET__
OFFLINE_MODE = false
[lfs]
PATH = /data/git/lfs
[database]
DB_TYPE = sqlite3
PATH = /data/gitea/gitea.db
LOG_SQL = false
[indexer]
ISSUE_INDEXER_PATH = /data/gitea/indexers/issues.bleve
[session]
PROVIDER_CONFIG = /data/gitea/sessions
PROVIDER = file
[picture]
AVATAR_UPLOAD_PATH = /data/gitea/avatars
REPOSITORY_AVATAR_UPLOAD_PATH = /data/gitea/repo-avatars
DISABLE_GRAVATAR = false
[attachment]
PATH = /data/gitea/attachments
[log]
MODE = console
LEVEL = info
ROUTER = console
ROOT_PATH = /data/gitea/log
[security]
INSTALL_LOCK = true
REVERSE_PROXY_LIMIT = 1
REVERSE_PROXY_TRUSTED_PROXIES = *
; INTERNAL_TOKEN injected at container start
INTERNAL_TOKEN = __GITEA_INTERNAL_TOKEN__
[service]
DISABLE_REGISTRATION = true
REQUIRE_SIGNIN_VIEW = false
REGISTER_EMAIL_CONFIRM = false
ENABLE_NOTIFY_MAIL = false
ALLOW_ONLY_EXTERNAL_REGISTRATION = true
ENABLE_CAPTCHA = false
DEFAULT_ALLOW_CREATE_ORGANIZATION = true
DEFAULT_ENABLE_TIMETRACKING = true
NO_REPLY_ADDRESS = noreply.localhost
ENABLE_REVERSE_PROXY_AUTHENTICATION = true
ENABLE_REVERSE_PROXY_AUTO_REGISTRATION = true
[mailer]
ENABLED = false
[openid]
ENABLE_OPENID_SIGNIN = false
ENABLE_OPENID_SIGNUP = false
[oauth2]
ENABLE = false
; JWT_SECRET injected at container start
JWT_SECRET = __GITEA_OAUTH2_JWT_SECRET__
'';
in
{
options.homey.gitea = {
@@ -139,60 +55,175 @@ in
sops.secrets."gitea/oauth2_jwt_secret" = { owner = "root"; };
sops.secrets."gitea/internal_token" = { owner = "root"; };
# -----------------------------------------------------------------------
# Write the app.ini template to /etc (will be processed by ExecStartPre)
# -----------------------------------------------------------------------
environment.etc."gitea/app.ini.tpl" = {
text = giteaAppIni;
mode = "0444";
};
# -----------------------------------------------------------------------
# Container
# -----------------------------------------------------------------------
virtualisation.oci-containers.containers.gitea = {
image = cfg.image;
ports = [ "127.0.0.1:${toString cfg.port}:3000" ];
# No ports mapping — --network=host means the container shares the host
# network stack directly. Gitea binds to 0.0.0.0:3000 on the host.
# All non-secret settings via GITEA__<SECTION>__<KEY> env vars.
# These are safe to store in the Nix store.
environment = {
USER_UID = "1000";
USER_GID = "1000";
# Tell gitea where to look for the config
USER_UID = "1000";
USER_GID = "1000";
GITEA_CUSTOM = "/data/gitea";
# [DEFAULT]
GITEA____APP_NAME = homeyConfig.organization;
GITEA____RUN_MODE = "prod";
# [repository]
GITEA__repository__ROOT = "/data/git/repositories";
# [server]
GITEA__server__APP_DATA_PATH = "/data/gitea";
GITEA__server__DOMAIN = "git.${domain}";
GITEA__server__HTTP_PORT = toString cfg.port;
GITEA__server__ROOT_URL = "https://git.${domain}/";
GITEA__server__DISABLE_SSH = "true";
GITEA__server__START_SSH_SERVER = "false";
GITEA__server__SSH_PORT = "2222";
GITEA__server__SSH_LISTEN_PORT = "2222";
GITEA__server__LFS_START_SERVER = "true";
GITEA__server__OFFLINE_MODE = "false";
# [lfs]
GITEA__lfs__PATH = "/data/git/lfs";
# [database]
GITEA__database__DB_TYPE = "sqlite3";
GITEA__database__PATH = "/data/gitea/gitea.db";
GITEA__database__LOG_SQL = "false";
# [indexer]
GITEA__indexer__ISSUE_INDEXER_PATH = "/data/gitea/indexers/issues.bleve";
# [session]
GITEA__session__PROVIDER = "file";
GITEA__session__PROVIDER_CONFIG = "/data/gitea/sessions";
# [picture]
GITEA__picture__AVATAR_UPLOAD_PATH = "/data/gitea/avatars";
GITEA__picture__REPOSITORY_AVATAR_UPLOAD_PATH = "/data/gitea/repo-avatars";
GITEA__picture__DISABLE_GRAVATAR = "false";
# [attachment]
GITEA__attachment__PATH = "/data/gitea/attachments";
# [log]
GITEA__log__MODE = "console";
GITEA__log__LEVEL = "info";
GITEA__log__ROOT_PATH = "/data/gitea/log";
# [security]
GITEA__security__INSTALL_LOCK = "true";
GITEA__security__REVERSE_PROXY_LIMIT = "1";
GITEA__security__REVERSE_PROXY_TRUSTED_PROXIES = "*";
# [service]
GITEA__service__DISABLE_REGISTRATION = "true";
GITEA__service__REQUIRE_SIGNIN_VIEW = "false";
GITEA__service__REGISTER_EMAIL_CONFIRM = "false";
GITEA__service__ENABLE_NOTIFY_MAIL = "false";
GITEA__service__ALLOW_ONLY_EXTERNAL_REGISTRATION = "true";
GITEA__service__ENABLE_CAPTCHA = "false";
GITEA__service__DEFAULT_ALLOW_CREATE_ORGANIZATION = "true";
GITEA__service__DEFAULT_ENABLE_TIMETRACKING = "true";
GITEA__service__NO_REPLY_ADDRESS = "noreply.localhost";
GITEA__service__ENABLE_REVERSE_PROXY_AUTHENTICATION = "true";
GITEA__service__ENABLE_REVERSE_PROXY_AUTO_REGISTRATION = "true";
# [mailer]
GITEA__mailer__ENABLED = "false";
# [openid]
GITEA__openid__ENABLE_OPENID_SIGNIN = "false";
GITEA__openid__ENABLE_OPENID_SIGNUP = "false";
# [oauth2]
GITEA__oauth2__ENABLED = "false";
};
# Secret env vars written at runtime by ExecStartPre — never in store.
environmentFiles = [ "/run/gitea-secrets.env" ];
volumes = [
"${dataDir}/gitea/data:/data"
# The processed app.ini is written by ExecStartPre into /run/gitea-conf/
"/run/gitea-conf/app.ini:/data/gitea/conf/app.ini:ro"
];
extraOptions = [ "--network=host" ];
};
# -----------------------------------------------------------------------
# ExecStartPre: substitute secret placeholders into the ini template
# ExecStartPre: write ephemeral secrets env file
# ExecStopPost: clean it up
# -----------------------------------------------------------------------
systemd.services."podman-gitea" = {
serviceConfig = {
ExecStartPre = [
(pkgs.writeShellScript "gitea-build-config" ''
(pkgs.writeShellScript "gitea-write-secrets" ''
set -euo pipefail
install -d -m 700 /run/gitea-conf
LFS=$(cat ${config.sops.secrets."gitea/lfs_jwt_secret".path})
OAUTH=$(cat ${config.sops.secrets."gitea/oauth2_jwt_secret".path})
TOKEN=$(cat ${config.sops.secrets."gitea/internal_token".path})
sed \
-e "s|__GITEA_LFS_JWT_SECRET__|$LFS|g" \
-e "s|__GITEA_OAUTH2_JWT_SECRET__|$OAUTH|g" \
-e "s|__GITEA_INTERNAL_TOKEN__|$TOKEN|g" \
/etc/gitea/app.ini.tpl > /run/gitea-conf/app.ini
chmod 444 /run/gitea-conf/app.ini
printf '%s\n' \
"GITEA__server__LFS_JWT_SECRET=$LFS" \
"GITEA__security__INTERNAL_TOKEN=$TOKEN" \
"GITEA__oauth2__JWT_SECRET=$OAUTH" \
> /run/gitea-secrets.env
chmod 600 /run/gitea-secrets.env
'')
];
ExecStopPost = [
(pkgs.writeShellScript "gitea-cleanup-secrets" ''
rm -f /run/gitea-secrets.env
'')
];
};
after = lib.mkAfter [ "mnt-data.mount" "podman-openldap.service" ];
requires = lib.mkAfter [ "mnt-data.mount" ];
after = lib.mkAfter [ "mnt-data.mount" "podman-openldap.service" ];
requires = lib.mkAfter [ "mnt-data.mount" ];
};
# -----------------------------------------------------------------------
# Ensure the Gitea admin user exists with the correct password after start.
# Runs as a oneshot after podman-gitea; idempotent (create or update).
# -----------------------------------------------------------------------
systemd.services."gitea-admin-setup" = {
description = "Ensure Gitea admin user exists with correct password";
wantedBy = [ "multi-user.target" ];
after = [ "podman-gitea.service" ];
requires = [ "podman-gitea.service" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
set -euo pipefail
PASS=$(cat ${config.sops.secrets."gitea/admin_password".path})
# Wait until Gitea's HTTP endpoint is up (max 60 s)
for i in $(seq 1 60); do
if ${pkgs.curl}/bin/curl -sf http://127.0.0.1:${toString cfg.port}/ -o /dev/null; then
break
fi
sleep 1
done
# Sync password if admin exists; create if not.
if ! ${pkgs.podman}/bin/podman exec -u 1000 gitea \
gitea admin user change-password --username admin --password "$PASS" 2>/dev/null; then
${pkgs.podman}/bin/podman exec -u 1000 gitea \
gitea admin user create \
--username admin \
--password "$PASS" \
--email "admin@${domain}" \
--admin
fi
'';
};
};
}