Compare commits
10 commits
e38ffbedf4
...
059bac76d1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
059bac76d1 | ||
|
|
7eaceeac27 | ||
|
|
20f8049d64 | ||
|
|
1a3d1f1fdd | ||
|
|
84bb31214c | ||
|
|
5df4cca133 | ||
|
|
884af65ed8 | ||
|
|
3385709208 | ||
|
|
2c1316a0a6 | ||
|
|
c923126eff |
14 changed files with 264 additions and 36 deletions
|
|
@ -3,5 +3,6 @@ let
|
||||||
web = import ./endpoints/web.nix;
|
web = import ./endpoints/web.nix;
|
||||||
matrix = import ./endpoints/matrix.nix;
|
matrix = import ./endpoints/matrix.nix;
|
||||||
vikunja = import ./endpoints/vikunja.nix;
|
vikunja = import ./endpoints/vikunja.nix;
|
||||||
|
git = import ./endpoints/git.nix;
|
||||||
in
|
in
|
||||||
local ++ web ++ matrix ++ vikunja ++ []
|
local ++ web ++ matrix ++ vikunja ++ git ++ []
|
||||||
18
config/endpoints/git.nix
Normal file
18
config/endpoints/git.nix
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
let
|
||||||
|
net = import ../network.nix;
|
||||||
|
in
|
||||||
|
[
|
||||||
|
{
|
||||||
|
type = "proxy";
|
||||||
|
domain = "git.${net.devices.remote_proxy.domain}";
|
||||||
|
endpoint = "/";
|
||||||
|
force_ssl = true;
|
||||||
|
port = 443;
|
||||||
|
content = {
|
||||||
|
type = "service";
|
||||||
|
ip = net.devices.tuserver.ip;
|
||||||
|
port = 3000;
|
||||||
|
proxyWebsockets = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -29,6 +29,19 @@ in
|
||||||
proxyWebsockets = true;
|
proxyWebsockets = true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
# {
|
||||||
|
# type = "proxy";
|
||||||
|
# domain = net.devices.remote_proxy.domain;
|
||||||
|
# endpoint = "/.well-known/matrix/";
|
||||||
|
# force_ssl = true;
|
||||||
|
# port = 443;
|
||||||
|
# content = {
|
||||||
|
# type = "service";
|
||||||
|
# ip = net.devices.pi.ip;
|
||||||
|
# port = services.continuwuity.port;
|
||||||
|
# proxyWebsockets = true;
|
||||||
|
# };
|
||||||
|
# }
|
||||||
{
|
{
|
||||||
type = "inline";
|
type = "inline";
|
||||||
domain = net.devices.remote_proxy.domain;
|
domain = net.devices.remote_proxy.domain;
|
||||||
|
|
@ -37,6 +50,11 @@ in
|
||||||
port = 443;
|
port = 443;
|
||||||
content = {
|
content = {
|
||||||
contentType = "application/json";
|
contentType = "application/json";
|
||||||
|
headers = {
|
||||||
|
Access-Control-Allow-Origin = "*";
|
||||||
|
Access-Control-Allow-Methods = "GET, POST, PUT, DELETE, OPTIONS";
|
||||||
|
Access-Control-Allow-Headers = "X-Requested-With, Content-Type, Authorization";
|
||||||
|
};
|
||||||
status = 200;
|
status = 200;
|
||||||
body = ''{"m.server":"${net.devices.remote_proxy.domain}:443"}'';
|
body = ''{"m.server":"${net.devices.remote_proxy.domain}:443"}'';
|
||||||
};
|
};
|
||||||
|
|
@ -49,8 +67,13 @@ in
|
||||||
port = 443;
|
port = 443;
|
||||||
content = {
|
content = {
|
||||||
contentType = "application/json";
|
contentType = "application/json";
|
||||||
|
headers = {
|
||||||
|
Access-Control-Allow-Origin = "*";
|
||||||
|
Access-Control-Allow-Methods = "GET, POST, PUT, DELETE, OPTIONS";
|
||||||
|
Access-Control-Allow-Headers = "X-Requested-With, Content-Type, Authorization";
|
||||||
|
};
|
||||||
status = 200;
|
status = 200;
|
||||||
body = ''{"m.homeserver":{"base_url":"https://${net.devices.remote_proxy.domain}"}}'';
|
body = ''{"m.homeserver": {"base_url": "https://${net.devices.remote_proxy.domain}"},"org.matrix.msc3575.proxy":{"url":"https://nudelerde.de/"},"org.matrix.msc4143.rtc_foci": [{"type": "livekit","livekit_service_url": "https://livekit.${net.devices.remote_proxy.domain}"}]}'';
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -16,4 +16,15 @@ in
|
||||||
proxyWebsockets = true;
|
proxyWebsockets = true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
type = "redirect";
|
||||||
|
domain = "wekan.${net.devices.remote_proxy.domain}";
|
||||||
|
endpoint = "/";
|
||||||
|
force_ssl = true;
|
||||||
|
port = 443;
|
||||||
|
content = {
|
||||||
|
target = "https://vikunja.${net.devices.remote_proxy.domain}/";
|
||||||
|
status = 301;
|
||||||
|
};
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -33,9 +33,18 @@ rec {
|
||||||
auto_ssh = {
|
auto_ssh = {
|
||||||
enable = true;
|
enable = true;
|
||||||
sshPort = 22;
|
sshPort = 22;
|
||||||
sshUser = "root";
|
sshUser = "autossh-incoming";
|
||||||
key = secrets.byName.autossh_remote_proxy_key.path;
|
key = secrets.byName.autossh_remote_proxy_key.path;
|
||||||
known_hosts = secrets.byName.autossh_remote_proxy_known_hosts.path;
|
known_hosts = secrets.byName.autossh_remote_proxy_known_hosts.path;
|
||||||
|
portOffset = 10000;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
"tuserver" = {
|
||||||
|
type = "local";
|
||||||
|
ip = "192.168.2.102";
|
||||||
|
reservation = {
|
||||||
|
hw_address = "00:23:24:f9:43:e6";
|
||||||
|
hostname = "tuserver";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,16 @@ in
|
||||||
rec {
|
rec {
|
||||||
continuwuity = {
|
continuwuity = {
|
||||||
port = 6167;
|
port = 6167;
|
||||||
|
server_name = "nudelerde.de";
|
||||||
|
trusted_servers = [ "matrix.org" ];
|
||||||
|
memory_max = "512M";
|
||||||
|
livekit_url = "https://livekit.nudelerde.de";
|
||||||
|
|
||||||
|
package = {
|
||||||
|
version = "0.5.6";
|
||||||
|
sourceHash = "sha256-p6dL1wL9n+1ivUItdlZuLxTneDBjCHEdNr0ukau2rHI=";
|
||||||
|
cargoHash = "sha256-lLbnFA2WS96er84G2e9bGrYhhqe2zL3Npn1SXB3De2w=";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
qbittorrent = {
|
qbittorrent = {
|
||||||
|
|
@ -25,10 +35,6 @@ rec {
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
matrix = {
|
|
||||||
trusted_servers = [ "matrix.org" ];
|
|
||||||
};
|
|
||||||
|
|
||||||
vikunja = {
|
vikunja = {
|
||||||
port = 8081;
|
port = 8081;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
timeZone = "Europe/Berlin";
|
timeZone = "Europe/Berlin";
|
||||||
defaultLocale = "en_US.UTF-8";
|
defaultLocale = "en_US.UTF-8";
|
||||||
|
storageConfig = import ./config/storage.nix;
|
||||||
storageModel = import ./intermediate/storage.nix;
|
storageModel = import ./intermediate/storage.nix;
|
||||||
in {
|
in {
|
||||||
imports = [
|
imports = [
|
||||||
|
|
@ -21,6 +22,13 @@ in {
|
||||||
|
|
||||||
fileSystems = storageModel.fileSystems;
|
fileSystems = storageModel.fileSystems;
|
||||||
|
|
||||||
|
swapDevices = [
|
||||||
|
{
|
||||||
|
device = "${storageConfig.ssd.path}/swapfile";
|
||||||
|
size = 8192;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
networking.hostName = "raspberry";
|
networking.hostName = "raspberry";
|
||||||
|
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
|
|
|
||||||
|
|
@ -205,6 +205,25 @@ let
|
||||||
proxyWebsockets = route.content.proxyWebsockets;
|
proxyWebsockets = route.content.proxyWebsockets;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
else if route.type == "redirect" then
|
||||||
|
let
|
||||||
|
redirectTarget =
|
||||||
|
if builtins.isString route.content then
|
||||||
|
route.content
|
||||||
|
else
|
||||||
|
route.content.target;
|
||||||
|
redirectStatus =
|
||||||
|
if builtins.isAttrs route.content && route.content ? status then
|
||||||
|
route.content.status
|
||||||
|
else
|
||||||
|
301;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
name = "= ${route.endpoint}";
|
||||||
|
value = {
|
||||||
|
return = "${toString redirectStatus} ${redirectTarget}";
|
||||||
|
};
|
||||||
|
}
|
||||||
else if route.type == "inline" then
|
else if route.type == "inline" then
|
||||||
let
|
let
|
||||||
inlineBody =
|
inlineBody =
|
||||||
|
|
@ -222,6 +241,14 @@ let
|
||||||
route.content.contentType
|
route.content.contentType
|
||||||
else
|
else
|
||||||
"text/plain; charset=utf-8";
|
"text/plain; charset=utf-8";
|
||||||
|
inlineHeaders =
|
||||||
|
if builtins.isAttrs route.content && route.content ? headers then
|
||||||
|
route.content.headers
|
||||||
|
else
|
||||||
|
{};
|
||||||
|
inlineHeaderLines = lib.concatStringsSep "\n" (
|
||||||
|
lib.mapAttrsToList (name: value: " add_header ${name} ${builtins.toJSON value} always;") inlineHeaders
|
||||||
|
);
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
name = "= ${route.endpoint}";
|
name = "= ${route.endpoint}";
|
||||||
|
|
@ -229,6 +256,7 @@ let
|
||||||
return = "${toString inlineStatus} ${builtins.toJSON inlineBody}";
|
return = "${toString inlineStatus} ${builtins.toJSON inlineBody}";
|
||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
default_type ${inlineContentType};
|
default_type ${inlineContentType};
|
||||||
|
${lib.optionalString (inlineHeaderLines != "") inlineHeaderLines}
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,22 +19,31 @@ let
|
||||||
getRemotePortMap = device:
|
getRemotePortMap = device:
|
||||||
autoSshValidation.getRemotePortMap device;
|
autoSshValidation.getRemotePortMap device;
|
||||||
|
|
||||||
resolveRemotePort = remotePortMap: endpoint:
|
getPortOffset = device:
|
||||||
|
autoSshValidation.getPortOffset device;
|
||||||
|
|
||||||
|
ensurePortRange = context: port:
|
||||||
|
if builtins.isInt port && port >= 1 && port <= 65535 then
|
||||||
|
port
|
||||||
|
else
|
||||||
|
throw "${context} must be in range 1..65535.";
|
||||||
|
|
||||||
|
resolveRemotePort = remotePortMap: portOffset: endpoint:
|
||||||
let
|
let
|
||||||
localPort =
|
localPort =
|
||||||
if endpoint ? port then
|
if endpoint ? port then
|
||||||
endpoint.port
|
ensurePortRange "Endpoint port '${toString endpoint.port}'" endpoint.port
|
||||||
else
|
else
|
||||||
throw "Endpoint is missing required field: port.";
|
throw "Endpoint is missing required field: port.";
|
||||||
overrides = lib.filter (entry: entry.localPort == localPort) remotePortMap;
|
overrides = lib.filter (entry: entry.localPort == localPort) remotePortMap;
|
||||||
in
|
in
|
||||||
if overrides != [] then
|
if overrides != [] then
|
||||||
(builtins.head overrides).remotePort
|
ensurePortRange "remotePortMap remotePort '${toString (builtins.head overrides).remotePort}'" (builtins.head overrides).remotePort
|
||||||
else
|
else
|
||||||
localPort;
|
ensurePortRange "Computed remote port (local ${toString localPort} + offset ${toString portOffset})" (localPort + portOffset);
|
||||||
|
|
||||||
mapEndpointToForward = remotePortMap: endpoint: {
|
mapEndpointToForward = remotePortMap: portOffset: endpoint: {
|
||||||
remote = resolveRemotePort remotePortMap endpoint;
|
remote = resolveRemotePort remotePortMap portOffset endpoint;
|
||||||
localAddress = "localhost";
|
localAddress = "localhost";
|
||||||
localPort = endpoint.port;
|
localPort = endpoint.port;
|
||||||
};
|
};
|
||||||
|
|
@ -50,6 +59,7 @@ let
|
||||||
else
|
else
|
||||||
throw "Auto SSH device is missing required field: domain.";
|
throw "Auto SSH device is missing required field: domain.";
|
||||||
remotePortMap = getRemotePortMap device;
|
remotePortMap = getRemotePortMap device;
|
||||||
|
portOffset = getPortOffset device;
|
||||||
matchedEndpoints =
|
matchedEndpoints =
|
||||||
lib.filter (endpoint:
|
lib.filter (endpoint:
|
||||||
if endpoint.force_ssl && endpoint.port == 80 then
|
if endpoint.force_ssl && endpoint.port == 80 then
|
||||||
|
|
@ -61,8 +71,8 @@ let
|
||||||
) endpoints;
|
) endpoints;
|
||||||
forwards = lib.concatMap (endpoint:
|
forwards = lib.concatMap (endpoint:
|
||||||
let
|
let
|
||||||
baseForward = mapEndpointToForward remotePortMap endpoint;
|
baseForward = mapEndpointToForward remotePortMap portOffset endpoint;
|
||||||
httpRedirectForward = mapEndpointToForward remotePortMap (endpointForHttpRedirect endpoint);
|
httpRedirectForward = mapEndpointToForward remotePortMap portOffset (endpointForHttpRedirect endpoint);
|
||||||
in
|
in
|
||||||
if endpoint.force_ssl then
|
if endpoint.force_ssl then
|
||||||
[ baseForward httpRedirectForward ]
|
[ baseForward httpRedirectForward ]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"data": "ENC[AES256_GCM,data:1ZzjJFqw28vXu6cw9efESy11132ntsaZwBmNqupqWju+y2iT2qq1wE+meGVH/e59YMC0ptNpsm+RbcfvC81vFvTFu46FUXPw2fXPb5UQmyDZxN7q+AM8SuOCqSy6,iv:FguIXmdsNUvhoqME97e5OIY+LMy5uuNd+d295U4lTuE=,tag:QlKokl5sOAtgwu06pnuRgA==,type:str]",
|
"data": "ENC[AES256_GCM,data:2O2/4FIDmmuwG0eRGmPUAm8Ji5lIly4dGT/ll+HZDvlC/sS6cbb3SEyplpVvqjMJNZACZtI9U87WJqKtoj9778Asxu0CFkwrsZK5bOf7XM27ZIN44/KWzbO6xezdcQ==,iv:XdgphFl56RNyjIIr9JQTUbh36j7UB8yjT0jgtCpMXDk=,tag:6OklZR86/gudejOFd9UneQ==,type:str]",
|
||||||
"sops": {
|
"sops": {
|
||||||
"age": [
|
"age": [
|
||||||
{
|
{
|
||||||
|
|
@ -11,8 +11,9 @@
|
||||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBiSVhGUHVJNVZVRXhxZzhx\ncEVSNWZBQW8vQXlYdnFUQTg4Q2VlY0lZckRnCkRKNnF5R3ZlUElWN0FOL1hrNHAr\nT2JPaDJ3bzVhZzZONHBkRHBueW16eVEKLS0tIEFxU2pWMVRqaC9BeGRsaWppT1lO\nMzI4UmkybmROL3dKK0xEaWpCM1VPQzQKWsLEb0Z7fbTVF8WQ8a1Lom8Bh7FQ2cPB\nIwIqWRM1L0oXNHyBoFkmHO434DZ8SXlyUYegBwrbVZGedRQFLQ4ibg==\n-----END AGE ENCRYPTED FILE-----\n"
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBiSVhGUHVJNVZVRXhxZzhx\ncEVSNWZBQW8vQXlYdnFUQTg4Q2VlY0lZckRnCkRKNnF5R3ZlUElWN0FOL1hrNHAr\nT2JPaDJ3bzVhZzZONHBkRHBueW16eVEKLS0tIEFxU2pWMVRqaC9BeGRsaWppT1lO\nMzI4UmkybmROL3dKK0xEaWpCM1VPQzQKWsLEb0Z7fbTVF8WQ8a1Lom8Bh7FQ2cPB\nIwIqWRM1L0oXNHyBoFkmHO434DZ8SXlyUYegBwrbVZGedRQFLQ4ibg==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"lastmodified": "2026-04-04T09:12:02Z",
|
"lastmodified": "2026-04-04T19:45:45Z",
|
||||||
"mac": "ENC[AES256_GCM,data:C6jOqg6lFy8A2UTIfZXjtNB/F+0UZJV8/F1sOm4aGgR5B4e2JT+3vL/A62m6pvrj1zSuoD1dH47y3OcpmO/FmsnYcCTdWW2MPEOA63NkHJMScaLJry8JDHpColG8WYmJfGb9QwLJ1O64vpUNXNscmUFs7PomBgJnMCmTyo3twKk=,iv:wiAJw8Vk70b/gnfcgPlT3exsnnp87mGoIXMtzqd5m88=,tag:oT9TPTNSDX9y0cJJCP/JkQ==,type:str]",
|
"mac": "ENC[AES256_GCM,data:EEAh4MsdWJa6GvI2CZ0dS19ERRNtYucvEVCENZnc4Yqyz1vyQbNYuBZKi15D/XL5A8WN0jweBFtJV8bNwbuwW1gJZW8x10uhGJ1WpZrT+/vlFQbEzmEvvjkiO46R4kkQ+yhzkmth+85S0m/nz6p3lgIzcTRBfHIMe9V+kVb2eZI=,iv:u3E9Ybjl9a91Z8ic1fjCUqLqedAVS1e6ZZ9PNE7Em7c=,tag:XOJJBCQBxShz4bC/2t/VXg==,type:str]",
|
||||||
|
"unencrypted_suffix": "_unencrypted",
|
||||||
"version": "3.12.1"
|
"version": "3.12.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,40 @@
|
||||||
{ config, pkgs, ... }:
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
net = import ../config/network.nix;
|
|
||||||
serv = import ../config/services.nix;
|
serv = import ../config/services.nix;
|
||||||
serviceValidation = import ../validation/service/continuwuity.nix;
|
serviceValidation = import ../validation/service/continuwuity.nix;
|
||||||
|
continuwuity = serv.continuwuity;
|
||||||
|
|
||||||
serverName =
|
serverName = serviceValidation.getServerName serv;
|
||||||
if net ? devices && builtins.isAttrs net.devices && net.devices ? remote_proxy && net.devices.remote_proxy ? domain && builtins.isString net.devices.remote_proxy.domain then
|
|
||||||
net.devices.remote_proxy.domain
|
|
||||||
else
|
|
||||||
throw "config/network.nix must define devices.remote_proxy.domain as string for continuwuity.";
|
|
||||||
|
|
||||||
trustedServers = serviceValidation.getTrustedServers serv;
|
trustedServers = serviceValidation.getTrustedServers serv;
|
||||||
|
memoryMax = serviceValidation.getMemoryMax serv;
|
||||||
|
|
||||||
|
package = if continuwuity ? package then
|
||||||
|
pkgs.matrix-continuwuity.overrideAttrs (old: rec {
|
||||||
|
version = continuwuity.package.version;
|
||||||
|
pname = old.pname or "matrix-continuwuity";
|
||||||
|
|
||||||
|
src = pkgs.fetchFromGitea {
|
||||||
|
domain = "forgejo.ellis.link";
|
||||||
|
owner = "continuwuation";
|
||||||
|
repo = "continuwuity";
|
||||||
|
tag = "v${version}";
|
||||||
|
hash = continuwuity.package.sourceHash;
|
||||||
|
};
|
||||||
|
|
||||||
|
cargoDeps = pkgs.rustPlatform.fetchCargoVendor {
|
||||||
|
inherit src;
|
||||||
|
hash = continuwuity.package.cargoHash;
|
||||||
|
};
|
||||||
|
})
|
||||||
|
else
|
||||||
|
pkgs.matrix-continuwuity;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
services.matrix-continuwuity = {
|
services.matrix-continuwuity = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
package = package;
|
||||||
settings = {
|
settings = {
|
||||||
global = {
|
global = {
|
||||||
server_name = serverName;
|
server_name = serverName;
|
||||||
|
|
@ -24,7 +43,17 @@ in
|
||||||
allow_federation = true;
|
allow_federation = true;
|
||||||
max_request_size = 20 * 1024 * 1024; # 20 MiB
|
max_request_size = 20 * 1024 * 1024; # 20 MiB
|
||||||
trusted_servers = trustedServers;
|
trusted_servers = trustedServers;
|
||||||
|
matrix_rtc = {
|
||||||
|
foci = [
|
||||||
|
{ type = "livekit"; livekit_service_url = continuwuity.livekit_url; }
|
||||||
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.matrix-continuwuity.serviceConfig =
|
||||||
|
lib.optionalAttrs (memoryMax != null) {
|
||||||
|
MemoryMax = memoryMax;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,9 +53,23 @@ let
|
||||||
else
|
else
|
||||||
device.auto_ssh.remotePortMap;
|
device.auto_ssh.remotePortMap;
|
||||||
|
|
||||||
|
getPortOffset = device:
|
||||||
|
if !(device ? auto_ssh) then
|
||||||
|
0
|
||||||
|
else if !builtins.isAttrs device.auto_ssh then
|
||||||
|
throw "Device auto_ssh must be an attrset when present."
|
||||||
|
else if !(device.auto_ssh ? portOffset) then
|
||||||
|
0
|
||||||
|
else if !builtins.isInt device.auto_ssh.portOffset then
|
||||||
|
throw "Device auto_ssh.portOffset must be an int."
|
||||||
|
else if device.auto_ssh.portOffset < 0 then
|
||||||
|
throw "Device auto_ssh.portOffset must be >= 0."
|
||||||
|
else
|
||||||
|
device.auto_ssh.portOffset;
|
||||||
|
|
||||||
isSafeName = name:
|
isSafeName = name:
|
||||||
builtins.match "^[a-z_][a-z0-9_-]*$" name != null;
|
builtins.match "^[a-z_][a-z0-9_-]*$" name != null;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
inherit getDevices getAutoSshDevices getAutoSshDomains getAutoSshConfig getRemotePortMap isSafeName;
|
inherit getDevices getAutoSshDevices getAutoSshDomains getAutoSshConfig getRemotePortMap getPortOffset isSafeName;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@ let
|
||||||
allowedProxyContentKeys = [ "type" "ip" "port" "proxyWebsockets" ];
|
allowedProxyContentKeys = [ "type" "ip" "port" "proxyWebsockets" ];
|
||||||
allowedWebContentKeys = [ "type" "files" ];
|
allowedWebContentKeys = [ "type" "files" ];
|
||||||
allowedWebFileKeys = [ "path" "filePath" "contentType" "status" ];
|
allowedWebFileKeys = [ "path" "filePath" "contentType" "status" ];
|
||||||
allowedInlineContentKeys = [ "body" "contentType" "status" ];
|
allowedInlineContentKeys = [ "body" "contentType" "headers" "status" ];
|
||||||
|
allowedRedirectContentKeys = [ "target" "status" ];
|
||||||
|
|
||||||
ensureNoUnknownKeys = context: obj: allowedKeys:
|
ensureNoUnknownKeys = context: obj: allowedKeys:
|
||||||
let
|
let
|
||||||
|
|
@ -38,10 +39,10 @@ let
|
||||||
else
|
else
|
||||||
throw "Endpoint at index ${toString index} must define type as a string.";
|
throw "Endpoint at index ${toString index} must define type as a string.";
|
||||||
_type =
|
_type =
|
||||||
if lib.elem typeValue [ "proxy" "web" "inline" ] then
|
if lib.elem typeValue [ "proxy" "web" "inline" "redirect" ] then
|
||||||
null
|
null
|
||||||
else
|
else
|
||||||
throw "Endpoint at index ${toString index} type must be 'proxy', 'web', or 'inline'.";
|
throw "Endpoint at index ${toString index} type must be 'proxy', 'web', 'inline', or 'redirect'.";
|
||||||
_domain =
|
_domain =
|
||||||
if endpoint ? domain && builtins.isString endpoint.domain && endpoint.domain != "" then
|
if endpoint ? domain && builtins.isString endpoint.domain && endpoint.domain != "" then
|
||||||
null
|
null
|
||||||
|
|
@ -131,6 +132,26 @@ let
|
||||||
) filesValue;
|
) filesValue;
|
||||||
in
|
in
|
||||||
null
|
null
|
||||||
|
else if typeValue == "redirect" then
|
||||||
|
if builtins.isString contentValue then
|
||||||
|
null
|
||||||
|
else if builtins.isAttrs contentValue then
|
||||||
|
let
|
||||||
|
____ = ensureNoUnknownKeys "Redirect content at endpoint index ${toString index}" contentValue allowedRedirectContentKeys;
|
||||||
|
_____ =
|
||||||
|
if contentValue ? target && builtins.isString contentValue.target && contentValue.target != "" then
|
||||||
|
null
|
||||||
|
else
|
||||||
|
throw "Redirect endpoint at index ${toString index} must define content.target as a non-empty string when content is an attrset.";
|
||||||
|
______ =
|
||||||
|
if !(contentValue ? status) || builtins.isInt contentValue.status then
|
||||||
|
null
|
||||||
|
else
|
||||||
|
throw "Redirect endpoint at index ${toString index} content.status must be an int when provided.";
|
||||||
|
in
|
||||||
|
null
|
||||||
|
else
|
||||||
|
throw "Redirect endpoint at index ${toString index} must define content as a string or an attrset."
|
||||||
else
|
else
|
||||||
if builtins.isString contentValue then
|
if builtins.isString contentValue then
|
||||||
null
|
null
|
||||||
|
|
@ -147,6 +168,20 @@ let
|
||||||
null
|
null
|
||||||
else
|
else
|
||||||
throw "Inline endpoint at index ${toString index} content.contentType must be a non-empty string when provided.";
|
throw "Inline endpoint at index ${toString index} content.contentType must be a non-empty string when provided.";
|
||||||
|
________ =
|
||||||
|
if contentValue ? headers then
|
||||||
|
if builtins.isAttrs contentValue.headers then
|
||||||
|
let
|
||||||
|
headerValues = builtins.attrValues contentValue.headers;
|
||||||
|
in
|
||||||
|
if lib.all (value: builtins.isString value && value != "") headerValues then
|
||||||
|
null
|
||||||
|
else
|
||||||
|
throw "Inline endpoint at index ${toString index} content.headers values must be non-empty strings."
|
||||||
|
else
|
||||||
|
throw "Inline endpoint at index ${toString index} content.headers must be an attrset when provided."
|
||||||
|
else
|
||||||
|
null;
|
||||||
_______ =
|
_______ =
|
||||||
if !(contentValue ? status) || builtins.isInt contentValue.status then
|
if !(contentValue ? status) || builtins.isInt contentValue.status then
|
||||||
null
|
null
|
||||||
|
|
|
||||||
|
|
@ -2,19 +2,54 @@ let
|
||||||
common = import ./common.nix;
|
common = import ./common.nix;
|
||||||
in
|
in
|
||||||
rec {
|
rec {
|
||||||
|
getServerName = serviceData:
|
||||||
|
let
|
||||||
|
continuwuity =
|
||||||
|
if serviceData ? continuwuity then
|
||||||
|
common.ensureAttrset "config/services.nix continuwuity" serviceData.continuwuity
|
||||||
|
else
|
||||||
|
throw "config/services.nix must define continuwuity attrset.";
|
||||||
|
|
||||||
|
serverName =
|
||||||
|
if continuwuity ? server_name then
|
||||||
|
common.ensureString "config/services.nix continuwuity.server_name" continuwuity.server_name
|
||||||
|
else
|
||||||
|
throw "config/services.nix continuwuity.server_name must exist.";
|
||||||
|
in
|
||||||
|
if serverName != "" then
|
||||||
|
serverName
|
||||||
|
else
|
||||||
|
throw "config/services.nix continuwuity.server_name must be a non-empty string.";
|
||||||
|
|
||||||
getTrustedServers = serviceData:
|
getTrustedServers = serviceData:
|
||||||
let
|
let
|
||||||
matrix =
|
continuwuity =
|
||||||
if serviceData ? matrix then
|
if serviceData ? continuwuity then
|
||||||
common.ensureAttrset "config/services.nix matrix" serviceData.matrix
|
common.ensureAttrset "config/services.nix continuwuity" serviceData.continuwuity
|
||||||
else
|
else
|
||||||
throw "config/services.nix must define matrix attrset.";
|
throw "config/services.nix must define continuwuity attrset.";
|
||||||
|
|
||||||
trustedServers =
|
trustedServers =
|
||||||
if matrix ? trusted_servers then
|
if continuwuity ? trusted_servers then
|
||||||
common.ensureList "config/services.nix matrix.trusted_servers" matrix.trusted_servers
|
common.ensureList "config/services.nix continuwuity.trusted_servers" continuwuity.trusted_servers
|
||||||
else
|
else
|
||||||
throw "config/services.nix matrix.trusted_servers must exist.";
|
throw "config/services.nix continuwuity.trusted_servers must exist.";
|
||||||
in
|
in
|
||||||
trustedServers;
|
trustedServers;
|
||||||
|
|
||||||
|
getMemoryMax = serviceData:
|
||||||
|
let
|
||||||
|
continuwuity =
|
||||||
|
if serviceData ? continuwuity then
|
||||||
|
common.ensureAttrset "config/services.nix continuwuity" serviceData.continuwuity
|
||||||
|
else
|
||||||
|
throw "config/services.nix must define continuwuity attrset.";
|
||||||
|
in
|
||||||
|
if !(continuwuity ? memory_max) then
|
||||||
|
null
|
||||||
|
else
|
||||||
|
let
|
||||||
|
value = common.ensureString "config/services.nix continuwuity.memory_max" continuwuity.memory_max;
|
||||||
|
in
|
||||||
|
if value != "" then value else throw "config/services.nix continuwuity.memory_max must be a non-empty string when provided.";
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue