initial commit
Some checks failed
Nix CI / build (push) Failing after 31s

This commit is contained in:
Rustam Efimov 2026-04-01 08:50:01 +03:00
commit 30ce0dafc2
No known key found for this signature in database
195 changed files with 8902 additions and 0 deletions

View file

@ -0,0 +1,7 @@
{
imports = [
./firewall.nix
./options.nix
./service.nix
];
}

View file

@ -0,0 +1,14 @@
{
config,
lib,
...
}:
let
cfg = config.machine.bind;
in
with lib; mkIf cfg.enable {
networking.firewall = {
allowedTCPPorts = [ cfg.port ];
allowedUDPPorts = [ cfg.port ];
};
}

25
services/bind/options.nix Normal file
View file

@ -0,0 +1,25 @@
{
dns,
lib,
...
}:
with lib;
{
options.machine.bind = {
enable = mkEnableOption "Bind Server";
domain = mkOption {
type = types.str;
description = "Domain name";
};
port = mkOption {
type = types.port;
default = 53;
description = "Port to listen on.";
};
zones = mkOption {
type = types.attrsOf dns.lib.types.zone;
default = { };
description = "DNS zones";
};
};
}

21
services/bind/service.nix Normal file
View file

@ -0,0 +1,21 @@
{
config,
pkgs,
lib,
...
}:
let
cfg = config.machine.bind;
in
with lib; mkIf cfg.enable {
services.bind = {
enable = cfg.enable;
listenOnPort = cfg.port;
zones = {
${cfg.domain} = {
master = true;
file = pkgs.writeText "zone-${cfg.domain}" (builtins.toString cfg.zones.${cfg.domain});
};
};
};
}

View file

@ -0,0 +1,7 @@
{
imports = [
./options.nix
./service.nix
./nginx.nix
];
}

View file

@ -0,0 +1,27 @@
{
lib,
config,
...
}:
let
cfg = config.machine.code-server;
in
with lib; mkIf cfg.enable {
services.nginx.virtualHosts = mkIf (cfg.domain != null) {
${cfg.domain} = {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "http://127.0.0.1:${toString cfg.port}";
proxyWebsockets = true;
extraConfig = ''
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_read_timeout 86400;
proxy_send_timeout 86400;
'';
};
};
};
}

View file

@ -0,0 +1,33 @@
{
lib,
config,
...
}:
let
cfg = config.machine.code-server;
in
with lib; {
options.machine.code-server = {
enable = mkEnableOption "code-server";
port = mkOption {
type = types.port;
default = 4444;
description = "Port to listen on.";
};
domain = mkOption {
type = types.nullOr types.str;
default = null;
description = "domain for the code-server instance.";
};
hashedPassword = mkOption {
type = types.nullOr types.str;
default = null;
description = "Hashed password for code-server";
};
user = mkOption {
type = types.str;
default = config.services.code-server.user;
description = "The user to run code-server as";
};
};
}

View file

@ -0,0 +1,49 @@
{
pkgs,
config,
lib,
...
}:
let
cfg = config.machine.code-server;
in
with lib; mkIf cfg.enable {
services.code-server = {
enable = cfg.enable;
port = cfg.port;
hashedPassword = cfg.hashedPassword;
user = cfg.user;
host = "127.0.0.1";
disableTelemetry = true;
disableUpdateCheck = true;
disableWorkspaceTrust = true;
package = pkgs.vscode-with-extensions.override {
vscode = pkgs.code-server;
vscodeExtensions =
(with pkgs.vscode-extensions; [
rust-lang.rust-analyzer
fill-labs.dependi
ms-python.black-formatter
ms-python.python
ms-python.isort
ms-python.mypy-type-checker
intellsmi.comment-translate
editorconfig.editorconfig
dbaeumer.vscode-eslint
davidanson.vscode-markdownlint
yzhang.markdown-all-in-one
jnoortheen.nix-ide
ziglang.vscode-zig
bbenoist.nix
])
++ pkgs.vscode-utils.extensionsFromVscodeMarketplace [
{
name = "purpleVoid";
publisher = "Rej";
version = "1.0.3";
sha256 = "oCZ2N8j2U0xGvechD7DlW64KiL0eSDKYwniYft0kVu4=";
}
];
};
};
}

View file

@ -0,0 +1,7 @@
{
imports = [
./firewall.nix
./options.nix
./service.nix
];
}

View file

@ -0,0 +1,33 @@
{
config,
lib,
...
}:
let
cfg = config.machine.coturn;
in
with lib; mkIf cfg.enable {
networking.firewall = {
interfaces.enp2s0 =
let
range = with config.services.coturn; [
{
from = min-port;
to = max-port;
}
];
in
{
allowedUDPPortRanges = range;
allowedUDPPorts = [
3478
5349
];
allowedTCPPortRanges = [ ];
allowedTCPPorts = [
3478
5349
];
};
};
}

View file

@ -0,0 +1,22 @@
{ lib, ... }:
with lib;
{
options.machine.coturn = {
enable = mkEnableOption "Coturn";
startPort = mkOption {
type = types.port;
default = 49000;
description = "Start port for Coturn.";
};
endPort = mkOption {
type = types.port;
default = 50000;
description = "End port for Coturn.";
};
realm = mkOption {
type = types.str;
default = "turn.example.com";
description = "Realm for Coturn.";
};
};
}

View file

@ -0,0 +1,60 @@
{
config,
lib,
sec,
...
}:
let
inherit (config.machine.coturn)
enable
startPort
endPort
realm
;
in
with lib; mkIf enable {
services.coturn = rec {
inherit realm enable;
no-cli = true;
no-tcp-relay = true;
min-port = startPort;
max-port = endPort;
use-auth-secret = true;
static-auth-secret-file = sec."turn/authSecret".path;
cert = "${config.security.acme.certs.${realm}.directory}/full.pem";
pkey = "${config.security.acme.certs.${realm}.directory}/key.pem";
extraConfig = ''
# for debugging
verbose
# ban private IP ranges
no-multicast-peers
denied-peer-ip=0.0.0.0-0.255.255.255
denied-peer-ip=10.0.0.0-10.255.255.255
denied-peer-ip=100.64.0.0-100.127.255.255
denied-peer-ip=127.0.0.0-127.255.255.255
denied-peer-ip=169.254.0.0-169.254.255.255
denied-peer-ip=172.16.0.0-172.31.255.255
denied-peer-ip=192.0.0.0-192.0.0.255
denied-peer-ip=192.0.2.0-192.0.2.255
denied-peer-ip=192.88.99.0-192.88.99.255
denied-peer-ip=192.168.0.0-192.168.255.255
denied-peer-ip=198.18.0.0-198.19.255.255
denied-peer-ip=198.51.100.0-198.51.100.255
denied-peer-ip=203.0.113.0-203.0.113.255
denied-peer-ip=240.0.0.0-255.255.255.255
denied-peer-ip=::1
denied-peer-ip=64:ff9b::-64:ff9b::ffff:ffff
denied-peer-ip=::ffff:0.0.0.0-::ffff:255.255.255.255
denied-peer-ip=100::-100::ffff:ffff:ffff:ffff
denied-peer-ip=2001::-2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff
denied-peer-ip=2002::-2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff
denied-peer-ip=fc00::-fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
denied-peer-ip=fe80::-febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff
'';
};
security.acme.certs.${config.services.coturn.realm} = {
postRun = "systemctl restart coturn.service";
group = "turnserver";
};
}

23
services/default.nix Normal file
View file

@ -0,0 +1,23 @@
{
imports = [
./bind
./code-server
./coturn
./forgejo
./mail
./minecraft-server
./mysql
./navidrome
./networking
./nextcloud
./nginx
./postgresql
./prosody
./redis
./roundcube
./synapse
./uptime-kuma
./vaultwarden
./xray-3x-ui
];
}

View file

@ -0,0 +1,10 @@
{
imports = [
./mail.nix
./network.nix
./options.nix
./runners.nix
./service.nix
./tmpfiles.nix
];
}

37
services/forgejo/mail.nix Normal file
View file

@ -0,0 +1,37 @@
{
config,
pkgs,
lib,
sec,
...
}:
let
inherit (config.machine.forgejo) domain;
address = "noreply@${domain}";
in
with lib; mkIf config.machine.mail.enable {
services.forgejo = {
secrets = {
mailer.PASSWD = sec."mail/servicePassword".path;
};
settings.mailer = {
ENABLED = true;
FROM = address;
USER = address;
SMTP_ADDR = config.machine.mail.fqdn;
SMTP_PORT = 465;
PROTOCOL = "smtps";
SENDMAIL_PATH = "${pkgs.system-sendmail}/bin/sendmail";
SEND_AS_PLAIN_TEXT = true;
};
};
mailserver = {
domains = [ domain ];
accounts.${address} = {
hashedPasswordFile = sec."mail/serviceHashedPassword".path;
aliases = [ ];
sendOnly = true;
};
};
}

View file

@ -0,0 +1,28 @@
{
config,
lib,
...
}:
let
inherit (config.machine.forgejo)
enable
domain
port
;
in
with lib; mkIf enable {
networking.firewall = {
allowedTCPPorts = [ port ];
};
services.nginx.virtualHosts = with lib; mkIf (domain != null) {
"${domain}" = {
forceSSL = true;
enableACME = true;
locations."/" = {
proxyPass = "http://[::1]:${toString port}";
proxyWebsockets = true;
};
};
};
}

View file

@ -0,0 +1,40 @@
{
lib,
config,
...
}:
let
inherit (lib)
mkEnableOption
mkOption
types
;
in
{
options.machine.forgejo = {
enable = mkEnableOption "Forgejo";
enableRunner = mkEnableOption "Actions runner";
domain = mkOption {
type = types.nullOr types.str;
default = null;
description = "Domain name. If not set, will be disabled, and use the localhost.";
};
port = mkOption {
type = types.port;
default = 3000;
description = "Listen port.";
};
database = {
host = mkOption {
type = types.str;
default = "127.0.0.1";
description = "PostgreSQL database host address.";
};
port = mkOption {
type = types.port;
default = config.services.postgresql.port;
description = "PostgreSQL database host port.";
};
};
};
}

View file

@ -0,0 +1,41 @@
{
config,
pkgs,
lib,
sec,
...
}:
let
url = config.services.forgejo.settings.server.ROOT_URL;
cfg = config.machine.forgejo;
in
with lib; mkIf cfg.enableRunner {
sops.secrets = {
"forgejo/runnerToken" = {
sopsFile = ./../../secrets/common.yaml;
};
};
services.gitea-actions-runner = {
package = pkgs.forgejo-runner;
instances.default = {
name = "forgejo-runner";
enable = cfg.enableRunner;
tokenFile = sec."forgejo/runnerToken".path;
inherit url;
labels = [
"ubuntu-latest:docker://ghcr.io/catthehacker/ubuntu:act-latest"
"ubuntu-26.04:docker://ghcr.io/catthehacker/ubuntu:act-26.04"
"ubuntu-24.04:docker://ghcr.io/catthehacker/ubuntu:act-24.04"
"ubuntu-22.04:docker://ghcr.io/catthehacker/ubuntu:act-22.04"
"nixos:docker://nixos/nix:latest"
"nixos-2.34.4:docker://nixos/nix:2.34.4"
];
settings = {
container = {
network = "host";
};
};
};
};
}

View file

@ -0,0 +1,64 @@
{
config,
lib,
...
}:
let
inherit (config.machine.forgejo)
enable
database
domain
port
;
in
{
environment.systemPackages = lib.optionals enable [
config.services.forgejo.package
];
services.openssh.settings.AllowUsers = [
"forgejo"
];
services.forgejo = {
inherit enable;
database = {
type = "postgres";
inherit (database) host;
inherit (database) port;
};
lfs.enable = true;
settings = {
DEFAULT = {
APP_NAME = "RuJect Forgejo";
};
server = {
DOMAIN = if (domain != null) then domain else "[::1]";
ROOT_URL = if (domain != null) then "https://${domain}/" else "http://[::1]/";
HTTP_PORT = port;
};
service = {
SHOW_REGISTRATION_BUTTON = true;
REGISTER_EMAIL_CONFIRM = true;
ENABLE_NOTIFY_MAIL = true;
};
"repository.signing" = {
DEFAULT_TRUST_MODEL = "committer";
};
actions = {
ENABLED = true;
DEFAULT_ACTIONS_URL = "github";
};
picture = {
AVATAR_MAX_FILE_SIZE = 10485760;
AVATAR_MAX_WIDTH = 8192;
AVATAR_MAX_HEIGHT = 8192;
AVATAR_MAX_ORIGIN_SIZE = 5242880;
};
ui = {
DEFAULT_THEME = "catppuccin-mocha";
THEMES = "forgejo-auto,forgejo-light,forgejo-dark,catppuccin-mocha";
};
};
};
}

View file

@ -0,0 +1,873 @@
:root {
color-scheme: dark;
--is-dark-theme: true;
accent-color: #b4befe;
--color-primary: #b4befe;
--color-primary-contrast: #11111b;
--color-primary-hover: rgb(154.8355263158, 168.1907894737, 253.6644736842);
--color-primary-dark-1: rgb(164.9013157895, 176.9144736842, 253.7986842105);
--color-primary-dark-2: rgb(149.8026315789, 163.8289473684, 253.5973684211);
--color-primary-dark-3: rgb(134.7039473684, 150.7434210526, 253.3960526316);
--color-primary-dark-4: rgb(119.6052631579, 137.6578947368, 253.1947368421);
--color-primary-dark-5: rgb(104.5065789474, 124.5723684211, 252.9934210526);
--color-primary-dark-6: rgb(89.4078947368, 111.4868421053, 252.7921052632);
--color-primary-dark-7: rgb(74.3092105263, 98.4013157895, 252.5907894737);
--color-primary-light-1: rgb(195.0986842105, 203.0855263158, 254.2013157895);
--color-primary-light-2: rgb(210.1973684211, 216.1710526316, 254.4026315789);
--color-primary-light-3: rgb(225.2960526316, 229.2565789474, 254.6039473684);
--color-primary-light-4: rgb(240.3947368421, 242.3421052632, 254.8052631579);
--color-primary-light-5: hsl(231.8918918919, 97.3684210526%, 100.0980392157%);
--color-primary-light-6: hsl(231.8918918919, 97.3684210526%, 103.0980392157%);
--color-primary-light-7: hsl(231.8918918919, 97.3684210526%, 106.0980392157%);
--color-primary-alpha-10: rgba(180, 190, 254, 0.1);
--color-primary-alpha-20: rgba(180, 190, 254, 0.2);
--color-primary-alpha-30: rgba(180, 190, 254, 0.3);
--color-primary-alpha-40: rgba(180, 190, 254, 0.4);
--color-primary-alpha-50: rgba(180, 190, 254, 0.5);
--color-primary-alpha-60: rgba(180, 190, 254, 0.6);
--color-primary-alpha-70: rgba(180, 190, 254, 0.7);
--color-primary-alpha-80: rgba(180, 190, 254, 0.8);
--color-primary-alpha-90: rgba(180, 190, 254, 0.9);
--color-secondary: #45475a;
--color-secondary-dark-1: rgb(55.4076923077, 56.5384615385, 76.8923076923);
--color-secondary-dark-2: rgb(61.8153846154, 63.0769230769, 85.7846153846);
--color-secondary-dark-3: rgb(68.2230769231, 69.6153846154, 94.6769230769);
--color-secondary-dark-4: rgb(74.6307692308, 76.1538461538, 103.5692307692);
--color-secondary-dark-5: rgb(81.0384615385, 82.6923076923, 112.4615384615);
--color-secondary-dark-6: rgb(87.4461538462, 89.2307692308, 121.3538461538);
--color-secondary-dark-7: rgb(93.8538461538, 95.7692307692, 130.2461538462);
--color-secondary-dark-8: rgb(100.2615384615, 102.3076923077, 139.1384615385);
--color-secondary-dark-9: rgb(106.6692307692, 108.8461538462, 148.0307692308);
--color-secondary-dark-10: rgb(115.5128205128, 117.5641025641, 154.4871794872);
--color-secondary-dark-11: rgb(124.4051282051, 126.3256410256, 160.8948717949);
--color-secondary-dark-12: rgb(133.2974358974, 135.0871794872, 167.3025641026);
--color-secondary-dark-13: rgb(142.1897435897, 143.8487179487, 173.7102564103);
--color-secondary-light-1: rgb(42.5923076923, 43.4615384615, 59.1076923077);
--color-secondary-light-2: rgb(36.1846153846, 36.9230769231, 50.2153846154);
--color-secondary-light-3: rgb(29.7769230769, 30.3846153846, 41.3230769231);
--color-secondary-light-4: rgb(23.3692307692, 23.8461538462, 32.4307692308);
--color-secondary-alpha-10: rgba(49, 50, 68, 0.1);
--color-secondary-alpha-20: rgba(49, 50, 68, 0.2);
--color-secondary-alpha-30: rgba(49, 50, 68, 0.3);
--color-secondary-alpha-40: rgba(49, 50, 68, 0.4);
--color-secondary-alpha-50: rgba(49, 50, 68, 0.5);
--color-secondary-alpha-60: rgba(49, 50, 68, 0.6);
--color-secondary-alpha-70: rgba(49, 50, 68, 0.7);
--color-secondary-alpha-80: rgba(49, 50, 68, 0.8);
--color-secondary-alpha-90: rgba(49, 50, 68, 0.9);
/* colors */
--color-red: #f38ba8;
--color-orange: #fab387;
--color-yellow: #f9e2af;
--color-olive: #a6e3a1;
--color-green: #a6e3a1;
--color-teal: #94e2d5;
--color-blue: #89b4fa;
--color-violet: #b4befe;
--color-purple: #cba6f7;
--color-pink: #f5c2e7;
--color-brown: #f2cdcd;
--color-grey: #9399b2;
--color-black: #181825;
/* light variants - produced via Sass scale-color(color, $lightness: -10%) */
--color-red-light: rgb(238.21875, 92.78125, 133.3359375);
--color-orange-light: rgb(247.96, 147.992, 86.04);
--color-yellow-light: rgb(245.4418604651, 208.8023255814, 127.5581395349);
--color-olive-light: rgb(128.7950819672, 215.2950819672, 121.7049180328);
--color-green-light: rgb(128.7950819672, 215.2950819672, 121.7049180328);
--color-teal-light: rgb(107.875, 215.125, 197.25);
--color-blue-light: rgb(88.0731707317, 148.9024390244, 247.9268292683);
--color-violet-light: rgb(129.6710526316, 146.3815789474, 253.3289473684);
--color-purple-light: rgb(175.6597938144, 119.206185567, 242.793814433);
--color-pink-light: rgb(237.8169014085, 150.1830985915, 213.7605633803);
--color-brown-light: rgb(231.4761904762, 164.5238095238, 164.5238095238);
--color-grey-light: rgb(117.227027027, 124.8810810811, 156.772972973);
--color-black-light: rgb(3.9344262295, 3.9344262295, 6.0655737705);
/* dark 1 variants - produced via Sass scale-color(color, $lightness: -10%) */
--color-red-dark-1: rgb(238.21875, 92.78125, 133.3359375);
--color-orange-dark-1: rgb(247.96, 147.992, 86.04);
--color-yellow-dark-1: rgb(245.4418604651, 208.8023255814, 127.5581395349);
--color-olive-dark-1: rgb(128.7950819672, 215.2950819672, 121.7049180328);
--color-green-dark-1: rgb(128.7950819672, 215.2950819672, 121.7049180328);
--color-teal-dark-1: rgb(107.875, 215.125, 197.25);
--color-blue-dark-1: rgb(88.0731707317, 148.9024390244, 247.9268292683);
--color-violet-dark-1: rgb(129.6710526316, 146.3815789474, 253.3289473684);
--color-purple-dark-1: rgb(175.6597938144, 119.206185567, 242.793814433);
--color-pink-dark-1: rgb(237.8169014085, 150.1830985915, 213.7605633803);
--color-brown-dark-1: rgb(231.4761904762, 164.5238095238, 164.5238095238);
--color-black-dark-1: rgb(3.9344262295, 3.9344262295, 6.0655737705);
/* dark 2 variants - produced via Sass scale-color(color, $lightness: -20%) */
--color-red-dark-2: rgb(233.4375, 46.5625, 98.671875);
--color-orange-dark-2: rgb(245.92, 116.984, 37.08);
--color-yellow-dark-2: rgb(241.8837209302, 191.6046511628, 80.1162790698);
--color-olive-dark-2: rgb(91.5901639344, 203.5901639344, 82.4098360656);
--color-green-dark-2: rgb(91.5901639344, 203.5901639344, 82.4098360656);
--color-teal-dark-2: rgb(67.75, 204.25, 181.5);
--color-blue-dark-2: rgb(39.1463414634, 117.8048780488, 245.8536585366);
--color-violet-dark-2: rgb(79.3421052632, 102.7631578947, 252.6578947368);
--color-purple-dark-2: rgb(148.3195876289, 72.412371134, 238.587628866);
--color-pink-dark-2: rgb(230.6338028169, 106.3661971831, 196.5211267606);
--color-brown-dark-2: rgb(220.9523809524, 124.0476190476, 124.0476190476);
--color-black-dark-2: hsl(240, 21.3114754098%, -8.0392156863%);
/* other colors */
--color-gold: #f5e0dc;
--color-white: #cdd6f4;
--color-diff-removed-word-bg: rgba(243, 139, 168, 0.15);
--color-diff-added-word-bg: rgba(166, 227, 161, 0.15);
--color-diff-removed-row-bg: rgba(243, 139, 168, 0.07);
--color-diff-moved-row-bg: rgba(249, 226, 175, 0.07);
--color-diff-added-row-bg: rgba(166, 227, 161, 0.07);
--color-diff-removed-row-border: rgba(243, 139, 168, 0.07);
--color-diff-moved-row-border: rgba(249, 226, 175, 0.07);
--color-diff-added-row-border: rgba(166, 227, 161, 0.07);
--color-diff-inactive: #9399b2;
--color-error-border: #f38ba8;
--color-error-bg: #f38ba8;
--color-error-bg-active: rgb(240.609375, 115.890625, 150.66796875);
--color-error-bg-hover: rgb(238.21875, 92.78125, 133.3359375);
--color-error-text: #11111b;
--color-success-border: rgb(128.7950819672, 215.2950819672, 121.7049180328);
--color-success-bg: #a6e3a1;
--color-success-text: #11111b;
--color-warning-border: rgb(245.4418604651, 208.8023255814, 127.5581395349);
--color-warning-bg: #f9e2af;
--color-warning-text: #11111b;
--color-info-border: rgb(88.0731707317, 148.9024390244, 247.9268292683);
--color-info-bg: #11111b;
--color-info-text: #cdd6f4;
--color-red-badge: rgb(238.21875, 92.78125, 133.3359375);
--color-red-badge-bg: #11111b;
--color-red-badge-hover-bg: rgb(240.609375, 115.890625, 150.66796875);
--color-green-badge: #a6e3a1;
--color-green-badge-bg: #a6e3a1;
--color-green-badge-hover-bg: rgb(147.3975409836, 221.1475409836, 141.3524590164);
--color-yellow-badge: #f9e2af;
--color-yellow-badge-bg: #11111b;
--color-yellow-badge-hover-bg: rgb(247.2209302326, 217.4011627907, 151.2790697674);
--color-orange-badge: #fab387;
--color-orange-badge-bg: #11111b;
--color-orange-badge-hover-bg: rgb(248.98, 163.496, 110.52);
--color-git: #fab387;
--color-highlight-bg: rgba(249, 226, 175, 0.15);
/* target-based colors */
--color-body: #11111b;
--color-box-header: #181825;
--color-box-body: #181825;
--color-box-body-highlight: #313244;
--color-text-dark: #a6adc8;
--color-text: #cdd6f4;
--color-text-light: #bac2de;
--color-text-light-1: #bac2de;
--color-text-light-2: #bac2de;
--color-text-light-3: #bac2de;
--color-footer: #181825;
--color-timeline: #313244;
--color-input-text: #cdd6f4;
--color-input-background: #313244;
--color-input-toggle-background: #313244;
--color-input-border: #45475a;
--color-input-border-hover: #585b70;
--color-nav-bg: #181825;
--color-nav-hover-bg: var(--color-hover);
--color-navbar: #181825;
--color-navbar-transparent: rgba(17, 17, 27, 0);
--color-light: rgba(88, 91, 112, 0.3);
--color-light-mimic-enabled: rgba(
0,
0,
0,
calc(40 / 255 * 222 / 255 / var(--opacity-disabled))
);
--color-light-border: #585b70;
--color-hover: rgba(108, 112, 134, 0.2);
--color-active: rgba(205, 214, 244, 0.1);
--color-menu: #313244;
--color-card: #313244;
--color-markup-table-row: rgba(205, 214, 244, 0.02);
--color-markup-code-block: rgba(205, 214, 244, 0.05);
--color-markup-code-inline: #313244;
--color-button: #313244;
--color-code-bg: #1e1e2e;
--color-code-sidebar-bg: #313244;
--color-shadow: rgba(17, 17, 27, 0.1);
--color-tooltip-bg: #313244;
--color-tooltip-text: var(--color-text);
--color-secondary-bg: #313244;
--color-text-focus: #cdd6f4;
--color-expand-button: #585b70;
--color-placeholder-text: #a6adc8;
--color-editor-line-highlight: var(--color-primary-light-5);
--color-project-board-bg: var(--color-secondary-light-2);
/* gitea source code: */
/* should ideally be --color-text-dark, see go-gitea/gitea#15651 */
--color-caret: var(--color-text);
--color-reaction-bg: rgba(205, 214, 244, 0.07);
--color-reaction-active-bg: var(--color-primary-alpha-40);
--color-header-wrapper: #181825;
--color-header-wrapper-transparent: rgba(24, 24, 37, 0);
--color-label-text: #11111b;
--color-label-bg: #b4befe;
--color-label-hover-bg: rgb(149.8026315789, 163.8289473684, 253.5973684211);
--color-label-active-bg: rgb(164.9013157895, 176.9144736842, 253.7986842105);
--color-accent: var(--color-primary-light-1);
--color-small-accent: var(--color-primary-light-5);
--color-active-line: #45475a;
}
/* invert emojis that are hard to read otherwise */
.emoji[aria-label="check mark"],
.emoji[aria-label="currency exchange"],
.emoji[aria-label="TOP arrow"],
.emoji[aria-label="END arrow"],
.emoji[aria-label="ON! arrow"],
.emoji[aria-label="SOON arrow"],
.emoji[aria-label="heavy dollar sign"],
.emoji[aria-label=copyright],
.emoji[aria-label=registered],
.emoji[aria-label="trade mark"],
.emoji[aria-label=multiply],
.emoji[aria-label=plus],
.emoji[aria-label=minus],
.emoji[aria-label=divide],
.emoji[aria-label="curly loop"],
.emoji[aria-label="double curly loop"],
.emoji[aria-label="wavy dash"],
.emoji[aria-label="paw prints"],
.emoji[aria-label="musical note"],
.emoji[aria-label="musical notes"] {
filter: invert(100%) hue-rotate(180deg);
}
.ui.ui.ui.button:not(.inverted, .basic).primary, .ui.ui.ui.button:not(.inverted, .basic).green, .ui.ui.ui.button:not(.inverted, .basic).red, .ui.ui.ui.button:not(.inverted, .basic).teal,
.ui.ui.ui.label:not(.inverted, .basic).primary,
.ui.ui.ui.label:not(.inverted, .basic).green,
.ui.ui.ui.label:not(.inverted, .basic).red,
.ui.ui.ui.label:not(.inverted, .basic).teal {
color: #11111b;
}
.ui.ui.ui.button:not(.inverted, .basic).primary:hover, .ui.ui.ui.button:not(.inverted, .basic).green:hover, .ui.ui.ui.button:not(.inverted, .basic).red:hover, .ui.ui.ui.button:not(.inverted, .basic).teal:hover,
.ui.ui.ui.label:not(.inverted, .basic).primary:hover,
.ui.ui.ui.label:not(.inverted, .basic).green:hover,
.ui.ui.ui.label:not(.inverted, .basic).red:hover,
.ui.ui.ui.label:not(.inverted, .basic).teal:hover {
color: #1e1e2e;
}
.ui.basic.modal {
background-color: #1e1e2e;
}
.ui.commit-header-row .svg.gitea-lock ~ a {
color: #11111b;
}
.ui.negative.message .header {
color: var(--color-error-text);
}
.ui.sha.isSigned.isVerified .shortsha {
color: #11111b;
}
.ui.sha.isSigned.isVerified svg.gitea-lock {
fill: #11111b;
}
.ui.basic.modal,
.ui.basic.modal > .header,
.ui.inverted.button {
color: #cdd6f4 !important;
}
#repo-topics,
#topic_edit > .ui.selection.dropdown {
color: var(--color-label-text) !important;
}
blockquote.attention-tip {
border-left-color: var(--color-success-bg);
}
blockquote.attention-warning {
border-left-color: var(--color-warning-bg);
}
svg.attention-tip,
strong.attention-tip {
color: var(--color-success-bg);
}
svg.attention-warning,
strong.attention-warning {
color: var(--color-warning-bg);
}
.inline-code-block {
color: #11111b;
}
::selection {
background: rgba(180, 190, 254, 0.3) !important;
}
/* NameBuiltinPseudo */
.chroma .bp {
color: #fab387;
}
/* Comment */
.chroma .c {
color: #6c7086;
}
/* CommentSingle */
.chroma .c1 {
color: #6c7086;
}
/* CommentHashbang */
.chroma .ch {
color: #6c7086;
}
/* CommentMultiline */
.chroma .cm {
color: #6c7086;
}
/* CommentPreproc */
.chroma .cp {
color: #89b4fa;
}
/* CommentPreprocFile */
.chroma .cpf {
color: #89b4fa;
}
/* CommentSpecial */
.chroma .cs {
color: #6c7086;
}
/* LiteralStringDelimiter */
.chroma .dl {
color: #89b4fa;
}
/* NameFunctionMagic */
/* Generic */
/* GenericDeleted */
.chroma .gd {
color: #cdd6f4;
background-color: rgba(243, 139, 168, 0.15);
}
/* GenericEmph */
.chroma .ge {
font-style: italic;
}
/* GenericHeading */
.chroma .gh {
color: #89dceb;
}
/* GenericInserted */
.chroma .gi {
color: #cdd6f4;
background-color: rgba(166, 227, 161, 0.15);
}
/* GenericUnderline */
/* GenericOutput */
.chroma .go {
color: #fab387;
}
/* GenericPrompt */
.chroma .gp {
color: #6c7086;
font-weight: bold;
}
/* GenericError */
.chroma .gr {
color: #eba0ac;
}
/* GenericStrong */
.chroma .gs {
font-weight: bold;
}
/* GenericTraceback */
.chroma .gt {
color: #eba0ac;
}
/* GenericSubheading */
.chroma .gu {
color: #89dceb;
}
/* LiteralNumberIntegerLong */
.chroma .il {
color: #fab387;
}
/* Keyword */
.chroma .k {
color: #cba6f7;
}
/* KeywordConstant */
.chroma .kc {
color: #f9e2af;
}
/* KeywordDeclaration */
.chroma .kd {
color: #cba6f7;
}
/* KeywordNamespace */
.chroma .kn {
color: #f9e2af;
}
/* KeywordPseudo */
.chroma .kp {
color: #cba6f7;
font-weight: bold;
}
/* KeywordReserved */
.chroma .kr {
color: #cba6f7;
}
/* KeywordType */
.chroma .kt {
color: #f9e2af;
}
/* Literal */
/* LiteralDate */
/* LiteralNumber */
.chroma .m {
color: #fab387;
}
/* LiteralNumberBin */
.chroma .mb {
color: #fab387;
}
/* LiteralNumberFloat */
.chroma .mf {
color: #fab387;
}
/* LiteralNumberHex */
.chroma .mh {
color: #fab387;
}
/* LiteralNumberInteger */
.chroma .mi {
color: #fab387;
}
/* LiteralNumberOct */
.chroma .mo {
color: #fab387;
}
/* Name */
.chroma .n {
color: #b4befe;
}
/* NameAttribute */
.chroma .na {
color: #f9e2af;
}
/* NameBuiltin */
.chroma .nb {
color: #fab387;
}
/* NameClass */
.chroma .nc {
color: #f9e2af;
}
/* NameDecorator */
.chroma .nd {
color: #f5c2e7;
}
/* NameException */
.chroma .ne {
color: #eba0ac;
}
/* NameFunction */
.chroma .nf {
color: #89b4fa;
}
/* NameEntity */
.chroma .ni {
color: #f5c2e7;
}
/* NameLabel */
.chroma .nl {
color: #f9e2af;
}
/* NameNamespace */
.chroma .nn {
color: #f9e2af;
}
/* NameConstant */
.chroma .no {
color: #f9e2af;
}
/* NameTag */
.chroma .nt {
color: #cba6f7;
}
/* NameVariable */
.chroma .nv {
color: #fab387;
}
/* NameOther */
.chroma .nx {
color: #fab387;
}
/* Operator */
.chroma .o {
color: #89dceb;
}
/* OperatorWord */
.chroma .ow {
color: #89dceb;
font-weight: bold;
}
/* Punctuation */
.chroma .p {
color: #9399b2;
}
/* NameProperty */
/* LiteralString */
.chroma .s {
color: #a6e3a1;
}
/* LiteralStringSingle */
.chroma .s1 {
color: #a6e3a1;
}
/* LiteralStringDouble */
.chroma .s2 {
color: #a6e3a1;
}
/* LiteralStringAffix */
.chroma .sa {
color: #a6e3a1;
}
/* LiteralStringBacktick */
.chroma .sb {
color: #a6e3a1;
}
/* LiteralStringChar */
.chroma .sc {
color: #a6e3a1;
}
/* LiteralStringDoc */
.chroma .sd {
color: #a6e3a1;
}
/* LiteralStringEscape */
.chroma .se {
color: #89b4fa;
}
/* LiteralStringHeredoc */
.chroma .sh {
color: #a6e3a1;
}
/* LiteralStringInterpol */
.chroma .si {
color: #a6e3a1;
}
/* LiteralStringRegex */
.chroma .sr {
color: #89b4fa;
}
/* LiteralStringSymbol */
.chroma .ss {
color: #a6e3a1;
}
/* LiteralStringOther */
.chroma .sx {
color: #a6e3a1;
}
/* NameVariableClass */
.chroma .vc {
color: #f9e2af;
}
/* NameVariableGlobal */
.chroma .vg {
color: #fab387;
}
/* NameVariableInstance */
.chroma .vi {
color: #f9e2af;
}
/* NameVariableMagic */
/* TextWhitespace */
.chroma .w {
color: #313244;
}
.CodeMirror .cm-property,
.CodeMirror.cm-s-default .cm-property,
.CodeMirror.cm-s-paper .cm-property {
color: #cdd6f4;
}
.CodeMirror .cm-header,
.CodeMirror.cm-s-default .cm-header,
.CodeMirror.cm-s-paper .cm-header {
color: #cdd6f4;
}
.CodeMirror .cm-quote,
.CodeMirror.cm-s-default .cm-quote,
.CodeMirror.cm-s-paper .cm-quote {
color: #a6e3a1;
}
.CodeMirror .cm-keyword,
.CodeMirror.cm-s-default .cm-keyword,
.CodeMirror.cm-s-paper .cm-keyword {
color: #cba6f7;
}
.CodeMirror .cm-atom,
.CodeMirror.cm-s-default .cm-atom,
.CodeMirror.cm-s-paper .cm-atom {
color: #f38ba8;
}
.CodeMirror .cm-number,
.CodeMirror.cm-s-default .cm-number,
.CodeMirror.cm-s-paper .cm-number {
color: #fab387;
}
.CodeMirror .cm-def,
.CodeMirror.cm-s-default .cm-def,
.CodeMirror.cm-s-paper .cm-def {
color: #cdd6f4;
}
.CodeMirror .cm-variable-2,
.CodeMirror.cm-s-default .cm-variable-2,
.CodeMirror.cm-s-paper .cm-variable-2 {
color: #89dceb;
}
.CodeMirror .cm-variable-3,
.CodeMirror.cm-s-default .cm-variable-3,
.CodeMirror.cm-s-paper .cm-variable-3 {
color: #94e2d5;
}
.CodeMirror .cm-comment,
.CodeMirror.cm-s-default .cm-comment,
.CodeMirror.cm-s-paper .cm-comment {
color: #585b70;
}
.CodeMirror .cm-string,
.CodeMirror.cm-s-default .cm-string,
.CodeMirror.cm-s-paper .cm-string {
color: #a6e3a1;
}
.CodeMirror .cm-string-2,
.CodeMirror.cm-s-default .cm-string-2,
.CodeMirror.cm-s-paper .cm-string-2 {
color: #a6e3a1;
}
.CodeMirror .cm-meta,
.CodeMirror.cm-s-default .cm-meta,
.CodeMirror.cm-s-paper .cm-meta {
color: #fab387;
}
.CodeMirror .cm-qualifier,
.CodeMirror.cm-s-default .cm-qualifier,
.CodeMirror.cm-s-paper .cm-qualifier {
color: #fab387;
}
.CodeMirror .cm-builtin,
.CodeMirror.cm-s-default .cm-builtin,
.CodeMirror.cm-s-paper .cm-builtin {
color: #fab387;
}
.CodeMirror .cm-bracket,
.CodeMirror.cm-s-default .cm-bracket,
.CodeMirror.cm-s-paper .cm-bracket {
color: #cdd6f4;
}
.CodeMirror .cm-tag,
.CodeMirror.cm-s-default .cm-tag,
.CodeMirror.cm-s-paper .cm-tag {
color: #f9e2af;
}
.CodeMirror .cm-attribute,
.CodeMirror.cm-s-default .cm-attribute,
.CodeMirror.cm-s-paper .cm-attribute {
color: #f9e2af;
}
.CodeMirror .cm-hr,
.CodeMirror.cm-s-default .cm-hr,
.CodeMirror.cm-s-paper .cm-hr {
color: #9399b2;
}
.CodeMirror .cm-url,
.CodeMirror.cm-s-default .cm-url,
.CodeMirror.cm-s-paper .cm-url {
color: #89b4fa;
}
.CodeMirror .cm-link,
.CodeMirror.cm-s-default .cm-link,
.CodeMirror.cm-s-paper .cm-link {
color: #89b4fa;
}
.CodeMirror .cm-error,
.CodeMirror.cm-s-default .cm-error,
.CodeMirror.cm-s-paper .cm-error {
color: #f38ba8;
}
.monaco-editor .selected-text {
background-color: #313244 !important;
}
.monaco-editor .margin-view-overlays .line-numbers {
color: #a6adc8 !important;
}
.monaco-editor .line-numbers.active-line-number {
color: #b4befe !important;
}
.monaco-editor .view-overlays .current-line,
.monaco-editor .margin-view-overlays .current-line-margin {
background-color: rgb(42.16, 42.8, 60.08) !important;
}
.monaco-editor .mtk1 {
color: #cdd6f4 !important;
}
.monaco-editor .mtk2 {
color: #ff69b4 !important;
}
.monaco-editor .mtk3 {
color: #fab387 !important;
}
.monaco-editor .mtk4 {
color: #94e2d5 !important;
}
.monaco-editor .mtk5 {
color: #cdd6f4 !important;
}
.monaco-editor .mtk6 {
color: #cba6f7 !important;
}
.monaco-editor .mtk7 {
color: #fab387 !important;
}
.monaco-editor .mtk8 {
color: #9399b2 !important;
}
.monaco-editor .mtk9 {
color: #cba6f7 !important;
}
.monaco-editor .mtk10 {
color: #a6adc8 !important;
}
.monaco-editor .mtk11 {
color: #94e2d5 !important;
}
.monaco-editor .mtk12 {
color: #94e2d5 !important;
}
.monaco-editor .mtk13 {
color: #ff69b4 !important;
}
.monaco-editor .mtk14 {
color: #ff69b4 !important;
}
.monaco-editor .mtk15 {
color: #cba6f7 !important;
}
.monaco-editor .mtk16 {
color: #9399b2 !important;
}
.monaco-editor .mtk17 {
color: #ff69b4 !important;
}
.monaco-editor .mtk18 {
color: #ff69b4 !important;
}
.monaco-editor .mtk19 {
color: #94e2d5 !important;
}
.monaco-editor .mtk20 {
color: #ff69b4 !important;
}
.monaco-editor .mtk21 {
color: #a6e3a1 !important;
}
.monaco-editor .mtk22 {
color: #ff69b4 !important;
}
.monaco-editor .mtk23 {
color: #89b4fa !important;
}
.monaco-editor .mtk24 {
color: #fab387 !important;
}
.monaco-editor .mtk25 {
color: #f5c2e7 !important;
}
.monaco-editor .bracket-highlighting-0 {
color: rgb(227.8, 169, 198.4) !important;
}
.monaco-editor .bracket-highlighting-1 {
color: rgb(232, 193, 178.6) !important;
}
.monaco-editor .bracket-highlighting-2 {
color: rgb(231.4, 221.2, 202.6) !important;
}
.monaco-editor .bracket-highlighting-3 {
color: rgb(181.6, 221.8, 194.2) !important;
}
.monaco-editor .bracket-highlighting-4 {
color: rgb(164.2, 193.6, 247.6) !important;
}
.monaco-editor .bracket-highlighting-5 {
color: rgb(203.8, 185.2, 245.8) !important;
}

View file

@ -0,0 +1,18 @@
{
config,
lib,
...
}:
let
cfg = config.machine.forgejo;
customDir = config.services.forgejo.customDir;
in
with lib; mkIf cfg.enable {
systemd.tmpfiles.rules = [
"d '${customDir}/templates' - forgejo forgejo - -"
"d '${customDir}/public' - forgejo forgejo - -"
"d '${customDir}/public/assets' - forgejo forgejo - -"
"d '${customDir}/public/assets/css' - forgejo forgejo - -"
"C+ '${customDir}/public/assets/css/theme-catppuccin-mocha.css' - forgejo forgejo - ${./themes/theme-catppuccin-mocha-lavender.css}"
];
}

View file

@ -0,0 +1,9 @@
{
imports = [
./options.nix
./rspamd.nix
./secrets.nix
./service.nix
./tmpfiles.nix
];
}

23
services/mail/options.nix Normal file
View file

@ -0,0 +1,23 @@
{ lib, ... }:
let
inherit (lib)
mkEnableOption
mkOption
types
;
in
{
options.machine.mail = {
enable = mkEnableOption "Mail";
domain = mkOption {
type = types.nullOr types.str;
default = null;
description = "Domain name";
};
fqdn = mkOption {
type = types.nullOr types.str;
default = null;
description = "fqdn";
};
};
}

20
services/mail/rspamd.nix Normal file
View file

@ -0,0 +1,20 @@
{
lib,
config,
...
}:
let
inherit (config.machine.mail) enable;
in
with lib; mkIf enable {
services.rspamd = {
enable = true;
overrides = {
"local.d/greylist.conf" = {
text = ''
enabled = false;
'';
};
};
};
}

25
services/mail/secrets.nix Normal file
View file

@ -0,0 +1,25 @@
{
sops.secrets = {
dkim_default_private = {
key = "dkim_default_private";
owner = "root";
group = "root";
mode = "0600";
};
"rus07tam/hashedPassword" = {
sopsFile = ./../../secrets/rus07tam.yaml;
};
"mail/serviceHashedPassword" = {
sopsFile = ./../../secrets/common.yaml;
};
"mail/servicePassword" = {
sopsFile = ./../../secrets/common.yaml;
};
"mail/NikitaHapanulStaff228HashedPassword" = {
sopsFile = ./../../secrets/common.yaml;
};
"mail/nikitapocox6prohype" = {
sopsFile = ./../../secrets/common.yaml;
};
};
}

66
services/mail/service.nix Normal file
View file

@ -0,0 +1,66 @@
{ config, lib, ... }:
let
sec = config.sops.secrets;
inherit (config.machine.mail)
enable
domain
fqdn
;
in
{
imports = [
(builtins.fetchTarball {
url = "https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/archive/master/nixos-mailserver-master.tar.gz";
sha256 = "0vnczps1ns4d7b3l5m4qwp2fp567pr6b38w40h1x48qfw70x8mf0";
})
];
mailserver = {
inherit enable;
inherit fqdn;
stateVersion = 5;
domains = [ domain ];
messageSizeLimit = 52428800; # 50MB
enableManageSieve = true;
localDnsResolver = false;
accounts = {
"admin@${domain}" = {
hashedPasswordFile = sec."mail/serviceHashedPassword".path;
aliases = [
"postmaster@${domain}"
"system@${domain}"
"contact@${domain}"
"sales@${domain}"
];
};
"rus07tam@${domain}" = {
hashedPasswordFile = sec."rus07tam/hashedPassword".path;
aliases = [ ];
};
"nikitahapanulstaff228@${domain}" = {
hashedPasswordFile = sec."mail/NikitaHapanulStaff228HashedPassword".path;
aliases = [
"mamutraxal@${domain}"
"sava.uwu@${domain}"
];
};
"nikitapocox6prohype@${domain}" = {
hashedPasswordFile = sec."mail/nikitapocox6prohype".path;
aliases = [
"pomoemuhype@${domain}"
"nikita.uwu@${domain}"
];
};
};
x509.useACMEHost = config.mailserver.fqdn;
dkim = {
enable = true;
keyDirectory = "/var/dkim";
defaults.selector = "default";
};
};
}

View file

@ -0,0 +1,14 @@
{
config,
lib,
sec,
...
}:
let
inherit (config.machine.mail) enable;
in
with lib; mkIf enable {
systemd.tmpfiles.rules = [
"C /var/dkim/default.private 0600 root root - - ${sec.dkim_default_private.path}"
];
}

View file

@ -0,0 +1,7 @@
{
imports = [
./options.nix
./firewall.nix
./service.nix
];
}

View file

@ -0,0 +1,16 @@
{
config,
lib,
...
}:
let
inherit (config.machine.minecraft-server)
enable
port
;
in
with lib; mkIf enable {
networking.firewall.allowedTCPPorts = [
port
];
}

View file

@ -0,0 +1,12 @@
{ lib, ... }:
with lib;
{
options.machine.minecraft-server = {
enable = mkEnableOption "Minecraft Server";
port = mkOption {
type = types.port;
default = 25565;
description = "Listen port.";
};
};
}

View file

@ -0,0 +1,46 @@
{
config,
lib,
pkgs,
...
}:
let
inherit (config.machine.minecraft-server)
enable
port
;
in
with lib; mkIf enable {
services.minecraft-server = {
inherit enable;
dataDir = "/var/lib/minecraft";
declarative = true;
eula = true;
jvmOpts = "-Xms2046M -Xmx2046M -XX:+UseZGC";
package = pkgs.papermcServers.papermc-1_21;
serverProperties = {
server-port = port;
query-port = port;
allow-flight = false;
broadcast-console-to-ops = false;
broadcast-rcon-to-ops = false;
difficulty = "hard";
enable-query = true;
enable-rcon = false;
enable-status = true;
enforce-whitelist = false;
force-gamemode = true;
gamemode = "survival";
log-ips = false;
max-players = 20;
max-tick-time = -1;
motd = "RuJect's Minecraft Server";
online-mode = false;
op-permission-level = 4;
simulation-distance = 16;
view-distance = 16;
spawn-protection = 0;
white-list = false;
};
};
}

View file

@ -0,0 +1,7 @@
{
imports = [
./firewall.nix
./options.nix
./service.nix
];
}

View file

@ -0,0 +1,13 @@
{
config,
lib,
...
}:
let
cfg = config.machine.mysql;
in
with lib; mkIf cfg.enable {
networking.firewall = {
allowedTCPPorts = [ cfg.port ];
};
}

View file

@ -0,0 +1,12 @@
{ lib, ... }:
with lib;
{
options.machine.mysql = {
enable = mkEnableOption "Postgresql";
port = mkOption {
type = types.port;
default = 3306;
description = "The port on which listens.";
};
};
}

View file

@ -0,0 +1,22 @@
{
config,
pkgs,
lib,
...
}:
let
inherit (config.machine.mysql)
port
enable
;
in
with lib; mkIf enable {
services.mysql = {
inherit enable;
package = pkgs.mysql84;
ensureDatabases = [ ];
settings.mysqld = {
inherit port;
};
};
}

View file

@ -0,0 +1,6 @@
{
imports = [
./options.nix
./service.nix
];
}

View file

@ -0,0 +1,22 @@
{ lib, ... }:
with lib;
{
options.machine.navidrome = {
enable = mkEnableOption "Navidrome";
domain = mkOption {
type = types.nullOr types.str;
default = null;
description = "Domain name. If not set, will be disabled, and use the localhost.";
};
port = mkOption {
type = types.port;
default = 4533;
description = "Listen port.";
};
folder = mkOption {
type = types.str;
default = "/mnt/music";
description = "Navidrome music folder.";
};
};
}

View file

@ -0,0 +1,36 @@
{
config,
lib,
...
}:
let
inherit (config.machine.navidrome)
enable
domain
port
folder
;
in
with lib; mkIf enable {
services.nginx.virtualHosts = with lib; mkIf (domain != null) {
"${domain}" = {
forceSSL = true;
enableACME = true;
locations."/" = {
proxyPass = "http://[::1]:${toString port}";
proxyWebsockets = true;
};
};
};
services.navidrome = {
inherit enable;
settings = {
Address = "0.0.0.0";
Port = port;
MusicFolder = folder;
DefaultTheme = "AMusic";
EnableSharing = true;
};
};
}

View file

@ -0,0 +1,6 @@
{
imports = [
./options.nix
./service.nix
];
}

View file

@ -0,0 +1,17 @@
{ lib, ... }:
with lib;
{
options.machine = {
gateway = mkOption {
type = types.nullOr types.str;
default = null;
description = "Default network gateway";
};
ipv4 = mkOption {
type = types.nullOr types.str;
default = null;
description = "Main IPv4 address";
};
};
}

View file

@ -0,0 +1,32 @@
{ config, lib, hostname, ... }:
let
inherit (config.machine) ipv4 gateway;
in
with lib; {
networking = {
hostName = hostname;
networkmanager.enable = mkDefault true;
firewall = {
enable = true;
allowPing = true;
};
nameservers = [
"1.1.1.1"
"1.0.0.1"
"9.9.9.9"
"8.8.8.8"
];
interfaces.ens3 = mkIf (ipv4 != null) {
ipv4.addresses = [
{
address = ipv4;
prefixLength = 32;
}
];
};
defaultGateway = mkIf (gateway != null) {
address = gateway;
interface = "ens3";
};
};
}

View file

@ -0,0 +1,32 @@
{
config,
lib,
...
}:
let
pgsqlEnable = config.machine.postgresql.enable;
cfg = config.machine.nextcloud;
in
with lib; mkIf cfg.enable {
services.nextcloud.config =
if pgsqlEnable then
{
dbtype = "pgsql";
dbhost = "localhost:${toString config.machine.postgresql.port}";
}
else
{
dbtype = "sqlite";
dbhost = "localhost";
};
services.postgresql = with lib; mkIf pgsqlEnable {
ensureDatabases = [ "nextcloud" ];
ensureUsers = [
{
name = "nextcloud";
ensureDBOwnership = true;
}
];
};
}

View file

@ -0,0 +1,8 @@
{
imports = [
./database.nix
./mail.nix
./options.nix
./service.nix
];
}

View file

@ -0,0 +1,40 @@
{
config,
lib,
sec,
...
}:
let
inherit (config.machine.nextcloud)
enable
host
;
address = "noreply@${host}";
in
with lib; mkIf enable {
services.nextcloud = {
settings = {
mail_smtpmode = "smtp";
mail_sendmailmode = "smtp";
mail_smtpsecure = "ssl";
mail_domain = host;
mail_from_address = "noreply";
mail_smtpname = address;
mail_smtphost = config.machine.mail.fqdn;
mail_smtpport = 465;
mail_smtpauth = true;
};
secrets = {
mail_smtppassword = sec."mail/servicePassword".path;
};
};
mailserver = {
domains = [ host ];
accounts.${address} = {
hashedPasswordFile = sec."mail/serviceHashedPassword".path;
aliases = [ ];
sendOnly = true;
};
};
}

View file

@ -0,0 +1,12 @@
{ lib, ... }:
with lib;
{
options.machine.nextcloud = {
enable = mkEnableOption "Nextcloud";
host = mkOption {
type = types.str;
default = "localhost";
description = "FQDN for the nextcloud instance.";
};
};
}

View file

@ -0,0 +1,44 @@
{
pkgs,
config,
lib,
sec,
...
}:
let
cfg = config.machine.nextcloud;
in
with lib; mkIf cfg.enable {
services.nextcloud = {
inherit enable;
appstoreEnable = false;
autoUpdateApps.enable = false;
config.adminpassFile = sec."nextcloud/adminPassword".path;
hostName = cfg.host;
package = pkgs.nextcloud33;
https = if cfg.host == "localhost" then false else true;
settings = {
default_phone_region = "RU";
log_type = "file";
loglevel = 1;
};
extraAppsEnable = true;
extraApps = with pkgs.nextcloud33Packages.apps; {
inherit
mail
contacts
collectives
impersonate
;
};
};
services.nginx.virtualHosts.${config.services.nextcloud.hostName} = {
forceSSL = true;
enableACME = true;
};
sops.secrets = {
"nextcloud/adminPassword" = { };
};
}

10
services/nginx/acme.nix Normal file
View file

@ -0,0 +1,10 @@
{ lib, config, ... }:
with lib; mkIf (config.nginx.enable) {
security.acme = {
acceptTerms = true;
defaults = {
email = "admin@ruject.fun";
webroot = "/var/lib/acme/acme-challenge/";
};
};
}

View file

@ -0,0 +1,7 @@
{
imports = [
./acme.nix
./firewall.nix
./service.nix
];
}

View file

@ -0,0 +1,14 @@
{
config,
lib,
...
}:
let
inherit (config.services.nginx) enable;
in
with lib; mkIf enable {
networking.firewall.allowedTCPPorts = [
80
443
];
}

View file

@ -0,0 +1,12 @@
{
users.groups.acme.members = [
"nginx"
];
services.nginx = {
recommendedProxySettings = true;
recommendedTlsSettings = true;
recommendedOptimisation = true;
recommendedGzipSettings = true;
};
}

View file

@ -0,0 +1,6 @@
{
imports = [
./options.nix
./service.nix
];
}

View file

@ -0,0 +1,14 @@
{ lib, ... }:
with lib;
{
options.machine.postgresql = {
enable = mkEnableOption "Postgresql";
port = mkOption {
type = types.port;
default = 5432;
description = ''
The port on which PostgreSQL listens.
'';
};
};
}

View file

@ -0,0 +1,29 @@
{
config,
pkgs,
lib,
...
}:
let
inherit (config.machine.postgresql)
port
enable
;
in
with lib; mkIf enable {
services.postgresql = {
inherit enable;
settings = {
inherit port;
};
ensureDatabases = [ ];
enableTCPIP = true;
authentication = mkOverride 10 ''
#type database DBuser origin-address auth-method
local all all trust
host all all 127.0.0.1/32 trust
host all all ::1/128 trust
host all all 0.0.0.0/0 md5
'';
};
}

View file

@ -0,0 +1,6 @@
{
imports = [
./options.nix
./service.nix
];
}

View file

@ -0,0 +1,15 @@
{
options.machine.prometheus = {
enable = mkEnableOption "Prometheus";
domain = mkOption {
type = types.nullOr types.str;
default = null;
description = "Domain name. If not set, will be disabled, and use the localhost.";
};
port = mkOption {
type = types.port;
default = cfg.port;
description = "Listen port.";
};
};
}

View file

@ -0,0 +1,16 @@
{
config,
lib,
...
}:
let
inherit (config.machine.prometheus)
enable
port
;
in
with lib; mkIf enable {
services.prometheus = {
inherit enable port;
};
}

View file

@ -0,0 +1,8 @@
{
imports = [
./firewall.nix
./nginx.nix
./options.nix
./service.nix
];
}

View file

@ -0,0 +1,38 @@
{
config,
lib,
...
}:
let
inherit (config.machine.prosody)
enable
;
in
with lib; mkIf enable {
networking.firewall = {
allowedTCPPorts = [
# HTTP filer
80
443
# C2S
5222
5223
# S2S
5269
5270
# WebSockets / BOSH
5280
5281
]
++ concatLists (
with config.services.prosody;
[
httpPorts
httpsPorts
]
);
};
}

View file

@ -0,0 +1,54 @@
{
config,
lib,
...
}:
let
inherit (config.machine.prosody)
enable
domain
;
localhost = "http://localhost:5280";
in
with lib; mkIf enable {
security.acme.certs."${domain}".extraDomainNames = [
"conference.${domain}"
"upload.${domain}"
];
users.groups.acme.members = [
"prosody"
];
services.nginx.virtualHosts = with lib; mkIf (domain != null) {
"${domain}".locations = {
"= /xmpp-websocket" = {
proxyPass = localhost;
proxyWebsockets = true;
};
"= /http-bind".proxyPass = localhost;
"/push".proxyPass = localhost;
"= /.well-known/host-meta".proxyPass = localhost;
"= /.well-known/host-meta.json".proxyPass = localhost;
};
"conference.${domain}" = {
http3 = true;
quic = true;
forceSSL = true;
kTLS = true;
useACMEHost = domain;
sslCertificate = "${config.security.acme.certs.${domain}.directory}/fullchain.pem";
sslCertificateKey = "${config.security.acme.certs.${domain}.directory}/key.pem";
locations."/".proxyPass = localhost;
};
"upload.${domain}" = {
http3 = true;
quic = true;
forceSSL = true;
kTLS = true;
useACMEHost = domain;
sslCertificate = "${config.security.acme.certs.${domain}.directory}/fullchain.pem";
sslCertificateKey = "${config.security.acme.certs.${domain}.directory}/key.pem";
locations."/".proxyPass = localhost;
};
};
}

View file

@ -0,0 +1,17 @@
{ lib, ... }:
with lib;
{
options.machine.prosody = {
enable = mkEnableOption "Prosody";
domain = mkOption {
type = types.nullOr types.str;
default = null;
description = "Domain name. If not set, will be disabled, and use the localhost.";
};
port = mkOption {
type = types.port;
default = 4000;
description = "Listen port.";
};
};
}

View file

@ -0,0 +1,104 @@
{
config,
pkgs,
lib,
...
}:
let
inherit (config.machine.prosody)
enable
domain
;
sslCertDir = config.security.acme.certs."${domain}".directory;
in
with lib; mkIf enable {
services.prosody = {
inherit enable;
package = pkgs.prosody.override {
withCommunityModules = [
"sasl2"
"sasl2_bind2"
"sasl_ssdp"
"sasl2_fast"
"sasl_ssdp"
"csi_battery_saver"
"muc_notifications"
];
};
admins = [
"admin@${domain}"
];
allowRegistration = true;
s2sSecureAuth = true;
c2sRequireEncryption = true;
modules = {
http_files = true;
limits = true;
server_contact_info = true;
bosh = true;
motd = true;
announce = true;
welcome = true;
admin_adhoc = true;
websocket = true;
watchregistrations = true;
};
extraModules = [
"turn_external"
];
xmppComplianceSuite = true;
checkConfig = false;
ssl = {
cert = "${sslCertDir}/fullchain.pem";
key = "${sslCertDir}/key.pem";
};
virtualHosts.${domain} = {
inherit domain;
enabled = enable;
ssl = {
cert = "${sslCertDir}/fullchain.pem";
key = "${sslCertDir}/key.pem";
};
};
muc = [
{
domain = "conference.${domain}";
restrictRoomCreation = "local";
}
];
httpFileShare = {
domain = "upload.${domain}";
http_host = domain;
expires_after = "never";
size_limit = 32 * 1024 * 1024;
};
extraConfig = ''
storage = "sql"
sql = {
driver = "SQLite3";
database = "prosody.sqlite";
}
-- Keep messages
archive_expires_after = "never"
muc_log_presences = true
muc_log_expires_after = "never"
-- Recommended by Monal dev
smacks_max_queue_size = 4000
c2s_direct_tls_ports = { 5223 };
s2s_direct_tls_ports = { 5270 };
trusted_proxies = { "127.0.0.1", "::1" };
http_external_url = "https://${domain}/"
consider_bosh_secure = true;
consider_websocket_secure = true;
statistics = "internal";
statistics_interval = "manual";
'';
};
}

View file

@ -0,0 +1,6 @@
{
imports = [
./options.nix
./service.nix
];
}

View file

@ -0,0 +1,12 @@
{ lib, ... }:
with lib;
{
options.machine.redis = {
enable = mkEnableOption "Redis";
port = mkOption {
type = types.port;
default = 6379;
description = "Port to listen on.";
};
};
}

View file

@ -0,0 +1,28 @@
{
config,
lib,
sec,
...
}:
let
inherit (config.machine.redis)
port
enable
;
in
with lib; mkIf enable {
sops.secrets = {
"redis/password" = { };
};
networking.firewall = {
allowedTCPPorts = [ port ];
};
services.redis.servers."default" = {
inherit enable;
inherit port;
bind = null;
requirePassFile = sec."redis/password".path;
};
}

View file

@ -0,0 +1,6 @@
{
imports = [
./options.nix
./service.nix
];
}

View file

@ -0,0 +1,12 @@
{ lib, ... }:
with lib;
{
options.machine.roundcube = {
enable = mkEnableOption "Roundcube";
domain = mkOption {
type = types.nullOr types.str;
default = null;
description = "Domain name. If not set, will be disabled, and use the localhost.";
};
};
}

View file

@ -0,0 +1,45 @@
{
config,
pkgs,
lib,
...
}:
let
inherit (config.machine.roundcube)
enable
domain
;
in
with lib; mkIf enable {
services.roundcube = {
inherit enable;
dicts = with pkgs.aspellDicts; [
ru
en
fr
];
hostName = domain;
configureNginx = true;
extraConfig = ''
// IMAP
$config['imap_host'] = 'ssl://${config.mailserver.fqdn}';
$config['imap_conn_options'] = array(
'ssl' => array(
'verify_peer' => false,
'verify_peer_name' => false,
),
);
// SMTP
$config['smtp_host'] = 'ssl://${config.mailserver.fqdn}:465';
$config['smtp_conn_options'] = array(
'ssl' => array(
'verify_peer' => false,
'verify_peer_name' => false,
),
);
$config['smtp_user'] = '%u';
$config['smtp_pass'] = '%p';
'';
};
}

View file

@ -0,0 +1,26 @@
{
config,
lib,
...
}:
let
pgsqlEnable = config.machine.postgresql.enable;
inherit (config.machine.synapse) enable;
in
with lib; mkIf enable {
services.postgresql = with lib; mkIf pgsqlEnable {
ensureUsers = [
{
name = "matrix-synapse";
ensureDBOwnership = true;
}
];
ensureDatabases = [ "matrix-synapse" ];
};
services.matrix-synapse.settings.database = {
name = if pgsqlEnable then "psycopg2" else "sqlite3";
args = with lib; mkIf pgsqlEnable {
host = "/run/postgresql";
};
};
}

View file

@ -0,0 +1,13 @@
{
imports = [
./database.nix
./element.nix
./mail.nix
./nginx.nix
./options.nix
./redis.nix
./secrets.nix
./synapse.nix
./turn.nix
];
}

View file

@ -0,0 +1,64 @@
{
lib,
config,
pkgs,
...
}:
let
inherit (config.machine.synapse.element) domain enable;
elementConfig = {
default_server_config = {
"m.homeserver" = with config.services.matrix-synapse.settings; {
base_url = public_baseurl;
inherit server_name;
};
"m.identity_server" = {
base_url = "";
};
};
disable_custom_urls = false;
disable_guests = true;
disable_login_language_selector = false;
disable_3pid_login = false;
brand = "Element";
default_country_code = "US";
show_labs_settings = true;
features = {
feature_video_rooms = true;
feature_group_calls = true;
feature_element_call_video_rooms = true;
feature_new_device_manager = true;
feature_wysiwyg_composer = true;
};
default_federate = true;
room_directory = with config.services.matrix-synapse.settings; {
servers = [
server_name
"matrix.org"
];
};
setting_defaults = {
breadcrumbs = true;
"MessageComposerInput.autoReplaceEmoji" = true;
sendTypingNotifications = true;
showTypingNotifications = true;
showReadReceipts = true;
showJoinLeaves = false;
urlPreviewsEnabled = true;
};
sso_redirect_options = {
immediate = false;
on_welcome_page = true;
};
};
in
with lib; mkIf enable {
services.nginx.virtualHosts.${domain} = {
enableACME = true;
forceSSL = true;
root = pkgs.element-web.override (_old: {
conf = elementConfig;
});
};
}

39
services/synapse/mail.nix Normal file
View file

@ -0,0 +1,39 @@
{
config,
lib,
sec,
...
}:
let
inherit (config.machine.synapse)
enable
domain
;
address = "noreply@${domain}";
in
with lib; mkIf (enable && config.machine.mail.enable) {
services.matrix-synapse = {
settings = {
admin_contact = address;
registrations_require_3pid = [ "email" ];
email = {
smtp_host = config.machine.mail.fqdn;
smtp_port = 465;
smtp_user = address;
require_transport_security = true;
enable_tls = true;
notif_from = "RuJect Matrix <${address}>";
app_name = "RuJect Matrix";
};
};
};
mailserver = {
domains = [ domain ];
accounts.${address} = {
hashedPasswordFile = sec."mail/serviceHashedPassword".path;
aliases = [ ];
sendOnly = true;
};
};
}

View file

@ -0,0 +1,98 @@
{ config, ... }:
let
inherit (config.machine.synapse) domain port;
maxUploadSize = config.services.matrix-synapse.settings.max_upload_size;
in
{
systemd.services.nginx.serviceConfig.SupplementaryGroups = [ "matrix-synapse" ];
services.nginx = {
appendHttpConfig = ''
limit_req_zone $binary_remote_addr zone=matrix_login:10m rate=1r/s;
limit_req_zone $binary_remote_addr zone=matrix_register:10m rate=1r/m;
limit_req_zone $binary_remote_addr zone=matrix_api:10m rate=20r/s;
limit_req_zone $binary_remote_addr zone=matrix_media:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=matrix_federation:10m rate=50r/s;
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
'';
upstreams."matrix-synapse".servers = {
"unix:/run/matrix-synapse/matrix-synapse.sock" = { };
};
virtualHosts.${domain} = {
forceSSL = true;
enableACME = true;
locations = {
# Synapse client API
"/_matrix/client" = {
proxyPass = "http://127.0.0.1:${toString port}";
extraConfig = ''
limit_req zone=matrix_api burst=50 nodelay;
client_max_body_size ${maxUploadSize};
'';
};
# Login endpoint with stricter rate limiting
"~ ^/_matrix/client/(r0|v3)/login$" = {
proxyPass = "http://127.0.0.1:${toString port}";
extraConfig = ''
limit_req zone=matrix_login burst=3 nodelay;
'';
};
# Registration with very strict rate limiting
"~ ^/_matrix/client/(r0|v3)/register" = {
proxyPass = "http://127.0.0.1:${toString port}";
extraConfig = ''
limit_req zone=matrix_register burst=2 nodelay;
'';
};
# Sync endpoint with longer timeout
"~ ^/_matrix/client/(r0|v3|unstable)/sync" = {
proxyPass = "http://127.0.0.1:${toString port}";
extraConfig = ''
limit_req zone=matrix_api burst=50 nodelay;
proxy_read_timeout 600s;
'';
};
# Media
"/_matrix/media" = {
proxyPass = "http://127.0.0.1:${toString port}";
extraConfig = ''
limit_req zone=matrix_media burst=100 nodelay;
client_max_body_size ${maxUploadSize};
'';
};
# Federation
"/_matrix/federation" = {
proxyPass = "http://127.0.0.1:${toString port}";
extraConfig = ''
limit_req zone=matrix_federation burst=100 nodelay;
client_max_body_size 50M;
'';
};
"/_matrix/key" = {
proxyPass = "http://127.0.0.1:${toString port}";
extraConfig = ''
limit_req zone=matrix_federation burst=50 nodelay;
'';
};
# Health check
"= /health" = {
proxyPass = "http://127.0.0.1:${toString port}";
extraConfig = ''
access_log off;
'';
};
# Block admin API from public
"/_synapse/admin".return = "404";
};
};
};
}

View file

@ -0,0 +1,37 @@
{
lib,
config,
...
}:
with lib;
{
options.machine.synapse = {
enable = mkEnableOption "Synapse";
element = {
enable = mkEnableOption "Elemenet web";
domain = mkOption {
type = types.nullOr types.str;
default = "chat.${config.machine.synapse.domain}";
description = "Domain name.";
};
};
domain = mkOption {
type = types.str;
default = "example.com";
description = "Domain name.";
};
port = mkOption {
type = types.port;
default = 8008;
description = "Listen port.";
};
metrics = {
enable = mkEnableOption "Synapse metrics";
port = mkOption {
type = types.port;
default = 9000;
description = "Listen port.";
};
};
};
}

View file

@ -0,0 +1,19 @@
{
config,
lib,
...
}:
let
redisEnable = config.machine.redis.enable;
inherit (config.machine.synapse) enable;
in
with lib; mkIf (redisEnable && enable) {
services.redis.servers.matrix-synapse = {
enable = true;
};
systemd.services.matrix-synapse.serviceConfig.SupplementaryGroups = [ "redis-matrix-synapse" ];
services.matrix-synapse.settings.redis = {
enabled = true;
path = config.services.redis.servers.matrix-synapse.unixSocket;
};
}

View file

@ -0,0 +1,46 @@
{
config,
lib,
...
}:
let
inherit (config.machine.synapse)
enable
;
in
with lib; mkIf enable {
sops.secrets = {
"matrix/registrationSharedSecret" = {
sopsFile = ./../../secrets/common.yaml;
owner = config.users.users.matrix-synapse.name;
inherit (config.users.users.matrix-synapse) group;
restartUnits = [ "matrix-synapse.service" ];
};
"matrix/signingKey" = {
sopsFile = ./../../secrets/common.yaml;
owner = config.users.users.matrix-synapse.name;
inherit (config.users.users.matrix-synapse) group;
restartUnits = [ "matrix-synapse.service" ];
};
"turn/authSecret" = {
sopsFile = ./../../secrets/common.yaml;
owner = config.users.users.turnserver.name;
inherit (config.users.users.turnserver) group;
restartUnits = [ "coturn.service" ];
};
};
sops.templates.matrix-synapse-config = {
owner = config.users.users.matrix-synapse.name;
inherit (config.users.users.matrix-synapse) group;
restartUnits = [ "matrix-synapse.service" ];
content = ''
registration_shared_secret: ${config.sops.placeholder."matrix/registrationSharedSecret"}
turn_shared_secret: ${config.sops.placeholder."turn/authSecret"}
mail:
smtp_pass: ${config.sops.placeholder."mail/servicePassword"}
'';
};
services.matrix-synapse.extraConfigFiles = with lib; mkIf config.machine.synapse.enable [
config.sops.templates.matrix-synapse-config.path
];
}

View file

@ -0,0 +1,94 @@
{
config,
lib,
sec,
...
}:
let
inherit (config.machine.synapse)
domain
enable
port
metrics
;
in
with lib; mkIf enable {
services.matrix-synapse = {
inherit enable;
enableRegistrationScript = true;
settings = {
server_name = domain;
public_baseurl = "https://${domain}";
signing_key_path = sec."matrix/signingKey".path;
listeners = [
{
inherit port;
bind_addresses = [ "127.0.0.1" ];
type = "http";
tls = false;
x_forwarded = true;
resources = [
{
compress = true;
names = [
"client"
"federation"
];
}
];
}
]
++ (optionals metrics.enable [
{
inherit (metrics) port;
bind_addresses = [ "127.0.0.1" ];
type = "metrics";
tls = false;
resources = [
{
names = [ "metrics" ];
}
];
}
]);
enable_metrics = metrics.enable;
enable_registration = true;
enable_registration_without_verification = false;
allow_public_rooms_over_federation = true;
federation_domain_whitelist = [ ];
allow_public_rooms_without_auth = true;
url_preview_enabled = true;
url_preview_ip_range_blacklist = [
"127.0.0.0/8"
"10.0.0.0/8"
"172.16.0.0/12"
"192.168.0.0/16"
"100.64.0.0/10"
"169.254.0.0/16"
"::1/128"
"fe80::/10"
"fc00::/7"
];
dynamic_thumbnails = true;
max_upload_size = "50M";
media_retention = {
local_media_lifetime = "90d";
remote_media_lifetime = "14d";
};
retention = {
enabled = true;
default_policy.max_lifetime = "180d";
purge_jobs = [
{ interval = "1d"; }
];
};
};
};
}

18
services/synapse/turn.nix Normal file
View file

@ -0,0 +1,18 @@
{
config,
lib,
...
}:
let
inherit (config.machine.coturn) enable;
in
with lib; mkIf (enable && config.machine.coturn.enable) {
services.matrix-synapse.settings = with config.services.coturn; {
turn_uris = [
"turn:${realm}:3478?transport=udp"
"turn:${realm}:3478?transport=tcp"
];
turn_user_lifetime = "1h";
turn_allow_guests = false;
};
}

View file

@ -0,0 +1,6 @@
{
imports = [
./options.nix
./service.nix
];
}

View file

@ -0,0 +1,17 @@
{ lib, ... }:
with lib;
{
options.machine.uptime-kuma = {
enable = mkEnableOption "Uptime Kuma";
domain = mkOption {
type = types.nullOr types.str;
default = null;
description = "Domain name. If not set, will be disabled, and use the localhost.";
};
port = mkOption {
type = types.port;
default = 4000;
description = "Listen port.";
};
};
}

View file

@ -0,0 +1,35 @@
{
config,
lib,
...
}:
let
inherit (config.machine.uptime-kuma)
domain
enable
port
;
in
with lib; mkIf enable {
services.uptime-kuma = {
inherit enable;
settings = {
PORT = toString port;
};
};
services.nginx.virtualHosts = with lib; mkIf (domain != null) {
"${domain}" = {
forceSSL = true;
enableACME = true;
locations."/" = {
proxyPass = "http://127.0.0.1:${toString port}";
proxyWebsockets = true;
};
};
};
networking.firewall = {
allowedTCPPorts = [ port ];
};
}

View file

@ -0,0 +1,6 @@
{
imports = [
./options.nix
./service.nix
];
}

View file

@ -0,0 +1,17 @@
{ lib, ... }:
with lib;
{
options.machine.vaultwarden = {
enable = mkEnableOption "Vaultwarden";
domain = mkOption {
type = types.nullOr types.str;
default = null;
description = "Domain name. If not set, will be disabled, and use the localhost.";
};
port = mkOption {
type = types.port;
default = 4534;
description = "Listen port.";
};
};
}

View file

@ -0,0 +1,55 @@
{
config,
lib,
...
}:
let
inherit (config.machine.vaultwarden)
enable
domain
port
;
in
with lib; mkIf enable {
networking.firewall = {
allowedTCPPorts = [ port ];
};
services.nginx.virtualHosts = with lib; mkIf (domain != null) {
"${domain}" = {
forceSSL = true;
enableACME = true;
locations."/" = {
proxyPass = "http://127.0.0.1:${toString port}";
proxyWebsockets = true;
extraConfig = ''
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;
'';
};
extraConfig = ''
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header Referrer-Policy same-origin;
add_header X-XSS-Protection "1; mode=block";
'';
};
};
services.vaultwarden = {
inherit enable;
backupDir = "/var/local/vaultwarden/backup";
environmentFile = "/var/lib/vaultwarden/vaultwarden.env";
config = {
DOMAIN = "https://${domain}";
SIGNUPS_ALLOWED = true;
ROCKET_ADDRESS = "127.0.0.1";
ROCKET_PORT = port;
ROCKET_LOG = "critical";
};
};
}

View file

@ -0,0 +1,8 @@
{
imports = [
./options.nix
./firewall.nix
./nginx.nix
./service.nix
];
}

View file

@ -0,0 +1,24 @@
{
config,
lib,
...
}:
let
inherit (config.machine.xray-3x-ui)
enable
port
;
in
with lib; mkIf enable {
networking.firewall.allowedTCPPorts = [
# Web panel
port
# SSL & HTTP
80
443
# Inbounds
1082
];
}

View file

@ -0,0 +1,44 @@
{
lib,
config,
...
}: let
inherit
(config.machine.xray-3x-ui)
enable
port
domain
subscriptions
;
in {
services.nginx.virtualHosts = with lib; mkIf enable {
${domain} = with lib; mkIf (domain != null) {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "http://127.0.0.1:${toString port}";
proxyWebsockets = true;
extraConfig = ''
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_redirect off;
'';
};
};
${subscriptions.domain} = with lib; mkIf (subscriptions.domain != null) {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "http://127.0.0.1:2096";
proxyWebsockets = true;
extraConfig = ''
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_redirect off;
'';
};
};
};
}

View file

@ -0,0 +1,33 @@
{ lib, config, ... }:
with lib;
{
options.machine.xray-3x-ui = {
enable = mkEnableOption "3x-ui Xray panel";
port = mkOption {
type = types.port;
default = 2053;
description = "Port for the web interface.";
};
domain = mkOption {
type = types.nullOr types.str;
default = null;
description = "domain for the web interface.";
};
subscriptions = {
domain = mkOption {
type = types.nullOr types.str;
default = null;
description = "domain for the web interface.";
};
};
dataDir = mkOption {
type = types.path;
default = "/var/lib/3x-ui";
description = "Directory to store 3x-ui data.";
};
};
}

View file

@ -0,0 +1,96 @@
# See https://github.com/sunmeplz/xray-3x-ui
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.machine.xray-3x-ui;
# Minimum Go version required for building
minGoVersion = "1.26.0";
xray-3x-ui =
assert assertMsg
(versionAtLeast pkgs.go.version minGoVersion)
"3x-ui requires Go >= ${minGoVersion}, but ${pkgs.go.version} is available";
pkgs.buildGoModule rec {
pname = "3x-ui";
version = "2.8.11";
src = pkgs.fetchFromGitHub {
owner = "MHSanaei";
repo = "3x-ui";
rev = "v${version}";
hash = "sha256-2I6t3caf2t7nKSFwxI/dVIobpBzuptrgauuXfFw8ltg=";
};
vendorHash = "sha256-M8YQTMfF/xZut4hxUcAfF2xGK625vwJNp4JS/zoXUCQ=";
ldflags = [ "-s" "-w" ];
meta = with lib; {
description = "Xray panel supporting multi-protocol multi-user";
homepage = "https://github.com/MHSanaei/3x-ui";
license = licenses.gpl3Only;
platforms = platforms.linux;
maintainers = [ ];
};
};
in {
# Service implementation
config = mkIf cfg.enable {
# User and group configuration
users.users.xray-3x-ui = {
isSystemUser = true;
group = "xray-3x-ui";
description = "3x-ui service user";
};
users.groups.xray-3x-ui = { };
# Directory structure
systemd.tmpfiles.rules = [
"d ${cfg.dataDir} 0755 xray-3x-ui xray-3x-ui -"
"d ${cfg.dataDir}/bin 0755 xray-3x-ui xray-3x-ui -"
"d ${cfg.dataDir}/logs 0755 xray-3x-ui xray-3x-ui -"
];
# Systemd service
systemd.services.xray-3x-ui = {
description = "3x-ui Xray Panel";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
environment = {
XUI_DB_FOLDER = cfg.dataDir;
XUI_BIN_FOLDER = "${cfg.dataDir}/bin";
XUI_LOG_FOLDER = "${cfg.dataDir}/logs";
};
preStart = ''
# Symlink xray-core binary to expected location
ln -sf ${pkgs.xray}/bin/xray ${cfg.dataDir}/bin/xray-linux-amd64
'';
serviceConfig = {
Type = "simple";
ExecStart = "${xray-3x-ui}/bin/3x-ui";
WorkingDirectory = cfg.dataDir;
Restart = "on-failure";
RestartSec = "10s";
User = "xray-3x-ui";
Group = "xray-3x-ui";
StateDirectory = "3x-ui 3x-ui/bin 3x-ui/logs";
StateDirectoryMode = "0755";
AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" "CAP_NET_ADMIN" ];
CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" "CAP_NET_ADMIN" ];
};
};
# Add to system packages for CLI access
environment.systemPackages = [ xray-3x-ui ];
};
}