forgejo good, nginx coming along

This commit is contained in:
Julian Sutter 2026-02-15 22:46:17 -08:00
parent 29eb6493be
commit 8fdbb33939
40 changed files with 153 additions and 493 deletions

0
servers/README.md Normal file → Executable file
View file

0
servers/common.nix Normal file → Executable file
View file

368
servers/forgejo.nix Normal file → Executable file
View file

@ -1,314 +1,92 @@
{ config, pkgs, lib, ... }:
with lib;
{ lib, pkgs, config, ... }:
let
cfg = config.services.forgejo;
in {
options.services.forgejo = {
enable = mkEnableOption "Forgejo Git server";
srv = cfg.settings.server;
domain = mkOption {
type = types.str;
example = "git.example.com";
description = "The domain name for Forgejo";
};
fqdn = "git.symbiotrip.com";
stateDir = mkOption {
type = types.str;
default = "/var/lib/forgejo";
description = "Directory for Forgejo data";
};
smtpPassword = "Monaco55";
runnerToken = "PUT_RUNNER_REGISTRATION_TOKEN_HERE";
adminPassword = "2wiggyWah!";
database = {
createLocally = mkOption {
type = types.bool;
default = true;
description = "Whether to create a local PostgreSQL database";
adminUser = "jsutter";
adminEmail = "jsutter@symbiotrip.com";
in
{
security.acme.certs.${fqdn}.group = config.services.nginx.group;
services.nginx.virtualHosts.${fqdn} = {
forceSSL = true;
enableACME = true;
useACMEHost = fqdn;
acmeRoot = null;
extraConfig = ''
client_max_body_size 512M;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
'';
locations."/".proxyPass = "http://localhost:${toString srv.HTTP_PORT}";
};
services.forgejo = {
enable = true;
database.type = "postgres";
lfs.enable = true;
settings = {
server = {
DOMAIN = fqdn;
ROOT_URL = "https://${fqdn}/";
HTTP_PORT = 3000;
};
passwordFile = mkOption {
type = types.nullOr types.path;
default = null;
example = "/run/keys/forgejo-db";
description = "Path to file containing database password";
};
};
service.DISABLE_REGISTRATION = true;
mailer = {
enable = mkOption {
type = types.bool;
default = false;
description = "Enable email notifications";
actions = {
ENABLED = true;
DEFAULT_ACTIONS_URL = "github";
};
host = mkOption {
type = types.str;
example = "smtp.gmail.com";
description = "SMTP server hostname";
};
port = mkOption {
type = types.port;
default = 587;
description = "SMTP server port";
};
from = mkOption {
type = types.str;
example = "git@example.com";
description = "Email sender address";
};
userFile = mkOption {
type = types.path;
example = "/run/keys/forgejo-smtp-user";
description = "Path to file containing SMTP username";
};
passwordFile = mkOption {
type = types.path;
example = "/run/keys/forgejo-smtp-pass";
description = "Path to file containing SMTP password";
};
};
oauth2 = {
enable = mkOption {
type = types.bool;
default = false;
description = "Enable OAuth2 authentication";
};
github = {
enable = mkOption {
type = types.bool;
default = false;
description = "Enable GitHub OAuth2 provider";
};
clientId = mkOption {
type = types.str;
description = "GitHub OAuth client ID";
};
clientSecret = mkOption {
type = types.path;
description = "Path to GitHub OAuth client secret";
};
};
};
lfs = {
enable = mkOption {
type = types.bool;
default = true;
description = "Enable Git LFS support";
};
storage = mkOption {
type = types.str;
default = "/var/lib/forgejo/lfs";
description = "Path to LFS storage";
};
};
backup = {
enable = mkOption {
type = types.bool;
default = true;
description = "Enable automated backups";
};
interval = mkOption {
type = types.str;
default = "daily";
description = "Backup interval (systemd timer format)";
};
retentionDays = mkOption {
type = types.int;
default = 7;
description = "Number of backups to keep";
};
backupDir = mkOption {
type = types.str;
default = "/var/backups/forgejo";
description = "Directory to store backups";
mailer = {
ENABLED = true;
SMTP_ADDR = "mail.example.com";
FROM = "noreply@${fqdn}";
USER = "noreply@${fqdn}";
PASSWD = smtpPassword;
};
};
};
config = mkIf cfg.enable {
# PostgreSQL database
services.postgresql = mkIf cfg.database.createLocally {
# Create/ensure admin user
systemd.services.forgejo.preStart = let
adminCmd = "${lib.getExe cfg.package} admin user";
in ''
${adminCmd} create \
--admin \
--username ${adminUser} \
--email "${adminEmail}" \
--password "${adminPassword}" \
--must-change-password=false || true
'';
# Actions runner (runs jobs in Docker containers per labels)
virtualisation.docker.enable = true;
services.gitea-actions-runner = {
package = pkgs.forgejo-runner;
instances.default = {
enable = true;
enableTCPIP = false;
ensureDatabases = [ "forgejo" ];
ensureUsers = [
{
name = "forgejo";
ensureDBOwnership = true;
}
name = "warp";
url = "https://${fqdn}";
token = runnerToken;
labels = [
"node-22:docker://node:22-bookworm"
"nixos-latest:docker://nixos/nix"
# "native:host"
];
};
# Forgejo service
services.forgejo = {
enable = true;
settings = {
server = {
HTTP_PORT = 3000;
DOMAIN = cfg.domain;
ROOT_URL = "https://${cfg.domain}/";
DISABLE_SSH = false;
SSH_PORT = 22;
LFS_START_SERVER = cfg.lfs.enable;
LFS_CONTENT_PATH = cfg.lfs.storage;
};
database = {
DB_TYPE = "postgres";
HOST = "/run/postgresql";
NAME = "forgejo";
USER = "forgejo";
PASSWD = mkIf (cfg.database.passwordFile != null) "#dbpass#";
};
service = {
DISABLE_REGISTRATION = false;
REQUIRE_SIGNIN_VIEW = false;
ENABLE_NOTIFY_MAIL = cfg.mailer.enable;
};
mailer = mkIf cfg.mailer.enable {
ENABLED = true;
FROM = cfg.mailer.from;
SMTP_ADDR = cfg.mailer.host;
SMTP_PORT = toString cfg.mailer.port;
USER = "#smtpuser#";
PASSWD = "#smtppass#";
PROTOCOL = "smtps";
};
session = {
COOKIE_SECURE = true;
COOKIE_SAMESITE = "strict";
};
security = {
INSTALL_LOCK = true;
PASSWORD_CHECK_PWN = true;
PASSWORD_COMPLEXITY = "lower,digit"
};
};
};
# Set database password from file
systemd.services.forgejo = {
serviceConfig = mkMerge [
(mkIf (cfg.database.passwordFile != null) {
EnvironmentFile = cfg.database.passwordFile;
})
(mkIf cfg.mailer.enable {
EnvironmentFile = [ cfg.mailer.userFile cfg.mailer.passwordFile ];
})
];
};
# Nginx reverse proxy
services.nginx = {
enable = true;
recommendedGzipSettings = true;
recommendedOptimisation = true;
recommendedProxySettings = true;
recommendedTlsSettings = true;
virtualHosts.${cfg.domain} = {
forceSSL = true;
enableACME = true;
locations."/".proxyPass = "http://localhost:3000";
# Proxy for WebSocket
locations."/" = {
proxyPass = "http://localhost:3000";
proxyWebsockets = true;
extraConfig = ''
proxy_buffering off;
proxy_read_timeout 86400;
'';
};
# Proxy for LFS
locations."/.git/info/lfs" = {
proxyPass = "http://localhost:3000";
extraConfig = ''
client_max_body_size 0;
'';
};
};
};
# Firewall
networking.firewall = {
allowedTCPPorts = [ 80 443 ];
allowedUDPPorts = [ 443 ];
};
# Backup service
systemd.services.forgejo-backup = mkIf cfg.backup.enable {
description = "Forgejo backup service";
serviceConfig = {
Type = "oneshot";
User = "forgejo";
Group = "forgejo";
WorkingDirectory = cfg.stateDir;
ExecStart = "${pkgs.forgejo}/bin/forgejo dump --type zip --file ${cfg.backup.backupDir}/forgejo-backup-%Y-%m-%d.zip";
};
};
systemd.timers.forgejo-backup = mkIf cfg.backup.enable {
description = "Forgejo backup timer";
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = cfg.backup.interval;
Persistent = true;
};
};
# Backup cleanup
systemd.services.forgejo-backup-cleanup = mkIf cfg.backup.enable {
description = "Clean up old Forgejo backups";
serviceConfig = {
Type = "oneshot";
User = "root";
ExecStart = pkgs.writeShellScript "forgejo-backup-cleanup" ''
find ${cfg.backup.backupDir} -name "forgejo-backup-*.zip" -mtime +${toString cfg.backup.retentionDays} -delete
'';
};
};
systemd.timers.forgejo-backup-cleanup = mkIf cfg.backup.enable {
description = "Forgejo backup cleanup timer";
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = "weekly";
Persistent = true;
};
};
# Create backup directory
system.activationScripts.forgejo-backup-dir = ''
mkdir -p ${cfg.backup.backupDir}
chown forgejo:forgejo ${cfg.backup.backupDir}
chmod 750 ${cfg.backup.backupDir}
'';
# Add Forgejo to known services
environment.systemPackages = with pkgs; [
forgejo
];
};
}

0
servers/hugo.nix Normal file → Executable file
View file

29
servers/nginx.nix Normal file
View file

@ -0,0 +1,29 @@
{ config, lib, pkgs, ... }:
let
# WARNING: this ends up world-readable in the Nix store if you inline it.
cloudflareEnv = pkgs.writeText "cloudflare-acme.env" ''
umnyPSYOr9U3m404_IBMl4PTOzg29nz_XzNEGw2v
'';
in
{
networking.firewall.allowedTCPPorts = [ 80 443 ];
services.nginx = {
enable = true;
recommendedGzipSettings = true;
recommendedOptimisation = true;
recommendedProxySettings = true;
recommendedTlsSettings = true;
};
security.acme = {
acceptTerms = true;
# These defaults are inherited by security.acme.certs.* unless overridden. :contentReference[oaicite:0]{index=0}
defaults = {
email = "admin@symbiotrip.com";
dnsProvider = "cloudflare"; # :contentReference[oaicite:1]{index=1}
environmentFile = cloudflareEnv; # :contentReference[oaicite:2]{index=2}
};
};
}