diff --git a/modules/services/default.nix b/modules/services/default.nix index 8a4f747..bea2139 100644 --- a/modules/services/default.nix +++ b/modules/services/default.nix @@ -32,5 +32,6 @@ ./tlp ./transmission ./wireguard + ./woodpecker ]; } diff --git a/modules/services/woodpecker/agent-docker/default.nix b/modules/services/woodpecker/agent-docker/default.nix new file mode 100644 index 0000000..23e61b1 --- /dev/null +++ b/modules/services/woodpecker/agent-docker/default.nix @@ -0,0 +1,48 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.services.woodpecker; + + hasRunner = (name: builtins.elem name cfg.runners); +in +{ + # TODO + config = lib.mkIf (cfg.enable && hasRunner "docker") { + services.woodpecker-agents = { + agents.docker = { + enable = true; + + package = pkgs.ambroisie.woodpecker-agent; + + environment = { + WOODPECKER_SERVER = "localhost:${toString cfg.rpcPort}"; + WOODPECKER_MAX_WORKFLOWS = "10"; + WOODPECKER_BACKEND = "docker"; + WOODPECKER_FILTER_LABELS = "type=docker"; + WOODPECKER_HEALTHCHECK = "false"; + }; + + environmentFile = [ cfg.sharedSecretFile ]; + + extraGroups = [ "docker" ]; + }; + }; + + # Make sure it is activated in that case + virtualisation.docker.enable = true; + + # FIXME: figure out the issue + services.unbound.resolveLocalQueries = false; + + # Adjust runner service for nix usage + systemd.services.woodpecker-agent-docker = { + after = [ "docker.socket" ]; # Needs the socket to be available + # might break deployment + restartIfChanged = false; + serviceConfig = { + BindPaths = [ + "/var/run/docker.sock" + ]; + }; + }; + }; +} diff --git a/modules/services/woodpecker/agent-exec/default.nix b/modules/services/woodpecker/agent-exec/default.nix new file mode 100644 index 0000000..743dfbb --- /dev/null +++ b/modules/services/woodpecker/agent-exec/default.nix @@ -0,0 +1,64 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.services.woodpecker; + + hasRunner = (name: builtins.elem name cfg.runners); +in +{ + # TODO + config = lib.mkIf (cfg.enable && hasRunner "exec") { + services.woodpecker-agents = { + agents.exec = { + enable = true; + + package = pkgs.ambroisie.woodpecker-agent; + + environment = { + WOODPECKER_SERVER = "localhost:${toString cfg.rpcPort}"; + WOODPECKER_MAX_WORKFLOWS = "10"; + WOODPECKER_BACKEND = "local"; + WOODPECKER_FILTER_LABELS = "type=exec"; + WOODPECKER_HEALTHCHECK = "false"; + + NIX_REMOTE = "daemon"; + PAGER = "cat"; + }; + + environmentFile = [ cfg.sharedSecretFile ]; + }; + }; + + # Adjust runner service for nix usage + systemd.services.woodpecker-agent-exec = { + # Might break deployment + restartIfChanged = false; + + path = with pkgs; [ + ambroisie.woodpecker-plugin-git + bash + coreutils + git + git-lfs + gnutar + gzip + nix + ]; + + serviceConfig = { + BindPaths = [ + "/nix/var/nix/daemon-socket/socket" + "/run/nscd/socket" + ]; + BindReadOnlyPaths = [ + "/etc/passwd:/etc/passwd" + "/etc/group:/etc/group" + "/nix/var/nix/profiles/system/etc/nix:/etc/nix" + "${config.environment.etc."ssh/ssh_known_hosts".source}:/etc/ssh/ssh_known_hosts" + "/etc/machine-id" + # channels are dynamic paths in the nix store, therefore we need to bind mount the whole thing + "/nix/" + ]; + }; + }; + }; +} diff --git a/modules/services/woodpecker/default.nix b/modules/services/woodpecker/default.nix new file mode 100644 index 0000000..34ffca6 --- /dev/null +++ b/modules/services/woodpecker/default.nix @@ -0,0 +1,46 @@ +{ lib, ... }: +{ + imports = [ + ./agent-docker + ./agent-exec + ./server + ]; + + options.my.services.woodpecker = with lib; { + enable = mkEnableOption "Woodpecker CI"; + runners = mkOption { + type = with types; listOf (enum [ "exec" "docker" ]); + default = [ ]; + example = [ "exec" "docker" ]; + description = "Types of runners to enable"; + }; + admin = mkOption { + type = types.str; + default = "ambroisie"; + example = "admin"; + description = "Name of the admin user"; + }; + port = mkOption { + type = types.port; + default = 3030; + example = 8080; + description = "Internal port of the Woodpecker UI"; + }; + rpcPort = mkOption { + type = types.port; + default = 3031; + example = 8080; + description = "Internal port of the Woodpecker UI"; + }; + secretFile = mkOption { + type = types.str; + example = "/run/secrets/woodpecker.env"; + description = "Secrets to inject into Woodpecker server"; + }; + sharedSecretFile = mkOption { + type = types.str; + example = "/run/secrets/woodpecker.env"; + description = "Shared RPC secret to inject into server and runners"; + }; + }; +} diff --git a/modules/services/woodpecker/server/default.nix b/modules/services/woodpecker/server/default.nix new file mode 100644 index 0000000..8d8a5a6 --- /dev/null +++ b/modules/services/woodpecker/server/default.nix @@ -0,0 +1,72 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.my.services.woodpecker; +in +{ + config = lib.mkIf cfg.enable { + services.woodpecker-server = { + enable = true; + + package = pkgs.ambroisie.woodpecker-server; + + environment = { + WOODPECKER_OPEN = "true"; + WOODPECKER_HOST = "https://woodpecker.${config.networking.domain}"; + WOODPECKER_DATABASE_DRIVER = "postgres"; + WOODPECKER_DATABASE_DATASOURCE = "postgres:///woodpecker?host=/run/postgresql"; + WOODPECKER_ADMIN = "${cfg.admin}"; + WOODPECKER_SERVER_ADDR = ":${toString cfg.port}"; + WOODPECKER_GRPC_ADDR = ":${toString cfg.rpcPort}"; + + WOODPECKER_GITEA = "true"; + WOODPECKER_GITEA_URL = config.services.gitea.rootUrl; + + WOODPECKER_LOG_LEVEL = "debug"; + + # FIXME: remove those? + WOODPECKER_JSONNET_ENABLED = "true"; + WOODPECKER_STARLARK_ENABLED = "true"; + }; + }; + + systemd.services.woodpecker-server = { + serviceConfig = { + # Set username for DB access + User = "woodpecker"; + + BindPaths = [ + # Allow access to DB path + "/run/postgresql" + ]; + + EnvironmentFile = [ + cfg.secretFile + cfg.sharedSecretFile + ]; + }; + }; + + services.postgresql = { + enable = true; + ensureDatabases = [ "woodpecker" ]; + ensureUsers = [{ + name = "woodpecker"; + ensurePermissions = { + "DATABASE woodpecker" = "ALL PRIVILEGES"; + }; + }]; + }; + + my.services.nginx.virtualHosts = [ + { + subdomain = "woodpecker"; + inherit (cfg) port; + } + # I might want to be able to RPC from other hosts in the future + { + subdomain = "woodpecker-rpc"; + port = cfg.rpcPort; + } + ]; + }; +}