move magenta and canigou to infra
This commit is contained in:
parent
574e0587c8
commit
c71e0c7573
38 changed files with 0 additions and 985 deletions
Binary file not shown.
Binary file not shown.
|
@ -1,30 +0,0 @@
|
||||||
{ pkgs, ... }:
|
|
||||||
|
|
||||||
let
|
|
||||||
data = import ../../../data.nix;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
./hardware-configuration.nix
|
|
||||||
./networking.secret.nix # generated at runtime by nixos-infect
|
|
||||||
|
|
||||||
../../modules/nix.nix
|
|
||||||
../../shared/common.nix
|
|
||||||
../../shared/fail2ban
|
|
||||||
../../shared/garbage-collector.nix
|
|
||||||
../../shared/docker-swarm.nix
|
|
||||||
|
|
||||||
./services/miniflux.nix
|
|
||||||
./services/telegram-bot.nix
|
|
||||||
];
|
|
||||||
|
|
||||||
boot.kernelPackages = pkgs.linuxPackages_6_1;
|
|
||||||
boot.tmp.cleanOnBoot = true;
|
|
||||||
|
|
||||||
zramSwap.enable = true;
|
|
||||||
|
|
||||||
networking.hostName = "canigou";
|
|
||||||
|
|
||||||
services.openssh.enable = true;
|
|
||||||
users.users.root.openssh.authorizedKeys.keys = data.publicKeys.users.jan;
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{ modulesPath, ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
imports = [ (modulesPath + "/profiles/qemu-guest.nix") ];
|
|
||||||
boot.loader.grub.device = "/dev/sda";
|
|
||||||
boot.initrd.availableKernelModules = [ "ata_piix" "uhci_hcd" "xen_blkfront" "vmw_pvscsi" ];
|
|
||||||
boot.initrd.kernelModules = [ "nvme" ];
|
|
||||||
fileSystems."/" = { device = "/dev/sda1"; fsType = "ext4"; };
|
|
||||||
swapDevices = [
|
|
||||||
{ device = "/dev/zram0"; }
|
|
||||||
{ device = "/var/swapfile"; size = 1536; }
|
|
||||||
];
|
|
||||||
}
|
|
Binary file not shown.
|
@ -1,20 +0,0 @@
|
||||||
{ config, pkgs, ... }:
|
|
||||||
|
|
||||||
let
|
|
||||||
port = 33001;
|
|
||||||
addr = "0.0.0.0:${toString port}";
|
|
||||||
in
|
|
||||||
{
|
|
||||||
services.miniflux = {
|
|
||||||
enable = true;
|
|
||||||
package = pkgs.unstable.miniflux;
|
|
||||||
adminCredentialsFile = config.age.secrets.miniflux-admin-credentials.path;
|
|
||||||
config = {
|
|
||||||
LISTEN_ADDR = addr;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
age.secrets.miniflux-admin-credentials.file = ../../../../secrets/miniflux-admin-credentials.age;
|
|
||||||
|
|
||||||
networking.firewall.allowedTCPPorts = [ port ];
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
{ config, ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
services.yandexgpt_telegram_bot = {
|
|
||||||
enabled = true;
|
|
||||||
environmentFile = config.age.secrets.yandexgpt-tg-bot-env.path;
|
|
||||||
};
|
|
||||||
|
|
||||||
age.secrets.yandexgpt-tg-bot-env = {
|
|
||||||
file = ../../../../secrets/yandexgpt-tg-bot-env.age;
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -55,27 +55,6 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
magenta = {
|
|
||||||
system = "x86_64-linux";
|
|
||||||
|
|
||||||
targetHost = (import ./magenta/data.secret.nix).addr;
|
|
||||||
|
|
||||||
extraModules = [
|
|
||||||
inputs.mailserver.nixosModule
|
|
||||||
../modules/docker-stack.nix
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
canigou = {
|
|
||||||
system = "x86_64-linux";
|
|
||||||
|
|
||||||
extraModules = [
|
|
||||||
yagpt_tg_bot.default
|
|
||||||
];
|
|
||||||
|
|
||||||
targetHost = (import ./canigou/data.secret.nix).addr;
|
|
||||||
};
|
|
||||||
|
|
||||||
istal = {
|
istal = {
|
||||||
system = "x86_64-linux";
|
system = "x86_64-linux";
|
||||||
|
|
||||||
|
|
Binary file not shown.
|
@ -1,33 +0,0 @@
|
||||||
{ pkgs, ... }:
|
|
||||||
|
|
||||||
let
|
|
||||||
data = import ../../../data.nix;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
./hardware-configuration.nix
|
|
||||||
./networking.secret.nix # generated at runtime by nixos-infect
|
|
||||||
|
|
||||||
../../modules/nix.nix
|
|
||||||
../../shared/common.nix
|
|
||||||
../../shared/fail2ban
|
|
||||||
../../shared/garbage-collector.nix
|
|
||||||
../../shared/docker-swarm.nix
|
|
||||||
../../shared/acme.nix
|
|
||||||
|
|
||||||
./services/mailserver.nix
|
|
||||||
./services/gitea.nix
|
|
||||||
./services/traefik.nix
|
|
||||||
./services/woodpecker
|
|
||||||
];
|
|
||||||
|
|
||||||
boot.kernelPackages = pkgs.linuxPackages_6_1;
|
|
||||||
boot.tmp.cleanOnBoot = true;
|
|
||||||
|
|
||||||
zramSwap.enable = true;
|
|
||||||
|
|
||||||
networking.hostName = "magenta";
|
|
||||||
|
|
||||||
services.openssh.enable = true;
|
|
||||||
users.users.root.openssh.authorizedKeys.keys = data.publicKeys.users.jan;
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{ modulesPath, ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
imports = [ (modulesPath + "/profiles/qemu-guest.nix") ];
|
|
||||||
boot.loader.grub.device = "/dev/sda";
|
|
||||||
boot.initrd.availableKernelModules = [ "ata_piix" "uhci_hcd" "xen_blkfront" ];
|
|
||||||
boot.initrd.kernelModules = [ "nvme" ];
|
|
||||||
fileSystems."/" = { device = "/dev/sda1"; fsType = "ext4"; };
|
|
||||||
swapDevices = [
|
|
||||||
{ device = "/dev/zram0"; }
|
|
||||||
{ device = "/var/swapfile"; size = 1536; }
|
|
||||||
];
|
|
||||||
}
|
|
Binary file not shown.
|
@ -1,152 +0,0 @@
|
||||||
{ config, pkgs, lib, ... }:
|
|
||||||
|
|
||||||
let
|
|
||||||
hostname = "git.pleshevski.ru";
|
|
||||||
httpPort = 9901;
|
|
||||||
|
|
||||||
giteaCfg = config.services.gitea;
|
|
||||||
|
|
||||||
robotsTxt = pkgs.writeText "robots.txt" ''
|
|
||||||
User-agent: *
|
|
||||||
Disallow: /github
|
|
||||||
Disallow: /external
|
|
||||||
'';
|
|
||||||
in
|
|
||||||
{
|
|
||||||
services.postgresql.package = pkgs.postgresql_14;
|
|
||||||
|
|
||||||
services.gitea = {
|
|
||||||
enable = true;
|
|
||||||
package = pkgs.unstable.gitea;
|
|
||||||
appName = "Pleshevskiy's Gitea";
|
|
||||||
mailerPasswordFile = config.age.secrets.gitea-smtp-passfile.path;
|
|
||||||
database = {
|
|
||||||
type = "postgres";
|
|
||||||
host = "/run/postgresql";
|
|
||||||
port = config.services.postgresql.port;
|
|
||||||
};
|
|
||||||
lfs.enable = true;
|
|
||||||
extraConfig = ''
|
|
||||||
[DEFAULT]
|
|
||||||
WORK_PATH = ${giteaCfg.stateDir}
|
|
||||||
'';
|
|
||||||
settings = {
|
|
||||||
log = {
|
|
||||||
LEVEL = "Info";
|
|
||||||
ENABLE_SSH_LOG = true;
|
|
||||||
};
|
|
||||||
database = {
|
|
||||||
CHARSET = "utf8";
|
|
||||||
LOG_SQL = false;
|
|
||||||
};
|
|
||||||
server = {
|
|
||||||
DOMAIN = hostname;
|
|
||||||
HTTP_PORT = httpPort;
|
|
||||||
ROOT_URL = "https://${hostname}";
|
|
||||||
LANDING_PAGE = "explore";
|
|
||||||
};
|
|
||||||
service = {
|
|
||||||
ALLOW_ONLY_EXTERNAL_REGISTRATION = false;
|
|
||||||
DEFAULT_KEEP_EMAIL_PRIVATE = false;
|
|
||||||
DEFAULT_ALLOW_CREATE_ORGANIZATION = true;
|
|
||||||
DEFAULT_ENABLE_TIMETRACKING = true;
|
|
||||||
DEFAULT_ENABLE_DEPENDENCIES = false;
|
|
||||||
DISABLE_REGISTRATION = true;
|
|
||||||
ENABLE_NOTIFY_MAIL = false;
|
|
||||||
ENABLE_CAPTCHA = false;
|
|
||||||
ENABLE_TIMETRACKING = false;
|
|
||||||
REQUIRE_SIGNIN_VIEW = false;
|
|
||||||
REGISTER_EMAIL_CONFIRM = false;
|
|
||||||
NO_REPLY_ADDRESS = "noreply.pleshevski.ru";
|
|
||||||
};
|
|
||||||
repository = {
|
|
||||||
DISABLE_MIGRATIONS = false;
|
|
||||||
DISABLE_HTTP_GIT = false;
|
|
||||||
DISABLE_STARS = false;
|
|
||||||
DEFAULT_BRANCH = "main";
|
|
||||||
DEFAULT_CLOSE_ISSUES_VIA_COMMITS_IN_ANY_BRANCH = false;
|
|
||||||
};
|
|
||||||
"repository.local" = {
|
|
||||||
LOCAL_COPY_PATH = "${giteaCfg.stateDir}/tmp/local-repo";
|
|
||||||
};
|
|
||||||
"repository.upload" = {
|
|
||||||
TEMP_PATH = "${giteaCfg.stateDir}/uploads";
|
|
||||||
ALLOWED_TYPES = "image/*";
|
|
||||||
};
|
|
||||||
"repository.pull-request" = {
|
|
||||||
WORK_IN_PROGRESS_PREFIXES = "Draft:,[Draft]:,WIP:,[WIP]:";
|
|
||||||
DEFAULT_MERGE_STYLE = "rebase";
|
|
||||||
POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES = true;
|
|
||||||
};
|
|
||||||
indexer = {
|
|
||||||
ISSUE_INDEXER_PATH = "${giteaCfg.stateDir}/indexers/issues.bleve";
|
|
||||||
};
|
|
||||||
sessions = {
|
|
||||||
PROVIDER = "file";
|
|
||||||
PROVIDER_CONFIG = "${giteaCfg.stateDir}/sessions";
|
|
||||||
};
|
|
||||||
picture = {
|
|
||||||
AVATAR_UPLOAD_PATH = "${giteaCfg.stateDir}/avatars";
|
|
||||||
REPOSITORY_AVATAR_UPLOAD_PATH = "${giteaCfg.stateDir}/repo-avatars";
|
|
||||||
DISABLE_GRAVATAR = false;
|
|
||||||
ENABLE_FEDERATED_AVATAR = true;
|
|
||||||
};
|
|
||||||
attachment = {
|
|
||||||
PATH = "${giteaCfg.stateDir}/attachments";
|
|
||||||
};
|
|
||||||
mailer = {
|
|
||||||
ENABLED = true;
|
|
||||||
MAILER_TYPE = "smtp";
|
|
||||||
SMTP_ADDR = "mail.pleshevski.ru";
|
|
||||||
SMTP_PORT = 465;
|
|
||||||
USER = "gitea@pleshevski.ru";
|
|
||||||
FROM = "\"${giteaCfg.appName}\" <gitea@pleshevski.ru>";
|
|
||||||
};
|
|
||||||
openid = {
|
|
||||||
ENABLE_OPENID_SIGNIN = true;
|
|
||||||
ENABLE_OPENID_SIGNUP = false;
|
|
||||||
};
|
|
||||||
# Don't check for new Gitea versions
|
|
||||||
"cron.update_checker".ENABLED = false;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd.services.gitea.preStart = lib.mkAfter ''
|
|
||||||
cp -f ${robotsTxt} ${giteaCfg.stateDir}/custom/robots.txt
|
|
||||||
'';
|
|
||||||
|
|
||||||
services.traefik.dynamicConfigOptions.http = {
|
|
||||||
routers.to_gitea = {
|
|
||||||
rule = "Host(`${hostname}`)";
|
|
||||||
entryPoints = [ "https" ];
|
|
||||||
tls.certResolver = "le";
|
|
||||||
service = "gitea";
|
|
||||||
};
|
|
||||||
services.gitea = {
|
|
||||||
loadBalancer.servers = [
|
|
||||||
{ url = "http://host.docker.internal:${toString httpPort}"; }
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
age.secrets.gitea-smtp-passfile = {
|
|
||||||
file = ../../../../secrets/gitea-smtp-passfile.age;
|
|
||||||
owner = giteaCfg.user;
|
|
||||||
group = "gitea";
|
|
||||||
};
|
|
||||||
|
|
||||||
services.fail2ban.jails.gitea = ''
|
|
||||||
enabled = true
|
|
||||||
filter = gitea
|
|
||||||
findtime = 3600
|
|
||||||
bantime = 900
|
|
||||||
action = iptables-allports
|
|
||||||
'';
|
|
||||||
|
|
||||||
environment.etc."fail2ban/filter.d/gitea.conf".text = ''
|
|
||||||
[Definition]
|
|
||||||
failregex = .*Failed authentication attempt for .* from <HOST>
|
|
||||||
ignoreregex =
|
|
||||||
journalmatch = _SYSTEMD_UNIT=gitea.service
|
|
||||||
'';
|
|
||||||
}
|
|
Binary file not shown.
|
@ -1,73 +0,0 @@
|
||||||
{ config, pkgs, ... }:
|
|
||||||
|
|
||||||
let
|
|
||||||
cfg = config.mailserver;
|
|
||||||
|
|
||||||
certsDir = "/var/certs";
|
|
||||||
|
|
||||||
# Extracting a Certificate from Traefik`s acme.json
|
|
||||||
# Source: https://www.zdyn.net/docker/2022/02/04/acme-certificate.html
|
|
||||||
dumpTraefikMailCerts = pkgs.writeScript "dump-mail-certs" ''
|
|
||||||
#!/bin/sh
|
|
||||||
mkdir -p $(dirname "${cfg.certificateFile}") $(dirname "${cfg.keyFile}")
|
|
||||||
${pkgs.jq}/bin/jq -r '.le.Certificates[] | select(.domain.main=="${cfg.fqdn}") | .certificate' /var/lib/traefik/acme.json | base64 -d > ${cfg.certificateFile}
|
|
||||||
${pkgs.jq}/bin/jq -r '.le.Certificates[] | select(.domain.main=="${cfg.fqdn}") | .key' /var/lib/traefik/acme.json | base64 -d > ${cfg.keyFile}
|
|
||||||
systemctl restart dovecot2.service
|
|
||||||
'';
|
|
||||||
|
|
||||||
in
|
|
||||||
{
|
|
||||||
imports = [ ./mailserver-accounts.secret.nix ];
|
|
||||||
|
|
||||||
# See: https://nixos-mailserver.readthedocs.io/en/latest/options.html
|
|
||||||
mailserver = {
|
|
||||||
enable = true;
|
|
||||||
|
|
||||||
# We use traefik to generate certificates
|
|
||||||
certificateScheme = 1;
|
|
||||||
certificateFile = "${certsDir}/cert-${cfg.fqdn}.pem";
|
|
||||||
keyFile = "${certsDir}/key-${cfg.fqdn}.pem";
|
|
||||||
|
|
||||||
hierarchySeparator = "/";
|
|
||||||
};
|
|
||||||
|
|
||||||
services.traefik.dynamicConfigOptions.http = {
|
|
||||||
routers.mailserver_acme = {
|
|
||||||
rule = "Host(`${cfg.fqdn}`)";
|
|
||||||
entryPoints = [ "http" ];
|
|
||||||
tls = {
|
|
||||||
certResolver = "le";
|
|
||||||
domains = [
|
|
||||||
{
|
|
||||||
main = cfg.fqdn;
|
|
||||||
sans = cfg.domains;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
service = "noop@internal";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd = {
|
|
||||||
# Watch traefik`s acme.json to update certs in /var/certs
|
|
||||||
# Source: https://superuser.com/questions/1171751/restart-systemd-service-automatically-whenever-a-directory-changes-any-file-ins
|
|
||||||
services.dump-traefik-mail-cert = {
|
|
||||||
unitConfig = {
|
|
||||||
Description = "Restart mail cert service";
|
|
||||||
After = [ "network.target" ];
|
|
||||||
};
|
|
||||||
|
|
||||||
serviceConfig = {
|
|
||||||
Type = "oneshot";
|
|
||||||
ExecStart = "${dumpTraefikMailCerts}";
|
|
||||||
};
|
|
||||||
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
};
|
|
||||||
|
|
||||||
paths.dump-traefik-mail-cert = {
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
pathConfig.PathChanged = "/var/lib/traefik/acme.json";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,133 +0,0 @@
|
||||||
{ config, lib, pkgs, ... }:
|
|
||||||
|
|
||||||
let
|
|
||||||
magentaData = import ../data.secret.nix;
|
|
||||||
|
|
||||||
dataDir = "/var/lib/traefik";
|
|
||||||
|
|
||||||
traefikCfg = config.services.traefik;
|
|
||||||
|
|
||||||
user = "traefik";
|
|
||||||
group = "traefik";
|
|
||||||
|
|
||||||
dynamicConfigFile = pkgs.runCommand "config.toml"
|
|
||||||
{
|
|
||||||
buildInputs = [ pkgs.remarshal ];
|
|
||||||
preferLocalBuild = true;
|
|
||||||
}
|
|
||||||
''
|
|
||||||
remarshal -if json -of toml \
|
|
||||||
< ${pkgs.writeText "dynamic_config.json" (builtins.toJSON traefikCfg.dynamicConfigOptions)} \
|
|
||||||
> $out
|
|
||||||
'';
|
|
||||||
|
|
||||||
staticConfigFile = pkgs.runCommand "config.toml"
|
|
||||||
{
|
|
||||||
buildInputs = [ pkgs.remarshal ];
|
|
||||||
preferLocalBuild = true;
|
|
||||||
}
|
|
||||||
''
|
|
||||||
remarshal -if json -of toml \
|
|
||||||
< ${
|
|
||||||
pkgs.writeText "static_config.json" (builtins.toJSON
|
|
||||||
(lib.recursiveUpdate traefikCfg.staticConfigOptions {
|
|
||||||
providers.file.filename = "${dynamicConfigFile}";
|
|
||||||
}))
|
|
||||||
} \
|
|
||||||
> $out
|
|
||||||
'';
|
|
||||||
|
|
||||||
mirrorVolume = path: "${path}:${path}";
|
|
||||||
in
|
|
||||||
{
|
|
||||||
networking.firewall.allowedTCPPorts = [ 80 443 8080 ];
|
|
||||||
|
|
||||||
users.users.${user} = {
|
|
||||||
isSystemUser = true;
|
|
||||||
createHome = true;
|
|
||||||
home = dataDir;
|
|
||||||
inherit group;
|
|
||||||
};
|
|
||||||
users.groups.${group} = { };
|
|
||||||
users.groups.docker.members = [ user ];
|
|
||||||
|
|
||||||
systemd.tmpfiles.rules = [ "d '${dataDir}' 0700 ${user} ${group} - -" ];
|
|
||||||
|
|
||||||
age.secrets.traefik-dashboard-basicauth-users = {
|
|
||||||
file = ../../../../secrets/traefik-dashboard-basicauth-users.age;
|
|
||||||
owner = user;
|
|
||||||
inherit group;
|
|
||||||
};
|
|
||||||
|
|
||||||
virtualisation.docker.stacks.traefik = {
|
|
||||||
networks.traefik_public.external = true;
|
|
||||||
services.traefik = {
|
|
||||||
image = "traefik:v2.9";
|
|
||||||
command = [
|
|
||||||
"--configFile=${staticConfigFile}"
|
|
||||||
];
|
|
||||||
ports = [
|
|
||||||
"80:80"
|
|
||||||
"443:443"
|
|
||||||
"8080:8080"
|
|
||||||
];
|
|
||||||
extra_hosts = [ "host.docker.internal:host-gateway" ];
|
|
||||||
networks = [ "traefik_public" ];
|
|
||||||
volumes = [
|
|
||||||
"${mirrorVolume "/var/run/docker.sock"}:ro"
|
|
||||||
"${mirrorVolume dataDir}"
|
|
||||||
"${mirrorVolume staticConfigFile}:ro"
|
|
||||||
"${mirrorVolume dynamicConfigFile}:ro"
|
|
||||||
"${mirrorVolume config.age.secrets.traefik-dashboard-basicauth-users.path}:ro"
|
|
||||||
];
|
|
||||||
deploy = {
|
|
||||||
placement.constraints = [ "node.role==manager" ];
|
|
||||||
update_config.order = "start-first";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
services.traefik = {
|
|
||||||
staticConfigOptions = {
|
|
||||||
entryPoints = {
|
|
||||||
http = {
|
|
||||||
address = ":80";
|
|
||||||
http.redirections.entryPoint = {
|
|
||||||
to = "https";
|
|
||||||
scheme = "https";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
https.address = ":443";
|
|
||||||
dashboard.address = ":8080";
|
|
||||||
};
|
|
||||||
api = { };
|
|
||||||
log = { };
|
|
||||||
accessLog = { };
|
|
||||||
certificatesResolvers.le.acme = {
|
|
||||||
storage = "${dataDir}/acme.json";
|
|
||||||
email = "dmitriy@pleshevski.ru";
|
|
||||||
tlschallenge = true;
|
|
||||||
};
|
|
||||||
providers.docker = {
|
|
||||||
network = "traefik_public";
|
|
||||||
constraints = "Label(`traefik.constraint-label`, `${config.networking.hostName}_public`)";
|
|
||||||
exposedByDefault = false;
|
|
||||||
swarmMode = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
dynamicConfigOptions.http = {
|
|
||||||
routers.to_traefik_dashboard = {
|
|
||||||
rule = "Host(`${magentaData.addr}`)";
|
|
||||||
entryPoints = [ "dashboard" ];
|
|
||||||
middlewares = [ "traefik_dashboard_auth" ];
|
|
||||||
service = "api@internal";
|
|
||||||
};
|
|
||||||
middlewares = {
|
|
||||||
traefik_dashboard_auth.basicAuth = {
|
|
||||||
usersFile = config.age.secrets.traefik-dashboard-basicauth-users.path;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
{ pkgs, config, ... }:
|
|
||||||
|
|
||||||
let
|
|
||||||
canigouData = import ../../data.secret.nix;
|
|
||||||
|
|
||||||
data = import ./data.secret.nix;
|
|
||||||
inherit (data) userAgent group grpcPort;
|
|
||||||
|
|
||||||
dockerSockVolume = "/var/run/docker.sock:/var/run/docker.sock";
|
|
||||||
dockerConfVolume = "${config.age.secrets.woodpecker-docker-config.path}:/root/.docker/config.json";
|
|
||||||
in
|
|
||||||
{
|
|
||||||
systemd.services.woodpecker-agent = {
|
|
||||||
enable = true;
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
after = [ "network-online.target" ];
|
|
||||||
wants = [ "network-online.target" ];
|
|
||||||
restartIfChanged = true;
|
|
||||||
serviceConfig = {
|
|
||||||
EnvironmentFile = [
|
|
||||||
config.age.secrets.woodpecker-common-env.path
|
|
||||||
];
|
|
||||||
Environment = [
|
|
||||||
"WOODPECKER_DEBUG_PRETTY=true"
|
|
||||||
"WOODPECKER_LOG_LEVEL=trace"
|
|
||||||
"WOODPECKER_SERVER=${canigouData.addr}:${toString grpcPort}"
|
|
||||||
"WOODPECKER_MAX_WORKFLOWS=2"
|
|
||||||
"WOODPECKER_BACKEND=docker"
|
|
||||||
"WOODPECKER_BACKEND_DOCKER_VOLUMES=${dockerSockVolume},${dockerConfVolume}"
|
|
||||||
];
|
|
||||||
ExecStart = "${pkgs.unstable.woodpecker-agent}/bin/woodpecker-agent";
|
|
||||||
User = userAgent;
|
|
||||||
Group = group;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
let
|
|
||||||
data = import ./data.secret.nix;
|
|
||||||
inherit (data) userServer userAgent group;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
users.groups.${group} = { };
|
|
||||||
|
|
||||||
users.users.${userServer} = {
|
|
||||||
description = "Woodpecker CI Server";
|
|
||||||
isSystemUser = true;
|
|
||||||
createHome = true;
|
|
||||||
inherit group;
|
|
||||||
};
|
|
||||||
|
|
||||||
users.users.${userAgent} = {
|
|
||||||
isSystemUser = true;
|
|
||||||
inherit group;
|
|
||||||
};
|
|
||||||
users.groups.docker.members = [ userAgent userServer ];
|
|
||||||
|
|
||||||
age.secrets.woodpecker-common-env.file = ../../../../../secrets/woodpecker-common-env.age;
|
|
||||||
age.secrets.woodpecker-server-env.file = ../../../../../secrets/woodpecker-server-env.age;
|
|
||||||
age.secrets.woodpecker-docker-config = {
|
|
||||||
file = ../../../../../secrets/docker-config.json.age;
|
|
||||||
mode = "440";
|
|
||||||
inherit group;
|
|
||||||
};
|
|
||||||
}
|
|
Binary file not shown.
|
@ -1,9 +0,0 @@
|
||||||
{ ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
./common.nix
|
|
||||||
./agent-docker.nix
|
|
||||||
./server.nix
|
|
||||||
];
|
|
||||||
}
|
|
|
@ -1,68 +0,0 @@
|
||||||
# https://github.com/Mic92/dotfiles/tree/035a2c22e161f4fbe4fcbd038c6464028ddce619/nixos/eve/modules/woodpecker
|
|
||||||
{ pkgs, config, ... }:
|
|
||||||
|
|
||||||
let
|
|
||||||
data = import ./data.secret.nix;
|
|
||||||
inherit (data) hostname port grpcPort userServer group database;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
networking.firewall.allowedTCPPorts = [ port grpcPort ];
|
|
||||||
|
|
||||||
services.postgresql.enable = true;
|
|
||||||
|
|
||||||
systemd.services.woodpecker-server = {
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
after = [ "network-online.target" ];
|
|
||||||
wants = [ "network-online.target" ];
|
|
||||||
serviceConfig = {
|
|
||||||
# See: https://woodpecker-ci.org/docs/administration/server-config
|
|
||||||
EnvironmentFile = [
|
|
||||||
config.age.secrets.woodpecker-common-env.path
|
|
||||||
config.age.secrets.woodpecker-server-env.path
|
|
||||||
];
|
|
||||||
Environment = [
|
|
||||||
"WOODPECKER_DEBUG_PRETTY=true"
|
|
||||||
"WOODPECKER_LOG_LEVEL=trace"
|
|
||||||
"WOODPECKER_HOST=https://${hostname}"
|
|
||||||
"WOODPECKER_SERVER_ADDR=:${toString port}"
|
|
||||||
"WOODPECKER_GRPC_ADDR=:${toString grpcPort}"
|
|
||||||
"WOODPECKER_ADMIN=pleshevskiy"
|
|
||||||
"WOODPECKER_DATABASE_DRIVER=postgres"
|
|
||||||
"WOODPECKER_DATABASE_DATASOURCE=postgres://${userServer}@:${toString config.services.postgresql.port}/${database}?host=/run/postgresql"
|
|
||||||
"WOODPECKER_GITEA=true"
|
|
||||||
"WOODPECKER_GITEA_URL=https://git.pleshevski.ru"
|
|
||||||
"WOODPECKER_DOCKER_CONFIG=${config.age.secrets.woodpecker-docker-config.path}"
|
|
||||||
"WOODPECKER_AUTHENTICATE_PUBLIC_REPOS=true"
|
|
||||||
];
|
|
||||||
ExecStart = "${pkgs.unstable.woodpecker-server}/bin/woodpecker-server";
|
|
||||||
User = userServer;
|
|
||||||
Group = group;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
services.postgresql = {
|
|
||||||
ensureDatabases = [ database ];
|
|
||||||
ensureUsers = [
|
|
||||||
{
|
|
||||||
name = userServer;
|
|
||||||
ensurePermissions = {
|
|
||||||
"DATABASE ${database}" = "ALL PRIVILEGES";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
services.traefik.dynamicConfigOptions.http = {
|
|
||||||
routers.to_woodpecker_server = {
|
|
||||||
rule = "Host(`${hostname}`)";
|
|
||||||
entryPoints = [ "https" ];
|
|
||||||
tls.certResolver = "le";
|
|
||||||
service = "woodpecker_server";
|
|
||||||
};
|
|
||||||
services.woodpecker_server = {
|
|
||||||
loadBalancer.servers = [
|
|
||||||
{ url = "http://host.docker.internal:${toString port}"; }
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,245 +0,0 @@
|
||||||
{ config, lib, pkgs, ... }:
|
|
||||||
|
|
||||||
let
|
|
||||||
cfg = config.virtualisation.docker;
|
|
||||||
|
|
||||||
proxyEnv = config.networking.proxy.envVars;
|
|
||||||
|
|
||||||
serviceOptions = { ... }: {
|
|
||||||
options = with lib; {
|
|
||||||
image = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
description = lib.mdDoc "OCI image to run.";
|
|
||||||
example = "library/hello-world";
|
|
||||||
};
|
|
||||||
|
|
||||||
command = mkOption {
|
|
||||||
type = types.listOf types.str;
|
|
||||||
default = [ ];
|
|
||||||
description = lib.mdDoc "Overrides the default command declared by the image (i.e. by Dockerfile's CMD).";
|
|
||||||
example = literalExpression ''
|
|
||||||
[
|
|
||||||
"--port=9000"
|
|
||||||
]
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
ports = mkOption {
|
|
||||||
type = types.listOf types.str;
|
|
||||||
default = [ ];
|
|
||||||
description = lib.mdDoc ''
|
|
||||||
Network ports to publish from the container to the outer host.
|
|
||||||
Valid formats:
|
|
||||||
- `<ip>:<hostPort>:<containerPort>`
|
|
||||||
- `<ip>::<containerPort>`
|
|
||||||
- `<hostPort>:<containerPort>`
|
|
||||||
- `<containerPort>`
|
|
||||||
Both `hostPort` and `containerPort` can be specified as a range of
|
|
||||||
ports. When specifying ranges for both, the number of container
|
|
||||||
ports in the range must match the number of host ports in the
|
|
||||||
range. Example: `1234-1236:1234-1236/tcp`
|
|
||||||
When specifying a range for `hostPort` only, the `containerPort`
|
|
||||||
must *not* be a range. In this case, the container port is published
|
|
||||||
somewhere within the specified `hostPort` range.
|
|
||||||
Example: `1234-1236:1234/tcp`
|
|
||||||
Refer to the
|
|
||||||
[Docker engine documentation](https://docs.docker.com/engine/reference/run/#expose-incoming-ports) for full details.
|
|
||||||
'';
|
|
||||||
example = literalExpression ''
|
|
||||||
[
|
|
||||||
"8080:9000"
|
|
||||||
]
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
volumes = mkOption {
|
|
||||||
type = types.listOf types.str;
|
|
||||||
default = [ ];
|
|
||||||
description = lib.mdDoc ''
|
|
||||||
List of volumes to attach to this container.
|
|
||||||
Note that this is a list of `"src:dst"` strings to
|
|
||||||
allow for `src` to refer to `/nix/store` paths, which
|
|
||||||
would be difficult with an attribute set. There are
|
|
||||||
also a variety of mount options available as a third
|
|
||||||
field; please refer to the
|
|
||||||
[docker engine documentation](https://docs.docker.com/engine/reference/run/#volume-shared-filesystems) for details.
|
|
||||||
'';
|
|
||||||
example = literalExpression ''
|
|
||||||
[
|
|
||||||
"volume_name:/path/inside/container"
|
|
||||||
"/path/on/host:/path/inside/container"
|
|
||||||
]
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
networks = mkOption {
|
|
||||||
type = types.listOf types.str;
|
|
||||||
default = [ ];
|
|
||||||
description = lib.mdDoc "Networks to join.";
|
|
||||||
example = literalExpression ''
|
|
||||||
[
|
|
||||||
"backend_internal"
|
|
||||||
"traefik_public"
|
|
||||||
]
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
extra_hosts = mkOption {
|
|
||||||
type = types.listOf types.str;
|
|
||||||
default = [ ];
|
|
||||||
description = lib.mdDoc "Add hostname mappings.";
|
|
||||||
example = literalExpression ''
|
|
||||||
[
|
|
||||||
"host.docker.internal:host-gateway"
|
|
||||||
"otherhost:50.31.209.229"
|
|
||||||
]
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
deploy = {
|
|
||||||
labels = mkOption {
|
|
||||||
default = [ ];
|
|
||||||
type = types.listOf types.str;
|
|
||||||
description = lib.mdDoc "Specify labels for the service.";
|
|
||||||
example = literalExpression ''
|
|
||||||
[
|
|
||||||
"com.example.description=This label will appear on the web service"
|
|
||||||
]
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
placement = {
|
|
||||||
constraints = mkOption {
|
|
||||||
default = [ ];
|
|
||||||
type = types.listOf types.str;
|
|
||||||
description = lib.mdDoc ''
|
|
||||||
You can limit the set of nodes where a task can be scheduled by defining constraint expressions.
|
|
||||||
Constraint expressions can either use a match (==) or exclude (!=) rule.
|
|
||||||
Multiple constraints find nodes that satisfy every expression (AND match).
|
|
||||||
'';
|
|
||||||
example = literalExample ''
|
|
||||||
[
|
|
||||||
"node.role==manager"
|
|
||||||
];
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
update_config = {
|
|
||||||
order = mkOption {
|
|
||||||
default = "stop-first";
|
|
||||||
type = types.str;
|
|
||||||
description = lib.mdDoc ''
|
|
||||||
Order of operations during updates.
|
|
||||||
- stop-first (old task is stopped before starting new one),
|
|
||||||
- start-first (new task is started first, and the running tasks briefly overlap)
|
|
||||||
|
|
||||||
Note: Only supported for v3.4 and higher.
|
|
||||||
'';
|
|
||||||
example = "start-first";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
networkOptions = { ... }: {
|
|
||||||
options = with lib; {
|
|
||||||
external = mkOption {
|
|
||||||
default = false;
|
|
||||||
type = types.nullOr types.bool;
|
|
||||||
description = lib.mdDoc ''
|
|
||||||
If set to true, specifies that this volume has been created outside of Compose.
|
|
||||||
The systemd service does not attempt to create it, and raises an error if it doesn’t exist.
|
|
||||||
'';
|
|
||||||
example = "true";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
stackOptions = { ... }: {
|
|
||||||
options = with lib; {
|
|
||||||
version = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = "3.8";
|
|
||||||
};
|
|
||||||
|
|
||||||
services = mkOption {
|
|
||||||
default = { };
|
|
||||||
type = types.attrsOf (types.submodule serviceOptions);
|
|
||||||
description = lib.mdDoc "";
|
|
||||||
};
|
|
||||||
|
|
||||||
networks = mkOption {
|
|
||||||
default = { };
|
|
||||||
type = types.attrsOf (types.submodule networkOptions);
|
|
||||||
description = lib.mdDoc "";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
mkComposeFile = stack:
|
|
||||||
pkgs.runCommand "compose.yml"
|
|
||||||
{
|
|
||||||
buildInputs = [ pkgs.remarshal ];
|
|
||||||
preferLocalBuild = true;
|
|
||||||
}
|
|
||||||
''
|
|
||||||
remarshal -if json -of yaml \
|
|
||||||
< ${ pkgs.writeText "compose.json" (builtins.toJSON stack)} \
|
|
||||||
> $out
|
|
||||||
'';
|
|
||||||
|
|
||||||
mkStackTimer = stackName: {
|
|
||||||
wantedBy = [ "timers.target" ];
|
|
||||||
timerConfig = {
|
|
||||||
OnBootSec = "5m";
|
|
||||||
OnUnitActiveSec = "5m";
|
|
||||||
Unit = "docker-stack-${stackName}.service";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
mkStackService = stackName: stack:
|
|
||||||
let
|
|
||||||
escapedStackName = lib.escapeShellArg stackName;
|
|
||||||
composeFile = mkComposeFile stack;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
description = "Deploy ${escapedStackName} stack";
|
|
||||||
enable = true;
|
|
||||||
|
|
||||||
after = [ "docker.service" "docker.socket" ];
|
|
||||||
environment = proxyEnv;
|
|
||||||
|
|
||||||
path = [ config.virtualisation.docker.package ];
|
|
||||||
|
|
||||||
script = lib.concatStringsSep " \\\n " ([
|
|
||||||
"exec docker stack deploy"
|
|
||||||
"--compose-file=${composeFile}"
|
|
||||||
escapedStackName
|
|
||||||
]);
|
|
||||||
|
|
||||||
serviceConfig = {
|
|
||||||
Type = "oneshot";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in
|
|
||||||
|
|
||||||
{
|
|
||||||
options.virtualisation.docker.stacks = with lib; mkOption {
|
|
||||||
default = { };
|
|
||||||
type = types.attrsOf (types.submodule stackOptions);
|
|
||||||
description = lib.mdDoc "Docker stacks to deploy using systemd services.";
|
|
||||||
};
|
|
||||||
|
|
||||||
config = lib.mkIf (cfg.stacks != { }) {
|
|
||||||
systemd.timers = lib.mapAttrs' (n: v: lib.nameValuePair "docker-stack-${n}" (mkStackTimer n)) cfg.stacks;
|
|
||||||
systemd.services = lib.mapAttrs' (n: v: lib.nameValuePair "docker-stack-${n}" (mkStackService n v)) cfg.stacks;
|
|
||||||
|
|
||||||
virtualisation.docker = {
|
|
||||||
enable = true;
|
|
||||||
liveRestore = false;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,70 +0,0 @@
|
||||||
{ config, lib, ... }:
|
|
||||||
|
|
||||||
let
|
|
||||||
cfg = config.local.traefik;
|
|
||||||
|
|
||||||
traefikCfg = config.services.traefik;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
options.local.traefik = with lib; {
|
|
||||||
enable = mkEnableOption "Enable traefik service";
|
|
||||||
dashboard = {
|
|
||||||
enable = mkEnableOption "Enable traefik dashboard";
|
|
||||||
host = mkOption {
|
|
||||||
type = types.nullOr types.str;
|
|
||||||
description = "Traefik dashboard host";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
|
||||||
networking.firewall.allowedTCPPorts = [ 80 443 ] ++ lib.optional cfg.dashboard.enable 8080;
|
|
||||||
|
|
||||||
services.traefik = {
|
|
||||||
enable = true;
|
|
||||||
staticConfigOptions = {
|
|
||||||
entryPoints = {
|
|
||||||
http = {
|
|
||||||
address = ":80";
|
|
||||||
http.redirections.entryPoint = {
|
|
||||||
to = "https";
|
|
||||||
scheme = "https";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
https.address = ":443";
|
|
||||||
};
|
|
||||||
log = { };
|
|
||||||
accessLog = { };
|
|
||||||
certificatesResolvers.le.acme = {
|
|
||||||
storage = "${traefikCfg.dataDir}/acme.json";
|
|
||||||
email = "dmitriy@pleshevski.ru";
|
|
||||||
tlschallenge = true;
|
|
||||||
};
|
|
||||||
providers.docker = {
|
|
||||||
network = "rp_public";
|
|
||||||
constraints = "Label(`traefik.constraint-label`, `${config.networking.hostName}_public`)";
|
|
||||||
exposedByDefault = false;
|
|
||||||
swarmMode = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
} // lib.mkIf cfg.dashboard.enable {
|
|
||||||
staticConfigOptions = {
|
|
||||||
api = { };
|
|
||||||
entryPoints.dashboard.address = ":8080";
|
|
||||||
};
|
|
||||||
dynamicConfigOptions.http = {
|
|
||||||
routers.to_traefik_dashboard = {
|
|
||||||
rule = "Host(`${cfg.dashboard.host}`)";
|
|
||||||
entryPoints = [ "dashboard" ];
|
|
||||||
middlewares = [ "traefik_dashboard_auth" ];
|
|
||||||
service = "api@internal";
|
|
||||||
};
|
|
||||||
middlewares = {
|
|
||||||
traefik_dashboard_auth.basicAuth = {
|
|
||||||
usersFile = config.age.secrets.traefik-dashboard-basicauth-users.path;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
{ ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
security.acme = {
|
|
||||||
acceptTerms = true;
|
|
||||||
defaults.email = "dmitriy@pleshevski.ru";
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
{ pkgs, ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
# Enable docker
|
|
||||||
virtualisation.docker = {
|
|
||||||
enable = true;
|
|
||||||
liveRestore = false;
|
|
||||||
package = pkgs.unstable.docker;
|
|
||||||
};
|
|
||||||
# Source: https://forums.docker.com/t/error-response-from-daemon-rpc-error-code-unavailable-desc-grpc-the-connection-is-unavailable/39066/12
|
|
||||||
networking.firewall = {
|
|
||||||
allowedTCPPorts = [ 2376 2377 7946 ];
|
|
||||||
allowedUDPPorts = [ 7946 4789 ];
|
|
||||||
trustedInterfaces = [ "docker0" "docker_gwbridge" ];
|
|
||||||
};
|
|
||||||
}
|
|
Binary file not shown.
|
@ -4,11 +4,6 @@ This repository contains configurations for my personal vps and workstations.
|
||||||
|
|
||||||
## Hosts
|
## Hosts
|
||||||
|
|
||||||
VPS:
|
|
||||||
|
|
||||||
- **magenta** - Docker swarm manager with additional services.
|
|
||||||
- **canigou** - Docker swarm worker.
|
|
||||||
|
|
||||||
Workstations:
|
Workstations:
|
||||||
|
|
||||||
- **home** - Home desktop computer for work.
|
- **home** - Home desktop computer for work.
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in a new issue