initial commit
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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 ];
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user