commit a1d895c20eeba48f818e312cba6696f883897f8f
parent 1de3fc7316426d1fee00bf583da083b2634f5bc2
Author: Katja (zaphyra) <git@ctu.cx>
Date: Fri, 23 May 2025 11:31:20 +0200
parent 1de3fc7316426d1fee00bf583da083b2634f5bc2
Author: Katja (zaphyra) <git@ctu.cx>
Date: Fri, 23 May 2025 11:31:20 +0200
config/nixos/modules/websites: add `git.zaphyra.eu` (and enable on host `morio`)
6 files changed, 343 insertions(+), 4 deletions(-)
A
|
284
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
diff --git a/config/home/katja/programs/ssh.nix b/config/home/katja/programs/ssh.nix @@ -14,6 +14,10 @@ user = "git"; hostname = "git.katja.wtf"; }; + "zaphyra-git" = { + user = "git"; + hostname = "git.zaphyra.eu"; + }; }; };
diff --git a/config/nixos/modules/websites/git.zaphyra.eu.nix b/config/nixos/modules/websites/git.zaphyra.eu.nix @@ -0,0 +1,284 @@ +{ + povSelf, + hostConfig, + config, + lib, + pkgs, + dnsNix, + ... +}: + +let + inherit (lib) types; + cfg = lib.getAttrFromPath povSelf config; + +in +{ + + options = { + enable = { + type = types.bool; + default = false; + }; + domain = { + type = types.str; + default = "zaphyra.eu"; + }; + subdomain = { + type = types.str; + default = "git"; + }; + title = { + type = types.str; + default = "zaphyra's git"; + }; + mail = { + type = types.str; + default = "git@zaphyra.eu"; + }; + categories = { + type = with types; listOf str; + default = [ + "nix" + "etc" + ]; + }; + adminPubkey = { + type = types.str; + default = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDIHFm/bePR+HT5MAuMslUHt68nTrEhlqcKIS+9Rfi9FzRKAia/DdLbwpfC1iXuM+iQd8fIMp4Ir+kBMqoZaVzyCtqKH6QHbhwBiWIdMA7FndMbfDcO9BzUcqCAVt8HxcGd1Z4bE9ZgZZuIsJiPbqJ+QbK1rkY8uJLDVR2MXI5jpU/m+9RJzrwVZ6JxwjdY4cNaIYwoOW6ZxL+ukLRwy+spBWmWdcHeq6zeLsl/OjUV6WIh2pM9O0o9nsiDekhOBf2MJLlM+e8rWICwYsfLqGAeRAuDe03BFBXsbDg/lqTYB5G8XSaT2R8ty2RyeEBySS32pUyErdKVXnyHNBEvxC6+cJiZtL8rhkpU1qRg/MIUjprMVUWisMlYnai2K0VpNpc5w09YXQl7aXSge8L/5+IzugPj17+FK4FVwRptXxynnYeKiwWEsOiiFe3IVaQ6vyRN66fbMjx/d0JSfadbwV7L++aT85bsb05zhNDpaqK1I5sGs3uV3CglkmhxBmky67Eq/qkMlJZMVtgE7i88H8+XzTiofJaKYTeyq+XQnK6a6OVGyca2dEorBFmBTEtz70nQnSrhPqQrS4zgr4OTSFtUtdFVDzgHaxRC+y4/SP5zCA8Xfwp0q1M0jVE9XpVpGydXtGGV08uXOsDPv5E4euxq6qgv8d2azDeBHXp+kEHm4w== cardno:6445184"; + }; + }; + + config = lib.mkIf cfg.enable ( + let + stagitFunctions = pkgs.writeShellScript "stagitFunctions" '' + is_public_and_listed() { + if [ ! -f "$1/git-daemon-export-ok" ]; then + return 1 + fi + return 0 + } + + is_forced_update() { + test "$oldrev" = "0000000000000000000000000000000000000000" && return 1 + test "$newrev" = "0000000000000000000000000000000000000000" && return 1 + + hasrevs="$(${pkgs.git}/bin/git rev-list "$oldrev" "^$newrev" | ${pkgs.gnused}/bin/sed 1q)" + if test -n "$hasrevs"; then + return 0 + fi + return 1 + } + + build_stagit_repo() { + reponame="$(basename "$1" ".git")" + printf "[%s] Generate stagit HTML pages... " "$reponame" + + mkdir -p "${config.modules.services.gitolite.dataDir}/stagit-cache" + mkdir -p "/var/lib/stagit/$reponame" + + cd "/var/lib/stagit/$reponame" || return 1 + + # build repo pages + ${pkgs.stagit}/bin/stagit -c "${config.modules.services.gitolite.dataDir}/stagit-cache/$reponame" -n "${cfg.title}" -h 'https://git.${cfg.domain}/' -s 'git@${config.networking.fqdn}:' "$1" + + # set correct permissions + chown git:git -R /var/lib/stagit/$reponame; + chmod 755 -R /var/lib/stagit/$reponame; + + echo "done" + } + + build_stagit_index() { + printf "Generating stagit index... " + + # set assets if not already there + ln -sf "${pkgs.stagit}/share/doc/stagit/highlight.min.js" "/var/lib/stagit/highlight.min.js" 2> /dev/null + ln -sf "${pkgs.stagit}/share/doc/stagit/style.css" "/var/lib/stagit/style.css" 2> /dev/null + + # generate index arguments + args="-n \"${cfg.title}\" -e '${cfg.mail}'" + + for category in ${lib.escapeShellArgs cfg.categories}; do + args="$args -c '$category'" + for repo in "$HOME/repositories/"*.git/; do + repo="''${repo%/}" + is_public_and_listed "$repo" || continue + + [ "$(${pkgs.gawk}/bin/awk -F '=' '/category/ {print $2}' $repo/config | ${pkgs.gnused}/bin/sed -e 's/^[[:space:]]*//')" = "$category" ] && args="$args $repo" + done + done + + # build index + echo "$args" | xargs ${pkgs.stagit}/bin/stagit-index > /var/lib/stagit/index.html + + # set correct permissions + chown git:git /var/lib/stagit/index.html; + chmod 755 /var/lib/stagit/index.html; + + echo "done" + } + + + update_stagit_repo() { + repo="$(pwd)" + reponame="$(basename "$repo" ".git")" + + cd "$repo" || return 1 + is_public_and_listed "$repo" || return 0 + + # if forced update, remove directory and cache file + is_forced_update && printf "[%s] Forced update, trigger complete regeneration of stagit-pages... \n" "$reponame" && rm -rf "/var/lib/stagit/$reponame" "/var/lib/gitolite/stagit-cache/$reponame" + + build_stagit_repo "$repo" + build_stagit_index + } + + ''; + + rebuildWebdir = '' + source ${stagitFunctions} + + # clear webdir + rm -rf /var/lib/stagit/* + + # clear cache + rm -rf ${config.modules.services.gitolite.dataDir}/stagit-cache/* + + # generate pages per repo + for repo in "$HOME/repositories/"*.git/; do + repo="''${repo%/}" + is_public_and_listed "$repo" || continue + + build_stagit_repo "$repo" + done + + # generate index page + build_stagit_index + ''; + + in + { + dns.zones."${cfg.domain}".subdomains."${cfg.subdomain}".CNAME = [ "${config.networking.fqdn}." ]; + + sops.secrets."resticPasswords/gitolite" = { + owner = "git"; + }; + + systemd.tmpfiles.settings.stagit = { + "/var/lib/stagit".d = { + group = "git"; + user = "git"; + mode = "775"; + age = "-"; + }; + }; + + modules.services = { + resticBackup.paths = { + gitolite = { + enable = true; + user = "git"; + passwordFile = config.sops.secrets."resticPasswords/gitolite".path; + paths = [ config.modules.services.gitolite.dataDir ]; + }; + }; + gitolite = { + enable = true; + user = "git"; + group = "git"; + adminPubkey = cfg.adminPubkey; + + extraGitoliteRc = '' + $RC{GIT_CONFIG_KEYS} = ".*"; + $RC{UMASK} = 0027; + + push(@{$RC{ENABLE}}, 'cgit'); + push(@{$RC{ENABLE}}, 'symbolic-ref'); + push(@{$RC{ENABLE}}, 'rebuild-webdir'); + push(@{$RC{ENABLE}}, 'rebuild-webdir'); + + $RC{NON_CORE} = "rebuild-webdir-trigger POST_COMPILE rebuild-stagit"; + ''; + + triggers.rebuild-webdir = rebuildWebdir; + commands.rebuild-webdir = rebuildWebdir; + commonHooks.post-receive = '' + # update stagit pages + source ${stagitFunctions} + update_stagit_repo "$1" + ''; + }; + }; + + services = { + fcgiwrap = { + instances.git = { + process.user = "git"; + process.group = "git"; + socket.user = "nginx"; + socket.group = "nginx"; + }; + }; + + nginx = { + enable = true; + virtualHosts."${cfg.subdomain}.${cfg.domain}" = { + useACMEHost = "${config.networking.fqdn}"; + forceSSL = true; + kTLS = true; + root = "/var/lib/stagit"; + locations = { + "@redir".return = "307 ../log.html"; + "~ '^/([a-zA-Z0-9_.]+)/commit/.*$'".extraConfig = "error_page 404 = @redir;"; + + "~* \.html$".extraConfig = '' + add_header Last-Modified $date_gmt; + add_header Cache-Control 'private no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0'; + if_modified_since off; + expires off; + etag off; + ''; + + "~ '^/[a-zA-Z0-9._-]+/raw'".extraConfig = '' + types { + application/json json; + + application/wasm wasm; + font/woff woff; + font/woff2 woff2; + + application/pdf pdf; + + image/gif gif; + image/jpeg jpeg jpg; + image/png png; + image/svg+xml svg svgz; + image/webp webp; + image/x-icon ico; + } + + default_type text/plain; + try_files $uri =404; + ''; + + "~ '^/[a-zA-Z0-9._-]+/(git-(receive|upload)-pack|HEAD|info/refs|objects/(info/(http-)?alternates|packs)|[0-9a-f]{2}/[0-9a-f]{38}|pack/pack-[0-9a-f]{40}\.(pack|idx))$'".extraConfig = + '' + if ($query_string = service=git-receive-pack) { + return 403; + } + + include "${pkgs.nginx}/conf/fastcgi_params"; + fastcgi_param SCRIPT_FILENAME "${pkgs.git}/libexec/git-core/git-http-backend"; + fastcgi_param GIT_PROJECT_ROOT /var/lib/gitolite/repositories; + fastcgi_param PATH_INFO $uri; + fastcgi_pass unix:${config.services.fcgiwrap.instances.git.socket.address}; + ''; + }; + }; + }; + }; + } + ); + +}
diff --git a/flake.lock b/flake.lock @@ -499,7 +499,8 @@ "nixSystemsDefault": "nixSystemsDefault", "nixpkgs": "nixpkgs", "nixpkgsUnstable": "nixpkgsUnstable", - "sopsNix": "sopsNix" + "sopsNix": "sopsNix", + "stagit": "stagit" } }, "rust-overlay": { @@ -546,6 +547,26 @@ "repo": "sops-nix", "type": "github" } + }, + "stagit": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1742749207, + "narHash": "sha256-3gGCusgJ9qEPMdevaYCU2UM54hhu1sAm5++mC7bNdy8=", + "ref": "refs/heads/main", + "rev": "a4b05b204f1854c98e7ae68960fc1582493de890", + "revCount": 445, + "type": "git", + "url": "https://git.katja.wtf/stagit" + }, + "original": { + "type": "git", + "url": "https://git.katja.wtf/stagit" + } } }, "root": "root",
diff --git a/flake.nix b/flake.nix @@ -79,6 +79,7 @@ inputs.self.overlays.packages inputs.self.overlays.nixpkgsUnstable inputs.ctucxWebsite.overlays.default + inputs.stagit.overlays.default ]; } @@ -183,6 +184,9 @@ ctucxWebsite.url = "git+https://git.katja.wtf/website"; ctucxWebsite.inputs.nixpkgs.follows = "nixpkgs"; + stagit.url = "git+https://git.katja.wtf/stagit"; + stagit.inputs.nixpkgs.follows = "nixpkgs"; + firefoxGnomeTheme.flake = false; firefoxGnomeTheme.url = "github:rafaelmardojai/firefox-gnome-theme/v137"; };
diff --git a/hosts/morio/default.nix b/hosts/morio/default.nix @@ -25,12 +25,23 @@ }; configuration = - { config, pkgs, ... }: + { + inputs, + config, + pkgs, + ... + }: { boot.initrd.systemd.emergencyAccess = true; boot.kernelPackages = pkgs.linuxPackages_latest; + sops.secrets = { + "resticEnv/novus" = { + sopsFile = inputs.self.sopsSecrets.common; + }; + }; + modules = { filesystem.rootDisk = { enable = true; @@ -53,6 +64,19 @@ }; }; + services = { + resticBackup.targets = { + novus = { + repository = "rest:https://restic.novus.infra.zaphyra.eu"; + environmentFile = config.sops.secrets."resticEnv/novus".path; + }; + }; + }; + + websites = { + "git.zaphyra.eu".enable = true; + }; + users.katja.enable = true; };
diff --git a/secrets/morio.yaml b/secrets/morio.yaml @@ -1,4 +1,6 @@ acmeTSIGKey: ENC[AES256_GCM,data:XbTSbHisL5ZszYY4hvKplyWG98eK4DUeiSpA24Am/QPjEw8ofHWzU2WmV9hzj8Jd29Z0Yf0u/m5T/FESS2Gt9w==,iv:liySg99CmJ9RePJ84pD2+2mNsvZ4SbEXt3d58kDsHgI=,tag:zNwYe1ZfhFGmfP2s+OLj3Q==,type:str] +resticPasswords: + gitolite: ENC[AES256_GCM,data:g28//NtKEYL+Dh0+Ws73ZKySl1L0avxqNXVn5lKaj1U=,iv:mGQ7pYjeMEGTCS1l6H/h043M2oAhgMOAlUHkgDir03E=,tag:E/ps0EZmlMEm+ziWzXzQPQ==,type:str] knotKeys: ENC[AES256_GCM,data:rlTFDvonfEQFST1eSHHcaG3e1CSt5paDUTvfoYmInBV7mjqe7PwT5dtg01W2ANZJYl+SN/cdI3eEvAdJvwYR6FK+7g1LPwn6G1coE68a/XwzsWM5WpSemmDfTykoUiguEUfRCZ0Q3M7YqV0/jDWrKMaH0iKqKqvlv7nEy6VXB5SZBX+aN18KvPVygw5FixQ/kD3XFI2HTTST4vqlMma3CTsjnK6Uwf1421JOIe3JR32qd0V7IfhFvL0mErMIRhLnITO9uJ//t1HJoeaOV7FEY4K6Ohacng1c68fkUjVX5wYBTd6X657nFqevvLiMRDiQnASOJrAJUAeq4Kwf5R7C/I/MeVh+1Hq/U+z4ZQKh/DViEE8+TkJwDMBAWarzlyOz7xDF8O+fj4iH5jTX8H3FmJLU/TVU0QXqnwjcAAVs/YNARNVt0wGdWTb9iyvD7vEIZE57wIp+TIGE8XFjOO11/DRC/0kC8HFkvoXke9IRrTIj1pCP1VIrv31v7aIyphWa5hBuBHfVb0f8g5eaqyKumM03Rge+Fo+jtM/NP7H2gao6uaZM/K4a625nVx+M1lUpW+1c0sIAME2SlDjSyuhTkMknOPGAAYXMwVQGazoOJna6sEBl6jYcgn31w3dHtJXGKyAB0eqELxjt5b0tzcBfJ4pXi7HO7w2yhKrqyL7GuE5LtLp5mkguC5eZbiX+VlGTLX6V2z1kDRUdYDDZMMh3cYGBIrGVoJhWx8xLWrLGm7TrvifiwYJq5Mq13tt2hS7HpY8T8YGBD2x3IdPAHtikZUYgv5cQxs7drSJi8zFQAwDUKofxhJQUvqrnmvNf+eiGkfgI7lQ0//NLg9o4t+5g+T3mV8IUkW4nbJsP46k6azQGBt3udYAVhgrFy/jTE++KrA==,iv:+5NBUUC1QhPjN+6E8nWhzd2SNuH9mLbhsFwDTm8Hy+U=,tag:RtSO5Rmb0wNR9ovtpwJIIg==,type:str] sops: kms: [] @@ -15,8 +17,8 @@ sops: bDRhUEtDdmlZa0ZENFhSVnNqVjFCR1UKEIkSg3tKFkwlnNXFFqCBtdZBGz1bEmWl wghkTtqTl++759zZAAmjdnFFQWs/AoCZ5g/GUidz6HHcFdxMpGVmiA== -----END AGE ENCRYPTED FILE----- - lastmodified: "2025-05-21T10:53:45Z" - mac: ENC[AES256_GCM,data:N7NTYDFRqb57D/sxbTGvOI1HqAJ3GmGCzwq7+Yi6refzpi8Ch3hh/gs5aqWmGJN1kMCR7P1kijnnCgMzpKNZ4hZ9VWtIwGmzkfAOuA8D8tE1uCS1D2eYuaiStKWgpDj4m//6nqaiUO7KN7snKE4M68ZPlh5k430dhBLvBRpF7sY=,iv:OcCo/c4P8zcAZWWXdQecZbUr1eLUq8wBJaCoXDqU1Dc=,tag:AVAdT5bC6lOsyhJehJ1qYA==,type:str] + lastmodified: "2025-05-22T22:59:42Z" + mac: ENC[AES256_GCM,data:5XIqoKdnnoHhX3Kkkq83X9cFu6Mm5OMDE9ZsjPBQ73fwgfl++XARaUhVVqKllvaCw4AHFQakS6VLgMfJ9/NrHw46fFUnixl91Som51T3+73JDi6ebCi69txNe5EYWRR5i3kWylus8dnnIWzTOouguFE6VT/fHPVZgndaiNScLqM=,iv:+Er27YY1//YQhvqnxVqO5hhwyiMCNFgo7ZRjTOtQiPY=,tag:bIuKUKHpdnjdcGq2Fj2xFg==,type:str] pgp: - created_at: "2025-05-21T08:09:28Z" enc: |-