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 = {
|
||||
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
|
||||
|
||||
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.
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