Files
Nix-Vibe/modules/services/gitea.nix
T
petere f1f74dc3af fix(gitea): allow anonymous browsing of public repos
Remove REQUIRE_SIGNIN_VIEW so public repos are viewable without login;
registration stays disabled and private repos still require auth.
2026-09-19 14:55:02 +01:00

109 lines
3.5 KiB
Nix

{ config
, pkgs
, lib
, ...
}:
# Gitea git server, publicly exposed through an external reverse proxy.
#
# Topology:
# internet -> gitea.edley.me -> external reverse proxy (TLS terminator)
# -> Tailscale -> homeserver-1:port (Gitea, plain HTTP behind the proxy)
#
# Hardening posture (public instance):
# - HTTPS only (DISABLE_SSH) so there is no second public port to forward.
# - Invite-only: registration disabled, everything hidden behind sign-in.
# - The firewall admits ONLY the proxy's Tailscale IP; no other tailnet
# node (or the LAN) can reach Gitea directly.
# - X-Forwarded-* headers are trusted only from that proxy IP.
# - PUBLIC_URL_DETECTION = never pins all generated URLs to ROOT_URL
# (no Host-header hijacking).
# - OpenID disabled, migrations disabled (SSRF reduction), argon2 password
# hashing, 12-char minimum password, git hooks disabled.
{
options.services.gitea-server = {
enable = lib.mkEnableOption "Gitea git server";
port = lib.mkOption {
type = lib.types.port;
default = 3000;
description = "Port Gitea listens on. Firewalled to the reverse proxy only.";
};
domain = lib.mkOption {
type = lib.types.str;
default = "gitea.edley.me";
description = "Public domain name of the instance.";
};
proxyIp = lib.mkOption {
type = lib.types.str;
description = "Tailscale IP of the external reverse proxy allowed to reach Gitea.";
};
};
config = lib.mkIf config.services.gitea-server.enable {
services.gitea = {
enable = true;
stateDir = "/data/gitea";
appName = "gitea: Git with a cup of tea";
database = {
type = "postgres";
createDatabase = true;
name = "gitea";
user = "gitea";
};
dump = {
enable = true;
interval = "daily";
type = "tar.zst";
};
settings = {
server = {
DOMAIN = config.services.gitea-server.domain;
ROOT_URL = "https://${config.services.gitea-server.domain}/";
HTTP_ADDR = "0.0.0.0";
HTTP_PORT = config.services.gitea-server.port;
DISABLE_SSH = true;
PUBLIC_URL_DETECTION = "never";
MINIMUM_KEY_SIZE_CHECK = true;
};
session = {
COOKIE_SECURE = true;
};
service = {
DISABLE_REGISTRATION = true;
# Allow anonymous browsing of public repos; private content still
# requires login. (REQUIRE_SIGNIN_VIEW would hide even public repos.)
REQUIRE_SIGNIN_VIEW = false;
SHOW_REGISTRATION_BUTTON = false;
};
security = {
REVERSE_PROXY_TRUSTED_PROXIES = config.services.gitea-server.proxyIp;
REVERSE_PROXY_LIMIT = 1;
MIN_PASSWORD_LENGTH = 12;
PASSWORD_HASH_ALGO = "argon2";
DISABLE_GIT_HOOKS = true;
};
openid = {
ENABLE_OPENID_SIGNIN = false;
ENABLE_OPENID_SIGNUP = false;
};
repository = {
DISABLE_MIGRATIONS = true;
};
other = {
SHOW_FOOTER_VERSION = false;
SHOW_FOOTER_TEMPLATE_LOAD_TIME = false;
};
};
};
# The external proxy (TLS terminator for gitea.edley.me) connects over
# Tailscale. Admit ONLY its address on the Gitea port; the default input
# policy drops everything else. Update proxyIp when the proxy changes.
networking.firewall.extraInputRules = ''
ip saddr ${config.services.gitea-server.proxyIp} tcp dport ${toString config.services.gitea-server.port} accept
'';
};
}