diff --git a/base.nix b/base.nix index ab3da5e..d34597e 100644 --- a/base.nix +++ b/base.nix @@ -19,13 +19,19 @@ in { example = "example@example.com"; description = "Email of the admin, use for ACME and stuff"; }; + swapSize = mkOption { + type = types.int; + default = 1024; + example = 2048; + description = "Size of the swap file"; + }; }; config = { swapDevices = [ { device = "/swapfile"; priority = 0; - size = 1024; + size = cfg.swapSize; } ]; @@ -44,13 +50,6 @@ in { keyMap = "fr"; }; - users.users.histausse = { - isNormalUser = true; - extraGroups = [ - "wheel" - ]; - }; - environment.systemPackages = with pkgs; [ vim git diff --git a/pp-forgejo-runner.nix b/pp-forgejo-runner.nix new file mode 100644 index 0000000..e504650 --- /dev/null +++ b/pp-forgejo-runner.nix @@ -0,0 +1,41 @@ +{ lib, config, pkgs, ... }: +with lib; +let + cfgBase = config.base; + cfg = config.services.ppForgejoRunner; +in +{ + options.services.ppForgejoRunner = { + forgeUrl = mkOption { + type = types.str; + default = "https://git.${cfgBase.domainName}"; + example = "https://git.example.com"; + description = "The domain of the forgejo server"; + }; + runnerName = mkOption { + type = types.str; + default = "${cfgBase.name}.${cfgBase.domainName}"; + example = "git-runner.example.com"; + description = "The name of the runner"; + }; + tokenFile = mkOption { + type = types.str; + default = "/etc/forgejo_runner_token"; + description = "The file containing the token to access forgejo. Be sure to secure it. The content of the file must be of the form TOKEN="; + }; + }; + + config = { + virtualisation.podman.enable = true; + services.gitea-actions-runner.package = pkgs.forgejo-actions-runner; + services.gitea-actions-runner.instances."${cfg.runnerName}" = { + enable = true; + name = cfg.runnerName; + url = cfg.forgeUrl; + tokenFile = cfg.tokenFile; + labels = [ + "debian:docker://debian:bookworm" + ]; + }; + }; +} diff --git a/pp-forgejo.nix b/pp-forgejo.nix new file mode 100644 index 0000000..87294cf --- /dev/null +++ b/pp-forgejo.nix @@ -0,0 +1,129 @@ +{ config, pkgs, lib, ... }: +with lib; +let + cfgBase = config.base; + cfg = config.services.ppForgejo; +in +{ + options.services.ppForgejo = { + domain = mkOption { + type = types.str; + default = "git.${cfgBase.domainName}"; + example = "git.example.com"; + description = "The domain of the server"; + }; + openIdEnabled = mkOption { + type = types.bool; + default = false; + description = "If OpenId provider is setup and should be used exclusively."; + }; + openIdClientName = mkOption { + type = types.str; + default = ""; + description = "The name (id) of the openId client to use exclusively."; + }; + dbPasswordFile = mkOption { + type = types.str; + default = "/etc/forgejo_db_pwd"; + description = "The file containing the database password. Be sure to secure it."; + }; + actionsEnabled = mkOption { + type = types.bool; + default = false; + description = "Enable the use of actions"; + }; + }; + + config = { + + services.forgejo.settings.DEFAULT.APP_NAME = "git"; + services.forgejo.stateDir = "/var/lib/forgejo"; # default value, /var/lib/gitea in gitea, move it before migration! + # carefull to change ownership from gitea to forgejo + # and to move /var/lib/forgejo/data/gitea.db to /var/lib/forgejo/data/forgejo.db + services.forgejo.enable = true; + services.forgejo.settings.server.ROOT_URL = "https://${cfg.domain}/"; + services.forgejo.settings.session.COOKIE_SECURE = lib.mkForce true; # Why do I need to override this??? + + services.forgejo.user = "git"; + users.users.git = { + home = config.services.forgejo.stateDir; + useDefaultShell = true; + isSystemUser = true; + group = "git"; + }; + users.groups.git = {}; + + # If true, openid users cannot create new account + #services.forgejo.settings.service.DISABLE_REGISTRATION = lib.mkForce (!cfg.openIdEnabled); + services.forgejo.settings.service.DISABLE_REGISTRATION = lib.mkForce false; + services.forgejo.settings.service.ALLOW_ONLY_EXTERNAL_REGISTRATION = cfg.openIdEnabled; + + services.forgejo.lfs.enable = true; + services.forgejo.settings.server.DOMAIN = cfg.domain; + # services.forgejo.database.type = "postgres"; # Default is sqlite3, probably better for a small instance + services.forgejo.database.passwordFile = cfg.dbPasswordFile; + + services.forgejo.settings.repository.ENABLE_PUSH_CREATE_USER = true; + services.forgejo.settings.repository.ENABLE_PUSH_CREATE_ORG = true; + services.forgejo.settings.repository.DEFAULT_REPO_UNITS = "repo.code,repo.releases,repo.issues,repo.pulls,repo.wiki,repo.projects,repo.packages,repo.actions"; + + # Set the permittions for the db file + system.activationScripts = { + forgejoDbFilePermission.text = + '' + chmod 400 ${cfg.dbPasswordFile} + chown ${config.services.forgejo.user} ${cfg.dbPasswordFile} + ''; + }; + environment.systemPackages = with pkgs; [ + forgejo + ]; + systemd.services.forgejo.environment.FORGEJO_CUSTOM = "${config.services.forgejo.stateDir}/custom"; + services.forgejo.settings = { + ui = { + THEMES = "forgejo-auto,forgejo-light,forgejo-dark,auto,gitea,arc-green"; + DEFAULT_THEME = "forgejo-auto"; + }; + "ui.meta" = { + DESCRIPTION = "Code everywhere"; + }; + }; + + + services.forgejo.settings.actions = lib.mkIf (cfg.actionsEnabled) { + ENABLED = true; + DEFAULT_ACTION_URL = "https://${cfg.domain}"; + }; + + + # NGINX + security.acme.acceptTerms = true; + security.acme.defaults.email = cfgBase.adminEmail; + services.nginx = { + enable = true; + virtualHosts = { + "${cfg.domain}" = { + forceSSL = true; + enableACME = true; + locations."/" = { + proxyPass = "http://127.0.0.1:3000"; + extraConfig = '' + client_max_body_size 0; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Server $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $host; + proxy_pass_request_headers on; + ''; + }; + locations."/user/login" = lib.mkIf (cfg.openIdEnabled) { + return = "301 https://$host/user/oauth2/${cfg.openIdClientName}"; + }; + }; + }; + }; + networking.firewall.allowedTCPPorts = [ 80 443 ]; + }; +} diff --git a/pp-gitea.nix b/pp-gitea.nix index f19b8d9..5178df3 100644 --- a/pp-gitea.nix +++ b/pp-gitea.nix @@ -54,6 +54,10 @@ in services.gitea.domain = cfg.domain; # services.gitea.database.type = "postgres"; # Default is sqlite3, probably better for a small instance services.gitea.database.passwordFile = cfg.dbPasswordFile; + + services.gitea.settings.repository.ENABLE_PUSH_CREATE_USER = true; + services.gitea.settings.repository.ENABLE_PUSH_CREATE_ORG = true; + # Set the permittions for the db file system.activationScripts = { giteaDbFilePermission.text = @@ -93,6 +97,7 @@ in locations."/" = { proxyPass = "http://127.0.0.1:3000"; extraConfig = '' + client_max_body_size 0; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Server $host; proxy_set_header X-Real-IP $remote_addr; diff --git a/pp-node-exporter.nix b/pp-node-exporter.nix new file mode 100644 index 0000000..11be08e --- /dev/null +++ b/pp-node-exporter.nix @@ -0,0 +1,77 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.services.ppNodeExporter; + prometheusCaFile = pkgs.writeTextFile { + name = "prometheus_ca.pem"; + text = cfg.prometheusCa; + }; + yaml = pkgs.formats.yaml { }; + nodeWebConfig = yaml.generate "prometheus-node-exporter-webconfig.yml" { + tls_server_config = { + client_ca_file = prometheusCaFile; + cert_file = cfg.prometheusNodeExporterCertFile; + key_file = cfg.prometheusNodeExporterCertKeyFile; + client_auth_type = "RequireAndVerifyClientCert"; + client_allowed_sans = lib.mkIf (cfg.prometheusNodeExporterAllowScrapperSans != null) cfg.prometheusNodeExporterAllowScrapperSans; + }; + }; +in { + options.services.ppNodeExporter = { + prometheusCa = lib.mkOption { + type = lib.types.str; + example = '' + -----BEGIN CERTIFICATE----- + MIIBaTCCAQ6gAwIBAgIUccDw/Xe2RC4p9gwdQMkcbPlS740wCgYIKoZIzj0EAwIw + EjEQMA4GA1UEAwwHZXhhbXBsZTAeFw0yNTAyMjMxMTQzMTlaFw0zNTAyMjExMTQz + MTlaMBIxEDAOBgNVBAMMB2V4YW1wbGUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC + AARk2SGMdAzOR+I+xAJDXO2nm8N4oa8V/kqstJrvd3gGTVsk8b0/EA+6ZrFISL0t + MroC27QCybMwRol9oalSVnoCo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB + /wQEAwIBhjAdBgNVHQ4EFgQUy13fD60aREMworuMEulXdkvTKOwwCgYIKoZIzj0E + AwIDSQAwRgIhALcoP/hicosVELvPfnomcEsWXTkkIVGbu1NeS5I2L72YAiEAi3AG + 7/hpeMxkaE0d2D8pr6exVlZR7kDa9FgDpfu/+a0= + -----END CERTIFICATE----- + ''; + description = "The CA that issues the prometheus scrapper certificate"; + }; + prometheusNodeExporterCertFile = lib.mkOption { + type = lib.types.path; + default = "/etc/prometheus-node-exporter/node-exporter.pem"; + description = "The file of the certificate use by prometheus node exporter."; + }; + prometheusNodeExporterCertKeyFile = lib.mkOption { + type = lib.types.path; + default = "/etc/prometheus-node-exporter/node-exporter.key"; + description = "The file of the key for the certificate used by prometheus node exporter."; + }; + prometheusNodeExporterAllowScrapperSans = lib.mkOption { + type = lib.types.nullOr (lib.types.listOf lib.types.str); + default = null; + example = [ "prometheus.example.com" ]; + description = "The list of Subject Alternative Names allowed to scrape node exporter. If not set, do not check Subject Names."; + }; + }; + config = { + system.activationScripts = { + prometheusNodeExporterFilePermission.text = + '' + chmod 640 ${cfg.prometheusNodeExporterCertFile} + chmod 640 ${cfg.prometheusNodeExporterCertKeyFile} + chown root:${config.services.prometheus.exporters.node.group} ${cfg.prometheusNodeExporterCertFile} + chown root:${config.services.prometheus.exporters.node.group} ${cfg.prometheusNodeExporterCertKeyFile} + ''; + }; + + services.prometheus = { + exporters = { + node = { + enable = true; + port = 9100; # default + enabledCollectors = [ "systemd" ]; # logind ? + extraFlags = [ + "--web.config.file=${nodeWebConfig}" + ]; + }; + }; + }; + }; +} diff --git a/pp-woodpecker.nix b/pp-woodpecker.nix new file mode 100644 index 0000000..ead633e --- /dev/null +++ b/pp-woodpecker.nix @@ -0,0 +1,117 @@ +{ config, pkgs, lib, ... }: +with lib; +let + cfgBase = config.base; + cfg = config.services.ppWoodpecker; +in +{ + imports = [ + # Woodpeeker is not in stable yet but the module is good enought + (builtins.fetchurl { + url = "https://github.com/NixOS/nixpkgs/raw/nixos-unstable/nixos/modules/services/continuous-integration/woodpecker/server.nix"; + sha256 = "13dzbcb0fi0bwam0mlf6d6ly0x90pr8sq68kzs65mszbvsd5lqjb"; + }) + (builtins.fetchurl { + url = "https://github.com/NixOS/nixpkgs/raw/nixos-unstable/nixos/modules/services/continuous-integration/woodpecker/agents.nix"; + sha256 = "14kjj9ybahmfqflvsa8p0va1z3zhliybggxd148fzz4bnjsqpsla"; + }) + ]; + options.services.ppWoodpecker = { + serverEnvFile = mkOption { + type = types.str; + default = "/etc/woodpecker_server_env"; + description = "The file containing the env secrets WOODPECKER_AGENT_SECRET and WOODPECKER_GITEA_SECRET, cf https://woodpecker-ci.org/docs/administration/vcs/gitea#configuration for gitea"; + }; + agentEnvFile = mkOption { + type = types.str; + default = "/etc/woodpecker_agent_env"; + description = "The file containing the env secrets WOODPECKER_AGENT_SECRET"; + }; + domain = mkOption { + type = types.str; + default = "ci.${cfgBase.domainName}"; + example = "ci.example.com"; + description = "The domain of the server"; + }; + giteaDomain = mkOption { + type = types.str; + default = "git.${cfgBase.domainName}"; + example = "git.example.com"; + description = "The domain of the gitea server"; + }; + giteaClientId = mkOption { + type = types.str; + example = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"; + description = "The domain of the gitea server"; + }; + admins = mkOption { + type = lib.types.listOf lib.types.string; + default = []; + example = [ "user1" "user2" ]; + description = "List of admins"; + }; + maxProcsPerAgent = mkOption { + type = types.int; + default = 1; + example = 4; + description = "Number of possible paralle process per agent"; + }; + }; + + config = { + + services.woodpecker-server.enable = true; + # To put in woodpecker_server_env: + # ``` + # WOODPECKER_AGENT_SECRET=XXXXXX + # WOODPECKER_GITEA_SECRET=gto_XXXXXX + # ``` + services.woodpecker-server.environmentFile = "${cfg.serverEnvFile}"; + services.woodpecker-server.environment = { + WOODPECKER_HOST = "https://${cfg.domain}"; + WOODPECKER_OPEN = "true"; # This means user of gitea can connect to the ci + WOODPECKER_GITEA = "true"; + WOODPECKER_GITEA_CLIENT = "${cfg.giteaClientId}"; + WOODPECKER_GITEA_URL = "https://${cfg.giteaDomain}"; + WOODPECKER_ADMIN = lib.mkIf (cfg.admins != []) (lib.concatStringsSep "," cfg.admins); + }; + + virtualisation.podman.enable = true; + virtualisation.podman.defaultNetwork.dnsname.enable = true; + services.woodpecker-agents.agents.podman = { + enable = true; + extraGroups = [ "podman" ]; + environmentFile = [ "${cfg.agentEnvFile}" ]; + environment = { + WOODPECKER_BACKEND = "docker"; + DOCKER_HOST = "unix:////run/podman/podman.sock"; + WOODPECKER_MAX_PROCS = builtins.toString cfg.maxProcsPerAgent; + }; + }; + + security.acme.acceptTerms = true; + security.acme.defaults.email = cfgBase.adminEmail; + services.nginx = { + enable = true; + virtualHosts = { + "${cfg.domain}" = { + forceSSL = true; + enableACME = true; + locations."/" = { + proxyPass = "http://127.0.0.1:8000"; + extraConfig = '' + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Server $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $host; + proxy_pass_request_headers on; + ''; + }; + }; + }; + }; + networking.firewall.allowedTCPPorts = [ 80 443 ]; + }; +}