initial commit
flake-check / flake-check (push) Canceled after 0s
lint / lint (push) Canceled after 0s
diff-tag / dix (push) Canceled after 0s

This commit is contained in:
Floweynt
2026-08-31 08:41:45 -05:00
commit 76fa38e34c
54 changed files with 6747 additions and 0 deletions
+54
View File
@@ -0,0 +1,54 @@
{
name,
hostInfo,
dsl,
...
}:
let
inherit (dsl) mkContainer mkService;
inherit (hostInfo.paths) data secrets;
in
mkContainer {
inherit name;
net.access = [ { to = "@internet"; } ];
svc.autoStart = true;
resources.fs = {
"/config" = {
hostPath = "${data}/web/devenv/config";
isReadOnly = false;
};
"/workspace" = {
hostPath = "${data}/web/devenv/workspace";
isReadOnly = false;
};
"/run/secrets/code-server-env" = {
hostPath = "${secrets}/code-server-env";
isReadOnly = true;
};
};
users = [
{
name = "coder";
uid = 1000;
home = "/config";
}
];
service =
{ pkgs, lib, ... }:
mkService {
description = "code-server";
execStart = "${lib.getExe' pkgs.code-server "code-server"} --bind-addr 0.0.0.0:8080 --user-data-dir /config/data --extensions-dir /config/extensions --auth password /workspace";
networkOnline = true;
serviceConfig = {
EnvironmentFile = "/run/secrets/code-server-env";
User = "coder";
ReadWritePaths = [
"/config"
"/workspace"
];
};
};
net.expose.tcp = [ 8080 ];
resources.mem = "4G";
resources.cpu = 2;
}
+178
View File
@@ -0,0 +1,178 @@
{
name,
hostInfo,
dsl,
...
}:
let
inherit (dsl) mkContainer mkVm;
inherit (hostInfo.paths) data secrets;
ciRunnerData = "${data}/web/git-ci-runner";
ciRunnerSecrets = "${secrets}/ci-runner";
in
{
${name} = mkContainer {
inherit name;
net.access = [ { to = "@internet"; } ];
net.forward.tcp = [
{
host = 23;
container = 8023;
}
];
svc.autoStart = true;
resources.fs."/var/lib/gitea" = {
hostPath = "${data}/web/git";
isReadOnly = false;
};
containerConfig = _: {
services.gitea = {
enable = true;
stateDir = "/var/lib/gitea";
settings = {
server = {
HTTP_ADDR = "0.0.0.0";
HTTP_PORT = 3000;
DOMAIN = "git.floweytf.com";
ROOT_URL = "https://git.floweytf.com/";
START_SSH_SERVER = true;
BUILTIN_SSH_SERVER_USER = "git";
SSH_PORT = 8023;
};
service.DISABLE_REGISTRATION = true;
};
};
users.users.gitea.uid = 1000;
users.groups.gitea.gid = 1000;
time.timeZone = "UTC";
systemd.services.gitea.serviceConfig.FailureAction = "exit-force";
};
net.expose.tcp = [ 3000 ];
};
ci-runner = mkVm {
name = "ci-runner";
net.access = [
{
to = "git";
tcp = [ 3000 ];
}
{ to = "@internet"; }
];
svc.autoStart = true;
resources.cpu = 4;
resources.mem = "8G";
resources.fs = [
{
proto = "virtiofs";
tag = "nix-rw-store";
source = "${ciRunnerData}/nix-rw-store";
mountPoint = "/nix/.rw-store";
}
{
proto = "virtiofs";
tag = "docker-data";
source = "${ciRunnerData}/docker";
mountPoint = "/var/lib/docker";
}
{
proto = "virtiofs";
tag = "runner-state";
source = "${ciRunnerData}/runner-state";
mountPoint = "/var/lib/gitea-runner";
}
{
proto = "virtiofs";
tag = "runner-token";
source = ciRunnerSecrets;
mountPoint = "/run/ci-runner-secrets";
readOnly = true;
}
];
guestConfig =
{
pkgs,
utils,
lib,
...
}:
let
runnerInstanceName = "ci-runner";
ciJobImage = pkgs.dockerTools.buildLayeredImage {
name = "ci-job-image";
tag = "latest";
contents = with pkgs; [
bashInteractive
coreutils
gitMinimal
nixVersions.latest
nodejs
cacert
];
config = {
Cmd = [ "/bin/sh" ];
Env = [
"SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"
"NIX_SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"
"NIX_REMOTE=daemon"
];
};
};
in
{
boot.kernelModules = [ "kvm-intel" ];
boot.extraModprobeConfig = "options kvm_intel nested=1";
microvm.writableStoreOverlay = "/nix/.rw-store";
nix.settings = {
experimental-features = [
"nix-command"
"flakes"
];
max-jobs = "auto";
};
nix.gc = {
automatic = true;
dates = "daily";
options = "--delete-older-than 14d";
};
# nix.optimise.automatic = true; - doesn't work with microvm.writableStoreOverlay
virtualisation.docker.enable = true;
systemd.services.load-ci-job-image = {
description = "Load the CI job Docker image";
wantedBy = [ "multi-user.target" ];
after = [ "docker.service" ];
requires = [ "docker.service" ];
before = [ "gitea-runner-${utils.escapeSystemdPath runnerInstanceName}.service" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStart = "${lib.getExe' pkgs.docker "docker"} load -i ${ciJobImage}";
};
};
services.gitea-actions-runner.instances.${runnerInstanceName} = {
enable = true;
name = runnerInstanceName;
url = "http://git:3000/";
tokenFile = "/run/ci-runner-secrets/token";
labels = [
"nix:docker://ci-job-image:latest"
];
settings.container = {
options = "--device=/dev/kvm --volume=/nix/store:/nix/store:ro --volume=/nix/var/nix/daemon-socket:/nix/var/nix/daemon-socket";
valid_volumes = [
"/nix/store"
"/nix/var/nix/daemon-socket"
];
};
};
};
};
}
+79
View File
@@ -0,0 +1,79 @@
{
name,
pkgs,
lib,
dsl,
containers,
customPkgs,
...
}:
let
inherit (dsl) mkContainer mkService collectContainers;
mkHopperConfig =
{ routes }:
let
header = ''
listen = "0.0.0.0:25565"
[routing.routes]
'';
mkRoute = { domain, ip }: ''
"${domain}" = { ip = "${ip}" }
'';
in
header + lib.concatMapStrings mkRoute routes;
backends = collectContainers "hopperRegister" containers;
mcRoutes = map ({ container, data }: {
domain = "${data.hopperSubdomain}.mc.floweytf.com";
ip = "${container.name}:${toString data.hopperPort}";
}) backends;
hopperCfg = pkgs.writeText "Config.toml" (mkHopperConfig {
routes = mcRoutes;
});
in
{
${name} = mkContainer {
inherit name;
net.access = map ({ container, data }: {
to = container.name;
tcp = [ data.hopperPort ];
}) backends;
svc.autoStart = true;
net.expose.tcp = [ 25565 ];
net.forward.tcp = [ 25565 ];
resources.fs."/Config.toml" = {
hostPath = toString hopperCfg;
isReadOnly = true;
};
service =
_:
mkService {
description = "Hopper Minecraft reverse proxy";
execStart = lib.getExe' customPkgs.hopper-rs "hopper";
workingDir = "/";
networkOnline = true;
serviceConfig = {
DynamicUser = true;
PrivateTmp = true;
PrivateDevices = true;
CapabilityBoundingSet = "";
RestrictAddressFamilies = "AF_INET AF_INET6";
};
};
};
_hopperRegister =
{
hopperSubdomain ? null,
hopperPort ? 25565,
}:
self: {
hopperRegister = {
hopperSubdomain = if hopperSubdomain == null then self.name else hopperSubdomain;
inherit hopperPort;
};
};
}
+264
View File
@@ -0,0 +1,264 @@
{
name,
pkgs,
lib,
hostInfo,
dsl,
...
}:
let
inherit (dsl) mkContainer mkService;
inherit (hostInfo.paths) data secrets;
domain = "mail.floweytf.com";
accountName = "flowey";
stateDir = "/var/lib/stalwart";
acmeDir = "/etc/mail-acme";
taggingScript = ''
require ["envelope", "variables", "imap4flags", "fileinto"];
if envelope :localpart :matches "to" "*" {
addflag "''${1}";
}
fileinto "INBOX";
'';
cli = lib.getExe' pkgs.stalwart-cli "stalwart-cli";
jq = lib.getExe pkgs.jq;
curl = lib.getExe pkgs.curl;
grep = lib.getExe' pkgs.gnugrep "grep";
paste = lib.getExe' pkgs.coreutils "paste";
# use sqlite b/c rocksdb segfaults
configJsonTemplate = pkgs.writeText "stalwart-config.json" (
builtins.toJSON {
"@type" = "Sqlite";
path = "${stateDir}/data.sqlite3";
}
);
staticPlan = pkgs.writeText "mail-static-plan.json" (
builtins.concatStringsSep "\n" (
map builtins.toJSON [
{
"@type" = "upsert";
object = "Domain";
matchOn = [ "name" ];
value."domain-mail" = {
name = domain;
catchAllAddress = "${accountName}@${domain}";
isEnabled = true;
};
}
{
"@type" = "upsert";
object = "SieveSystemScript";
matchOn = [ "name" ];
value."sieve-tag" = {
name = "tag-by-recipient";
isActive = true;
contents = taggingScript;
};
}
]
)
+ "\n"
);
stalwartAuthEnv = ''
export STALWART_URL="http://127.0.0.1:8080"
export STALWART_USER="admin"
export STALWART_PASSWORD="$(cat "$CREDENTIALS_DIRECTORY/admin_password")"
'';
readyGate = pkgs.writeShellScript "mail-ready-gate" ''
set -uo pipefail
until ${curl} -sS -o /dev/null "http://127.0.0.1:8080/api/account"; do
if ! systemctl -q is-active stalwart.service; then
echo "mail: stalwart is not running" >&2
exit 1
fi
sleep 1
done
'';
bootstrapScript = pkgs.writeShellScript "mail-bootstrap" ''
set -uo pipefail
${stalwartAuthEnv}
out="$(${cli} update Bootstrap singleton \
--field defaultDomain=${domain} \
--field serverHostname=${domain} \
--field generateDkimKeys=true \
--field requestTlsCertificate=false \
--field 'directory={"@type":"Internal"}' 2>&1)"
status=$?
if [ "$status" -eq 0 ]; then
echo "mail-bootstrap: first-time setup complete, restarting stalwart into normal mode" >&2
systemctl restart stalwart.service
exit 0
fi
if ${grep} -qi 'bootstrap mode' <<<"$out"; then
exit 0
fi
echo "mail-bootstrap: $out" >&2
exit 1
'';
certCredCert = "/run/credentials/stalwart.service/mail_tls_cert";
certCredKey = "/run/credentials/stalwart.service/mail_tls_key";
retryOnFailure = {
Restart = "on-failure";
RestartSec = 5;
};
retryLimit = {
StartLimitBurst = 5;
StartLimitIntervalSec = 120;
};
provisionScript = pkgs.writeShellScript "mail-provision" ''
set -euo pipefail
${stalwartAuthEnv}
export account_pass="$(cat "$CREDENTIALS_DIRECTORY/account_password")"
account_entry="$(${jq} -nc --arg name "${accountName}" \
'{"@type":"upsert","object":"Account","matchOn":["name"],"value":{("account-"+$name):{"@type":"User","name":$name,"domainId":"#domain-mail","credentials":{"0":{"@type":"Password","secret":env.account_pass}}}}}')"
{ cat ${staticPlan}; echo "$account_entry"; } | ${cli} apply --stdin
existing_cert_ids="$(${cli} query Certificate --json --fields id | ${jq} -r .id | ${paste} -sd, -)"
if [ -n "$existing_cert_ids" ]; then
${cli} delete Certificate --ids "$existing_cert_ids"
fi
${cli} create Certificate \
--field 'certificate={"@type":"File","filePath":"${certCredCert}"}' \
--field 'privateKey={"@type":"File","filePath":"${certCredKey}"}'
systemctl restart stalwart.service
'';
in
mkContainer {
inherit name;
net.access = [ { to = "@internet"; } ];
svc.autoStart = true;
net.forward.tcp = [
25
993
];
resources.fs = {
"${stateDir}" = {
hostPath = "${data}/core/mail";
isReadOnly = false;
};
"${acmeDir}" = {
hostPath = "${data}/core/nginx/acme/${domain}";
isReadOnly = true;
};
"/run/secrets/mail-admin-password" = {
hostPath = "${secrets}/mail-admin-password";
isReadOnly = true;
};
"/run/secrets/mail-account-password" = {
hostPath = "${secrets}/mail-account-password";
isReadOnly = true;
};
};
users = [
{
name = "stalwart";
uid = 1000;
home = stateDir;
}
];
containerConfig = _: {
systemd.tmpfiles.rules = [
"Z ${stateDir} 0750 stalwart stalwart -"
"C ${stateDir}/config.json 0640 stalwart stalwart - ${configJsonTemplate}"
];
systemd.services.stalwart = mkService {
description = "Stalwart Mail Server";
execStart = pkgs.writeShellScript "stalwart-run" ''
set -euo pipefail
export STALWART_RECOVERY_ADMIN="admin:$(cat "$CREDENTIALS_DIRECTORY/admin_password")"
exec ${lib.getExe' pkgs.stalwart_0_16 "stalwart"} -c ${stateDir}/config.json
'';
serviceConfig = {
User = "stalwart";
Group = "stalwart";
AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
ReadWritePaths = [ stateDir ];
LoadCredential = [
"admin_password:/run/secrets/mail-admin-password"
"mail_tls_cert:${acmeDir}/fullchain.pem"
"mail_tls_key:${acmeDir}/key.pem"
];
Restart = "on-failure";
RestartSec = 5;
};
};
systemd.paths.mail-cert-renew = {
wantedBy = [ "multi-user.target" ];
pathConfig = {
PathChanged = "${acmeDir}/key.pem";
Unit = "mail-provision.service";
};
};
systemd.services.mail-ready = mkService {
description = "Wait for the Stalwart management API";
execStart = readyGate;
after = [ "stalwart.service" ];
wants = [ "stalwart.service" ];
wantedBy = [ ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
TimeoutStartSec = 60;
};
};
systemd.services.mail-bootstrap = mkService {
description = "One-time Stalwart bootstrap (server identity + admin account)";
execStart = bootstrapScript;
after = [ "mail-ready.service" ];
wants = [ "mail-ready.service" ];
unitConfig = retryLimit;
serviceConfig = {
Type = "oneshot";
LoadCredential = [ "admin_password:/run/secrets/mail-admin-password" ];
}
// retryOnFailure;
};
systemd.services.mail-provision = mkService {
description = "Provision mail.floweytf.com domain + catch-all account in Stalwart";
execStart = provisionScript;
after = [
"mail-ready.service"
"mail-bootstrap.service"
];
wants = [
"mail-ready.service"
"mail-bootstrap.service"
];
serviceConfig = {
Type = "oneshot";
LoadCredential = [
"admin_password:/run/secrets/mail-admin-password"
"account_password:/run/secrets/mail-account-password"
];
}
// retryOnFailure;
unitConfig = retryLimit;
};
};
}
+41
View File
@@ -0,0 +1,41 @@
{
name,
hostInfo,
dsl,
...
}:
let
inherit (dsl) mkContainer mkService;
inherit (hostInfo.paths) data;
in
mkContainer {
inherit name;
svc.autoStart = true;
resources.fs."/app/data" = {
hostPath = "${data}/web/reposilite";
isReadOnly = false;
};
users = [
{
name = "reposilite";
uid = 1000;
home = "/app";
}
];
service =
{ pkgs, lib, ... }:
mkService {
description = "Reposilite";
execStart = "${lib.getExe' pkgs.reposilite "reposilite"} --working-directory /app/data";
workingDir = "/app/data";
serviceConfig = {
User = "reposilite";
PrivateTmp = true;
ReadWritePaths = [ "/app/data" ];
};
}
// {
environment.JAVA_OPTS = "-Xmx128M";
};
net.expose.tcp = [ 8080 ];
}
+205
View File
@@ -0,0 +1,205 @@
{
lib,
pkgs,
hostInfo,
dsl,
self,
...
}:
let
inherit (dsl) mkContainer mkService;
inherit (hostInfo.paths) data;
mkMcTrait =
{
jdk,
dataPath,
extraPackages ? [ ],
}:
{
resources.fs."/data" = {
hostPath = dataPath;
isReadOnly = false;
};
users = [
{
name = "runner";
uid = 1000;
}
];
containerConfig =
{
pkgs,
...
}:
{
environment.systemPackages = [
jdk
pkgs.bash
]
++ extraPackages;
systemd.services.minecraft = mkService {
description = "Minecraft server";
wantedBy = [ "multi-user.target" ];
after = [
"local-fs.target"
"container-ready.target"
];
wants = [ "container-ready.target" ];
path = [
jdk
pkgs.bash
];
execStart = "${lib.getExe' pkgs.bash "bash"} -c ${
lib.escapeShellArg (
"exec ${lib.getExe' pkgs.dtach "dtach"} -N /run/ct-attach/attach.sock "
+ "${lib.getExe' pkgs.util-linux "script"} -qfc /data/launch.sh "
+ ">(exec ${lib.getExe' pkgs.systemd "systemd-cat"} -t minecraft)"
)
}";
serviceConfig = {
WorkingDirectory = "/data";
User = "runner";
RuntimeDirectory = "ct-attach";
RuntimeDirectoryMode = "0700";
StandardInput = "null";
StandardOutput = "journal";
StandardError = "journal";
ReadWritePaths = [ "/data" ];
};
};
};
};
extractAttrs =
l: s: lib.mergeAttrsList (map (f: lib.optionalAttrs (s ? ${f}) { ${f} = s.${f}; }) l);
mkMcSet =
{ dataDir }:
servers:
lib.listToAttrs (
map (
args@{
name,
jdk,
autoStart ? false,
allowInternet ? true,
dataPath ? "${dataDir}/${name}",
extraPackages ? [ ],
mem ? "6G",
cpu ? 2,
...
}:
lib.nameValuePair "mc-${name}" (mkContainer {
name = "mc-${name}";
net.access = lib.optional allowInternet { to = "@internet"; };
net.expose.tcp = [ (args.hopperPort or 25565) ];
svc.autoStart = autoStart;
resources = { inherit cpu mem; };
traits = [ (mkMcTrait { inherit jdk dataPath extraPackages; }) ];
miscProperties = self._hopperRegister (extractAttrs [ "hopperSubdomain" "hopperPort" ] args);
includedInTests = false;
})
) servers
);
jdk8FontPkgs = with pkgs; [
freetype
fontconfig
dejavu_fonts
];
mcServers = [
{
name = "nomifactory";
jdk = pkgs.jdk8;
extraPackages = jdk8FontPkgs;
hopperSubdomain = "nomi";
hopperPort = 25555;
}
{
name = "monifactory";
jdk = pkgs.jdk21;
hopperSubdomain = "moni";
}
{
name = "rad2";
jdk = pkgs.jdk8;
extraPackages = jdk8FontPkgs;
}
{
name = "smp";
jdk = pkgs.jdk17;
}
{
name = "rc4";
jdk = pkgs.jdk17;
}
{
name = "vh";
jdk = pkgs.jdk17;
hopperPort = 25555;
}
{
name = "gregblock";
jdk = pkgs.jdk8;
extraPackages = jdk8FontPkgs;
}
{
name = "tfg";
jdk = pkgs.jdk17;
}
{
name = "ratgame";
jdk = pkgs.jdk21;
}
{
name = "maidrpg";
jdk = pkgs.jdk17;
}
{
name = "maidsmp";
jdk = pkgs.jdk21;
}
{
name = "maid-event";
jdk = pkgs.jdk21;
hopperSubdomain = "maidevent";
dataPath = "${data}/mc/maidevent/speedrun";
}
{
name = "gtnh";
jdk = pkgs.jdk21;
}
{
name = "turing";
jdk = pkgs.jdk21;
}
{
name = "tfg2";
jdk = pkgs.jdk21;
autoStart = true;
mem = "10G";
cpu = 8;
}
{
name = "tl";
jdk = pkgs.jdk21;
}
{
name = "botm";
jdk = pkgs.jdk17;
}
{
name = "rogule-build";
jdk = pkgs.jdk21;
}
{
name = "rogule-dev";
jdk = pkgs.jdk21;
}
];
in
mkMcSet {
dataDir = "${data}/mc";
} mcServers
+84
View File
@@ -0,0 +1,84 @@
{
name,
pkgs,
lib,
hostInfo,
dsl,
inputs,
...
}:
let
inherit (dsl) mkContainer mkService;
inherit (hostInfo.paths) data secrets;
inherit (inputs) mtce;
dataDir = "/var/lib/mtce-bot";
threads = 64;
timeout = 1800;
configJson = pkgs.writeText "mtce-bot-config.json" (
builtins.toJSON {
inherit threads timeout;
}
);
mtce-engine = mtce.lib.mkMtce pkgs {
cpuFlags = [
"-O3"
"-march=cascadelake"
];
vectorizedBitSize = 512;
};
mtce-bot = mtce.lib.mkBot pkgs;
in
mkContainer {
inherit name;
svc.autoStart = true;
resources.fs = {
"${dataDir}/data.json" = {
hostPath = "${data}/misc/mtce-discord-bot/data.json";
isReadOnly = false;
};
"${dataDir}/audit" = {
hostPath = "${data}/misc/mtce-discord-bot/audit";
isReadOnly = false;
};
"${dataDir}/config.json" = {
hostPath = "${configJson}";
isReadOnly = true;
};
"${dataDir}/mtce" = {
hostPath = lib.getExe' mtce-engine "mtce";
isReadOnly = true;
};
"/run/secrets/mtce-discord-bot-env" = {
hostPath = "${secrets}/mtce-discord-bot-env";
isReadOnly = true;
};
};
users = [
{
name = "bot";
uid = 1000;
}
];
service =
_:
mkService {
description = "Discord bot (monumenta-charm-eval)";
execStart = lib.getExe' mtce-bot "mtce-bot";
workingDir = dataDir;
networkOnline = true;
serviceConfig = {
User = "bot";
EnvironmentFile = "/run/secrets/mtce-discord-bot-env";
PrivateTmp = true;
CapabilityBoundingSet = "";
ReadWritePaths = [ dataDir ];
};
};
resources.mem = "2G";
resources.cpu = 64;
net.access = [ { to = "@internet"; } ];
includedInTests = false;
}
+215
View File
@@ -0,0 +1,215 @@
{
name,
hostInfo,
dsl,
lib,
containers,
...
}:
let
inherit (dsl) mkContainer;
inherit (hostInfo.paths) data;
mkNginxConfig =
{
email,
vhosts,
resolver,
}:
let
mkVhostEntries =
{
domain,
upstream ? null,
websockets ? true,
redirect ? true,
listenHttp ? false,
extraLocations ? { },
}:
let
httpsListen = {
addr = "0.0.0.0";
port = 443;
ssl = true;
};
httpListen = {
addr = "0.0.0.0";
port = 80;
ssl = false;
};
defaultLoc = lib.optionalAttrs (upstream != null) {
"/" = {
proxyWebsockets = websockets;
# avoid startup error when containers aren't registered yet
# proxyPass is left unset for that reason, so recommendedProxySettings
# won't auto-include its headers here - set them manually to match
extraConfig = ''
set $backend "${upstream.container.name}:${toString upstream.port}";
proxy_pass http://$backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $hostname;
'';
};
};
acmePrimaryOnMain = !redirect && listenHttp;
mainVhost = lib.nameValuePair domain (
{
listen = [ httpsListen ] ++ lib.optional listenHttp httpListen;
locations = defaultLoc // extraLocations;
}
// (
if acmePrimaryOnMain then
{
addSSL = true;
enableACME = true;
}
else
{
onlySSL = true;
useACMEHost = domain;
}
)
);
redirectVhost = lib.nameValuePair "${domain}-redirect" {
serverName = domain;
enableACME = true;
listen = [ httpListen ];
locations = {
"/" = {
return = "301 https://$host$request_uri";
};
};
};
in
[ mainVhost ] ++ lib.optional redirect redirectVhost;
in
{
access = map (v: {
to = v.upstream.container.name;
tcp = [ v.upstream.port ];
}) (lib.filter (v: v.upstream or null != null) vhosts);
containerConfig =
{
lib,
config,
...
}:
{
services.nginx = {
enable = true;
recommendedTlsSettings = true;
recommendedOptimisation = true;
recommendedGzipSettings = true;
recommendedProxySettings = true;
clientMaxBodySize = "100M";
appendHttpConfig = ''
access_log syslog:server=unix:/dev/log,tag=nginx combined;
'';
resolver = {
addresses = [ resolver ];
ipv6 = false;
valid = "30s";
};
virtualHosts = lib.listToAttrs (lib.concatLists (map mkVhostEntries vhosts)) // {
"_" = {
listen = [
{
addr = "0.0.0.0";
port = 443;
extraParameters = [
"ssl"
"default_server"
];
}
{
addr = "0.0.0.0";
port = 80;
extraParameters = [ "default_server" ];
}
];
extraConfig = "ssl_reject_handshake on;";
locations."/".return = "404";
};
};
};
security.acme = {
acceptTerms = true;
defaults.email = email;
};
systemd.services =
lib.mapAttrs' (
cert: _:
lib.nameValuePair "acme-${cert}" {
after = [ "container-ready.target" ];
wants = [ "container-ready.target" ];
}
) config.security.acme.certs
// {
nginx.serviceConfig.FailureAction = "exit-force";
};
};
};
up = container: port: { inherit container port; };
nginxSetup =
hostAddr:
mkNginxConfig {
email = "floweydev@gmail.com";
resolver = hostAddr;
vhosts = [
{
domain = "devenv.floweytf.com";
upstream = up containers.devenv 8080;
}
{
domain = "maven.floweytf.com";
upstream = up containers.maven 8080;
}
{
domain = "git.floweytf.com";
upstream = up containers.git 3000;
}
{
domain = "utils.floweytf.com";
redirect = false;
listenHttp = true;
extraLocations = {
"/static/" = {
alias = "/usr/share/nginx/static/";
extraConfig = "autoindex on;";
};
};
}
{
# no upstream, vhost only exists for certs
domain = "mail.floweytf.com";
}
];
};
in
let
setup = nginxSetup hostAddr;
c = mkContainer {
inherit name;
net.access = setup.access ++ [ { to = "@internet"; } ];
svc.autoStart = true;
svc.depends = lib.attrVals [ "devenv" "maven" "git" ] containers;
net.forward.tcp = [
80
443
];
resources.fs."/var/lib/acme" = {
hostPath = "${data}/core/nginx/acme";
isReadOnly = false;
};
inherit (setup) containerConfig;
};
hostAddr = c.hostAddr;
in
c
+64
View File
@@ -0,0 +1,64 @@
{
name,
hostInfo,
dsl,
...
}:
let
inherit (dsl) mkContainer;
inherit (hostInfo.paths) data;
in
mkContainer {
inherit name;
net.access = [ { to = "@internet"; } ];
svc.autoStart = false;
net.expose.tcp = [ 53 ];
# TODO: figure out how to have this be the external dns resolver only
resources.fs."/var/lib/pihole" = {
hostPath = "${data}/misc/pihole";
isReadOnly = false;
};
containerConfig = _: {
services.pihole-ftl = {
enable = true;
stateDirectory = "/var/lib/pihole";
openFirewallDNS = true;
settings.dns.upstreams = [
"1.1.1.1"
"8.8.8.8"
];
lists = [
{
url = "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts";
type = "block";
description = "StevenBlack unified hosts";
enabled = true;
}
{
url = "https://big.oisd.nl/domainswild";
type = "block";
description = "OISD Big";
enabled = true;
}
{
url = "https://cdn.jsdelivr.net/gh/hagezi/dns-blocklists@latest/domains/pro.txt";
type = "block";
description = "HaGeZi Multi Pro";
enabled = true;
}
{
url = "https://cdn.jsdelivr.net/gh/hagezi/dns-blocklists@latest/domains/tif.txt";
type = "block";
description = "HaGeZi Threat Intelligence Feeds";
enabled = true;
}
];
};
services.pihole-web = {
enable = true;
ports = [ "443s" ];
};
systemd.services.pihole-ftl.serviceConfig.FailureAction = "exit-force";
};
resources.mem = "512M";
}
+186
View File
@@ -0,0 +1,186 @@
{
pkgs,
hostInfo,
dsl,
self,
lib,
customPkgs,
...
}:
let
inherit (dsl) mkContainer;
inherit (hostInfo.paths) server data;
sshTrait = {
resources.fs."/etc/ssh/authorized_keys" = {
hostPath = "${server}/keypair/id_ed25519.pub";
isReadOnly = true;
};
users = [
{
name = "runner";
uid = 1000;
}
];
containerConfig =
{
lib,
pkgs,
...
}:
lib.mkMerge [
{
services.openssh = {
enable = true;
authorizedKeysFiles = [ "/etc/ssh/authorized_keys" ];
settings = {
PasswordAuthentication = false;
UseDns = false;
PrintMotd = false;
};
};
systemd.services.sshd.serviceConfig.FailureAction = "exit-force";
}
{
environment.systemPackages = [
customPkgs.kitty-terminfo
pkgs.wezterm.terminfo
];
}
];
};
mkSsh =
{
name,
packages ? [ ],
nixDaemon ? true,
routingInfo ? {
username = lib.strings.removePrefix "ssh-" name;
},
net ? { },
resources ? { },
traits ? [ ],
}:
mkContainer {
inherit name;
inherit net resources;
svc.autoStart = true;
isolation.nixDaemon = nixDaemon;
traits = [ sshTrait ] ++ traits;
packages = with pkgs; [ tmux ] ++ packages;
miscProperties = self._sshpiperRegister routingInfo;
};
in
{
ssh-admin = mkSsh {
name = "ssh-admin";
net.access = [
{
to = "overseer";
tcp = [ 80 ];
}
];
resources.fs = {
"/home/runner/data" = {
hostPath = server;
isReadOnly = false;
};
"/run/dbus/system_bus_socket" = {
hostPath = "/run/dbus/system_bus_socket";
isReadOnly = false;
};
};
packages = with pkgs; [
git
systemd
];
};
ssh-botm = mkSsh {
name = "ssh-botm";
resources.fs."/home/runner/botm" = {
hostPath = "${data}/mc/botm";
isReadOnly = false;
};
packages = with pkgs; [
python3
jdk17
tmux
];
};
ssh-generic = mkSsh {
name = "ssh-generic";
net.access = [ { to = "@internet"; } ];
resources = {
fs."/home/runner/data" = {
hostPath = "${data}/ssh/generic";
isReadOnly = false;
};
mem = "16G";
cpu = 65;
};
routingInfo = [
{
username = "autumn";
keysName = "generic";
}
];
};
ssh-proxy = mkSsh {
name = "ssh-proxy";
resources.fs."/home/runner/data" = {
hostPath = "${data}/ssh/nyaa";
isReadOnly = true;
};
packages = with pkgs; [ python3 ];
routingInfo = [
{
username = "nyaa";
keysName = "nyaa";
}
];
};
ssh-rogule = mkSsh {
name = "ssh-rogule";
resources.fs = {
"/home/runner/rogule-dev" = {
hostPath = "${data}/mc/rogule-dev";
isReadOnly = false;
};
"/home/runner/rogule-build" = {
hostPath = "${data}/mc/rogule-build";
isReadOnly = false;
};
};
packages = with pkgs; [
python3
jdk21
];
};
ssh-starwalker = mkSsh {
name = "ssh-starwalker";
resources.fs."/home/runner/starwalker" = {
hostPath = "${data}/mc/starwalker";
isReadOnly = false;
};
packages = with pkgs; [
jdk21
];
};
ssh-tl = mkSsh {
name = "ssh-tl";
resources.fs."/home/runner/tl" = {
hostPath = "${data}/mc/tl";
isReadOnly = false;
};
packages = with pkgs; [
jdk21
];
};
}
+122
View File
@@ -0,0 +1,122 @@
{
name,
pkgs,
hostInfo,
dsl,
lib,
containers,
customPkgs,
...
}:
let
inherit (dsl) mkContainer mkService collectContainers;
inherit (hostInfo.paths) server secrets;
dataDir = hostInfo.paths.data;
mkSshpiperConfig =
{
pipes,
privateKey ? "/common_id",
}:
let
header = ''
version: "1.0"
pipes:
'';
mkPipe =
{
username,
host,
keysName ? username,
}:
''
- from:
- username: "${username}"
authorized_keys: /etc/sshpiperd/keys/user_${keysName}/authorized_keys
to:
host: ${host}:22
username: "runner"
private_key: "${privateKey}"
ignore_hostkey: true
'';
in
header + lib.concatMapStrings mkPipe pipes;
sshPipes = lib.concatMap (
{ container, data }: map (lib.mergeAttrs { host = container.name; }) data
) (collectContainers "sshpiperRoutes" containers);
sshpiperYaml = pkgs.writeText "sshpiper.yaml" (mkSshpiperConfig {
pipes = sshPipes;
});
in
{
${name} = mkContainer {
inherit name;
net.access = map (p: {
to = p.host;
tcp = [ 22 ];
}) sshPipes;
svc.autoStart = true;
net.forward.tcp = [
{
host = 22;
container = 2222;
}
];
resources.fs = {
"/etc/sshpiperd/keys" = {
hostPath = "${dataDir}/core/sshpiper";
isReadOnly = true;
};
"/common_id" = {
hostPath = "${server}/keypair/id_ed25519";
isReadOnly = true;
};
"/etc/ssh/ssh_host_ed25519_key" = {
hostPath = "${secrets}/ssh_host_ed25519_key";
isReadOnly = true;
};
"/etc/ssh/ssh_host_rsa_key" = {
hostPath = "${secrets}/ssh_host_rsa_key";
isReadOnly = true;
};
"/etc/ssh/ssh_host_ecdsa_key" = {
hostPath = "${secrets}/ssh_host_ecdsa_key";
isReadOnly = true;
};
};
service =
{ pkgs, ... }:
mkService {
description = "sshpiper SSH multiplexer";
execStart = "${lib.getExe' customPkgs.sshpiper "sshpiperd"} --log-level trace --server-key '/etc/ssh/ssh_host_*_key' ${lib.getExe' customPkgs.sshpiper "yaml"} --config /run/sshpiperd/config.yaml";
networkOnline = true;
serviceConfig = {
# sshpiperd rejects world-readable configs; copy store (444) -> runtime (600)
ExecStartPre = "${lib.getExe' pkgs.coreutils "install"} -m 600 ${sshpiperYaml} /run/sshpiperd/config.yaml";
RuntimeDirectory = "sshpiperd";
PrivateTmp = true;
RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX";
};
};
};
_sshpiperRegister =
let
doTranslate =
self:
{
username,
keysName ? username,
}:
{
inherit username keysName;
host = self.name;
};
in
args: self: {
sshpiperRoutes =
if lib.isAttrs args then [ (doTranslate self args) ] else lib.map (doTranslate self) args;
};
}