move magenta and canigou to infra

This commit is contained in:
Dmitriy Pleshevskiy 2023-11-24 01:19:02 +03:00
parent 574e0587c8
commit c71e0c7573
Signed by: pleshevskiy
GPG Key ID: 79C4487B44403985
38 changed files with 0 additions and 985 deletions

Binary file not shown.

Binary file not shown.

View File

@ -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;
}

View File

@ -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; }
];
}

View File

@ -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 ];
}

View File

@ -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;
};
}

View File

@ -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 = {
system = "x86_64-linux";

Binary file not shown.

View File

@ -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;
}

View File

@ -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; }
];
}

View File

@ -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
'';
}

View File

@ -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";
};
};
}

View File

@ -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;
};
};
};
};
}

View File

@ -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;
};
};
}

View File

@ -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;
};
}

View File

@ -1,9 +0,0 @@
{ ... }:
{
imports = [
./common.nix
./agent-docker.nix
./server.nix
];
}

View File

@ -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}"; }
];
};
};
}

View File

@ -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 doesnt 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;
};
};
}

View File

@ -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;
};
};
};
};
};
}

View File

@ -1,8 +0,0 @@
{ ... }:
{
security.acme = {
acceptTerms = true;
defaults.email = "dmitriy@pleshevski.ru";
};
}

View File

@ -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.

View File

@ -4,11 +4,6 @@ This repository contains configurations for my personal vps and workstations.
## Hosts
VPS:
- **magenta** - Docker swarm manager with additional services.
- **canigou** - Docker swarm worker.
Workstations:
- **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.