First attempt at a woodpecker module

This commit is contained in:
TEC 2022-08-05 00:56:48 +08:00
parent b499ffe37a
commit 6d88a3f83e
Signed by: tec
SSH Key Fingerprint: SHA256:eobz41Mnm0/iYWBvWThftS0ElEs1ftBr6jamutnXc/A
8 changed files with 376 additions and 0 deletions

View File

@ -12,6 +12,9 @@ respond "Hello, world!"
'';
virtualHosts."git.tecosaur.net".extraConfig = ''
reverse_proxy localhost:3000
'';
virtualHosts."ci.tecosaur.net".extraConfig = ''
reverse_proxy localhost:3030
'';
};
}

View File

@ -0,0 +1,42 @@
{ config, lib, pkgs, ... }:
{
imports = [
./woodpecker-server.nix
./woodpecker-agent.nix
];
age.secrets.woodpecker-client-id = {
owner = "woodpecker-server";
group = "users";
file = ../../secrets/woodpecker-client-id.age;
};
age.secrets.woodpecker-client-secret = {
owner = "woodpecker-server";
group = "users";
file = ../../secrets/woodpecker-client-secret.age;
};
age.secrets.woodpecker-agent-secret = {
owner = "woodpecker-server";
group = "users";
file = ../../secrets/woodpecker-agent-secret.age;
};
services.woodpecker-server = {
enable = true;
rootUrl = "https://ci.tecosaur.net";
httpPort = 3030;
database = {
type = "postgres";
};
giteaClientIdFile = config.age.secrets.woodpecker-client-id.path;
giteaClientSecretFile = config.age.secrets.woodpecker-client-secret.path;
agentSecretFile = config.age.secrets.woodpecker-agent-secret.path;
};
services.woodpecker-agent = {
enable = true;
};
}

View File

@ -0,0 +1,86 @@
{ config, lib, pkgs, ... }:
with lib;
let cfg = config.services.woodpecker-agent;
servercfg = config.services.woodpecker-server;
in
{
options = {
services.woodpecker-agent = {
enable = mkOption {
default = false;
type = types.bool;
description = lib.mdDoc "Enable Woodpecker agent.";
};
user = mkOption {
type = types.str;
default = "woodpecker-agent";
description = lib.mdDoc "User account under which woodpecker agent runs.";
};
agentSecretFile = mkOption {
type = types.nullOr types.path;
default = servercfg.agentSecretFile;
description = lib.mdDoc "Read the agent secret from this file path.";
};
maxProcesses = mkOption {
type = types.int;
default = 1;
description = lib.mdDoc "The maximum number of processes per agent.";
};
backend = mkOption {
type = types.enum [ "auto-detect" "docker" "local" "ssh" ];
default = "auto-detect";
description = lib.mdDoc "Configures the backend engine to run pipelines on.";
};
server = mkOption {
type = types.str;
default = "localhost:${if servercfg.enabled then toString servercfg.gRPCPort else "9000"}";
description = lib.mdDoc "The gPRC address of the server.";
};
};
};
config = mkIf cfg.enable {
systemd.services.woodpecker-agent = {
description = "woodpecker-agent";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "simple";
User = cfg.user;
Group = "woodpecker-agent";
ExecStart = "${pkgs.woodpecker-agent}/bin/woodpecker-agent";
Restart = "always";
# TODO add security/sandbox params.
};
environment = mkMerge [
{
WOODPECKER_SERVER=true;
WOODPECKER_MAX_PROCS=cfg.maxProcesses;
WOODPECKER_BACKEND=cfg.backend;
}
(mkIf (cfg.agentSecretFile != null) {
WOODPECKER_AGENT_SECRET_FILE=cfg.agentSecretFile;
})
];
};
users.users = mkIf (cfg.user == "woodpecker-agent") {
woodpecker-agent = {
createHome = true;
home = cfg.stateDir;
useDefaultShell = true;
group = "woodpecker-agent";
extraGroups = [ "woodpecker" ];
isSystemUser = true;
};
};
users.groups.woodpecker-agent = { };
};
}

View File

@ -0,0 +1,223 @@
{ config, lib, options, pkgs, ... }:
with lib;
let
cfg = config.services.woodpecker-server;
useMysql = cfg.database.type == "mysql";
usePostgresql = cfg.database.type == "postgres";
useSqlite = cfg.database.type == "sqlite3";
in
{
options = {
services.woodpecker-server = {
enable = mkOption {
default = false;
type = types.bool;
description = lib.mdDoc "Enable Woodpecker Server.";
};
stateDir = mkOption {
default = "/var/lib/woodpecker-server";
type = types.str;
description = lib.mdDoc "woodpecker server data directory.";
};
user = mkOption {
type = types.str;
default = "woodpecker-server";
description = lib.mdDoc "User account under which woodpecker server runs.";
};
rootUrl = mkOption {
default = "http://localhost:3030";
type = types.str;
description = lib.mkDoc "Full public URL of Woodpecker server";
};
httpPort = mkOption {
type = types.int;
default = 3030;
description = lib.mdDoc "HTTP listen port.";
};
gRPCPort = mkOption {
type = types.int;
default = 9000;
description = lib.mdDoc "The gPRC listener port.";
};
admins = mkOption {
default = "";
type = types.str;
description = lib.mdDoc "Woodpecker admin users.";
};
agentSecretFile = mkOption {
type = types.nullOr types.path;
default = null;
description = lib.mdDoc "Read the agent secret from this file path.";
};
database = {
type = mkOption {
type = types.enum [ "sqlite3" "mysql" "postgres" ];
example = "mysql";
default = "sqlite3";
description = lib.mdDoc "Database engine to use.";
};
host = mkOption {
type = types.str;
default = "127.0.0.1";
description = lib.mdDoc "Database host address.";
};
port = mkOption {
type = types.port;
default = (if !usePostgresql then 3306 else pg.port);
defaultText = literalExpression ''
if config.${opt.database.type} != "postgresql"
then 3306
else config.${options.services.postgresql.port}
'';
description = lib.mdDoc "Database host port.";
};
name = mkOption {
type = types.str;
default = "woodpecker-server";
description = lib.mdDoc "Database name.";
};
password = mkOption {
type = types.str;
default = "";
description = lib.mdDoc ''
The password corresponding to {option}`database.user`.
Warning: this is stored in cleartext in the Nix store!
Use {option}`database.passwordFile` instead.
'';
};
user = mkOption {
type = types.str;
default = "woodpecker-server";
description = lib.mdDoc "Database user.";
};
socket = mkOption {
type = types.nullOr types.path;
default = if (cfg.database.createDatabase && usePostgresql) then "/run/postgresql" else if (cfg.database.createDatabase && useMysql) then "/run/mysqld/mysqld.sock" else null;
defaultText = literalExpression "null";
example = "/run/mysqld/mysqld.sock";
description = lib.mdDoc "Path to the unix socket file to use for authentication.";
};
createDatabase = mkOption {
type = types.bool;
default = true;
description = lib.mdDoc "Whether to create a local database automatically.";
};
};
useGitea = mkOption {
default = options.services.gitea.enabled;
type = types.bool;
description = lib.mkDoc "Whether to integrate with gitea.";
};
giteaUrl = mkOption {
default = options.services.gitea.rootUrl;
type = types.str;
description = lib.mkDoc "Full public URL of gitea server.";
};
giteaClientIdFile = mkOption {
type = types.nullOr types.path;
default = null;
};
giteaClientSecretFile = mkOption {
type = types.nullOr types.path;
default = null;
};
};
};
config = mkIf cfg.enable {
systemd.services.woodpecker-server = {
description = "woodpecker-server";
after = [ "network.target" ] ++ lib.optional usePostgresql "postgresql.service" ++ lib.optional useMysql "mysql.service";
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "simple";
User = cfg.user;
Group = "woodpecker-server";
WorkingDirectory = cfg.stateDir;
ExecStart = "${pkgs.woodpecker-server}/bin/woodpecker-server";
Restart = "always";
# TODO add security/sandbox params.
};
environment = mkMerge [
{
WOODPECKER_OPEN=true;
WOODPECKER_ADMIN=cfg.admins;
WOODPECKER_HOST=cfg.rootUrl;
WOODPECKER_SERVER_ADDR=":${toString cfg.httpPort}";
WOODPECKER_GRPC_ADDR=cfg.gRPCPort;
}
(mkIf cfg.useGitea {
WOODPECKER_GITEA=true;
WOODPECKER_GITEA_URL=cfg.giteaUrl;
WOODPECKER_GITEA_CLIENT_FILE=cfg.giteaClientIdFile;
WOODPECKER_GITEA_SECRET_FILE=cfg.giteaClientSecretFile;
})
(mkIf usePostgresql {
WOODPECKER_DATABASE_DRIVER="postgres";
WOODPECKER_DATABASE_DATASOURCE=
"postgres://${cfg.database.user}:${cfg.database.password}/${cfg.database.name}" +
"?host=${if cfg.database.socket != null then cfg.database.socket else cfg.database.host + ":" + toString cfg.database.port}";
})
(mkIf (cfg.agentSecretFile != null) {
WOODPECKER_AGENT_SECRET_FILE=cfg.agentSecretFile;
})
];
};
services.postgresql = optionalAttrs (usePostgresql && cfg.database.createDatabase) {
enable = mkDefault true;
ensureDatabases = [ cfg.database.name ];
ensureUsers = [
{ name = cfg.database.user;
ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
}
];
};
services.mysql = optionalAttrs (useMysql && cfg.database.createDatabase) {
enable = mkDefault true;
package = mkDefault pkgs.mariadb;
ensureDatabases = [ cfg.database.name ];
ensureUsers = [
{ name = cfg.database.user;
ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
}
];
};
users.users = mkIf (cfg.user == "woodpecker-server") {
woodpecker-server = {
createHome = true;
home = cfg.stateDir;
useDefaultShell = true;
group = "woodpecker-server";
extraGroups = [ "woodpecker" ];
isSystemUser = true;
};
};
users.groups.woodpecker-server = { };
};
}

View File

@ -6,4 +6,7 @@ in
{
"postgres-gitea.age".publicKeys = systems;
"fastmail.age".publicKeys = systems;
"woodpecker-client-id.age".publicKeys = systems;
"woodpecker-client-secret.age".publicKeys = systems;
"woodpecker-agent-secret.age".publicKeys = systems;
}

Binary file not shown.

View File

@ -0,0 +1,9 @@
age-encryption.org/v1
-> ssh-ed25519 eobz4w v1Bc0BdCfgiN7dTmuHgYd8haeEer2r+YT15yMZKjb0g
9RgF4DClwpKmyTwzJVsDYYHinnqWE6HvnqTw5vqu9Po
-> ssh-ed25519 kfYPBA Y+ObzE8+k6YU+jwJvVfjjqQ6B7L6GkNOo5uWPWZkuiI
krMwU5xP3vxgAQeTkJS7Fls0DPJRMGaoykb2phS+39A
-> &c-grease WABF'z !%]UQi WqPSzW #slup3
RrOi8Q
--- ay1ApsHJc6hlQ1p0oWR3Rf1W1YR3XAyfg5mPINrJDvM
óix þQïDêa®º»-¡î˜”3‡#Ña Ÿ4UKšDÐkê*,½þ»Z*w ÚÎIçö.e>•~÷qZºÏ

View File

@ -0,0 +1,10 @@
age-encryption.org/v1
-> ssh-ed25519 eobz4w CycaujAgrBsG5P6i5196hreJtnOxReq6k1GlJRhiqxA
rRuPITF5VTkzets/bCRqVF7o462dvjJdohNX+0iN+2A
-> ssh-ed25519 kfYPBA ttSZFpctapkEbRmrw8VNkr+cTd0XkRx4Vlqv6obxKBw
uZojzuzLiTzZd0SjlyCDYDPNeBGTQE8SrrQxSYFogtY
-> !s*M_-grease B@C0U:D *)+1 m
xj2sW2uN/ZVeM0oH73SOilLHvn83CXyjYVxgTWnWglpS+WN/Tq7NvCuilXu0ZJiS
zUZxFCqe
--- IPpz1GE6p+itJUERyejUAgyga/vOOOhTFnrNUJe/8qs
Û¼^b2ÎNšÕ…Ôæ(a™B7‰¼uÕd Æ »ùä€3ôKÐÜÖeFÅgál }±…Þ ]·Arñ`:‹âß,´´öíM•­—ð…F¼ŸRdˆ¹…ëÿ