Compare commits

..

1 commit

Author SHA1 Message Date
sid
f64ee1322f overleaf: init
All checks were successful
Flake check / flake-check (pull_request) Successful in 18s
2026-05-17 17:55:00 +02:00
32 changed files with 612 additions and 596 deletions

View file

@ -25,26 +25,23 @@ rec {
subdomain = "f";
fqdn = subdomain + "." + domain;
};
librechat-oci = {
fqdn = "lc." + domain;
port = 3080;
};
mailserver = rec {
subdomain = "mail";
fqdn = subdomain + "." + domain;
};
miniflux = {
fqdn = "rss." + domain;
port = 8085;
};
netdata = {
fqdn = "mon." + domain;
fqdn = "netdata." + intranet;
port = 19999;
};
open-webui-oci = {
fqdn = "ai." + domain;
port = 8083;
};
overleaf-oci = rec {
subdomain = "of";
fqdn = subdomain + "." + domain;
port = 8081;
};
rss-bridge = rec {
subdomain = "rss-bridge";
fqdn = subdomain + "." + domain;
@ -54,8 +51,12 @@ rec {
port = 1200;
};
vaultwarden = {
fqdn = "pw." + domain;
fqdn = "pw." + intranet;
port = 8222;
};
webdav = {
fqdn = "dav." + intranet;
port = 8080;
};
};
}

8
flake.lock generated
View file

@ -5199,11 +5199,11 @@
"stylix": "stylix_6"
},
"locked": {
"lastModified": 1779222589,
"narHash": "sha256-pFlaPXus8e+mY9C7/xQhBwux6tPk5P30K2uaN2Qluh0=",
"lastModified": 1778016348,
"narHash": "sha256-C8PtC95r1KJync8qDEroIont1VT8tiwsjonYjwGLhbY=",
"ref": "release-25.11",
"rev": "1ab817090ff5989578caefd8786e9450b37e3da5",
"revCount": 96,
"rev": "8ad8b1f633f6c3875032a0ead0e87255dff4ab3c",
"revCount": 57,
"type": "git",
"url": "https://git.sid.ovh/sid/synix.git"
},

View file

@ -13,17 +13,12 @@ syncthing:
gui-pw: ENC[AES256_GCM,data:mN4rxYr5DZgvbpIkwSFIuPvviJE=,iv:Kyl3mZFOejVwEwBCKteJQpgbCosREp9C4T4JYhWz6KQ=,tag:6myk9lr/44CH/hyUPgRH0Q==,type:str]
forgejo-runner:
token: ENC[AES256_GCM,data:DZgi6ocpV0MplgQ6Et85vHxmkMfC4qYbLLdyRuj/4z8tJauz1w6DUQ==,iv:+SZYsv6sDn2Nc1WxhTn0dJGN9nXYZw16/HVtXJGXpHc=,tag:8Oa5mC7cUy85+lXHbRcCcg==,type:str]
webdav:
user: ENC[AES256_GCM,data:vCLx,iv:Nra/FprNfd02HpvqOb5uYK+IGRFHhNwnFXWrX71c0C0=,tag:TjbKKOKBTq31o/5MxmqIsA==,type:str]
pass: ENC[AES256_GCM,data:jfIoob6R6OhqKa2EujRzTQbvIlA=,iv:HvB088H2Z2uLCveT4YfNEdkK5VU0lBFD5FrZhx79fg0=,tag:1RnrfeUEURx0C575GTxi9A==,type:str]
vaultwarden:
admin-token: ENC[AES256_GCM,data:HhD0xNZ/Ep7pCOX1j6p/M/ZZ3gs=,iv:7QT71KlYz+HQYBhiRavpiXS9sNS2PoJiM/WkxM3Hk/g=,tag:SYTRWpyA2+WMSMiRM8mvew==,type:str]
smtp-password: ENC[AES256_GCM,data:eQo7op5+74EID6689hL0/J1pq2s=,iv:JqrEqxabWGydRuJJ/27e1q+4YnQhTQ1bKRSsOvjQ+bE=,tag:weqnrhqK+LGEfAacBcuPUA==,type:str]
hetzner-api-key: ENC[AES256_GCM,data:casjNOXzuQDWgnSFftbBMygA8kGpGiZDqup08faWO9kfjvgOyWOXeqPd2VA1ND8yfM2LvoLYvPs6gUWtni2ldQ==,iv:p2W24uhJgBvpi3g4+cHw0/XbbTM5oYCPHreMBUR4CNs=,tag:lpwjZGoJe/91+CHX/hAkKA==,type:str]
librechat:
jwt-secret: ENC[AES256_GCM,data:/OJr23Sw975byjyHN6yqWxuk5FeRfLdQYYOPYJeDHTjzq9X78c3VHqdvnN2a9ZUEtzRi1sx6YLIjNkxBkGbvuQ==,iv:2D0iBj2U3iy3JPtKZBWP5nCfmXMA2/pBhBKUD2f5DoM=,tag:0ZYNxBhUdCBOne0otcG2iQ==,type:str]
jwt-refresh-secret: ENC[AES256_GCM,data:qIaunHUMTUFyp88whrxe65eM3Mfi3EX0ieWOUCmYYojSKQQRudh8d4Cb1zMqPbXJLG3zqTVCaZl9xwQn5K4Z/g==,iv:k5+oSCd0TzdOmIUe8BQBesofjvjuRiPXdLT6H9yQf18=,tag:4wcJjX7MvJNx19PCxgqyhw==,type:str]
creds-key: ENC[AES256_GCM,data:EljwEqFByJaOjd8lRFGwo/FyXHUtl5an0xS1EjRe+kmpo5z4P33EUKbMeeIl69rEcziMHZQLiadzSEcS2cb2uA==,iv:sidBN6VTBeFhMUtN67HZuyofiXCeGFG4tuMRckLZv84=,tag:n7vI8LuPgER3J6r6Q6Jkjg==,type:str]
creds-iv: ENC[AES256_GCM,data:oc0sPm5RM/7AbH3vdDLJ2m0q6C7eAAME0GPbiojHZUspP8Cto5QX5WKnUjUVLLcvgK+t6pnu7BEmAuD3PLr11A==,iv:Z6XJmlqv0ULFiwqHyRO5v7lb/iyv4g9aSTV4xw9VTXU=,tag:7kptbQwc6lBZ70aXw7wOVA==,type:str]
meili-master-key: ENC[AES256_GCM,data:eugFl40a6Ks3ba8hcn83WS76AwA0TXkhu3K4gSrbNHtXRliLQCWhGTEvoaQSeb7whmpszh4zh8cKSxByBdhJiQ==,iv:rrWlcVyBlrE5dnBBFWjheIo6SgQTbkzqskGQvQczR+U=,tag:fjKOSVoPxomA3qUw+baV4w==,type:str]
requesty-key: ENC[AES256_GCM,data:vxr+m3c9qu6ChFvuAbBCFrneDP9xDIPJBRmB9diw5uSQD9XDl0IK954OzmMMXaSl2AeHgY8WiugvgvQjUwywjKG6TxxMEYMFsQkMpSnV7xHYv2MAE/TIC74CsHRL823MesUQ7agoIasFtjr+CnZ5RRUNHZOG,iv:pa64BT1yay5vYwn/XBdK7meYzOBk4M+MmgLzaHR5Hfw=,tag:L+lUPTE0ZI303jtVefIrvQ==,type:str]
sops:
age:
- recipient: age19yeqvv28fgrtk6jsh3xyaf0lch86kna6rcz4dwe962yyyyevu30sx474xy
@ -44,7 +39,7 @@ sops:
NE5yK3ZaOG5PdXNSUnlIUmFSSmRFancKk57hCmo79HvI3hzzgQvgOK7oK5/dcQR8
f3R4OGF5+212VXEHR/hAEbKzV7CY4y6HhFyrGZ9bUKm1RrxtnVqUyA==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-05-25T18:14:59Z"
mac: ENC[AES256_GCM,data:eh/jcKrqyCTh+2n4phHQ2LKF71DaCDwrrfXms6HaD0ER4xVOkYERTe7IN4cX//qjY/91wSzAzwLg3yphWK4k920tiYTBog9LcWUz6l6X5lpmKHQp+vdoQH41WrA1ZgOcXzSfmZoblcD1qoJNCaHGt5N8hjXRcUc3lEqcPrdoC7A=,iv:8kBd9Daai3wJgzxONX4eIkeZLMzJO2DX439sBv/pER4=,tag:l8Q3gzMHoSTCdOqwzaKgCA==,type:str]
lastmodified: "2026-05-02T17:10:11Z"
mac: ENC[AES256_GCM,data:uf5TqZaevyUUjW6pM6K8c4CZFFdwTXFGIaHmYr5Q4XFR1uW3kBsVLeQKxq26duLuQ4UiZkUpW27a/PW797Z+iIpBdqbnoQ35q7RnOW+GpnAv8TaRW1PpqQ+JR3/R0LMXsi3cMt7ioG2ad1bIHztiNz+SmePiv3Yt9WxQ7PIqBdY=,iv:dAzuyKSo0OW+j02AH0chCdLBm7Wv6PZgqZrEWhEVnxQ=,tag:k6EKWHHY4fwTd03d4TVcNg==,type:str]
unencrypted_suffix: _unencrypted
version: 3.12.1

View file

@ -10,20 +10,20 @@
inputs.clients.nixosModules.syncthing
outputs.nixosModules.tailscale
# outputs.nixosModules.promtail
./forgejo.nix
./jirafeau.nix
./librechat-oci.nix
./miniflux.nix
./netdata.nix
./nginx.nix
./overleaf-oci.nix
./open-webui-oci.nix
./print-server.nix
./rsshub-oci.nix
./samba.nix
./vaultwarden.nix
# ./alditalk-extender.nix # FIXME
# ./webdav.nix # FIXME
];
# bootstrap

View file

@ -1,71 +0,0 @@
{
inputs,
constants,
config,
...
}:
let
inherit (constants.hosts.rx4) ip;
inherit (constants.services.librechat-oci) fqdn port;
in
{
imports = [
inputs.synix.nixosModules.librechat-oci
];
services.librechat-oci = {
enable = true;
inherit port;
configFile = ./librechat.yaml;
externalUrl = "https://${fqdn}";
environmentFile = config.sops.templates.librechat-env-file.path;
environment = {
# ALLOW_REGISTRATION = "true";
SEARXNG_INSTANCE_URL = "https://searxng.website/";
};
};
services.nginx.virtualHosts."${fqdn}" = {
useACMEHost = fqdn;
forceSSL = true;
listen = [
{
addr = "${ip}:443";
ssl = true;
}
];
locations."/" = {
proxyPass = "http://127.0.0.1:${toString port}";
proxyWebsockets = true;
};
};
security.acme.certs."${fqdn}" = {
domain = fqdn;
postRun = "systemctl restart podman-librechat.service";
group = "nginx";
};
sops = {
# generate with:
# openssl rand -hex 32
secrets."librechat/jwt-secret" = { };
secrets."librechat/jwt-refresh-secret" = { };
secrets."librechat/creds-key" = { };
secrets."librechat/creds-iv" = { };
secrets."librechat/meili-master-key" = { };
secrets."librechat/requesty-key" = { };
templates.librechat-env-file.content = ''
JWT_SECRET=${config.sops.placeholder."librechat/jwt-secret"}
JWT_REFRESH_SECRET=${config.sops.placeholder."librechat/jwt-refresh-secret"}
CREDS_KEY=${config.sops.placeholder."librechat/creds-key"}
CREDS_IV=${config.sops.placeholder."librechat/creds-iv"}
MEILI_MASTER_KEY=${config.sops.placeholder."librechat/meili-master-key"}
REQUESTY_KEY=${config.sops.placeholder."librechat/requesty-key"}
'';
};
}

View file

@ -1,53 +0,0 @@
version: 1.3.11
cache: true
interface:
customWelcome: "Such compose. Much yaml. Wow"
modelSelect: true
parameters: true
presets: true
prompts:
use: true
create: true
bookmarks: true
multiConvo: true
agents: true
fileSearch: true
webSearch: true
# TODO: add cohere rerank
webSearch:
searchProvider: "searxng"
searxngInstanceUrl: "${SEARXNG_INSTANCE_URL}"
endpoints:
agents:
disableBuilder: false
recursionLimit: 50
maxRecursionLimit: 100
maxCitations: 30
maxCitationsPerFile: 7
minRelevanceScore: 0.45
capabilities:
- "deferred_tools"
- "execute_code"
- "file_search"
- "web_search"
- "artifacts"
- "actions"
- "context"
- "tools"
- "chain"
custom:
- name: "Requesty"
apiKey: "${REQUESTY_KEY}"
baseURL: "https://router.requesty.ai/v1"
headers:
x-librechat-body-parentmessageid: "{{LIBRECHAT_BODY_PARENTMESSAGEID}}"
models:
default: ["meta-llama/llama-3-70b-instruct"]
fetch: true
titleConvo: true
titleModel: "meta-llama/llama-3-70b-instruct"
dropParams: ["stop"]

View file

@ -1,14 +1,12 @@
{
config,
constants,
pkgs,
...
}:
{
services.netdata = {
enable = true;
package = pkgs.netdata.override { withCloudUi = false; };
config.global = {
"debug log" = "syslog";
"access log" = "syslog";
@ -19,10 +17,6 @@
};
};
services.journald.storage = "persistent";
users.users.netdata.extraGroups = [ "systemd-journal" ];
sops =
let
owner = config.services.netdata.user;

View file

@ -7,8 +7,6 @@
let
cfg = config.services.nginx;
inherit (constants) domain;
in
{
imports = [
@ -36,21 +34,4 @@ in
};
};
};
security.acme = {
acceptTerms = true;
defaults = {
email = "admin@${domain}";
dnsProvider = "hetzner";
credentialFiles = {
HETZNER_API_TOKEN_FILE = config.sops.secrets.hetzner-api-key.path;
};
};
};
sops.secrets.hetzner-api-key = {
mode = "0400";
owner = "acme";
group = "acme";
};
}

View file

@ -2,19 +2,12 @@
inputs,
constants,
config,
lib,
pkgs,
...
}:
let
inherit (lib) getExe;
in
{
imports = [
inputs.synix.nixosModules.open-webui-oci
inputs.synix.nixosModules.mcpo
];
imports = [ inputs.synix.nixosModules.open-webui-oci ];
services.open-webui-oci = {
enable = true;
@ -28,27 +21,6 @@ in
};
};
services.mcpo = {
enable = true;
package = pkgs.synix.mcpo;
port = 8765;
settings = {
mcpServers = {
fetcher-mcp = {
command = getExe pkgs.synix.fetcher-mcp;
};
nixos = {
command = getExe pkgs.nix;
args = [
"run"
"github:utensils/mcp-nixos"
"--"
];
};
};
};
};
# sops = {
# secrets."open-webui-oci/stt-api-key" = { };
# secrets."open-webui-oci/tts-api-key" = { };

View file

@ -0,0 +1,18 @@
{ outputs, constants, ... }:
let
inherit (constants.services.overleaf-oci) port subdomain;
in
{
imports = [ outputs.nixosModules.overleaf-oci ];
services.overleaf-oci = {
enable = true;
inherit port;
reverseProxy = {
enable = true;
inherit subdomain;
forceSSL = false;
};
};
}

View file

@ -0,0 +1,12 @@
{
inputs,
...
}:
{
imports = [
inputs.synix.nixosModules.print-server
];
services.print-server.enable = true;
}

View file

@ -1,27 +0,0 @@
{ config, ... }:
{
services.samba = {
enable = true;
openFirewall = false;
nmbd.enable = false;
winbindd.enable = false;
settings = {
global = {
workgroup = "WORKGROUP";
"server string" = config.networking.hostName;
security = "user";
"map to guest" = "Bad User";
"guest account" = "nobody";
};
share = {
path = "/home/sid";
browseable = "yes";
"read only" = "yes";
"guest ok" = "yes";
"force user" = "sid";
"directory mask" = "0750";
};
};
};
}

View file

@ -6,7 +6,6 @@
let
inherit (constants) domain;
inherit (constants.hosts.rx4) ip;
inherit (constants.services.vaultwarden) fqdn port;
in
{
@ -22,52 +21,21 @@ in
environmentFile = [ config.sops.templates."vaultwarden/env-file".path ];
config = {
ENABLE_WEBSOCKET = true;
SIGNUPS_ALLOWED = false;
SMTP_FROM = "vaultwarden@${domain}";
SMTP_FROM_NAME = "${domain} Vaultwarden server";
SMTP_HOST = constants.services.mailserver.fqdn;
SMTP_PORT = 465;
SMTP_SECURITY = "force_tls";
SMTP_HOST = constants.hosts.sid.ip;
SMTP_PORT = 587;
SMTP_SECURITY = "starttls";
SMTP_USERNAME = "vaultwarden@${domain}";
ROCKET_ADDRESS = "127.0.0.1";
ROCKET_ADDRESS = "0.0.0.0";
ROCKET_PORT = port;
ROCKET_LOG = "critical";
};
};
services.nginx.virtualHosts."${fqdn}" = {
useACMEHost = "pw-custom";
forceSSL = true;
listen = [
{
addr = "${ip}:443";
ssl = true;
}
];
locations = {
"/" = {
proxyPass = "http://127.0.0.1:${toString port}";
};
"= /notifications/alerts" = {
proxyPass = "http://127.0.0.1:${toString port}";
proxyWebsockets = true;
};
"= /notifications/hub" = {
proxyPass = "http://127.0.0.1:${toString port}";
proxyWebsockets = true;
};
};
};
security.acme.certs."pw-custom" = {
domain = fqdn;
postRun = "systemctl restart vaultwarden.service";
group = "nginx";
};
sops =
let
owner = config.users.users.vaultwarden.name;

View file

@ -0,0 +1,86 @@
{ constants, config, ... }:
# FIXME: floccus throws error: NetworkError when attempting to fetch resource.
let
cfg = config.services.webdav;
inherit (constants.services.webdav) fqdn port;
in
{
services.webdav = {
enable = true;
environmentFile = config.sops.templates."webdav/env-file".path;
settings = {
inherit port;
address = "127.0.0.1";
prefix = "/";
directory = "/srv/webdav";
users = [
{
username = "{env}WEBDAV_USER";
password = "{env}WEBDAV_PASS";
permissions = "CRUD";
}
];
};
};
systemd.tmpfiles.rules = [
"d ${cfg.settings.directory} 0750 ${cfg.user} ${cfg.group} -"
];
networking.firewall.allowedTCPPorts = [ port ];
services.nginx = {
enable = true;
virtualHosts."${fqdn}" = {
listen = [
{
addr = "0.0.0.0";
inherit port;
}
];
locations."/" = {
proxyPass = "http://127.0.0.1:${toString port}";
extraConfig = ''
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, PROPFIND, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Authorization,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Depth' always;
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, PROPFIND, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Authorization,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Depth';
return 204;
}
'';
};
};
};
sops =
let
owner = cfg.user;
group = cfg.group;
mode = "0400";
in
{
secrets = {
"webdav/user" = {
inherit owner group mode;
};
"webdav/pass" = {
inherit owner group mode;
};
};
templates."webdav/env-file" = {
inherit owner group mode;
content = ''
WEBDAV_USER=${config.sops.placeholder."webdav/user"}
WEBDAV_PASS=${config.sops.placeholder."webdav/pass"}
'';
};
};
}

View file

@ -35,7 +35,6 @@ radicale:
step-ca:
password: ENC[AES256_GCM,data:8/6NA3WpII0LmDOp5ISnHKeaXn5LM4gpiI47JTso23c=,iv:fi2eMGG1lOwdK5+98Hp7vZ101GKRip5Xgq9k+vnC9yI=,tag:oENvvsEbKSHFfLoXcJlPkg==,type:str]
intermediate-key: ENC[AES256_GCM,data:yGZLSd7ydx9wNFpWWPcyUBwZQZbyziGleCWSxurFniBCauw2h4hcPc4c4I/7cjl1vRUv41WfzWu1PtXnZ3lNHOC6tTbiikHFBgGiHk2Lhddx+NESUWmgNiejJR/UDW4T25W9OHxwLCV9pmHf4fjyT/REymGIB7kbcRryWqcWtoZWYaL7JooJornm5mMU1Be+MCfxusTGQA4gQsT5/bu20iEGPwgY3fEgZLQWzKFI2kD2lYlMC8CRxoZO32uTizzooW1+zKng1qSZ7aobFJsbSKRYpYDv9Vvfwltcczb+xo+yZL3pfoEiqAxPzeG/48lRVNf1nftM5esBRGIIPr9BV9+7fbe5DFbSRDtAWspEnp9R5ENj1rbNint/fjCcStg3OfFMdv6N8cQyIpQyHCiBLiG4z+xyFcn0iW4=,iv:BhUoeaoetI5vJk9wOHhBI2ebHWCPeXz8U2ta/xEeUxM=,tag:7xg5ilOSJP1rFlSmmZVZUg==,type:str]
hetzner-api-key: ENC[AES256_GCM,data:NhgWjitvgJrcBEDSkZH0S0VmaW37NupkiEUcQDZe/6oYyrE/VgEwrGSag/s2Fgv6uHmSsdbv1vqdc0iDO8GJ8w==,iv:ChEicL0jtjQrgn8CCUnrzErRr3YVdDhMbvcIlI3t7H8=,tag:cjjbEEYqEyNa5qDZCytjxw==,type:str]
sops:
age:
- recipient: age19yeqvv28fgrtk6jsh3xyaf0lch86kna6rcz4dwe962yyyyevu30sx474xy
@ -56,7 +55,7 @@ sops:
RzhnczA0S1pxcXZncGpWVHNYQW96L28K+ytH3PPyg4+wibpAQhp02RiSfZ83EDRB
UJ8UV1d+51D0e2A1sI95r2AzDj4jfwUnI+LYDPC/qEpsu5LFLGVyeg==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-05-22T19:19:21Z"
mac: ENC[AES256_GCM,data:hOtmWizEaIcybM14UEDsXw4GNQZob5SoFn49bWeccxA3dkGlYl67kVkDJGg0cQIO1qr/vGcZ8h/OmnOxU3geP0DaflG0h1/40lDQ3+E6BTb6HP2JmhgEmlRBRBdv87cRDHnDytBzcWARTvff3SsP2J2pLpLBTDiihlaZaiQYtgU=,iv:TvFpvcTydXO3fbh5x9ZXIOtMChlE7WXl2Xx2a9ujh00=,tag:XHvsZh6r9fzbbYFWWQyI5g==,type:str]
lastmodified: "2026-05-02T17:10:22Z"
mac: ENC[AES256_GCM,data:f4KQ26/zvg2nLLeW5qVeI8uH2GmPpJUKohNu68nEiIjP5AT53zjBaGoLOTGl9+oVRomSOGZtLGkJGaExB6tLMon5HN6xkQbugqvq08UkZ7FnR1Sa8/OtTr/+eexPNzF8VSdZE2TZCboUSQODV8+0Cy5T918g5kedxnT62SyY4As=,iv:P4TnpJvHwnZPl7kRNjv9d1WLZP9J0sg6R3KbdDMJqyc=,tag:ylYOcg6825jT29lWUaFRYA==,type:str]
unencrypted_suffix: _unencrypted
version: 3.12.1

View file

@ -0,0 +1,37 @@
{ constants, ... }:
{
services.resolved.enable = false;
networking.resolvconf.enable = false;
networking.nameservers = [ "127.0.0.1" ];
services.coredns = {
enable = true;
config = with constants; ''
.:53 {
bind 0.0.0.0
hosts {
${hosts.sid.ip} ${ca-fqdn}
${hosts.rx4.ip} rx4.tail
${hosts.sid.ip} sid.tail
${hosts.vde.ip} vde.tail
${hosts.sid.ip} ${services.netdata.fqdn}
${hosts.sid.ip} ${services.vaultwarden.fqdn}
${hosts.sid.ip} ${services.webdav.fqdn}
fallthrough
}
forward . 1.1.1.1 8.8.8.8
cache 30
log
errors
}
'';
};
networking.firewall.allowedUDPPorts = [ 53 ];
networking.firewall.allowedTCPPorts = [ 53 ];
}

View file

@ -9,9 +9,8 @@
inputs.synix.nixosModules.openssh
outputs.nixosModules.tailscale
# outputs.nixosModules.loki
# outputs.nixosModules.promtail
./coredns.nix
./headscale.nix
./mailserver.nix
./matrix-synapse.nix
@ -19,5 +18,6 @@
./nginx.nix
./radicale.nix
./rss-bridge.nix
./step-ca.nix
];
}

View file

@ -1,6 +1,5 @@
{
inputs,
constants,
...
}:
@ -25,12 +24,5 @@
enable = true;
subdomain = "hs";
};
settings.dns.extra_records = [
{
name = constants.services.vaultwarden.fqdn;
type = "A";
value = constants.hosts.rx4.ip;
}
];
};
}

View file

@ -1,21 +1,15 @@
{ inputs, constants, ... }:
{ inputs, config, ... }:
let
inherit (constants.services.mailserver) subdomain;
in
{
imports = [ inputs.synix.nixosModules.mailserver ];
mailserver = {
enable = true;
inherit subdomain;
stateVersion = 3;
localDnsResolver = !config.services.coredns.enable;
accounts = {
sid = {
aliases = [
"admin"
"postmaster"
];
aliases = [ "postmaster" ];
};
vaultwarden = { };
};

View file

@ -1,37 +1,18 @@
{
config,
constants,
lib,
pkgs,
...
}:
let
email = "sid@${config.networking.domain}";
netdata-dashboard = pkgs.stdenvNoCC.mkDerivation {
pname = "netdata-dashboard";
version = "2.31.0";
src = pkgs.fetchurl {
url = "https://github.com/netdata/dashboard/releases/download/v2.31.0/dashboard.tar.gz";
hash = "sha256-n7M7Y8LIb4tbgQ8wQIr5bMKxLT5fPDID5LnX47ayH/o=";
};
dontUnpack = true;
installPhase = ''
mkdir -p $out
tar -xzf $src --strip-components=1 -C $out
'';
meta.license = lib.licenses.gpl3Only;
};
in
{
services.netdata = {
enable = true;
package = pkgs.netdata.override { withCloudUi = false; };
package = pkgs.netdata.override {
withCloudUi = true;
};
config.global = {
"debug log" = "syslog";
"access log" = "syslog";
@ -55,47 +36,6 @@ in
NETDATA_USER_CONFIG_DIR = "/etc/netdata/conf.d";
};
services.nginx.virtualHosts."${constants.services.netdata.fqdn}" = {
useACMEHost = constants.services.netdata.fqdn;
forceSSL = true;
listen = [
{
addr = "${constants.hosts.sid.ip}:443";
ssl = true;
}
];
locations."/" = {
root = netdata-dashboard;
tryFiles = "$uri $uri/ /index.html";
};
locations."~ ^/(api|v[0-9]+|netdata.conf|registry|stream|version.txt)(/|$)" = {
proxyPass = "http://127.0.0.1:${toString constants.services.netdata.port}";
recommendedProxySettings = true;
};
locations."~ ^/host/" = {
proxyPass = "http://127.0.0.1:${toString constants.services.netdata.port}";
recommendedProxySettings = true;
};
};
security.acme = {
acceptTerms = true;
certs."${constants.services.netdata.fqdn}" = {
domain = constants.services.netdata.fqdn;
webroot = lib.mkForce null;
dnsProvider = "hetzner";
credentialFiles.HETZNER_API_TOKEN_FILE = config.sops.secrets.hetzner-api-key.path;
group = "nginx";
};
};
services.journald.storage = "persistent";
users.users.netdata.extraGroups = [ "systemd-journal" ];
sops =
let
owner = config.services.netdata.user;
@ -104,12 +44,6 @@ in
restartUnits = [ "netdata.service" ];
in
{
secrets.hetzner-api-key = {
inherit mode;
owner = "acme";
group = "acme";
};
secrets."netdata/stream/rx4/uuid" = {
inherit
owner
@ -131,7 +65,6 @@ in
[${config.sops.placeholder."netdata/stream/rx4/uuid"}]
enabled = yes
default history = 3600
allow from = *
'';
};
};

View file

@ -56,6 +56,15 @@ in
address = constants.hosts.rx4.ip;
port = constants.services.miniflux.port;
};
virtualHosts."${constants.services.netdata.fqdn}" = {
useACMEHost = "sid-internal";
forceSSL = ssl;
locations."/" = {
# proxyPass = "http://${constants.hosts.sid.ip}:${toString constants.services.netdata.port}";
proxyPass = "http://127.0.0.1:${toString constants.services.netdata.port}";
proxyWebsockets = true;
};
};
virtualHosts."${constants.services.open-webui-oci.fqdn}" = mkVirtualHost {
inherit ssl;
address = constants.hosts.rx4.ip;
@ -68,11 +77,33 @@ in
error_log /var/log/nginx/open-webui-error.log debug;
'';
};
virtualHosts."${constants.services.overleaf-oci.fqdn}" = mkVirtualHost {
inherit ssl;
address = constants.hosts.rx4.ip;
port = constants.services.overleaf-oci.port;
};
virtualHosts."${constants.services.rsshub-oci.fqdn}" = mkVirtualHost {
inherit ssl;
address = constants.hosts.rx4.ip;
port = constants.services.rsshub-oci.port;
};
virtualHosts."${constants.services.vaultwarden.fqdn}" = {
useACMEHost = "sid-internal";
forceSSL = ssl;
locations = {
"/" = {
proxyPass = "http://${constants.hosts.rx4.ip}:${toString constants.services.vaultwarden.port}";
};
};
};
virtualHosts."${constants.services.webdav.fqdn}" = {
useACMEHost = "sid-internal";
forceSSL = ssl;
locations."/" = {
proxyPass = "http://${constants.hosts.rx4.ip}:${toString constants.services.webdav.port}";
proxyWebsockets = true;
};
};
# FIXME
# virtualHosts."print.sid.ovh" = {
# enableACME = true;

View file

@ -0,0 +1,108 @@
{
constants,
config,
pkgs,
...
}:
let
cfg = config.services.step-ca;
in
{
services.step-ca = {
enable = true;
address = "0.0.0.0";
port = 8443;
openFirewall = true;
intermediatePasswordFile = config.sops.secrets."step-ca/password".path;
# nix-shell -p step-cli --run "step ca init"
settings = {
# FIXME: nix-store paths do not work
# root = ../../../certs/root_ca.crt;
# crt = ../../../certs/intermediate_ca.crt;
# FIXME: not reproducible
root = "/var/lib/step-ca/certs/root_ca.crt";
crt = "/var/lib/step-ca/certs/intermediate_ca.crt";
key = config.sops.secrets."step-ca/intermediate-key".path;
dnsNames = [
constants.ca-fqdn
constants.hosts.sid.ip
];
logger = {
format = "text";
};
db = {
type = "badgerv2";
dataSource = "/var/lib/step-ca/db";
};
authority = {
provisioners = [
{
type = "ACME";
name = "acme";
}
{
type = "JWK";
name = "sid@sid.ovh";
key = {
use = "sig";
kty = "EC";
kid = "w3fV4U-frlyTnBMg4yNYrLsn8_mY98H8HthoscpoVrg";
crv = "P-256";
alg = "ES256";
x = "KZCDecn4sb87T3UO6JsIzJVtr4Aa0UcYzYDNBUM6F7M";
y = "CbGHn9tXQbV0Ur2VuXITLnWgfxCRmKEoUdMUmrP9Qkw";
};
encryptedKey = "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjdHkiOiJqd2sranNvbiIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjYwMDAwMCwicDJzIjoiZS1MUDhDYlE4dzVuMF9nUGhXOWtGdyJ9.rgsqo58rJFWaociSqiPg3E1alAeqoHWubJi4n2uoUFYp3YTWaYZzqA.6P6oimHsKGdCWruo.fNaDr50IXCtCe7W7VIXuS3rlfin_R0nogNpIJ9C6szYg8k10UylircUs14Zl1EHQ9lFeJovb1y1uljzBajMGkOAGlMvashrphVkXiSxHWKDhzbrItJx3qChLtSLJJtXiXPbJQKCAeBjztqPuTw6dI4Z6IR9---kiTvzF6I9KE8afGFlMSubGjr9FnqgiOb2JiZuTfcBGDx78puxdWzUrEEVlliHdv2agbKhY0b13x-obaTIWwlqLFbasv7kPneJ8Ggp7IHHr5uDcUrqVKkTfBrD0lelXm6SwJTHGMkty6inlwSflT9mxvkNq7OGV9triPQc8AGVv0c7t7dHoX_E.tSjJqttCS6zLI_-7zPdXNQ";
}
];
};
tls = {
cipherSuites = [
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256"
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
];
renegotiation = false;
};
};
};
environment.systemPackages = [
pkgs.step-cli
];
systemd.tmpfiles.rules = [
"d /var/lib/acme/acme-challenge 0755 acme nginx"
];
security.acme = {
certs."sid-internal" = {
# domain = constants.intranet;
domain = constants.services.vaultwarden.fqdn;
extraDomainNames = [
constants.services.netdata.fqdn
# constants.services.vaultwarden.fqdn
constants.services.webdav.fqdn
];
server = "https://${constants.ca-fqdn}:${toString cfg.port}/acme/acme/directory";
group = "nginx";
};
};
sops =
let
owner = "step-ca";
group = "step-ca";
mode = "0400";
in
{
secrets = {
"step-ca/password" = {
inherit owner group mode;
};
"step-ca/intermediate-key" = {
inherit owner group mode;
};
};
};
}

View file

@ -5,6 +5,10 @@
./nix.nix
./overlays.nix
../pki
inputs.synix.nixosModules.device.server
];
nixpkgs.config.allowUnfree = true;
}

View file

@ -5,9 +5,9 @@
forgejo = import ./forgejo;
forgejo-runner = import ./forgejo-runner;
gnome = import ./gnome;
loki = import ./loki;
monero = import ./monero;
promtail = import ./promtail;
overleaf-oci = import ./overleaf-oci;
pki = import ./pki;
rsshub-oci = import ./rsshub-oci;
tailscale = import ./tailscale;
xfce = import ./xfce;

View file

@ -1,75 +0,0 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Graphics --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": 1,
"links": [],
"panels": [
{
"datasource": {
"type": "loki",
"uid": "loki"
},
"gridPos": {
"h": 24,
"w": 24,
"x": 0,
"y": 0
},
"id": 1,
"options": {
"dedupStrategy": "none",
"enableLogDetails": true,
"prettifyLogMessage": false,
"showCommonLabels": false,
"showLabels": false,
"showTime": true,
"sortOrder": "Descending",
"wrapLogMessage": false
},
"targets": [
{
"datasource": {
"type": "loki",
"uid": "Loki"
},
"expr": "{job=\"systemd-journal\"}",
"refId": "A"
}
],
"title": "System Logs",
"type": "logs"
}
],
"schemaVersion": 38,
"style": "dark",
"tags": [],
"templating": {
"list": []
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "System Logs",
"uid": "system-logs",
"version": 1
}

View file

@ -1,115 +0,0 @@
{
services.loki = {
enable = true;
configuration = {
auth_enabled = false;
server = {
http_listen_address = "0.0.0.0";
http_listen_port = 3100;
grpc_listen_port = 9096;
};
common = {
ring = {
instance_addr = "127.0.0.1";
kvstore.store = "inmemory";
};
replication_factor = 1;
path_prefix = "/var/lib/loki";
};
ingester = {
wal = {
enabled = true;
dir = "/var/lib/loki/wal";
};
chunk_encoding = "snappy";
chunk_idle_period = "30m";
max_chunk_age = "2h";
chunk_target_size = 1572864;
chunk_block_size = 262144;
};
limits_config = {
reject_old_samples = true;
reject_old_samples_max_age = "168h";
ingestion_rate_mb = 10;
ingestion_burst_size_mb = 20;
per_stream_rate_limit = "3MB";
per_stream_rate_limit_burst = "15MB";
max_line_size = "256KB";
};
schema_config = {
configs = [
{
from = "2026-01-01";
store = "tsdb";
object_store = "filesystem";
schema = "v13";
index = {
prefix = "index_";
period = "24h";
};
}
];
};
storage_config = {
filesystem = {
directory = "/var/lib/loki/chunks";
};
};
compactor = {
working_directory = "/var/lib/loki/compactor";
compaction_interval = "10m";
retention_enabled = true;
retention_delete_delay = "2h";
retention_delete_worker_count = 150;
delete_request_store = "filesystem";
};
};
};
services.grafana = {
enable = true;
settings = {
server = {
http_addr = "0.0.0.0";
http_port = 3003;
};
"auth.anonymous" = {
enabled = true;
org_name = "Main Org.";
org_role = "Admin";
};
};
provision = {
enable = true;
datasources.settings = {
apiVersion = 1;
datasources = [
{
name = "Loki";
type = "loki";
access = "proxy";
url = "http://127.0.0.1:3100";
isDefault = true;
uid = "loki";
}
];
};
dashboards.settings.providers = [
{
name = "default";
options.path = ./dashboards;
}
];
};
};
networking.firewall.allowedTCPPorts = [ 3100 ];
}

View file

@ -0,0 +1,257 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.overleaf-oci;
domain = config.networking.domain;
subdomain = cfg.reverseProxy.subdomain;
fqdn = if (cfg.reverseProxy.enable && subdomain != "") then "${subdomain}.${domain}" else domain;
mongodbInitReplicaSet = ./mongodb-init-replica-set.js;
images = {
# https://hub.docker.com/r/sharelatex/sharelatex
sharelatex = pkgs.dockerTools.pullImage {
imageName = "sharelatex/sharelatex";
imageDigest = "sha256:fa5e5a049721b0973aa6ddabd15cb04da2d492f38c685fda0805dab97d4f3c8a";
hash = "sha256-2Om6b+xPcBFObMKAlZ+qsvPw1xd8B/8ODyI5dsWoaQA=";
finalImageName = "sharelatex/sharelatex";
finalImageTag = "6.1.2";
};
# https://hub.docker.com/_/mongo
mongo = pkgs.dockerTools.pullImage {
imageName = "mongo";
imageDigest = "sha256:ce32a4b67580c982e2020b07d4ffafdbd9ce474088434294d89c6bd9257f7788";
hash = "sha256-NtMSeH5hYD69uF7gZWPppxoI9esyPTfxaw50yh0ChpY=";
finalImageName = "mongo";
finalImageTag = "8.3.1";
};
# https://hub.docker.com/_/redis
redis = pkgs.dockerTools.pullImage {
imageName = "redis";
imageDigest = "sha256:0c341492924cad6f5483f9133e43bd6c51ecdecbcadfac5b51657393b6a7936c";
hash = "sha256-nU5+UdvA+29sbr7dAD2jpKYACxYb3BfStlOE/25ET+w=";
finalImageName = "redis";
finalImageTag = "8.6.3";
};
};
defaultEnv = {
EMAIL_CONFIRMATION_DISABLED = "true";
ENABLED_LINKED_FILE_TYPES = "project_file,project_output_file";
ENABLE_CONVERSIONS = "true";
OVERLEAF_APP_NAME = "Overleaf Community Edition";
OVERLEAF_MONGO_URL = "mongodb://mongo/sharelatex";
OVERLEAF_REDIS_HOST = "redis";
REDIS_HOST = "redis";
};
inherit (lib)
mkEnableOption
mkIf
mkOption
mkOverride
optional
types
;
inherit (lib.utils)
mkReverseProxyOption
mkVirtualHost
;
in
{
options.services.overleaf-oci = {
enable = mkEnableOption "Overleaf service stack with Podman";
port = mkOption {
type = types.port;
default = 80;
description = "The port Overleaf will listen on.";
};
environment = mkOption {
type = types.attrsOf types.str;
default = { };
description = "Extra environment variables for Overleaf.";
};
environmentFile = mkOption {
type = types.nullOr types.path;
default = null;
description = "Environment file for secrets.";
};
reverseProxy = mkReverseProxyOption "Overleaf" "overleaf";
};
config = mkIf cfg.enable {
virtualisation.podman = {
enable = true;
autoPrune.enable = true;
dockerCompat = true;
};
services.nginx.virtualHosts = mkIf cfg.reverseProxy.enable {
"${fqdn}" = mkVirtualHost {
inherit (cfg) port;
ssl = cfg.reverseProxy.forceSSL;
};
};
networking.firewall.interfaces =
let
matchAll = if !config.networking.nftables.enable then "podman+" else "podman*";
in
{
"${matchAll}".allowedUDPPorts = [ 53 ];
};
virtualisation.oci-containers.backend = "podman";
virtualisation.oci-containers.containers = {
overleaf-mongo = {
image = with images.mongo; imageName + ":" + imageTag;
imageFile = images.mongo;
environment = {
MONGO_INITDB_DATABASE = "sharelatex";
};
volumes = [
"overleaf_mongo-data:/data/db:rw"
"${mongodbInitReplicaSet}:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js:rw"
];
cmd = [
"--replSet"
"overleaf"
];
log-driver = "journald";
extraOptions = [
"--network-alias=mongo"
"--network=overleaf_default"
"--add-host=mongo:127.0.0.1"
"--health-cmd=echo 'db.stats().ok' | mongosh localhost:27017/test --quiet"
"--health-interval=10s"
"--health-retries=5"
"--health-timeout=10s"
];
};
overleaf-redis = {
image = with images.redis; imageName + ":" + imageTag;
imageFile = images.redis;
volumes = [ "overleaf_redis-data:/data:rw" ];
log-driver = "journald";
extraOptions = [
"--network-alias=redis"
"--network=overleaf_default"
];
};
overleaf-sharelatex = {
image = with images.sharelatex; imageName + ":" + imageTag;
imageFile = images.sharelatex;
log-driver = "journald";
environment = defaultEnv // cfg.environment;
environmentFiles = optional (cfg.environmentFile != null) cfg.environmentFile;
volumes = [ "overleaf_data:/var/lib/overleaf:rw" ];
ports = [ "${toString cfg.port}:80/tcp" ];
extraOptions = [
"--network-alias=sharelatex"
"--network=overleaf_default"
];
};
};
systemd.services =
let
commonServiceCfg = {
serviceConfig.Restart = mkOverride 90 "always";
partOf = [ "podman-compose-overleaf-root.target" ];
wantedBy = [ "podman-compose-overleaf-root.target" ];
};
in
{
podman-overleaf-mongo = commonServiceCfg // {
after = [
"podman-network-overleaf_default.service"
"podman-volume-overleaf_mongo-data.service"
];
requires = [
"podman-network-overleaf_default.service"
"podman-volume-overleaf_mongo-data.service"
];
};
podman-overleaf-redis = commonServiceCfg // {
after = [
"podman-network-overleaf_default.service"
"podman-volume-overleaf_redis-data.service"
];
requires = [
"podman-network-overleaf_default.service"
"podman-volume-overleaf_redis-data.service"
];
};
podman-overleaf-sharelatex = commonServiceCfg // {
after = [
"podman-network-overleaf_default.service"
"podman-overleaf-mongo.service"
"podman-overleaf-redis.service"
];
requires = [
"podman-network-overleaf_default.service"
"podman-overleaf-mongo.service"
"podman-overleaf-redis.service"
];
serviceConfig.ExecStartPre = [
"${pkgs.bash}/bin/bash -c 'until ${pkgs.podman}/bin/podman healthcheck run overleaf-mongo; do sleep 2; done'"
];
};
podman-network-overleaf_default = {
path = [ pkgs.podman ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStop = "podman network rm -f overleaf_default";
};
script = ''
podman network inspect overleaf_default || podman network create overleaf_default
'';
partOf = [ "podman-compose-overleaf-root.target" ];
wantedBy = [ "podman-compose-overleaf-root.target" ];
};
podman-volume-overleaf_redis-data = {
path = [ pkgs.podman ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
podman volume inspect overleaf_redis-data || podman volume create overleaf_redis-data
'';
partOf = [ "podman-compose-overleaf-root.target" ];
wantedBy = [ "podman-compose-overleaf-root.target" ];
};
podman-volume-overleaf_mongo-data = {
path = [ pkgs.podman ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
podman volume inspect overleaf_mongo-data || podman volume create overleaf_mongo-data
'';
partOf = [ "podman-compose-overleaf-root.target" ];
wantedBy = [ "podman-compose-overleaf-root.target" ];
};
};
systemd.targets."podman-compose-overleaf-root" = {
unitConfig.Description = "Root target for Overleaf services.";
wantedBy = [ "multi-user.target" ];
};
};
}

View file

@ -0,0 +1,3 @@
/* eslint-disable no-undef */
rs.initiate({ _id: 'overleaf', members: [{ _id: 0, host: 'mongo:27017' }] })

View file

@ -0,0 +1,3 @@
{
security.pki.certificateFiles = [ ./root_ca.crt ];
}

View file

@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE-----
MIIBrzCCAVWgAwIBAgIQDV0M0pLkCXvARpa+ipSx8jAKBggqhkjOPQQDAjA2MRUw
EwYDVQQKEwxzaWQtaW50ZXJuYWwxHTAbBgNVBAMTFHNpZC1pbnRlcm5hbCBSb290
IENBMB4XDTI2MDQxODIwMzkwMloXDTM2MDQxNTIwMzkwMlowNjEVMBMGA1UEChMM
c2lkLWludGVybmFsMR0wGwYDVQQDExRzaWQtaW50ZXJuYWwgUm9vdCBDQTBZMBMG
ByqGSM49AgEGCCqGSM49AwEHA0IABCH2VmIwKEjdma4UymD7RWuGcaT2algrL5nm
TE0NzP8giezdU9bEP487AvUPPibSYDWxdp4ycbl6qNVTiy29xkmjRTBDMA4GA1Ud
DwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBRaiBACRDZk
HZMU9y8YsUF4WPB+5TAKBggqhkjOPQQDAgNIADBFAiAh+b49V2VTnT6nRCRM0Qwq
ruzayrrnmF7pIxi9PVFwBQIhANQsL3ok4gCTRAnT0mUXSyWexzSESZ1lkpLYiyoj
RgLi
-----END CERTIFICATE-----

View file

@ -1,43 +0,0 @@
{ config, constants, ... }:
{
services.promtail = {
enable = true;
configuration = {
server = {
http_listen_port = 9080;
grpc_listen_port = 0;
};
clients = [
{
url = "http://${constants.hosts.sid.ip}:3100/loki/api/v1/push";
}
];
scrape_configs = [
{
job_name = "journal";
journal = {
max_age = "12h";
path = "/var/log/journal";
labels = {
job = "systemd-journal";
host = config.networking.hostName;
};
};
relabel_configs = [
{
source_labels = [ "__journal__systemd_unit" ];
target_label = "unit";
}
];
}
];
};
};
users.users.promtail.extraGroups = [ "systemd-journal" ];
}

View file

@ -11,7 +11,7 @@
loginServer = "https://hs.sid.ovh";
authKeyFile = config.sops.secrets."tailscale/personal-key".path;
enableSSH = true;
acceptDNS = true;
acceptDNS = false; # use coredns
};
};
};