Compare commits

...

15 Commits

Author SHA1 Message Date
6ec24bd694
nixos: clean up removed service modules
Some checks failed
Check flake / build-amd64-linux (push) Failing after 2m45s
2025-03-10 16:28:18 +00:00
df7ebdb825
music-assistant: move out of module 2025-03-10 16:27:25 +00:00
8d4f1eb419
home-assistant: move out of module 2025-03-10 16:24:34 +00:00
d7779df053
mail: move out of module 2025-03-10 16:23:17 +00:00
2e48aa9ef3
gitea-runner: move out of module 2025-03-10 16:16:26 +00:00
f5e418fb46
chrony: remove 2025-03-10 16:12:23 +00:00
d77edfab75
netbird: remove 2025-03-10 16:11:39 +00:00
68506d4868
nginx: move out of module 2025-03-10 16:08:11 +00:00
5d308dce5e
coturn: move out of module 2025-03-10 16:05:10 +00:00
c0c435c2da
headscale: move out of module 2025-03-10 16:02:05 +00:00
0c390a7c91
matrix: move out of module 2025-03-10 15:59:15 +00:00
de562313ea
photoprism: move out of module 2025-03-10 15:51:28 +00:00
06600f76ca
outline: refactor 2025-03-10 15:42:19 +00:00
cbf449c356
kanidm: refactor 2025-03-10 15:39:07 +00:00
6bcc543cb4
gitea: move out of module 2025-03-10 15:33:25 +00:00
32 changed files with 1454 additions and 1721 deletions

View File

@ -9,6 +9,7 @@
self.inputs.disko.nixosModules.disko
./hardware-configuration.nix
./disko-config.nix
./mail.nix
../server.nix
];
@ -47,7 +48,6 @@
];
repoPath = "ssh://kg2mpt28@kg2mpt28.repo.borgbase.com/./repo";
};
mail.enable = true;
};
};

72
hosts/mail/mail.nix Normal file
View File

@ -0,0 +1,72 @@
{
config,
self,
...
}:
let
domains = [
"h0lt.com"
"jdholt.com"
"jordanholt.xyz"
"vimium.co"
"vimium.com"
"vimium.co.uk"
"vimium.info"
"vimium.net"
"vimium.org"
"vimium.xyz"
];
in
{
imports = [
self.inputs.nixos-mailserver.nixosModule
];
services.roundcube = {
enable = true;
hostName = config.mailserver.fqdn;
extraConfig = ''
$config['smtp_server'] = "tls://${config.mailserver.fqdn}";
$config['smtp_user'] = "%u";
$config['smtp_pass'] = "%p";
'';
plugins = [ "contextmenu" ];
};
services.nginx.enable = true;
networking.firewall.allowedTCPPorts = [
80
443
];
mailserver = {
enable = true;
fqdn = "mail.vimium.com";
domains = domains;
indexDir = "/var/lib/dovecot/indices";
certificateDomains = [
"imap.vimium.com"
"smtp.vimium.com"
];
certificateScheme = "acme-nginx";
fullTextSearch.enable = true;
loginAccounts = {
"jordan@vimium.com" = {
hashedPasswordFile = config.users.users.jordan.hashedPasswordFile;
catchAll = domains;
};
};
extraVirtualAliases = {
"hostmaster@vimium.com" = "jordan@vimium.com";
"postmaster@vimium.com" = "jordan@vimium.com";
"webmaster@vimium.com" = "jordan@vimium.com";
"abuse@vimium.com" = "jordan@vimium.com";
};
};
}

View File

@ -8,6 +8,7 @@
{
imports = [
./hardware-configuration.nix
./gitea-runner.nix
../desktop.nix
];
@ -112,7 +113,6 @@
];
repoPath = "ssh://iqwu22oq@iqwu22oq.repo.borgbase.com/./repo";
};
gitea-runner.enable = true;
};
shell = {
zsh.enable = true;

View File

@ -0,0 +1,228 @@
{
pkgs,
config,
self,
...
}:
# Based on: https://git.clan.lol/clan/clan-infra/src/branch/main/modules/web01/gitea/actions-runner.nix
let
hostname = config.networking.hostName;
giteaUrl = "https://git.vimium.com";
storeDepsBins = with pkgs; [
coreutils
findutils
gnugrep
gawk
git
nix
nix-update
bash
jq
nodejs
];
storeDeps = pkgs.runCommand "store-deps" { } ''
mkdir -p $out/bin
for dir in ${toString storeDepsBins}; do
for bin in "$dir"/bin/*; do
ln -s "$bin" "$out/bin/$(basename "$bin")"
done
done
# Add SSL CA certs
mkdir -p $out/etc/ssl/certs
cp -a "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt" $out/etc/ssl/certs/ca-bundle.crt
'';
in
{
modules.podman.enable = true;
systemd.services = {
gitea-runner-nix-image = {
wantedBy = [ "multi-user.target" ];
after = [ "podman.service" ];
requires = [ "podman.service" ];
path = [
config.virtualisation.podman.package
pkgs.gnutar
pkgs.shadow
pkgs.getent
];
script = ''
set -eux -o pipefail
mkdir -p etc/nix
# Create an unpriveleged user that we can use also without the run-as-user.sh script
touch etc/passwd etc/group
groupid=$(cut -d: -f3 < <(getent group nix-ci-user))
userid=$(cut -d: -f3 < <(getent passwd nix-ci-user))
groupadd --prefix $(pwd) --gid "$groupid" nix-ci-user
emptypassword='$6$1ero.LwbisiU.h3D$GGmnmECbPotJoPQ5eoSTD6tTjKnSWZcjHoVTkxFLZP17W9hRi/XkmCiAMOfWruUwy8gMjINrBMNODc7cYEo4K.'
useradd --prefix $(pwd) -p "$emptypassword" -m -d /tmp -u "$userid" -g "$groupid" -G nix-ci-user nix-ci-user
cat <<NIX_CONFIG > etc/nix/nix.conf
accept-flake-config = true
experimental-features = nix-command flakes
NIX_CONFIG
cat <<NSSWITCH > etc/nsswitch.conf
passwd: files mymachines systemd
group: files mymachines systemd
shadow: files
hosts: files mymachines dns myhostname
networks: files
ethers: files
services: files
protocols: files
rpc: files
NSSWITCH
# list the content as it will be imported into the container
tar -cv . | tar -tvf -
tar -cv . | podman import - gitea-runner-nix
'';
serviceConfig = {
RuntimeDirectory = "gitea-runner-nix-image";
WorkingDirectory = "/run/gitea-runner-nix-image";
Type = "oneshot";
RemainAfterExit = true;
};
};
gitea-runner-nix = {
after = [ "gitea-runner-nix-image.service" ];
requires = [ "gitea-runner-nix-image.service" ];
serviceConfig = {
# Hardening (may overlap with DynamicUser=)
# The following options are only for optimizing output of systemd-analyze
AmbientCapabilities = "";
CapabilityBoundingSet = "";
# ProtectClock= adds DeviceAllow=char-rtc r
DeviceAllow = "";
NoNewPrivileges = true;
PrivateDevices = true;
PrivateMounts = true;
PrivateTmp = true;
PrivateUsers = true;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectSystem = "strict";
RemoveIPC = true;
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
UMask = "0066";
ProtectProc = "invisible";
SystemCallFilter = [
"~@clock"
"~@cpu-emulation"
"~@module"
"~@mount"
"~@obsolete"
"~@raw-io"
"~@reboot"
"~@swap"
# needed by go?
#"~@resources"
"~@privileged"
"~capset"
"~setdomainname"
"~sethostname"
];
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_UNIX"
"AF_NETLINK"
];
# Needs network access
PrivateNetwork = false;
# Cannot be true due to Node
MemoryDenyWriteExecute = false;
# The more restrictive "pid" option makes `nix` commands in CI emit
# "GC Warning: Couldn't read /proc/stat"
# You may want to set this to "pid" if not using `nix` commands
ProcSubset = "all";
# Coverage programs for compiled code such as `cargo-tarpaulin` disable
# ASLR (address space layout randomization) which requires the
# `personality` syscall
# You may want to set this to `true` if not using coverage tooling on
# compiled code
LockPersonality = false;
# Note that this has some interactions with the User setting; so you may
# want to consult the systemd docs if using both.
DynamicUser = true;
};
};
};
users.users.nix-ci-user = {
group = "nix-ci-user";
description = "Used for running nix-based CI jobs";
home = "/var/empty";
isSystemUser = true;
};
users.groups.nix-ci-user = { };
age.secrets."files/services/gitea-runner/${hostname}-token" = {
file = "${self.inputs.secrets}/files/services/gitea-runner/${hostname}-token.age";
group = "podman";
};
services.gitea-actions-runner.instances = {
act = {
enable = true;
url = giteaUrl;
name = "act-runner-${hostname}";
tokenFile = config.age.secrets."files/services/gitea-runner/${hostname}-token".path;
settings = {
cache.enabled = true;
runner.capacity = 4;
};
labels = [
"debian-latest:docker://ghcr.io/catthehacker/ubuntu:act-latest"
"ubuntu-latest:docker://ghcr.io/catthehacker/ubuntu:act-latest"
];
};
nix = {
enable = true;
url = giteaUrl;
name = "nix-runner-${hostname}";
tokenFile = config.age.secrets."files/services/gitea-runner/${hostname}-token".path;
settings = {
cache.enabled = true;
container = {
options = "-e NIX_BUILD_SHELL=/bin/bash -e PAGER=cat -e PATH=/bin -e SSL_CERT_FILE=/etc/ssl/certs/ca-bundle.crt --device /dev/kvm -v /nix:/nix -v ${storeDeps}/bin:/bin -v ${storeDeps}/etc/ssl:/etc/ssl --user nix-ci-user";
network = "host";
valid_volumes = [
"/nix"
"${storeDeps}/bin"
"${storeDeps}/etc/ssl"
];
};
runner.capacity = 4;
};
labels = [
"nix:docker://gitea-runner-nix"
];
};
};
}

View File

@ -8,6 +8,7 @@
imports = [
self.inputs.nixos-hardware.nixosModules.raspberry-pi-4
./hardware-configuration.nix
./home-assistant
../server.nix
];
@ -124,7 +125,6 @@
enable = true;
repoPath = "ssh://qcw86s11@qcw86s11.repo.borgbase.com/./repo";
};
home-assistant.enable = true;
};
};

View File

@ -0,0 +1,297 @@
{
config,
pkgs,
self,
...
}:
{
imports = [
./floorplan/default.nix
./mqtt.nix
];
age.secrets."files/services/home-assistant/secrets.yaml" = {
file = "${self.inputs.secrets}/files/services/home-assistant/secrets.yaml.age";
path = "${config.services.home-assistant.configDir}/secrets.yaml";
owner = "hass";
group = "hass";
};
services.home-assistant = {
enable = true;
config = {
automation = "!include automations.yaml";
backup = { };
binary_sensor = [ ];
default_config = { };
http = {
server_host = "::1";
trusted_proxies = [ "::1" ];
use_x_forwarded_for = true;
};
ffmpeg = { };
homeassistant = {
name = "Home";
latitude = "!secret latitude";
longitude = "!secret longitude";
country = "GB";
temperature_unit = "C";
time_zone = config.time.timeZone;
unit_system = "metric";
auth_providers = [
{
type = "trusted_networks";
trusted_networks = [
"100.64.0.0/10"
"127.0.0.1"
];
allow_bypass_login = true;
}
{
type = "homeassistant";
}
];
};
logger = {
default = "info";
logs = { };
};
lovelace = {
mode = "yaml";
};
media_player = [ ];
mobile_app = { };
onkyo = { };
open_meteo = { };
recorder = {
purge_keep_days = 365;
};
scene = "!include scenes.yaml";
script = "!include scripts.yaml";
sensor = [ ];
system_health = { };
zeroconf = { };
};
extraComponents = [
"air_quality"
"airly"
"alert"
"api"
"application_credentials"
"asuswrt"
"auth"
"automation"
"bayesian"
"binary_sensor"
# "blackbird"
"blueprint"
"bluetooth_adapters"
"bluetooth_le_tracker"
"button"
"calendar"
"camera"
"cast"
"cert_expiry"
"climate"
"co2signal"
"color_extractor"
"command_line"
"compensation"
"configurator"
"counter"
"cover"
"cpuspeed"
"default_config"
"demo"
"derivative"
"device_automation"
"device_sun_light_trigger"
"device_tracker"
"dlna_dmr"
"dlna_dms"
"dnsip"
"esphome"
"fail2ban"
"fan"
"feedreader"
"ffmpeg"
"file"
"file_upload"
"filesize"
"folder"
"folder_watcher"
"forecast_solar"
"frontend"
"gdacs"
"generic"
"generic_hygrostat"
"generic_thermostat"
"geo_json_events"
"geo_location"
"geo_rss_events"
"github"
"group"
"hardware"
"hdmi_cec"
"history_stats"
"homeassistant"
"homekit"
"homekit_controller"
"html5"
"http"
"humidifier"
"icloud"
"image_processing"
"input_boolean"
"input_button"
"input_datetime"
"input_number"
"input_select"
"input_text"
"integration"
"ios"
"jellyfin"
"light"
"local_calendar"
"local_file"
"local_ip"
"local_todo"
"lock"
"logentries"
"logger"
"lovelace"
"manual"
"manual_mqtt"
"matter"
"media_player"
"min_max"
"mjpeg"
"modern_forms"
"mold_indicator"
"moon"
"mysensors"
"network"
"nmap_tracker"
"notify"
"number"
"onboarding"
"onkyo"
"panel_custom"
"persistent_notification"
"person"
"ping"
"plant"
"prometheus"
"proximity"
"push"
"proximity"
"python_script"
"radio_browser"
"random"
"recorder"
"remote"
"repairs"
"rest"
"rest_command"
"rss_feed_template"
"scene"
"schedule"
"scrape"
"script"
"search"
"season"
"select"
"sense"
"sensor"
"sensorpush"
"shell_command"
"shopping_list"
"siren"
"smtp"
"snmp"
"sql"
"statistics"
"sun"
"switch"
"switch_as_x"
"system_health"
"system_log"
"systemmonitor"
"tag"
"tailscale"
"tcp"
"template"
"text"
"thread"
"threshold"
"time_date"
"timer"
"tod"
"todo"
"tomorrowio"
"trend"
"universal"
"upb"
"update"
"upnp"
"uptime"
"utility_meter"
"vacuum"
"vlc"
"vlc_telnet"
"wake_on_lan"
"water_heater"
"weather"
"websocket_api"
"wled"
"workday"
"worldclock"
"zone"
];
extraPackages =
python3Packages: with python3Packages; [
onkyo-eiscp
zeroconf
];
customComponents = with pkgs; [
home-assistant-browser-mod
];
customLovelaceModules = with pkgs.home-assistant-custom-lovelace-modules; [
bubble-card
button-card
card-mod
light-entity-card
mini-graph-card
mini-media-player
mushroom
sankey-chart
universal-remote-card
];
lovelaceConfigWritable = true;
};
modules.services.borgmatic.directories = [
config.services.home-assistant.configDir
];
services.nginx = {
enable = true;
virtualHosts."home.mesh.vimium.net" = {
forceSSL = false;
extraConfig = ''
proxy_buffering off;
'';
locations."/" = {
proxyPass = "http://[::1]:8123";
proxyWebsockets = true;
};
};
};
}

View File

@ -0,0 +1,25 @@
{
config,
...
}:
{
services = {
nginx.virtualHosts."music-assistant.${config.networking.hostName}" = {
extraConfig = ''
proxy_buffering off;
'';
locations."/" = {
proxyPass = "http://[::1]:8095";
proxyWebsockets = true;
};
};
music-assistant = {
enable = true;
providers = [
"hass"
"jellyfin"
];
};
};
}

117
hosts/vps1/coturn.nix Normal file
View File

@ -0,0 +1,117 @@
{
config,
lib,
self,
...
}:
let
realm = "turn.vimium.com";
matrixIntegration = true;
in
{
networking.firewall =
let
range =
with config.services.coturn;
lib.singleton {
from = min-port;
to = max-port;
};
in
{
allowedTCPPorts = [
3478 # TURN listener
5349 # STUN TLS
5350 # STUN TLS alt
];
allowedUDPPorts = [
3478 # TURN listener
5349 # TLS
5350 # TLS alt
];
allowedUDPPortRanges = range; # TURN peer relays
};
security.acme.certs = {
"${config.services.coturn.realm}" = {
group = "turnserver";
reloadServices = [ "coturn" ];
};
};
age.secrets =
{
"passwords/services/coturn/static-auth-secret" = {
file = "${self.inputs.secrets}/passwords/services/coturn/static-auth-secret.age";
owner = "turnserver";
group = "turnserver";
};
}
// (
if matrixIntegration then
{
"passwords/services/coturn/matrix-turn-config.yml" = {
file = "${self.inputs.secrets}/passwords/services/coturn/matrix-turn-config.yml.age";
owner = "matrix-synapse";
group = "matrix-synapse";
};
}
else
{ }
);
services.coturn = rec {
enable = true;
realm = realm;
use-auth-secret = true;
static-auth-secret-file = config.age.secrets."passwords/services/coturn/static-auth-secret".path;
cert = "${config.security.acme.certs.${realm}.directory}/full.pem";
pkey = "${config.security.acme.certs.${realm}.directory}/key.pem";
min-port = 49000;
max-port = 50000;
no-cli = true;
no-tcp-relay = true;
extraConfig = ''
cipher-list="HIGH"
no-multicast-peers
# Ban private CIDR blocks
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
'';
};
services.matrix-synapse = lib.mkIf matrixIntegration {
settings = with config.services.coturn; {
turn_uris = [
"turn:${realm}:3478?transport=udp"
"turn:${realm}:3478?transport=tcp"
];
turn_user_lifetime = "1h";
};
extraConfigFiles = [
config.age.secrets."passwords/services/coturn/matrix-turn-config.yml".path
];
};
}

View File

@ -1,20 +1,24 @@
{
config,
pkgs,
lib,
self,
...
}:
{
imports = [
./hardware-configuration.nix
./coturn.nix
./gitea.nix
./headscale.nix
./kanidm.nix
./matrix.nix
./nginx.nix
./outline.nix
./photoprism.nix
../server.nix
];
nixpkgs = {
hostPlatform = "x86_64-linux";
config.allowUnfree = true;
};
networking = {
@ -46,92 +50,11 @@
groups = {
jellyfin = { };
};
extraGroups.acme.members = [
"kanidm"
"nginx"
];
};
services.openssh.settings.PermitRootLogin = lib.mkForce "prohibit-password";
security.acme.certs."auth.vimium.com" = {
postRun = "systemctl restart kanidm.service";
group = "acme";
};
services.kanidm =
let
baseDomain = "vimium.com";
domain = "auth.${baseDomain}";
uri = "https://${domain}";
in
{
package = pkgs.unstable.kanidm;
enableClient = true;
enableServer = true;
clientSettings = {
inherit uri;
};
serverSettings = {
bindaddress = "127.0.0.1:3013";
ldapbindaddress = "100.64.0.1:636";
domain = baseDomain;
origin = uri;
tls_chain = "${config.security.acme.certs.${domain}.directory}/full.pem";
tls_key = "${config.security.acme.certs.${domain}.directory}/key.pem";
};
};
services.nginx.virtualHosts = {
"auth.vimium.com" = {
useACMEHost = "auth.vimium.com";
forceSSL = true;
locations."/" = {
proxyPass = "https://127.0.0.1:3013";
};
};
"outline.vimium.com" = {
forceSSL = true;
enableACME = true;
locations."/" = {
proxyPass = "http://127.0.0.1:3000";
extraConfig = ''
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
'';
};
};
};
age.secrets."passwords/services/outline/oidc-client-secret" = {
file = "${self.inputs.secrets}/passwords/services/outline/oidc-client-secret.age";
owner = "outline";
group = "outline";
};
services.outline = {
enable = true;
forceHttps = false;
oidcAuthentication = {
clientId = "outline";
clientSecretFile = config.age.secrets."passwords/services/outline/oidc-client-secret".path;
displayName = "Vimium";
authUrl = "https://auth.vimium.com/ui/oauth2";
tokenUrl = "https://auth.vimium.com/oauth2/token";
userinfoUrl = "https://auth.vimium.com/oauth2/openid/outline/userinfo";
};
publicUrl = "https://outline.vimium.com";
storage.storageType = "local";
};
modules = rec {
modules = {
services = {
borgmatic = {
enable = true;
@ -142,23 +65,6 @@
];
repoPath = "ssh://p91y8oh7@p91y8oh7.repo.borgbase.com/./repo";
};
coturn = {
enable = true;
realm = "turn.vimium.com";
matrixIntegration = true;
};
gitea.enable = true;
headscale.enable = true;
matrix = {
enable = true;
bridges = {
signal = true;
whatsapp = true;
};
usePostgresql = services.postgresql.enable;
};
nginx.enable = true;
photoprism.enable = true;
postgresql.enable = true;
};
};

89
hosts/vps1/gitea.nix Normal file
View File

@ -0,0 +1,89 @@
{
config,
lib,
pkgs,
self,
...
}:
let
domain = "git.vimium.com";
in
{
users = {
users.git = {
isSystemUser = true;
useDefaultShell = true;
group = "git";
extraGroups = [ "gitea" ];
home = config.services.gitea.stateDir;
};
groups.git = { };
};
services.nginx = {
upstreams.gitea = {
servers = {
"unix:${config.services.gitea.settings.server.HTTP_ADDR}" = { };
};
};
virtualHosts = {
"${domain}" = {
forceSSL = true;
enableACME = true;
locations."/".proxyPass = "http://gitea";
};
};
};
systemd.tmpfiles.rules = [
"d '${config.services.gitea.customDir}/public/assets/css' 0750 ${config.services.gitea.user} ${config.services.gitea.group} - -"
"L+ '${config.services.gitea.customDir}/public/assets/css/theme-github.css' - - - - ${self.inputs.gitea-github-theme}/theme-github.css"
"L+ '${config.services.gitea.customDir}/public/assets/css/theme-github-auto.css' - - - - ${self.inputs.gitea-github-theme}/theme-github-auto.css"
"L+ '${config.services.gitea.customDir}/public/assets/css/theme-github-dark.css' - - - - ${self.inputs.gitea-github-theme}/theme-github-dark.css"
];
services.gitea = rec {
package = pkgs.unstable.gitea;
enable = true;
user = "git";
appName = "Vimium Git";
stateDir = "/var/lib/gitea";
repositoryRoot = "${stateDir}/repositories";
database = {
type = "sqlite3";
inherit user;
path = "${stateDir}/gitea.db";
};
lfs = {
enable = true;
contentDir = "${stateDir}/lfs";
};
settings = {
server = {
DOMAIN = config.networking.domain;
LANDING_PAGE = "explore";
OFFLINE_MODE = true;
PROTOCOL = "http+unix";
SSH_USER = "git";
SSH_DOMAIN = "${domain}";
SSH_PORT = lib.head config.services.openssh.ports;
ROOT_URL = "https://${domain}/";
};
service.DISABLE_REGISTRATION = true;
session.COOKIE_SECURE = true;
log = {
ROOT_PATH = "${stateDir}/log";
"logger.router.MODE" = "";
};
ui = {
THEMES = "gitea,arc-green,github,github-auto,github-dark";
DEFAULT_THEME = "github-dark";
};
actions.ENABLED = true;
indexer = {
REPO_INDEXER_ENABLED = true;
};
packages.CHUNKED_UPLOAD_PATH = lib.mkForce "${stateDir}/data/tmp/package-upload";
};
};
}

61
hosts/vps1/headscale.nix Normal file
View File

@ -0,0 +1,61 @@
{
config,
pkgs,
...
}:
let
domain = "headscale.vimium.net";
in
{
environment.systemPackages = [ pkgs.headscale ];
services.headscale = {
enable = true;
port = 8080;
settings = {
policy.path = null;
ip_prefixes = [
"100.64.0.0/10"
];
server_url = "https://${domain}";
derp = {
auto_update_enable = false;
update_frequency = "24h";
};
dns = {
base_domain = "mesh.vimium.net";
extra_records = [
{
name = "grafana.mesh.vimium.net";
type = "A";
value = "100.64.0.6";
}
{
name = "home.mesh.vimium.net";
type = "A";
value = "100.64.0.7";
}
];
magic_dns = true;
nameservers.global = [
"9.9.9.9"
];
};
logtail.enabled = false;
};
};
services.nginx.virtualHosts = {
"${domain}" = {
forceSSL = true;
enableACME = true;
locations."/" = {
proxyPass = "http://localhost:${toString config.services.headscale.port}";
proxyWebsockets = true;
};
};
};
}

51
hosts/vps1/kanidm.nix Normal file
View File

@ -0,0 +1,51 @@
{
config,
pkgs,
...
}:
let
baseDomain = "vimium.com";
domain = "auth.${baseDomain}";
in
{
services.kanidm =
let
uri = "https://${domain}";
in
{
package = pkgs.unstable.kanidm;
enableClient = true;
enableServer = true;
clientSettings = {
inherit uri;
};
serverSettings = {
bindaddress = "127.0.0.1:3013";
ldapbindaddress = "100.64.0.1:636";
domain = baseDomain;
origin = uri;
tls_chain = "${config.security.acme.certs.${domain}.directory}/full.pem";
tls_key = "${config.security.acme.certs.${domain}.directory}/key.pem";
};
};
services.nginx.virtualHosts = {
"${domain}" = {
useACMEHost = "${domain}";
forceSSL = true;
locations."/" = {
proxyPass = "https://127.0.0.1:3013";
};
};
};
users.extraGroups.acme.members = [
"kanidm"
"nginx"
];
security.acme.certs."${domain}" = {
postRun = "systemctl restart kanidm.service";
group = "acme";
};
}

221
hosts/vps1/matrix.nix Normal file
View File

@ -0,0 +1,221 @@
{
config,
lib,
pkgs,
...
}:
let
serverName = "vimium.com";
useElement = true;
usePostgresql = true;
bridges = {
signal = true;
whatsapp = true;
};
matrixSubdomain = "matrix.${serverName}";
elementSubdomain = "chat.${serverName}";
matrixClientConfig = {
"m.homeserver" = {
base_url = "https://${matrixSubdomain}";
server_name = serverName;
};
"m.identity_server" = {
"base_url" = "https://vector.im";
};
};
matrixServerConfig."m.server" = "${matrixSubdomain}:443";
commonBridgeSettings = bridge: {
appservice = {
database = lib.mkIf usePostgresql {
type = "postgres";
uri = "postgresql:///${bridge}?host=/run/postgresql";
};
};
bridge = {
encryption = {
allow = true;
default = true;
require = true;
};
permissions = {
"${serverName}" = "user";
"@jordan:${serverName}" = "admin";
};
provisioning = {
shared_secret = "disable";
};
};
homeserver = {
address = "https://${matrixSubdomain}";
domain = serverName;
};
};
in
{
networking.firewall.allowedTCPPorts = [
8448 # Matrix federation
];
security.acme.certs = {
"${matrixSubdomain}" = {
reloadServices = [ "matrix-synapse" ];
};
};
services.nginx.virtualHosts =
{
"${matrixSubdomain}" = {
forceSSL = true;
enableACME = true;
listen = [
{
addr = "0.0.0.0";
port = 443;
ssl = true;
}
{
addr = "0.0.0.0";
port = 80;
}
{
addr = "0.0.0.0";
port = 8448;
ssl = true;
}
{
addr = "[::1]";
port = 443;
ssl = true;
}
{
addr = "[::1]";
port = 80;
}
{
addr = "[::1]";
port = 8448;
ssl = true;
}
];
locations = {
"/" = {
proxyPass = "http://localhost:8008";
extraConfig = ''
proxy_set_header X-Forwarded-For $remote_addr;
'';
};
"/_matrix" = {
proxyPass = "http://localhost:8008";
extraConfig = ''
proxy_set_header X-Forwarded-For $remote_addr;
client_max_body_size 50M;
'';
};
"/_synapse/client".proxyPass = "http://localhost:8008";
};
};
"${cfg.serverName}" =
let
mkWellKnown = data: ''
more_set_headers 'Content-Type: application/json';
return 200 '${builtins.toJSON data}';
'';
in
{
locations."= /.well-known/matrix/server".extraConfig = (mkWellKnown matrixServerConfig);
locations."= /.well-known/matrix/client".extraConfig = (mkWellKnown matrixClientConfig);
};
}
// (
if useElement then
{
"${elementSubdomain}" = {
forceSSL = true;
enableACME = true;
root = pkgs.unstable.element-web.override {
conf = {
default_server_config = matrixClientConfig;
brand = "Vimium Chat";
branding = {
auth_header_logo_url = "https://vimium.com/images/logo.svg";
auth_footer_links = [
{
"text" = "Vimium.com";
"url" = "https://vimium.com";
}
];
};
};
};
};
}
else
{ }
);
nixpkgs.config.permittedInsecurePackages = [
"jitsi-meet-1.0.8043"
"olm-3.2.16"
];
services.matrix-synapse = {
enable = true;
enableRegistrationScript = true;
settings = {
database.name = (if usePostgresql then "psycopg2" else "sqlite3");
enable_metrics = false;
enable_registration = false;
max_upload_size = "100M";
report_stats = false;
server_name = serverName;
};
};
systemd.services.matrix-synapse.serviceConfig.SupplementaryGroups = (
lib.optional bridges.whatsapp config.systemd.services.mautrix-whatsapp.serviceConfig.Group
);
services.postgresql = lib.mkIf usePostgresql {
ensureUsers =
[
{
name = "matrix-synapse";
ensureDBOwnership = true;
}
]
++ (lib.optional bridges.signal {
name = "mautrix-signal";
ensureDBOwnership = true;
})
++ (lib.optional bridges.whatsapp {
name = "mautrix-whatsapp";
ensureDBOwnership = true;
});
ensureDatabases =
[
"matrix-synapse"
]
++ (lib.optional bridges.signal "mautrix-signal")
++ (lib.optional bridges.whatsapp "mautrix-whatsapp");
};
services.mautrix-signal = lib.mkIf bridges.signal {
enable = true;
settings = commonBridgeSettings "mautrix-signal";
};
services.mautrix-whatsapp = lib.mkIf bridges.whatsapp {
enable = true;
settings = {
bridge = {
history_sync = {
backfill = true;
max_initial_conversations = -1;
message_count = 50;
request_full_sync = true;
};
mute_bridging = true;
};
} // commonBridgeSettings "mautrix-whatsapp";
};
}

179
hosts/vps1/nginx.nix Normal file
View File

@ -0,0 +1,179 @@
{
pkgs,
...
}:
let
nginxErrorPages = ''
location @error_pages {
rewrite ^.*$ /''${status}.html break;
root "/var/www/html/errors";
}
'';
nginxEdgeHeaders = ''
more_set_headers 'Server: Vimium';
more_set_headers 'Access-Control-Allow-Origin: *';
add_header Expect-CT max-age=30 always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header Vimium-Responding-Instance $hostname;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options nosniff always;
'';
nginxStrictHeaders = ''
add_header X-Frame-Options SAMEORIGIN always;
add_header Permissions-Policy "fullscreen=(self), sync-xhr=(self)" always;
'';
mkRedirect = from: to: {
"${from}" = {
forceSSL = true;
enableACME = true;
serverAliases = [ "www.${from}" ];
locations."/".return = "301 https://${to}$request_uri";
extraConfig = nginxErrorPages + nginxEdgeHeaders + nginxStrictHeaders;
};
};
in
{
networking.firewall.allowedTCPPorts = [
80 # HTTP
443 # HTTPS
];
services.nginx = {
enable = true;
package = pkgs.openresty;
recommendedGzipSettings = true;
recommendedOptimisation = true;
recommendedTlsSettings = true;
clientMaxBodySize = "2G";
sslProtocols = "TLSv1.2 TLSv1.3";
sslCiphers = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";
appendHttpConfig = ''
error_page 400 @error_pages;
error_page 401 @error_pages;
error_page 403 @error_pages;
error_page 404 @error_pages;
error_page 405 @error_pages;
error_page 429 @error_pages;
error_page 500 @error_pages;
error_page 501 @error_pages;
error_page 502 @error_pages;
error_page 503 @error_pages;
error_page 504 @error_pages;
client_body_buffer_size 16k;
client_header_buffer_size 8k;
'';
appendConfig = ''
worker_processes auto;
worker_cpu_affinity auto;
worker_rlimit_nofile 50000;
'';
eventsConfig = ''
worker_connections 20000;
multi_accept off;
'';
proxyCachePath = {
"skycam" = {
enable = true;
keysZoneName = "skycam_cache";
maxSize = "100m";
};
};
virtualHosts =
{
## Static sites
"jellyfin.vimium.com" = {
forceSSL = true;
enableACME = true;
extraConfig = nginxErrorPages + nginxEdgeHeaders;
locations."/" = {
proxyPass = "http://localhost:8000";
extraConfig = ''
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header Range $http_range;
proxy_set_header If-Range $http_if_range;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
'';
};
};
"jdholt.com" = {
forceSSL = true;
enableACME = true;
serverAliases = [ "www.jdholt.com" ];
extraConfig = nginxErrorPages + nginxEdgeHeaders + nginxStrictHeaders;
locations."/skycam/snapshot.jpg" = {
extraConfig = ''
set $backend "skycam.mesh.vimium.net:8080";
resolver 100.100.100.100;
proxy_pass http://$backend/snapshot;
proxy_cache skycam_cache;
proxy_cache_valid any 10s;
proxy_ignore_headers Cache-Control Expires Set-Cookie;
'';
};
locations."/".return = "301 https://vimium.com$request_uri";
};
"pki.vimium.com" = {
addSSL = true;
forceSSL = false;
enableACME = true;
extraConfig = ''
${nginxErrorPages}
more_set_headers 'Server: Vimium';
'';
locations."/" = {
root = "/var/www/pki.vimium.com";
};
};
"suhailhussain.com" = {
forceSSL = true;
enableACME = true;
serverAliases = [ "www.suhailhussain.com" ];
extraConfig = nginxErrorPages + nginxEdgeHeaders + nginxStrictHeaders;
locations."/" = {
root = "/var/www/suhailhussain.com";
};
};
"vimium.com" = {
default = true;
forceSSL = true;
enableACME = true;
serverAliases = [ "www.vimium.com" ];
extraConfig =
nginxErrorPages
+ nginxEdgeHeaders
+ nginxStrictHeaders
+ ''
add_header Content-Security-Policy "default-src 'self' https://vimium.com https://www.vimium.com; style-src 'unsafe-inline'; object-src 'none'; upgrade-insecure-requests" always;
'';
locations."/" = {
root = "/var/www/vimium.com";
};
};
}
## Redirects
// (mkRedirect "h0lt.com" "jdholt.com")
// (mkRedirect "jordanholt.xyz" "jdholt.com")
// (mkRedirect "omnimagic.com" "vimium.com")
// (mkRedirect "omnimagic.net" "vimium.com")
// (mkRedirect "thelostlegend.com" "suhailhussain.com")
// (mkRedirect "vimium.co" "vimium.com")
// (mkRedirect "vimium.co.uk" "vimium.com")
// (mkRedirect "vimium.info" "vimium.com")
// (mkRedirect "vimium.net" "vimium.com")
// (mkRedirect "vimium.org" "vimium.com")
// (mkRedirect "vimium.xyz" "vimium.com");
};
}

53
hosts/vps1/outline.nix Normal file
View File

@ -0,0 +1,53 @@
{
config,
self,
...
}:
let
domain = "outline.vimium.com";
in
{
nixpkgs.config.allowUnfree = true;
services.nginx.virtualHosts = {
"${domain}" = {
forceSSL = true;
enableACME = true;
locations."/" = {
proxyPass = "http://127.0.0.1:3000";
extraConfig = ''
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
'';
};
};
};
age.secrets."passwords/services/outline/oidc-client-secret" = {
file = "${self.inputs.secrets}/passwords/services/outline/oidc-client-secret.age";
owner = "outline";
group = "outline";
};
services.outline = {
enable = true;
forceHttps = false;
oidcAuthentication = {
clientId = "outline";
clientSecretFile = config.age.secrets."passwords/services/outline/oidc-client-secret".path;
displayName = "Vimium";
authUrl = "https://auth.vimium.com/ui/oauth2";
tokenUrl = "https://auth.vimium.com/oauth2/token";
userinfoUrl = "https://auth.vimium.com/oauth2/openid/outline/userinfo";
};
publicUrl = "https://${domain}";
storage.storageType = "local";
};
}

49
hosts/vps1/photoprism.nix Normal file
View File

@ -0,0 +1,49 @@
{
config,
self,
...
}:
let
domain = "gallery.vimium.com";
in
{
services.nginx.virtualHosts = {
"${domain}" = {
forceSSL = true;
enableACME = true;
locations."/" = {
proxyPass = "http://localhost:${toString config.services.photoprism.port}";
extraConfig = ''
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_buffering off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
'';
};
};
};
age.secrets."passwords/services/photoprism/admin" = {
file = "${self.inputs.secrets}/passwords/services/photoprism/admin.age";
};
services.photoprism = {
enable = true;
address = "localhost";
passwordFile = config.age.secrets."passwords/services/photoprism/admin".path;
originalsPath = "${config.services.photoprism.storagePath}/originals";
settings = {
PHOTOPRISM_APP_NAME = "Vimium Gallery";
PHOTOPRISM_SITE_AUTHOR = "Vimium";
PHOTOPRISM_SITE_TITLE = "Vimium Gallery";
PHOTOPRISM_SITE_CAPTION = "Vimium Gallery";
PHOTOPRISM_DISABLE_TLS = "true";
PHOTOPRISM_SPONSOR = "true";
};
};
}

View File

@ -3,18 +3,6 @@
./hardware/presonus-studio
./podman.nix
./services/borgmatic.nix
./services/chrony.nix
./services/coturn.nix
./services/gitea.nix
./services/gitea-runner.nix
./services/headscale.nix
./services/home-assistant
./services/mail.nix
./services/matrix.nix
./services/music-assistant.nix
./services/netbird.nix
./services/nginx.nix
./services/photoprism.nix
./services/postgresql.nix
./services/tailscale.nix
./system/desktop/gnome.nix

View File

@ -1,46 +0,0 @@
{
config,
lib,
...
}:
with lib;
let
cfg = config.modules.services.chrony;
in
{
options.modules.services.chrony = {
enable = mkOption {
default = false;
example = true;
description = "Enable chrony NTP deamon";
};
config = mkIf cfg.enable {
services.chrony = {
enable = true;
servers = [
"uk.pool.ntp.org"
"time.cloudflare.com"
];
extraConfig = ''
makestep 1.0 3
bindaddress 0.0.0.0
port 123
allow
'';
};
services.timesyncd.enable = mkForce false;
networking.firewall = {
allowedUDPPorts = [ 123 ];
allowedTCPPorts = [ 123 ];
};
};
};
}

View File

@ -1,135 +0,0 @@
{
config,
lib,
self,
...
}:
let
cfg = config.modules.services.coturn;
in
{
options.modules.services.coturn = {
enable = lib.mkOption {
default = false;
example = true;
};
realm = lib.mkOption {
type = lib.types.str;
description = "The realm to be used by the TURN server.";
example = "turn.vimium.com";
};
matrixIntegration = lib.mkOption {
default = false;
description = "Configure the matrix-synapse module to use this TURN server.";
example = true;
};
};
config = lib.mkIf cfg.enable {
networking.firewall =
let
range =
with config.services.coturn;
lib.singleton {
from = min-port;
to = max-port;
};
in
{
allowedTCPPorts = [
3478 # TURN listener
5349 # STUN TLS
5350 # STUN TLS alt
];
allowedUDPPorts = [
3478 # TURN listener
5349 # TLS
5350 # TLS alt
];
allowedUDPPortRanges = range; # TURN peer relays
};
security.acme.certs = {
"${config.services.coturn.realm}" = {
group = "turnserver";
reloadServices = [ "coturn" ];
};
};
age.secrets =
{
"passwords/services/coturn/static-auth-secret" = {
file = "${self.inputs.secrets}/passwords/services/coturn/static-auth-secret.age";
owner = "turnserver";
group = "turnserver";
};
}
// (
if cfg.matrixIntegration then
{
"passwords/services/coturn/matrix-turn-config.yml" = {
file = "${self.inputs.secrets}/passwords/services/coturn/matrix-turn-config.yml.age";
owner = "matrix-synapse";
group = "matrix-synapse";
};
}
else
{ }
);
services.coturn = rec {
enable = true;
realm = cfg.realm;
use-auth-secret = true;
static-auth-secret-file = config.age.secrets."passwords/services/coturn/static-auth-secret".path;
cert = "${config.security.acme.certs.${realm}.directory}/full.pem";
pkey = "${config.security.acme.certs.${realm}.directory}/key.pem";
min-port = 49000;
max-port = 50000;
no-cli = true;
no-tcp-relay = true;
extraConfig = ''
cipher-list="HIGH"
no-multicast-peers
# Ban private CIDR blocks
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
'';
};
services.matrix-synapse = lib.mkIf cfg.matrixIntegration {
settings = with config.services.coturn; {
turn_uris = [
"turn:${realm}:3478?transport=udp"
"turn:${realm}:3478?transport=tcp"
];
turn_user_lifetime = "1h";
};
extraConfigFiles = [
config.age.secrets."passwords/services/coturn/matrix-turn-config.yml".path
];
};
};
}

View File

@ -1,242 +0,0 @@
{
pkgs,
config,
lib,
self,
...
}:
# Based on: https://git.clan.lol/clan/clan-infra/src/branch/main/modules/web01/gitea/actions-runner.nix
with lib;
let
cfg = config.modules.services.gitea-runner;
hostname = config.networking.hostName;
giteaUrl = "https://git.vimium.com";
storeDepsBins = with pkgs; [
coreutils
findutils
gnugrep
gawk
git
nix
nix-update
bash
jq
nodejs
];
storeDeps = pkgs.runCommand "store-deps" { } ''
mkdir -p $out/bin
for dir in ${toString storeDepsBins}; do
for bin in "$dir"/bin/*; do
ln -s "$bin" "$out/bin/$(basename "$bin")"
done
done
# Add SSL CA certs
mkdir -p $out/etc/ssl/certs
cp -a "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt" $out/etc/ssl/certs/ca-bundle.crt
'';
in
{
options.modules.services.gitea-runner = {
enable = mkOption {
default = false;
example = true;
description = mdDoc "Enable a runner for Gitea Actions on this host";
};
};
config = mkIf cfg.enable {
modules.podman.enable = true;
systemd.services = {
gitea-runner-nix-image = {
wantedBy = [ "multi-user.target" ];
after = [ "podman.service" ];
requires = [ "podman.service" ];
path = [
config.virtualisation.podman.package
pkgs.gnutar
pkgs.shadow
pkgs.getent
];
script = ''
set -eux -o pipefail
mkdir -p etc/nix
# Create an unpriveleged user that we can use also without the run-as-user.sh script
touch etc/passwd etc/group
groupid=$(cut -d: -f3 < <(getent group nix-ci-user))
userid=$(cut -d: -f3 < <(getent passwd nix-ci-user))
groupadd --prefix $(pwd) --gid "$groupid" nix-ci-user
emptypassword='$6$1ero.LwbisiU.h3D$GGmnmECbPotJoPQ5eoSTD6tTjKnSWZcjHoVTkxFLZP17W9hRi/XkmCiAMOfWruUwy8gMjINrBMNODc7cYEo4K.'
useradd --prefix $(pwd) -p "$emptypassword" -m -d /tmp -u "$userid" -g "$groupid" -G nix-ci-user nix-ci-user
cat <<NIX_CONFIG > etc/nix/nix.conf
accept-flake-config = true
experimental-features = nix-command flakes
NIX_CONFIG
cat <<NSSWITCH > etc/nsswitch.conf
passwd: files mymachines systemd
group: files mymachines systemd
shadow: files
hosts: files mymachines dns myhostname
networks: files
ethers: files
services: files
protocols: files
rpc: files
NSSWITCH
# list the content as it will be imported into the container
tar -cv . | tar -tvf -
tar -cv . | podman import - gitea-runner-nix
'';
serviceConfig = {
RuntimeDirectory = "gitea-runner-nix-image";
WorkingDirectory = "/run/gitea-runner-nix-image";
Type = "oneshot";
RemainAfterExit = true;
};
};
gitea-runner-nix = {
after = [ "gitea-runner-nix-image.service" ];
requires = [ "gitea-runner-nix-image.service" ];
serviceConfig = {
# Hardening (may overlap with DynamicUser=)
# The following options are only for optimizing output of systemd-analyze
AmbientCapabilities = "";
CapabilityBoundingSet = "";
# ProtectClock= adds DeviceAllow=char-rtc r
DeviceAllow = "";
NoNewPrivileges = true;
PrivateDevices = true;
PrivateMounts = true;
PrivateTmp = true;
PrivateUsers = true;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectSystem = "strict";
RemoveIPC = true;
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
UMask = "0066";
ProtectProc = "invisible";
SystemCallFilter = [
"~@clock"
"~@cpu-emulation"
"~@module"
"~@mount"
"~@obsolete"
"~@raw-io"
"~@reboot"
"~@swap"
# needed by go?
#"~@resources"
"~@privileged"
"~capset"
"~setdomainname"
"~sethostname"
];
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_UNIX"
"AF_NETLINK"
];
# Needs network access
PrivateNetwork = false;
# Cannot be true due to Node
MemoryDenyWriteExecute = false;
# The more restrictive "pid" option makes `nix` commands in CI emit
# "GC Warning: Couldn't read /proc/stat"
# You may want to set this to "pid" if not using `nix` commands
ProcSubset = "all";
# Coverage programs for compiled code such as `cargo-tarpaulin` disable
# ASLR (address space layout randomization) which requires the
# `personality` syscall
# You may want to set this to `true` if not using coverage tooling on
# compiled code
LockPersonality = false;
# Note that this has some interactions with the User setting; so you may
# want to consult the systemd docs if using both.
DynamicUser = true;
};
};
};
users.users.nix-ci-user = {
group = "nix-ci-user";
description = "Used for running nix-based CI jobs";
home = "/var/empty";
isSystemUser = true;
};
users.groups.nix-ci-user = { };
age.secrets."files/services/gitea-runner/${hostname}-token" = {
file = "${self.inputs.secrets}/files/services/gitea-runner/${hostname}-token.age";
group = "podman";
};
services.gitea-actions-runner.instances = {
act = {
enable = true;
url = giteaUrl;
name = "act-runner-${hostname}";
tokenFile = config.age.secrets."files/services/gitea-runner/${hostname}-token".path;
settings = {
cache.enabled = true;
runner.capacity = 4;
};
labels = [
"debian-latest:docker://ghcr.io/catthehacker/ubuntu:act-latest"
"ubuntu-latest:docker://ghcr.io/catthehacker/ubuntu:act-latest"
];
};
nix = {
enable = true;
url = giteaUrl;
name = "nix-runner-${hostname}";
tokenFile = config.age.secrets."files/services/gitea-runner/${hostname}-token".path;
settings = {
cache.enabled = true;
container = {
options = "-e NIX_BUILD_SHELL=/bin/bash -e PAGER=cat -e PATH=/bin -e SSL_CERT_FILE=/etc/ssl/certs/ca-bundle.crt --device /dev/kvm -v /nix:/nix -v ${storeDeps}/bin:/bin -v ${storeDeps}/etc/ssl:/etc/ssl --user nix-ci-user";
network = "host";
valid_volumes = [
"/nix"
"${storeDeps}/bin"
"${storeDeps}/etc/ssl"
];
};
runner.capacity = 4;
};
labels = [
"nix:docker://gitea-runner-nix"
];
};
};
};
}

View File

@ -1,100 +0,0 @@
{
config,
lib,
pkgs,
self,
...
}:
let
cfg = config.modules.services.gitea;
in
{
options.modules.services.gitea = {
enable = lib.mkEnableOption "gitea";
domain = lib.mkOption {
type = lib.types.string;
default = "git.vimium.com";
};
};
config = lib.mkIf cfg.enable {
users = {
users.git = {
isSystemUser = true;
useDefaultShell = true;
group = "git";
extraGroups = [ "gitea" ];
home = config.services.gitea.stateDir;
};
groups.git = { };
};
services.nginx = {
upstreams.gitea = {
servers = {
"unix:${config.services.gitea.settings.server.HTTP_ADDR}" = { };
};
};
virtualHosts = {
"${cfg.domain}" = {
forceSSL = true;
enableACME = true;
locations."/".proxyPass = "http://gitea";
};
};
};
systemd.tmpfiles.rules = [
"d '${config.services.gitea.customDir}/public/assets/css' 0750 ${config.services.gitea.user} ${config.services.gitea.group} - -"
"L+ '${config.services.gitea.customDir}/public/assets/css/theme-github.css' - - - - ${self.inputs.gitea-github-theme}/theme-github.css"
"L+ '${config.services.gitea.customDir}/public/assets/css/theme-github-auto.css' - - - - ${self.inputs.gitea-github-theme}/theme-github-auto.css"
"L+ '${config.services.gitea.customDir}/public/assets/css/theme-github-dark.css' - - - - ${self.inputs.gitea-github-theme}/theme-github-dark.css"
];
services.gitea = rec {
package = pkgs.unstable.gitea;
enable = true;
user = "git";
appName = "Vimium Git";
stateDir = "/var/lib/gitea";
repositoryRoot = "${stateDir}/repositories";
database = {
type = "sqlite3";
inherit user;
path = "${stateDir}/gitea.db";
};
lfs = {
enable = true;
contentDir = "${stateDir}/lfs";
};
settings = {
server = {
DOMAIN = config.networking.domain;
LANDING_PAGE = "explore";
OFFLINE_MODE = true;
PROTOCOL = "http+unix";
SSH_USER = "git";
SSH_DOMAIN = "${cfg.domain}";
SSH_PORT = lib.head config.services.openssh.ports;
ROOT_URL = "https://${cfg.domain}/";
};
service.DISABLE_REGISTRATION = true;
session.COOKIE_SECURE = true;
log = {
ROOT_PATH = "${stateDir}/log";
"logger.router.MODE" = "";
};
ui = {
THEMES = "gitea,arc-green,github,github-auto,github-dark";
DEFAULT_THEME = "github-dark";
};
actions.ENABLED = true;
indexer = {
REPO_INDEXER_ENABLED = true;
};
packages.CHUNKED_UPLOAD_PATH = lib.mkForce "${stateDir}/data/tmp/package-upload";
};
};
};
}

View File

@ -1,72 +0,0 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.modules.services.headscale;
fqdn = "headscale.vimium.net";
in
{
options.modules.services.headscale = {
enable = lib.mkOption {
default = false;
example = true;
};
};
config = lib.mkIf cfg.enable {
environment.systemPackages = [ pkgs.headscale ];
services.headscale = {
enable = true;
port = 8080;
settings = {
policy.path = null;
ip_prefixes = [
"100.64.0.0/10"
];
server_url = "https://${fqdn}";
derp = {
auto_update_enable = false;
update_frequency = "24h";
};
dns = {
base_domain = "mesh.vimium.net";
extra_records = [
{
name = "grafana.mesh.vimium.net";
type = "A";
value = "100.64.0.6";
}
{
name = "home.mesh.vimium.net";
type = "A";
value = "100.64.0.7";
}
];
magic_dns = true;
nameservers.global = [
"9.9.9.9"
];
};
logtail.enabled = false;
};
};
services.nginx.virtualHosts = {
"${fqdn}" = {
forceSSL = true;
enableACME = true;
locations."/" = {
proxyPass = "http://localhost:${toString config.services.headscale.port}";
proxyWebsockets = true;
};
};
};
};
}

View File

@ -1,306 +0,0 @@
{
config,
lib,
pkgs,
self,
...
}:
let
cfg = config.modules.services.home-assistant;
in
{
imports = [
./floorplan/default.nix
./mqtt.nix
];
options.modules.services.home-assistant.enable = lib.mkEnableOption "home-assistant";
config = lib.mkIf cfg.enable {
age.secrets."files/services/home-assistant/secrets.yaml" = {
file = "${self.inputs.secrets}/files/services/home-assistant/secrets.yaml.age";
path = "${config.services.home-assistant.configDir}/secrets.yaml";
owner = "hass";
group = "hass";
};
services.home-assistant = {
enable = true;
config = {
automation = "!include automations.yaml";
backup = { };
binary_sensor = [ ];
default_config = { };
http = {
server_host = "::1";
trusted_proxies = [ "::1" ];
use_x_forwarded_for = true;
};
ffmpeg = { };
homeassistant = {
name = "Home";
latitude = "!secret latitude";
longitude = "!secret longitude";
country = "GB";
temperature_unit = "C";
time_zone = config.time.timeZone;
unit_system = "metric";
auth_providers = [
{
type = "trusted_networks";
trusted_networks = [
"100.64.0.0/10"
"127.0.0.1"
];
allow_bypass_login = true;
}
{
type = "homeassistant";
}
];
};
logger = {
default = "info";
logs = { };
};
lovelace = {
mode = "yaml";
};
media_player = [ ];
mobile_app = { };
onkyo = { };
open_meteo = { };
recorder = {
purge_keep_days = 365;
};
scene = "!include scenes.yaml";
script = "!include scripts.yaml";
sensor = [ ];
system_health = { };
zeroconf = { };
};
extraComponents = [
"air_quality"
"airly"
"alert"
"api"
"application_credentials"
"asuswrt"
"auth"
"automation"
"bayesian"
"binary_sensor"
# "blackbird"
"blueprint"
"bluetooth_adapters"
"bluetooth_le_tracker"
"button"
"calendar"
"camera"
"cast"
"cert_expiry"
"climate"
"co2signal"
"color_extractor"
"command_line"
"compensation"
"configurator"
"counter"
"cover"
"cpuspeed"
"default_config"
"demo"
"derivative"
"device_automation"
"device_sun_light_trigger"
"device_tracker"
"dlna_dmr"
"dlna_dms"
"dnsip"
"esphome"
"fail2ban"
"fan"
"feedreader"
"ffmpeg"
"file"
"file_upload"
"filesize"
"folder"
"folder_watcher"
"forecast_solar"
"frontend"
"gdacs"
"generic"
"generic_hygrostat"
"generic_thermostat"
"geo_json_events"
"geo_location"
"geo_rss_events"
"github"
"group"
"hardware"
"hdmi_cec"
"history_stats"
"homeassistant"
"homekit"
"homekit_controller"
"html5"
"http"
"humidifier"
"icloud"
"image_processing"
"input_boolean"
"input_button"
"input_datetime"
"input_number"
"input_select"
"input_text"
"integration"
"ios"
"jellyfin"
"light"
"local_calendar"
"local_file"
"local_ip"
"local_todo"
"lock"
"logentries"
"logger"
"lovelace"
"manual"
"manual_mqtt"
"matter"
"media_player"
"min_max"
"mjpeg"
"modern_forms"
"mold_indicator"
"moon"
"mysensors"
"network"
"nmap_tracker"
"notify"
"number"
"onboarding"
"onkyo"
"panel_custom"
"persistent_notification"
"person"
"ping"
"plant"
"prometheus"
"proximity"
"push"
"proximity"
"python_script"
"radio_browser"
"random"
"recorder"
"remote"
"repairs"
"rest"
"rest_command"
"rss_feed_template"
"scene"
"schedule"
"scrape"
"script"
"search"
"season"
"select"
"sense"
"sensor"
"sensorpush"
"shell_command"
"shopping_list"
"siren"
"smtp"
"snmp"
"sql"
"statistics"
"sun"
"switch"
"switch_as_x"
"system_health"
"system_log"
"systemmonitor"
"tag"
"tailscale"
"tcp"
"template"
"text"
"thread"
"threshold"
"time_date"
"timer"
"tod"
"todo"
"tomorrowio"
"trend"
"universal"
"upb"
"update"
"upnp"
"uptime"
"utility_meter"
"vacuum"
"vlc"
"vlc_telnet"
"wake_on_lan"
"water_heater"
"weather"
"websocket_api"
"wled"
"workday"
"worldclock"
"zone"
];
extraPackages =
python3Packages: with python3Packages; [
onkyo-eiscp
zeroconf
];
customComponents = with pkgs; [
home-assistant-browser-mod
];
customLovelaceModules = with pkgs.home-assistant-custom-lovelace-modules; [
bubble-card
button-card
card-mod
light-entity-card
mini-graph-card
mini-media-player
mushroom
sankey-chart
universal-remote-card
];
lovelaceConfigWritable = true;
};
modules.services.borgmatic.directories = [
config.services.home-assistant.configDir
];
services.nginx = {
enable = true;
virtualHosts."home.mesh.vimium.net" = {
forceSSL = false;
extraConfig = ''
proxy_buffering off;
'';
locations."/" = {
proxyPass = "http://[::1]:8123";
proxyWebsockets = true;
};
};
};
};
}

View File

@ -1,83 +0,0 @@
{
config,
lib,
self,
...
}:
let
cfg = config.modules.services.mail;
domains = [
"h0lt.com"
"jdholt.com"
"jordanholt.xyz"
"vimium.co"
"vimium.com"
"vimium.co.uk"
"vimium.info"
"vimium.net"
"vimium.org"
"vimium.xyz"
];
in
{
options.modules.services.mail = {
enable = lib.mkOption {
default = false;
example = true;
};
};
imports = [
self.inputs.nixos-mailserver.nixosModule
];
config = lib.mkIf cfg.enable {
services.roundcube = {
enable = true;
hostName = config.mailserver.fqdn;
extraConfig = ''
$config['smtp_server'] = "tls://${config.mailserver.fqdn}";
$config['smtp_user'] = "%u";
$config['smtp_pass'] = "%p";
'';
plugins = [ "contextmenu" ];
};
services.nginx.enable = true;
networking.firewall.allowedTCPPorts = [
80
443
];
mailserver = {
enable = true;
fqdn = "mail.vimium.com";
domains = domains;
indexDir = "/var/lib/dovecot/indices";
certificateDomains = [
"imap.vimium.com"
"smtp.vimium.com"
];
certificateScheme = "acme-nginx";
fullTextSearch.enable = true;
loginAccounts = {
"jordan@vimium.com" = {
hashedPasswordFile = config.users.users.jordan.hashedPasswordFile;
catchAll = domains;
};
};
extraVirtualAliases = {
"hostmaster@vimium.com" = "jordan@vimium.com";
"postmaster@vimium.com" = "jordan@vimium.com";
"webmaster@vimium.com" = "jordan@vimium.com";
"abuse@vimium.com" = "jordan@vimium.com";
};
};
};
}

View File

@ -1,248 +0,0 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.modules.services.matrix;
in
{
options.modules.services.matrix = {
enable = lib.mkEnableOption "matrix";
element = {
enable = lib.mkOption {
type = lib.types.bool;
default = true;
};
};
bridges = {
signal = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Enable Signal bridge.";
};
whatsapp = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Enable WhatsApp bridge.";
};
};
serverName = lib.mkOption {
type = lib.types.str;
default = "vimium.com";
example = "vimium.com";
};
usePostgresql = lib.mkEnableOption "postgresql";
};
config =
let
matrixSubdomain = "matrix.${cfg.serverName}";
elementSubdomain = "chat.${cfg.serverName}";
matrixClientConfig = {
"m.homeserver" = {
base_url = "https://${matrixSubdomain}";
server_name = cfg.serverName;
};
"m.identity_server" = {
"base_url" = "https://vector.im";
};
};
matrixServerConfig."m.server" = "${matrixSubdomain}:443";
commonBridgeSettings = bridge: {
appservice = {
database = lib.mkIf cfg.usePostgresql {
type = "postgres";
uri = "postgresql:///${bridge}?host=/run/postgresql";
};
};
bridge = {
encryption = {
allow = true;
default = true;
require = true;
};
permissions = {
"${cfg.serverName}" = "user";
"@jordan:${cfg.serverName}" = "admin";
};
provisioning = {
shared_secret = "disable";
};
};
homeserver = {
address = "https://${matrixSubdomain}";
domain = cfg.serverName;
};
};
in
lib.mkIf cfg.enable {
networking.firewall.allowedTCPPorts = [
8448 # Matrix federation
];
security.acme.certs = {
"${matrixSubdomain}" = {
reloadServices = [ "matrix-synapse" ];
};
};
services.nginx.virtualHosts =
{
"${matrixSubdomain}" = {
forceSSL = true;
enableACME = true;
listen = [
{
addr = "0.0.0.0";
port = 443;
ssl = true;
}
{
addr = "0.0.0.0";
port = 80;
}
{
addr = "0.0.0.0";
port = 8448;
ssl = true;
}
{
addr = "[::1]";
port = 443;
ssl = true;
}
{
addr = "[::1]";
port = 80;
}
{
addr = "[::1]";
port = 8448;
ssl = true;
}
];
locations = {
"/" = {
proxyPass = "http://localhost:8008";
extraConfig = ''
proxy_set_header X-Forwarded-For $remote_addr;
'';
};
"/_matrix" = {
proxyPass = "http://localhost:8008";
extraConfig = ''
proxy_set_header X-Forwarded-For $remote_addr;
client_max_body_size 50M;
'';
};
"/_synapse/client".proxyPass = "http://localhost:8008";
};
};
"${cfg.serverName}" =
let
mkWellKnown = data: ''
more_set_headers 'Content-Type: application/json';
return 200 '${builtins.toJSON data}';
'';
in
{
locations."= /.well-known/matrix/server".extraConfig = (mkWellKnown matrixServerConfig);
locations."= /.well-known/matrix/client".extraConfig = (mkWellKnown matrixClientConfig);
};
}
// (
if cfg.element.enable then
{
"${elementSubdomain}" = {
forceSSL = true;
enableACME = true;
root = pkgs.unstable.element-web.override {
conf = {
default_server_config = matrixClientConfig;
brand = "Vimium Chat";
branding = {
auth_header_logo_url = "https://vimium.com/images/logo.svg";
auth_footer_links = [
{
"text" = "Vimium.com";
"url" = "https://vimium.com";
}
];
};
};
};
};
}
else
{ }
);
nixpkgs.config.permittedInsecurePackages = [
"jitsi-meet-1.0.8043"
"olm-3.2.16"
];
services.matrix-synapse = {
enable = true;
enableRegistrationScript = true;
settings = {
database.name = (if cfg.usePostgresql then "psycopg2" else "sqlite3");
enable_metrics = false;
enable_registration = false;
max_upload_size = "100M";
report_stats = false;
server_name = cfg.serverName;
};
};
systemd.services.matrix-synapse.serviceConfig.SupplementaryGroups = (
lib.optional cfg.bridges.whatsapp config.systemd.services.mautrix-whatsapp.serviceConfig.Group
);
services.postgresql = lib.mkIf cfg.usePostgresql {
ensureUsers =
[
{
name = "matrix-synapse";
ensureDBOwnership = true;
}
]
++ (lib.optional cfg.bridges.signal {
name = "mautrix-signal";
ensureDBOwnership = true;
})
++ (lib.optional cfg.bridges.whatsapp {
name = "mautrix-whatsapp";
ensureDBOwnership = true;
});
ensureDatabases =
[
"matrix-synapse"
]
++ (lib.optional cfg.bridges.signal "mautrix-signal")
++ (lib.optional cfg.bridges.whatsapp "mautrix-whatsapp");
};
services.mautrix-signal = lib.mkIf cfg.bridges.signal {
enable = true;
settings = commonBridgeSettings "mautrix-signal";
};
services.mautrix-whatsapp = lib.mkIf cfg.bridges.whatsapp {
enable = true;
settings = {
bridge = {
history_sync = {
backfill = true;
max_initial_conversations = -1;
message_count = 50;
request_full_sync = true;
};
mute_bridging = true;
};
} // commonBridgeSettings "mautrix-whatsapp";
};
};
}

View File

@ -1,37 +0,0 @@
{
config,
lib,
...
}:
let
inherit (lib)
mkEnableOption
mkIf
;
cfg = config.modules.services.music-assistant;
in
{
options.modules.services.music-assistant.enable = mkEnableOption "music-assistant";
config = mkIf cfg.enable {
services = {
nginx.virtualHosts."music-assistant.${config.networking.hostName}" = {
extraConfig = ''
proxy_buffering off;
'';
locations."/" = {
proxyPass = "http://[::1]:8095";
proxyWebsockets = true;
};
};
music-assistant = {
enable = true;
providers = [
"hass"
"jellyfin"
];
};
};
};
}

View File

@ -1,77 +0,0 @@
{
config,
lib,
self,
...
}:
let
cfg = config.modules.services.netbird;
in
{
options.modules.services.netbird = {
enable = lib.mkEnableOption "netbird";
coordinatorDomain = lib.mkOption {
type = lib.types.str;
default = "netbird.vimium.net";
};
meshDomain = lib.mkOption {
type = lib.types.str;
default = "mesh.vimium.net";
};
};
config = lib.mkIf cfg.enable {
age.secrets."passwords/services/netbird/data-store-encryption-key" = {
file = "${self.inputs.secrets}/passwords/services/netbird/data-store-encryption-key.age";
};
services.netbird = {
enable = true;
};
services.netbird.server = {
domain = cfg.coordinatorDomain;
enable = true;
enableNginx = true;
dashboard.settings = {
AUTH_AUTHORITY = "https://auth.vimium.com/oauth2/openid/netbird";
};
management = rec {
disableAnonymousMetrics = true;
dnsDomain = cfg.meshDomain;
oidcConfigEndpoint = "https://auth.vimium.com/oauth2/openid/netbird/.well-known/openid-configuration";
settings = {
DataStoreEncryptionKey = {
_secret = config.age.secrets."passwords/services/netbird/data-store-encryption-key".path;
};
HttpConfig = {
AuthAudience = "netbird";
};
StoreConfig = {
Engine = "sqlite";
};
TURNConfig = {
Secret._secret = config.age.secrets."passwords/services/coturn/static-auth-secret".path;
TimeBasedCredentials = true;
};
PKCEAuthorizationFlow.ProviderConfig = {
AuthorizationEndpoint = "https://auth.vimium.com/ui/oauth2";
TokenEndpoint = "https://auth.vimium.com/oauth2/token";
};
};
singleAccountModeDomain = dnsDomain;
turnDomain = config.services.coturn.realm;
turnPort = config.services.coturn.listening-port;
};
};
systemd.services.netbird-signal.serviceConfig.RestartSec = "60";
systemd.services.netbird-management.serviceConfig.RestartSec = "60";
services.nginx.virtualHosts."netbird.vimium.net" = {
enableACME = true;
forceSSL = true;
};
};
}

View File

@ -1,193 +0,0 @@
{
config,
lib,
pkgs,
...
}:
with lib;
let
cfg = config.modules.services.nginx;
nginxErrorPages = ''
location @error_pages {
rewrite ^.*$ /''${status}.html break;
root "/var/www/html/errors";
}
'';
nginxEdgeHeaders = ''
more_set_headers 'Server: Vimium';
more_set_headers 'Access-Control-Allow-Origin: *';
add_header Expect-CT max-age=30 always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header Vimium-Responding-Instance $hostname;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options nosniff always;
'';
nginxStrictHeaders = ''
add_header X-Frame-Options SAMEORIGIN always;
add_header Permissions-Policy "fullscreen=(self), sync-xhr=(self)" always;
'';
mkRedirect = from: to: {
"${from}" = {
forceSSL = true;
enableACME = true;
serverAliases = [ "www.${from}" ];
locations."/".return = "301 https://${to}$request_uri";
extraConfig = nginxErrorPages + nginxEdgeHeaders + nginxStrictHeaders;
};
};
in
{
options.modules.services.nginx = {
enable = mkOption {
default = false;
example = true;
};
};
config = mkIf cfg.enable {
networking.firewall.allowedTCPPorts = [
80 # HTTP
443 # HTTPS
];
services.nginx = {
enable = true;
package = pkgs.openresty;
recommendedGzipSettings = true;
recommendedOptimisation = true;
recommendedTlsSettings = true;
clientMaxBodySize = "2G";
sslProtocols = "TLSv1.2 TLSv1.3";
sslCiphers = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";
appendHttpConfig = ''
error_page 400 @error_pages;
error_page 401 @error_pages;
error_page 403 @error_pages;
error_page 404 @error_pages;
error_page 405 @error_pages;
error_page 429 @error_pages;
error_page 500 @error_pages;
error_page 501 @error_pages;
error_page 502 @error_pages;
error_page 503 @error_pages;
error_page 504 @error_pages;
client_body_buffer_size 16k;
client_header_buffer_size 8k;
'';
appendConfig = ''
worker_processes auto;
worker_cpu_affinity auto;
worker_rlimit_nofile 50000;
'';
eventsConfig = ''
worker_connections 20000;
multi_accept off;
'';
proxyCachePath = {
"skycam" = {
enable = true;
keysZoneName = "skycam_cache";
maxSize = "100m";
};
};
virtualHosts =
{
## Static sites
"jellyfin.vimium.com" = {
forceSSL = true;
enableACME = true;
extraConfig = nginxErrorPages + nginxEdgeHeaders;
locations."/" = {
proxyPass = "http://localhost:8000";
extraConfig = ''
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header Range $http_range;
proxy_set_header If-Range $http_if_range;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
'';
};
};
"jdholt.com" = {
forceSSL = true;
enableACME = true;
serverAliases = [ "www.jdholt.com" ];
extraConfig = nginxErrorPages + nginxEdgeHeaders + nginxStrictHeaders;
locations."/skycam/snapshot.jpg" = {
extraConfig = ''
set $backend "skycam.mesh.vimium.net:8080";
resolver 100.100.100.100;
proxy_pass http://$backend/snapshot;
proxy_cache skycam_cache;
proxy_cache_valid any 10s;
proxy_ignore_headers Cache-Control Expires Set-Cookie;
'';
};
locations."/".return = "301 https://vimium.com$request_uri";
};
"pki.vimium.com" = {
addSSL = true;
forceSSL = false;
enableACME = true;
extraConfig = ''
${nginxErrorPages}
more_set_headers 'Server: Vimium';
'';
locations."/" = {
root = "/var/www/pki.vimium.com";
};
};
"suhailhussain.com" = {
forceSSL = true;
enableACME = true;
serverAliases = [ "www.suhailhussain.com" ];
extraConfig = nginxErrorPages + nginxEdgeHeaders + nginxStrictHeaders;
locations."/" = {
root = "/var/www/suhailhussain.com";
};
};
"vimium.com" = {
default = true;
forceSSL = true;
enableACME = true;
serverAliases = [ "www.vimium.com" ];
extraConfig =
nginxErrorPages
+ nginxEdgeHeaders
+ nginxStrictHeaders
+ ''
add_header Content-Security-Policy "default-src 'self' https://vimium.com https://www.vimium.com; style-src 'unsafe-inline'; object-src 'none'; upgrade-insecure-requests" always;
'';
locations."/" = {
root = "/var/www/vimium.com";
};
};
}
## Redirects
// (mkRedirect "h0lt.com" "jdholt.com")
// (mkRedirect "jordanholt.xyz" "jdholt.com")
// (mkRedirect "omnimagic.com" "vimium.com")
// (mkRedirect "omnimagic.net" "vimium.com")
// (mkRedirect "thelostlegend.com" "suhailhussain.com")
// (mkRedirect "vimium.co" "vimium.com")
// (mkRedirect "vimium.co.uk" "vimium.com")
// (mkRedirect "vimium.info" "vimium.com")
// (mkRedirect "vimium.net" "vimium.com")
// (mkRedirect "vimium.org" "vimium.com")
// (mkRedirect "vimium.xyz" "vimium.com");
};
};
}

View File

@ -1,64 +0,0 @@
{
config,
lib,
self,
...
}:
with lib;
let
cfg = config.modules.services.photoprism;
in
{
options.modules.services.photoprism = {
enable = mkOption {
default = false;
example = true;
};
};
config = mkIf cfg.enable {
services.nginx = {
virtualHosts = {
"gallery.vimium.com" = {
forceSSL = true;
enableACME = true;
locations."/" = {
proxyPass = "http://localhost:${toString config.services.photoprism.port}";
extraConfig = ''
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_buffering off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
'';
};
};
};
};
age.secrets."passwords/services/photoprism/admin" = {
file = "${self.inputs.secrets}/passwords/services/photoprism/admin.age";
};
services.photoprism = {
enable = true;
address = "localhost";
passwordFile = config.age.secrets."passwords/services/photoprism/admin".path;
originalsPath = "${config.services.photoprism.storagePath}/originals";
settings = {
PHOTOPRISM_APP_NAME = "Vimium Gallery";
PHOTOPRISM_SITE_AUTHOR = "Vimium";
PHOTOPRISM_SITE_TITLE = "Vimium Gallery";
PHOTOPRISM_SITE_CAPTION = "Vimium Gallery";
PHOTOPRISM_DISABLE_TLS = "true";
PHOTOPRISM_SPONSOR = "true";
};
};
};
}