Compare commits
No commits in common. '80ea31cc30deefff157b504fee991c499877722f' and '6796f8daa6312d6c206353dee426aa857066443f' have entirely different histories.
80ea31cc30
...
6796f8daa6
@ -1,28 +0,0 @@ |
|||||||
{ config, lib, pkgs, modulesPath, ... }: |
|
||||||
|
|
||||||
{ |
|
||||||
imports = [ |
|
||||||
(modulesPath + "/installer/scan/not-detected.nix") |
|
||||||
]; |
|
||||||
|
|
||||||
boot.initrd.availableKernelModules = [ "xhci_pci" "ehci_pci" "ahci" "usb_storage" "sd_mod" "sr_mod" ]; |
|
||||||
boot.initrd.kernelModules = [ ]; |
|
||||||
boot.kernelModules = [ "kvm-intel" ]; |
|
||||||
boot.extraModulePackages = [ ]; |
|
||||||
|
|
||||||
fileSystems."/" = { |
|
||||||
device = "/dev/disk/by-label/nixos"; |
|
||||||
fsType = "ext4"; |
|
||||||
}; |
|
||||||
|
|
||||||
fileSystems."/boot" = { |
|
||||||
device = "/dev/disk/by-label/boot"; |
|
||||||
fsType = "vfat"; |
|
||||||
}; |
|
||||||
|
|
||||||
swapDevices = [ { device = "/dev/disk/by-label/swap"; } ]; |
|
||||||
|
|
||||||
networking.useDHCP = lib.mkDefault true; |
|
||||||
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; |
|
||||||
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; |
|
||||||
} |
|
||||||
@ -1,61 +0,0 @@ |
|||||||
# Let's Encrypt (ACME) certificate issuance, per-host HTTP-01. |
|
||||||
# |
|
||||||
# Termination & deployment scheme |
|
||||||
# ------------------------------- |
|
||||||
# Each NixOS host terminates TLS itself for the subdomains it serves. |
|
||||||
# There is no central certificate store and no distribution step. |
|
||||||
# |
|
||||||
# 1. A host declares which subdomains it serves via `domains = [ ... ]` |
|
||||||
# in flake.nix (consumed by modules/nginx.nix). |
|
||||||
# 2. With `infra.acme.enable = true`, the nginx module sets |
|
||||||
# `enableACME = true; forceSSL = true;` on every served vhost. |
|
||||||
# 3. security.acme requests one cert per vhost via the HTTP-01 |
|
||||||
# challenge; nginx answers /.well-known/acme-challenge/ on :80. |
|
||||||
# 4. On issue/renewal, nginx is reloaded automatically |
|
||||||
# (`reloadServices`), so the new certificate goes live at once. |
|
||||||
# 5. Renewals run on a systemd timer; every host (core, deploy, ...) |
|
||||||
# keeps its own certificates fresh — nothing is deployed by hand. |
|
||||||
# |
|
||||||
# Adding a new endpoint (e.g. deploy.domain.com): |
|
||||||
# - add the host (or add the subdomain to an existing host's `domains`), |
|
||||||
# - enable `infra.acme` on that host, |
|
||||||
# - point the subdomain's DNS at the host and leave :80 reachable so |
|
||||||
# the HTTP-01 challenge can complete. |
|
||||||
|
|
||||||
{ config, lib, pkgs, ... }: |
|
||||||
|
|
||||||
with lib; |
|
||||||
|
|
||||||
let |
|
||||||
cfg = config.infra.acme; |
|
||||||
in { |
|
||||||
options.infra.acme = { |
|
||||||
enable = mkEnableOption "Let's Encrypt (ACME) TLS certificates"; |
|
||||||
|
|
||||||
email = mkOption { |
|
||||||
type = types.str; |
|
||||||
description = "Account email used for Let's Encrypt"; |
|
||||||
}; |
|
||||||
|
|
||||||
staging = mkOption { |
|
||||||
type = types.bool; |
|
||||||
default = false; |
|
||||||
description = '' |
|
||||||
Use the Let's Encrypt staging environment |
|
||||||
(untrusted certificates, no rate limiting) for testing. |
|
||||||
''; |
|
||||||
}; |
|
||||||
}; |
|
||||||
|
|
||||||
config = mkIf cfg.enable { |
|
||||||
security.acme = { |
|
||||||
acceptTerms = true; |
|
||||||
defaults = { |
|
||||||
inherit (cfg) email; |
|
||||||
reloadServices = [ "nginx.service" ]; |
|
||||||
} // optionalAttrs cfg.staging { |
|
||||||
server = "https://acme-staging-v02.api.letsencrypt.org/directory"; |
|
||||||
}; |
|
||||||
}; |
|
||||||
}; |
|
||||||
} |
|
||||||
@ -1,138 +0,0 @@ |
|||||||
{ config, lib, pkgs, domain, vhosts ? { }, ... }: |
|
||||||
|
|
||||||
with lib; |
|
||||||
|
|
||||||
let |
|
||||||
cfg = config.infra.gitea; |
|
||||||
vhost = vhosts.${cfg.hostname} or { }; |
|
||||||
httpPort = vhost.ports.http or 3000; |
|
||||||
sshPort = vhost.ports.ssh or 2222; |
|
||||||
|
|
||||||
giteaBin = "${config.services.gitea.package}/bin/gitea"; |
|
||||||
discoveryUrl = "${cfg.oidc.issuerUrl}/.well-known/openid-configuration"; |
|
||||||
in { |
|
||||||
options.infra.gitea = { |
|
||||||
enable = mkEnableOption "Gitea with Keycloak OIDC"; |
|
||||||
|
|
||||||
hostname = mkOption { |
|
||||||
type = types.str; |
|
||||||
default = "git.${domain}"; |
|
||||||
description = "Public hostname for Gitea"; |
|
||||||
}; |
|
||||||
|
|
||||||
oidc = { |
|
||||||
enable = mkOption { |
|
||||||
type = types.bool; |
|
||||||
default = true; |
|
||||||
description = "Register Keycloak as an OIDC authentication source"; |
|
||||||
}; |
|
||||||
|
|
||||||
name = mkOption { |
|
||||||
type = types.str; |
|
||||||
default = "Keycloak"; |
|
||||||
description = "Display name for the OIDC auth source"; |
|
||||||
}; |
|
||||||
|
|
||||||
clientId = mkOption { |
|
||||||
type = types.str; |
|
||||||
description = "Keycloak OIDC client id"; |
|
||||||
}; |
|
||||||
|
|
||||||
clientSecretFile = mkOption { |
|
||||||
type = types.path; |
|
||||||
description = '' |
|
||||||
Path to a file (readable by the gitea user) containing the |
|
||||||
Keycloak OIDC client secret. |
|
||||||
''; |
|
||||||
}; |
|
||||||
|
|
||||||
issuerUrl = mkOption { |
|
||||||
type = types.str; |
|
||||||
default = "https://auth.${domain}/realms/master"; |
|
||||||
description = "Keycloak realm issuer URL"; |
|
||||||
}; |
|
||||||
}; |
|
||||||
}; |
|
||||||
|
|
||||||
config = mkIf cfg.enable { |
|
||||||
services.gitea = { |
|
||||||
enable = true; |
|
||||||
settings = { |
|
||||||
server = { |
|
||||||
DOMAIN = cfg.hostname; |
|
||||||
ROOT_URL = "https://${cfg.hostname}/"; |
|
||||||
PROTOCOL = "http"; |
|
||||||
HTTP_ADDR = "127.0.0.1"; |
|
||||||
HTTP_PORT = httpPort; |
|
||||||
SSH_DOMAIN = cfg.hostname; |
|
||||||
SSH_PORT = sshPort; |
|
||||||
START_SSH_SERVER = true; |
|
||||||
SSH_LISTEN_PORT = sshPort; |
|
||||||
LANDING_PAGE = "explore"; |
|
||||||
}; |
|
||||||
service = { |
|
||||||
DISABLE_REGISTRATION = false; |
|
||||||
SHOW_REGISTRATION_BUTTON = false; |
|
||||||
}; |
|
||||||
openid = { |
|
||||||
ENABLE_OPENID_SIGNIN = true; |
|
||||||
ENABLE_OPENID_SIGNUP = true; |
|
||||||
}; |
|
||||||
oauth2_client = { |
|
||||||
ENABLE_AUTO_REGISTRATION = true; |
|
||||||
ACCOUNT_LINKING = "login"; |
|
||||||
UPDATE_AVATAR = true; |
|
||||||
}; |
|
||||||
}; |
|
||||||
}; |
|
||||||
|
|
||||||
networking.firewall.allowedTCPPorts = [ sshPort ]; |
|
||||||
|
|
||||||
systemd.services.gitea-oidc = mkIf cfg.oidc.enable { |
|
||||||
description = "Register Keycloak OIDC authentication source in Gitea"; |
|
||||||
after = [ "gitea.service" ]; |
|
||||||
bindsTo = [ "gitea.service" ]; |
|
||||||
wantedBy = [ "multi-user.target" ]; |
|
||||||
serviceConfig = { |
|
||||||
Type = "oneshot"; |
|
||||||
User = "gitea"; |
|
||||||
Group = "gitea"; |
|
||||||
RemainAfterExit = true; |
|
||||||
}; |
|
||||||
environment = { |
|
||||||
GITEA_CUSTOM = "/var/lib/gitea/custom"; |
|
||||||
GITEA_WORK_DIR = "/var/lib/gitea"; |
|
||||||
}; |
|
||||||
script = '' |
|
||||||
secret="$(cat ${cfg.oidc.clientSecretFile})" |
|
||||||
|
|
||||||
# Wait until Gitea has finished initialising its database |
|
||||||
for i in $(seq 1 60); do |
|
||||||
if ${giteaBin} admin auth list > /tmp/gitea-auth-list 2>/dev/null; then |
|
||||||
break |
|
||||||
fi |
|
||||||
sleep 2 |
|
||||||
done |
|
||||||
|
|
||||||
${giteaBin} admin auth list > /tmp/gitea-auth-list || true |
|
||||||
existing=$(grep -w '${cfg.oidc.name}' /tmp/gitea-auth-list | awk '{print $1}' | head -n1) |
|
||||||
|
|
||||||
if [ -n "$existing" ]; then |
|
||||||
${giteaBin} admin auth update-oauth --id "$existing" \ |
|
||||||
--name '${cfg.oidc.name}' \ |
|
||||||
--provider openidConnect \ |
|
||||||
--key '${cfg.oidc.clientId}' \ |
|
||||||
--secret "$secret" \ |
|
||||||
--auto-discover-url '${discoveryUrl}' |
|
||||||
else |
|
||||||
${giteaBin} admin auth add-oauth \ |
|
||||||
--name '${cfg.oidc.name}' \ |
|
||||||
--provider openidConnect \ |
|
||||||
--key '${cfg.oidc.clientId}' \ |
|
||||||
--secret "$secret" \ |
|
||||||
--auto-discover-url '${discoveryUrl}' |
|
||||||
fi |
|
||||||
''; |
|
||||||
}; |
|
||||||
}; |
|
||||||
} |
|
||||||
@ -1,31 +0,0 @@ |
|||||||
{ config, lib, pkgs, vhosts ? { }, domains ? [ ], ... }: |
|
||||||
|
|
||||||
let |
|
||||||
served = lib.filterAttrs (k: _: builtins.elem k domains) vhosts; |
|
||||||
enabled = served != { }; |
|
||||||
acme = config.infra.acme.enable or false; |
|
||||||
in { |
|
||||||
config = lib.mkIf enabled { |
|
||||||
services.nginx = { |
|
||||||
enable = true; |
|
||||||
virtualHosts = lib.mapAttrs (name: vhost: |
|
||||||
let |
|
||||||
port = vhost.ports.http or null; |
|
||||||
hasProxy = port != null; |
|
||||||
root = vhost.root or null; |
|
||||||
in { |
|
||||||
enableACME = acme; |
|
||||||
forceSSL = acme; |
|
||||||
root = lib.mkIf (root != null) root; |
|
||||||
extraConfig = vhost.extraConfig or ""; |
|
||||||
locations."/" = lib.mkIf hasProxy { |
|
||||||
proxyPass = "http://localhost:${toString port}"; |
|
||||||
proxyWebsockets = vhost.websockets or false; |
|
||||||
}; |
|
||||||
} |
|
||||||
) served; |
|
||||||
}; |
|
||||||
|
|
||||||
networking.firewall.allowedTCPPorts = [ 80 443 ]; |
|
||||||
}; |
|
||||||
} |
|
||||||
Loading…
Reference in new issue