Compare commits

..

8 Commits

10 changed files with 380 additions and 54 deletions

View File

@@ -39,13 +39,8 @@
}; };
services = { services = {
nginx = {
enable = true;
domain = "lab.tux.rs";
};
pangolin = { pangolin = {
enable = false; enable = true;
domain = "pangolin.lab.tux.rs"; domain = "pangolin.lab.tux.rs";
baseDomain = "lab.tux.rs"; baseDomain = "lab.tux.rs";
environmentFile = innerArgs.config.sops.secrets."pangolin".path; environmentFile = innerArgs.config.sops.secrets."pangolin".path;
@@ -53,19 +48,11 @@
uptime-kuma = { uptime-kuma = {
enable = true; enable = true;
port = 1111;
domain = "status.lab.tux.rs"; domain = "status.lab.tux.rs";
}; };
vaultwarden = {
enable = true;
port = 9999;
domain = "bw.lab.tux.rs";
};
mediaflow-proxy = { mediaflow-proxy = {
enable = true; enable = true;
port = 8888;
environmentFile = innerArgs.config.sops.secrets."mediaflow-proxy".path; environmentFile = innerArgs.config.sops.secrets."mediaflow-proxy".path;
}; };
}; };

View File

@@ -43,7 +43,7 @@
openssh.enable = true; openssh.enable = true;
netbird-client.enable = true; netbird-client.enable = true;
newt = { newt = {
enable = false; enable = true;
environmentFile = innerArgs.config.sops.secrets.newt.path; environmentFile = innerArgs.config.sops.secrets.newt.path;
}; };
}; };
@@ -53,6 +53,12 @@
enable = true; enable = true;
environmentFile = innerArgs.config.sops.secrets.discord-token.path; environmentFile = innerArgs.config.sops.secrets.discord-token.path;
}; };
vaultwarden = {
enable = true;
domain = "bw.lab.tux.rs";
configurePangolin = true;
};
}; };
virtualisation = { virtualisation = {

View File

@@ -4,7 +4,7 @@ gemini-api-key: ENC[AES256_GCM,data:gLZSoYTdKY+rwIpYiXvN9n9PGkUD6q8Oe7dHnYkjEjwD
openrouter-api-key: ENC[AES256_GCM,data:6xONCl9lqOoO7b4CEyCz9607tICDUAkpglRjGS5nYq2ppg2UKqYTrWD1BGCA5Xfs/CWskniVhoNG3vscjKiYCCh9gbM6aqdmTQ==,iv:7Iwc9t00HOOBjA7URXcUO41badqYyJCkFHM/uPkLFxY=,tag:Cl39kitr2e0//HVwAdsdUQ==,type:str] openrouter-api-key: ENC[AES256_GCM,data:6xONCl9lqOoO7b4CEyCz9607tICDUAkpglRjGS5nYq2ppg2UKqYTrWD1BGCA5Xfs/CWskniVhoNG3vscjKiYCCh9gbM6aqdmTQ==,iv:7Iwc9t00HOOBjA7URXcUO41badqYyJCkFHM/uPkLFxY=,tag:Cl39kitr2e0//HVwAdsdUQ==,type:str]
opencode-go-api-key: ENC[AES256_GCM,data:dmeRKn7TWHnqvpyPQpcEG6yHTb2bRby/rh10ytL0jHj5R+lRmNVdmqUF92GTznY9vEaB6ZYCJecWhpm8g4upNfOWBg==,iv:9UMJpAlD8gpcNiN+liu3nawoAZQKapEg7sCp561N9E8=,tag:OZlASpOa5BQaQwFWjoLCRw==,type:str] opencode-go-api-key: ENC[AES256_GCM,data:dmeRKn7TWHnqvpyPQpcEG6yHTb2bRby/rh10ytL0jHj5R+lRmNVdmqUF92GTznY9vEaB6ZYCJecWhpm8g4upNfOWBg==,iv:9UMJpAlD8gpcNiN+liu3nawoAZQKapEg7sCp561N9E8=,tag:OZlASpOa5BQaQwFWjoLCRw==,type:str]
netbird-key: ENC[AES256_GCM,data:q6eKisca04qn/CvALrvXF79MsToDhvLRLv2JTiUBAZglCC9m,iv:jj0/ZD7IDgopprTVUgSfJmdAJmUP3iqewU3dqssGYbk=,tag:6IPRdCm2FGdlTEIX7jt3qA==,type:str] netbird-key: ENC[AES256_GCM,data:q6eKisca04qn/CvALrvXF79MsToDhvLRLv2JTiUBAZglCC9m,iv:jj0/ZD7IDgopprTVUgSfJmdAJmUP3iqewU3dqssGYbk=,tag:6IPRdCm2FGdlTEIX7jt3qA==,type:str]
newt: ENC[AES256_GCM,data:Agv3ljcUhld+e+YpRNsOXuQPq5uyZWQ+S6epUH044CIlvrC3DMFskOFVPnDco9P5y0pBnL5m9rltlRiQtMP4RNMzYFz5yR/weskmvZkt3dtZ2sdyaK36p6PTsQDOLl3l8rCDoFxaGRHBz3gD5Gyz9lS5GmLcZql6xg/VIAMdwv36/g0=,iv:NrsrTVJ+ycjEU1/nPigCXGUoIZqheEf+WqYhVqVsuJ0=,tag:0UqLf5ZdVCoQrhKDq4EgPw==,type:str] newt: ENC[AES256_GCM,data:r5Rd81ZrzrIYbXtFkypbJltoGCOYrW6P4IpwGCtaH7lqCg7wD/WMboMx13HN4Mm0J4qbBpDakpfVv9qUTrisYP1xwpKkmY1kqPlFWRZN8hzuuNS2Tv3qUjavTp5XGotVPLHj1Cg/cwGcf8EAssYTwTy7H0PgdQKwqigQD1f1s/j4iXQ=,iv:1xP38/Ayt1xvn2fJGa5zgOrVNOw2J5taG1Orx6uNgZI=,tag:H9JXu9gzyyvTPWgXaoTsiw==,type:str]
sops: sops:
age: age:
- enc: | - enc: |
@@ -25,7 +25,7 @@ sops:
TLKEYsPQgOJ7s8P9gw2uPUY6HRz86CtiC6EbO27u0+8BbI85x1QScg== TLKEYsPQgOJ7s8P9gw2uPUY6HRz86CtiC6EbO27u0+8BbI85x1QScg==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
recipient: age1huqa3hc7wcxk4dpelrzny437nzrx4fnll3d8g9ahznzk268yju5qufapxy recipient: age1huqa3hc7wcxk4dpelrzny437nzrx4fnll3d8g9ahznzk268yju5qufapxy
lastmodified: "2026-08-08T11:57:44Z" lastmodified: "2026-08-09T11:21:03Z"
mac: ENC[AES256_GCM,data:0BYRw78P4Q0x/Wm5XmT1Jg3o/njic4qmaCFrZ+Egz34TL42zLCjax7TvVrJ/Rzb6UqGgeQb0Nca+JOHjOqs5J+J3SqnTpWnQa51LBu3naPwMphPhGrXgmvzHIFNlTXmljxjGo7tHt8hWHtHy6SgAOuep4tP7vgV/XeY91HGQyXc=,iv:mEbJ8OZ+uaTjhG4tsrHtPjQ6N6UDo7yAJAA7JMdO/r8=,tag:JE2aqSv4hJT4hpWZiyu9/g==,type:str] mac: ENC[AES256_GCM,data:XFy+AiJRfrLhbX3j+0oO24qiHD5Hj2bVFqTMbUOOzsJzR0r8FBrwSNAZ0FVlFPFPAMitAIpZySS+kvbmEdZUfZ0soo83F5qwH6i/H5PZxqyDHst/Ig6Mrypp2DZMNZXNlqyLLmuaNGDf2pqApxCM3eSsgvCsNxdapRKlLSFJou4=,iv:D7gB4J4dhCfRo+1DaUAvOe3dWpoGvzIFouCJBNpwKiY=,tag:eLBZq/+Lce2S5Hu9OIC3gw==,type:str]
unencrypted_suffix: _unencrypted unencrypted_suffix: _unencrypted
version: 3.13.3 version: 3.13.3

View File

@@ -18,7 +18,7 @@
config = mkIf cfg.enable { config = mkIf cfg.enable {
services.netbird.clients = { services.netbird.clients = {
${hostName} = { ${hostName} = {
port = 51820; port = 61820;
login = { login = {
enable = true; enable = true;
setupKeyFile = config.sops.secrets.netbird-key.path; setupKeyFile = config.sops.secrets.netbird-key.path;

View File

@@ -15,6 +15,12 @@
options.tnix.services.aiostreams = { options.tnix.services.aiostreams = {
enable = mkEnableOption "AIOStreams"; enable = mkEnableOption "AIOStreams";
host = mkOption {
type = types.str;
default = "127.0.0.1";
description = "Host on which AIOStreams listens";
};
port = mkOption { port = mkOption {
type = types.port; type = types.port;
default = 3000; default = 3000;
@@ -24,7 +30,19 @@
domain = mkOption { domain = mkOption {
type = types.str; type = types.str;
default = ""; default = "";
description = "Domain on which nginx serves AIOStreams (disabled when empty)"; description = "Domain on which AIOStreams is available";
};
configureNginx = mkOption {
type = types.bool;
default = false;
description = "Whether to configure Nginx as a reverse proxy for AIOStreams";
};
configurePangolin = mkOption {
type = types.bool;
default = false;
description = "Whether to configure Pangolin as a reverse proxy for AIOStreams";
}; };
image = mkOption { image = mkOption {
@@ -47,10 +65,17 @@
}; };
config = mkIf cfg.enable { config = mkIf cfg.enable {
assertions = [
{
assertion = cfg.domain != "";
message = "tnix.services.aiostreams.domain must be set when tnix.services.aiostreams.enable is true.";
}
];
virtualisation.oci-containers.containers.aiostreams = { virtualisation.oci-containers.containers.aiostreams = {
image = cfg.image; image = cfg.image;
ports = [ ports = [
"127.0.0.1:${port}:3000" "${cfg.host}:${port}:3000"
]; ];
environment = { environment = {
ADDON_ID = cfg.domain; ADDON_ID = cfg.domain;
@@ -62,11 +87,39 @@
]; ];
}; };
services.nginx.virtualHosts.${cfg.domain} = mkIf (cfg.domain != "") { services = {
nginx.virtualHosts.${cfg.domain} = mkIf cfg.configureNginx {
forceSSL = acmeHost != ""; forceSSL = acmeHost != "";
useACMEHost = mkIf (acmeHost != "") acmeHost; useACMEHost = mkIf (acmeHost != "") acmeHost;
locations."/" = { locations."/" = {
proxyPass = "http://127.0.0.1:${port}"; proxyPass = "http://${cfg.host}:${port}";
proxyWebsockets = true;
};
};
newt.blueprint.proxy-resources = mkIf cfg.configurePangolin {
aiostreams = {
auth = {
sso-enabled = false;
};
full-domain = cfg.domain;
name = "aiostreams";
protocol = "http";
targets = [
{
hostname = "localhost";
method = "http";
port = cfg.port;
healthcheck = {
hostname = "localhost";
port = cfg.port;
scheme = "http";
method = "GET";
path = "/";
};
}
];
};
}; };
}; };
}; };

View File

@@ -0,0 +1,123 @@
{
flake.modules.nixos.services =
{
config,
lib,
...
}:
with lib;
let
cfg = config.tnix.services.gitea;
port = toString cfg.port;
acmeHost = config.tnix.services.nginx.domain;
in
{
options.tnix.services.gitea = {
enable = mkEnableOption "Gitea";
host = mkOption {
type = types.str;
default = "127.0.0.1";
description = "Host on which Gitea listens";
};
port = mkOption {
type = types.port;
default = 1114;
description = "Port on which Gitea listens";
};
domain = mkOption {
type = types.str;
default = "";
description = "Domain on which Gitea is available";
};
configureNginx = mkOption {
type = types.bool;
default = false;
description = "Whether to configure Nginx as a reverse proxy for Gitea";
};
configurePangolin = mkOption {
type = types.bool;
default = false;
description = "Whether to configure Pangolin as a reverse proxy for Gitea";
};
};
config = mkIf cfg.enable {
assertions = [
{
assertion = cfg.domain != "";
message = "tnix.services.gitea.domain must be set when tnix.services.gitea.enable is true.";
}
];
services = {
gitea = {
enable = true;
settings = {
service.DISABLE_REGISTRATION = true;
server = {
HTTP_ADDR = cfg.host;
HTTP_PORT = cfg.port;
DOMAIN = cfg.domain;
ROOT_URL = "https://${cfg.domain}";
};
};
database = {
type = "postgres";
name = "gitea";
user = "gitea";
};
};
nginx.virtualHosts.${cfg.domain} = mkIf cfg.configureNginx {
forceSSL = acmeHost != "";
useACMEHost = mkIf (acmeHost != "") acmeHost;
locations."/" = {
proxyPass = "http://${cfg.host}:${port}";
proxyWebsockets = true;
};
};
newt.blueprint.proxy-resources = mkIf cfg.configurePangolin {
gitea = {
auth = {
sso-enabled = false;
};
full-domain = cfg.domain;
name = "gitea";
protocol = "http";
targets = [
{
hostname = "localhost";
method = "http";
port = cfg.port;
healthcheck = {
hostname = "localhost";
port = cfg.port;
scheme = "http";
method = "GET";
path = "/";
};
}
];
};
};
postgresql = {
enable = true;
ensureDatabases = [ "gitea" ];
ensureUsers = [
{
name = "gitea";
ensureDBOwnership = true;
}
];
};
};
};
};
}

View File

@@ -15,16 +15,34 @@
options.tnix.services.mediaflow-proxy = { options.tnix.services.mediaflow-proxy = {
enable = mkEnableOption "MediaFlow Proxy"; enable = mkEnableOption "MediaFlow Proxy";
host = mkOption {
type = types.str;
default = "0.0.0.0";
description = "Host on which MediaFlow Proxy listens";
};
port = mkOption { port = mkOption {
type = types.port; type = types.port;
default = 8888; default = 1113;
description = "Port on which MediaFlow Proxy listens"; description = "Port on which MediaFlow Proxy listens";
}; };
domain = mkOption { domain = mkOption {
type = types.str; type = types.str;
default = ""; default = "";
description = "Domain on which nginx serves MediaFlow Proxy (disabled when empty)"; description = "Domain on which MediaFlow Proxy is available";
};
configureNginx = mkOption {
type = types.bool;
default = false;
description = "Whether to configure Nginx as a reverse proxy for MediaFlow Proxy";
};
configurePangolin = mkOption {
type = types.bool;
default = false;
description = "Whether to configure Pangolin as a reverse proxy for MediaFlow Proxy";
}; };
image = mkOption { image = mkOption {
@@ -41,10 +59,21 @@
}; };
config = mkIf cfg.enable { config = mkIf cfg.enable {
assertions = [
{
assertion = !cfg.configureNginx || cfg.domain != "";
message = "tnix.services.mediaflow-proxy.domain must be set when tnix.services.mediaflow-proxy.configureNginx is enabled.";
}
{
assertion = !cfg.configurePangolin || cfg.domain != "";
message = "tnix.services.mediaflow-proxy.domain must be set when tnix.services.mediaflow-proxy.configurePangolin is enabled.";
}
];
virtualisation.oci-containers.containers.mediaflow-proxy = { virtualisation.oci-containers.containers.mediaflow-proxy = {
image = cfg.image; image = cfg.image;
ports = [ ports = [
"${port}:${port}" "${cfg.host}:${port}:${port}"
]; ];
environment = { environment = {
APP__SERVER__HOST = "0.0.0.0"; APP__SERVER__HOST = "0.0.0.0";
@@ -53,14 +82,41 @@
environmentFiles = optional (cfg.environmentFile != null) cfg.environmentFile; environmentFiles = optional (cfg.environmentFile != null) cfg.environmentFile;
}; };
services.nginx.virtualHosts.${cfg.domain} = mkIf (cfg.domain != "") { services = {
nginx.virtualHosts.${cfg.domain} = mkIf cfg.configureNginx {
forceSSL = acmeHost != ""; forceSSL = acmeHost != "";
useACMEHost = mkIf (acmeHost != "") acmeHost; useACMEHost = mkIf (acmeHost != "") acmeHost;
locations."/" = { locations."/" = {
proxyPass = "http://127.0.0.1:${port}"; proxyPass = "http://${cfg.host}:${port}";
proxyWebsockets = true; proxyWebsockets = true;
}; };
}; };
newt.blueprint.proxy-resources = mkIf cfg.configurePangolin {
mediaflow-proxy = {
auth = {
sso-enabled = false;
};
full-domain = cfg.domain;
name = "mediaflow-proxy";
protocol = "http";
targets = [
{
hostname = "localhost";
method = "http";
port = cfg.port;
healthcheck = {
hostname = "localhost";
port = cfg.port;
scheme = "http";
method = "GET";
path = "/";
};
}
];
};
};
};
}; };
}; };
} }

View File

@@ -4,6 +4,7 @@
config, config,
lib, lib,
userEmail, userEmail,
pkgs,
... ...
}: }:
with lib; with lib;
@@ -17,13 +18,13 @@
domain = mkOption { domain = mkOption {
type = types.str; type = types.str;
default = ""; default = "";
description = "Domain on which nginx serves MediaFlow Proxy (disabled when empty)"; description = "Domain on which Pangolin Dashboard is available";
}; };
baseDomain = mkOption { baseDomain = mkOption {
type = types.str; type = types.str;
default = ""; default = "";
description = "Domain on which nginx serves MediaFlow Proxy (disabled when empty)"; description = "Pangolin base fully qualified domain name";
}; };
environmentFile = mkOption { environmentFile = mkOption {
@@ -37,6 +38,11 @@
services = { services = {
pangolin = { pangolin = {
enable = true; enable = true;
package = (
pkgs.fosrl-pangolin.override {
databaseType = "pg";
}
);
openFirewall = true; openFirewall = true;
baseDomain = cfg.baseDomain; baseDomain = cfg.baseDomain;
dashboardDomain = cfg.domain; dashboardDomain = cfg.domain;
@@ -58,20 +64,14 @@
# see https://github.com/fosrl/newt/issues/37 # see https://github.com/fosrl/newt/issues/37
internal_hostname = "localhost"; internal_hostname = "localhost";
}; };
gerbil = { gerbil.base_endpoint = config.services.pangolin.dashboardDomain;
base_endpoint = "178.105.102.63";
port = 51820;
clients_start_port = 21820;
};
flags = { flags = {
disable_signup_without_invite = true; disable_signup_without_invite = true;
enable_integration_api = false; enable_integration_api = false;
allow_raw_resources = true; allow_raw_resources = true;
disable_enterprise_features = true; disable_enterprise_features = true;
}; };
postgres = { postgres.connection_string = "postgresql:///pangolin?host=/run/postgresql";
connection_string = "postgresql:///pangolin?host=/run/postgresql";
};
}; };
}; };

View File

@@ -15,6 +15,12 @@
options.tnix.services.uptime-kuma = { options.tnix.services.uptime-kuma = {
enable = mkEnableOption "Uptime Kuma"; enable = mkEnableOption "Uptime Kuma";
host = mkOption {
type = types.str;
default = "127.0.0.1";
description = "Host on which Uptime Kuma listens";
};
port = mkOption { port = mkOption {
type = types.port; type = types.port;
default = 1111; default = 1111;
@@ -24,25 +30,70 @@
domain = mkOption { domain = mkOption {
type = types.str; type = types.str;
default = ""; default = "";
description = "Domain on which nginx serves Uptime Kuma (disabled when empty)"; description = "Domain on which Uptime Kuma is available";
};
configureNginx = mkOption {
type = types.bool;
default = false;
description = "Whether to configure Nginx as a reverse proxy for Uptime Kuma";
};
configurePangolin = mkOption {
type = types.bool;
default = false;
description = "Whether to configure Pangolin as a reverse proxy for Uptime Kuma";
}; };
}; };
config = mkIf cfg.enable { config = mkIf cfg.enable {
assertions = [
{
assertion = cfg.domain != "";
message = "tnix.services.uptime-kuma.domain must be set when tnix.services.uptime-kuma.enable is true.";
}
];
services = { services = {
uptime-kuma = { uptime-kuma = {
enable = true; enable = true;
settings = { settings = {
HOST = "127.0.0.1"; HOST = cfg.host;
PORT = port; PORT = port;
}; };
}; };
nginx.virtualHosts.${cfg.domain} = mkIf (cfg.domain != "") { nginx.virtualHosts.${cfg.domain} = mkIf cfg.configureNginx {
forceSSL = acmeHost != ""; forceSSL = acmeHost != "";
useACMEHost = mkIf (acmeHost != "") acmeHost; useACMEHost = mkIf (acmeHost != "") acmeHost;
locations."/" = { locations."/" = {
proxyPass = "http://127.0.0.1:${port}"; proxyPass = "http://${cfg.host}:${port}";
proxyWebsockets = true;
};
};
newt.blueprint.proxy-resources = mkIf cfg.configurePangolin {
uptime-kuma = {
auth = {
sso-enabled = false;
};
full-domain = cfg.domain;
name = "uptime-kuma";
protocol = "http";
targets = [
{
hostname = "localhost";
method = "http";
port = cfg.port;
healthcheck = {
hostname = "localhost";
port = cfg.port;
scheme = "http";
method = "GET";
path = "/";
};
}
];
}; };
}; };
}; };

View File

@@ -15,26 +15,51 @@
options.tnix.services.vaultwarden = { options.tnix.services.vaultwarden = {
enable = mkEnableOption "Vaultwarden"; enable = mkEnableOption "Vaultwarden";
host = mkOption {
type = types.str;
default = "127.0.0.1";
description = "Host on which Vaultwarden listens";
};
port = mkOption { port = mkOption {
type = types.port; type = types.port;
default = 8000; default = 1112;
description = "Port on which Vaultwarden listens"; description = "Port on which Vaultwarden listens";
}; };
domain = mkOption { domain = mkOption {
type = types.str; type = types.str;
default = ""; default = "";
description = "Domain on which nginx serves Vaultwarden (disabled when empty)"; description = "Domain on which Vaultwarden is available";
};
configureNginx = mkOption {
type = types.bool;
default = false;
description = "Whether to configure Nginx as a reverse proxy for Vaultwarden";
};
configurePangolin = mkOption {
type = types.bool;
default = false;
description = "Whether to configure Pangolin as a reverse proxy for Vaultwarden";
}; };
}; };
config = mkIf cfg.enable { config = mkIf cfg.enable {
assertions = [
{
assertion = cfg.domain != "";
message = "tnix.services.vaultwarden.domain must be set when tnix.services.vaultwarden.enable is true.";
}
];
services = { services = {
vaultwarden = { vaultwarden = {
enable = true; enable = true;
dbBackend = "postgresql"; dbBackend = "postgresql";
config = { config = {
ROCKET_ADDRESS = "127.0.0.1"; ROCKET_ADDRESS = cfg.host;
ROCKET_PORT = cfg.port; ROCKET_PORT = cfg.port;
DOMAIN = "https://${cfg.domain}"; DOMAIN = "https://${cfg.domain}";
@@ -45,15 +70,40 @@
}; };
}; };
nginx.virtualHosts.${cfg.domain} = mkIf (cfg.domain != "") { nginx.virtualHosts.${cfg.domain} = mkIf cfg.configureNginx {
forceSSL = acmeHost != ""; forceSSL = acmeHost != "";
useACMEHost = mkIf (acmeHost != "") acmeHost; useACMEHost = mkIf (acmeHost != "") acmeHost;
locations."/" = { locations."/" = {
proxyPass = "http://127.0.0.1:${port}"; proxyPass = "http://${cfg.host}:${port}";
proxyWebsockets = true; proxyWebsockets = true;
}; };
}; };
newt.blueprint.proxy-resources = mkIf cfg.configurePangolin {
vaultwarden = {
auth = {
sso-enabled = false;
};
full-domain = cfg.domain;
name = "vaultwarden";
protocol = "http";
targets = [
{
hostname = "localhost";
method = "http";
port = cfg.port;
healthcheck = {
hostname = "localhost";
port = cfg.port;
scheme = "http";
method = "GET";
path = "/";
};
}
];
};
};
postgresql = { postgresql = {
enable = true; enable = true;
ensureDatabases = [ "vaultwarden" ]; ensureDatabases = [ "vaultwarden" ];