From f5345d8f6a214212dedd99bbd2195c9de1de732f Mon Sep 17 00:00:00 2001 From: Tobias Ostner Date: Sat, 11 Apr 2026 11:38:46 +0200 Subject: [PATCH] Initial commit --- .gitignore | 1 + configuration.nix | 101 ++++++++++++++++++++ flake.lock | 27 ++++++ flake.nix | 24 +++++ hardware-configuration.nix | 44 +++++++++ modules/bitcoin.nix | 63 +++++++++++++ modules/electrs.nix | 30 ++++++ modules/mempool.nix | 182 +++++++++++++++++++++++++++++++++++++ modules/nextcloud.nix | 120 ++++++++++++++++++++++++ modules/totap.nix | 27 ++++++ 10 files changed, 619 insertions(+) create mode 100644 .gitignore create mode 100644 configuration.nix create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 hardware-configuration.nix create mode 100644 modules/bitcoin.nix create mode 100644 modules/electrs.nix create mode 100644 modules/mempool.nix create mode 100644 modules/nextcloud.nix create mode 100644 modules/totap.nix diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c4a847d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/result diff --git a/configuration.nix b/configuration.nix new file mode 100644 index 0000000..66db669 --- /dev/null +++ b/configuration.nix @@ -0,0 +1,101 @@ +{ config, pkgs, ... }: + +{ + + programs = { + gnupg.agent = { + enable = true; + enableSSHSupport = true; + + }; + }; + + nix.settings.experimental-features = [ "nix-command" "flakes" ]; + + networking = { + hostName = "TobServer"; + + useDHCP = false; + useNetworkd = true; + + interfaces.eno1.ipv4.addresses = [ + { + address = "162.55.103.218"; + prefixLength = 26; + } + ]; + + defaultGateway = { + address = "162.55.103.193"; + interface = "eno1"; + }; + + nameservers = [ + "185.12.64.1" + "185.12.64.2" + ]; + + firewall = { + enable = true; + allowedTCPPorts = [ 22 80 443 50001 ]; + }; + }; + + users.users.root.openssh.authorizedKeys.keys = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG3PPXoEV1gt1tR4MyfR3nn1ttzZ4kGTiNfj8703MJdm hetzner-nixos" + ]; + + services = { + openssh.enable = true; + openssh.settings.PermitRootLogin = "no"; + + btrfs.autoScrub.enable = true; + + protonmail-bridge = { + enable = true; + }; + + nginx = { + enable = true; + }; + }; + + security.acme = { + acceptTerms = true; + defaults.email = "tobias.ostner@proton.me"; + }; + + time.timeZone = "Europe/Berlin"; + + boot.loader.grub.enable = true; + boot.loader.grub.device = "/dev/nvme0n1"; + + environment.systemPackages = with pkgs; [ + btop + ffmpeg + git + gnupg + jq + pass + vim + ]; + + system.stateVersion = "25.11"; + + users.users.tobi = { + isNormalUser = true; + extraGroups = [ "wheel" "networkmanager" "podman" "bitcoind-mainnet" ]; + openssh.authorizedKeys.keys = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG3PPXoEV1gt1tR4MyfR3nn1ttzZ4kGTiNfj8703MJdm hetzner-nixos" + ]; + }; + + security.sudo.enable = true; + security.sudo.wheelNeedsPassword = false; + + virtualisation.podman = { + enable = true; + dockerCompat = true; + defaultNetwork.settings.dns_enabled = true; + }; +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..8b40c7b --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1770136044, + "narHash": "sha256-tlFqNG/uzz2++aAmn4v8J0vAkV3z7XngeIIB3rM3650=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e576e3c9cf9bad747afcddd9e34f51d18c855b4e", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-25.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..38872e6 --- /dev/null +++ b/flake.nix @@ -0,0 +1,24 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; + }; + + outputs = { self, nixpkgs, ... }: + let + system = "x86_64-linux"; + in { + nixosConfigurations.tobserver = nixpkgs.lib.nixosSystem { + inherit system; + modules = [ + ./hardware-configuration.nix + ./configuration.nix + ./modules/bitcoin.nix + ./modules/electrs.nix + ./modules/mempool.nix + ./modules/nextcloud.nix + ./modules/totap.nix + ]; + }; + }; +} + diff --git a/hardware-configuration.nix b/hardware-configuration.nix new file mode 100644 index 0000000..8972863 --- /dev/null +++ b/hardware-configuration.nix @@ -0,0 +1,44 @@ +# Do not modify this file! It was generated by ‘nixos-generate-config’ +# and may be overwritten by future invocations. Please make changes +# to /etc/nixos/configuration.nix instead. +{ config, lib, pkgs, modulesPath, ... }: + +{ + imports = + [ (modulesPath + "/installer/scan/not-detected.nix") + ]; + + boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "nvme" ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ "kvm-intel" ]; + boot.extraModulePackages = [ ]; + + fileSystems."/" = + { device = "/dev/disk/by-uuid/30139f34-fe96-4cb3-bb27-f8dd84283eee"; + fsType = "btrfs"; + options = [ "subvol=@" ]; + }; + + fileSystems."/home" = + { device = "/dev/disk/by-uuid/30139f34-fe96-4cb3-bb27-f8dd84283eee"; + fsType = "btrfs"; + options = [ "subvol=@home" ]; + }; + + fileSystems."/nix" = + { device = "/dev/disk/by-uuid/30139f34-fe96-4cb3-bb27-f8dd84283eee"; + fsType = "btrfs"; + options = [ "subvol=@nix" ]; + }; + + fileSystems."/var/log" = + { device = "/dev/disk/by-uuid/30139f34-fe96-4cb3-bb27-f8dd84283eee"; + fsType = "btrfs"; + options = [ "subvol=@varlog" ]; + }; + + swapDevices = [ ]; + + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; + hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; +} diff --git a/modules/bitcoin.nix b/modules/bitcoin.nix new file mode 100644 index 0000000..f1a3e13 --- /dev/null +++ b/modules/bitcoin.nix @@ -0,0 +1,63 @@ +{ config, pkgs, lib, ... }: + +{ + services = { + bitcoind.mainnet = { + enable = true; + prune = 0; + testnet = false; + dataDir = "/var/lib/bitcoind"; + extraConfig = '' + bind=127.0.0.1 + bind=[::1] + listen=1 + listenonion=1 + onlynet=onion + proxy=127.0.0.1:9050 + proxyrandomize=1 + torcontrol=127.0.0.1:9051 + + discover=0 + + txindex=1 + disablewallet=0 + + maxconnections=60 + maxuploadtarget=20480 + dbcache=8192 + bantime=86400 + + whitelist=download@127.0.0.1 + + rpccookiefile=/var/lib/bitcoind/.cookie + rpccookieperms=group + rpcbind=127.0.0.1 + rpcbind=10.88.0.1 + rpcallowip=127.0.0.1 + rpcallowip=10.88.0.0/16 + ''; + }; + + tor = { + enable = true; + client.enable = true; + + settings = { + ControlPort = 9051; + CookieAuthentication = true; + CookieAuthFile = "/run/tor/control.authcookie"; + CookieAuthFileGroupReadable = true; + }; + }; + }; + + systemd.services.tor.serviceConfig.Group = "tor"; + + systemd.tmpfiles.rules = [ + "d /var/lib/tor 0750 tor tor - -" + ]; + + users.groups.tor = { }; + + users.users.bitcoind-mainnet.extraGroups = [ "tor" ]; +} diff --git a/modules/electrs.nix b/modules/electrs.nix new file mode 100644 index 0000000..b9194a0 --- /dev/null +++ b/modules/electrs.nix @@ -0,0 +1,30 @@ +{ config, pkgs, lib, ... }: + +{ + systemd.services.electrs = { + description ="Electrs Bitcoin Electrum Server"; + after = [ "bitcoind-mainnet.service" ]; + wants = [ "bitcoind-mainnet.service" ]; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + User = "bitcoind-mainnet"; + Group = "bitcoind-mainnet"; + ExecStart = "${pkgs.electrs}/bin/electrs --conf /etc/electrs.toml"; + Restart = "on-failure"; + StateDirectory = "electrs"; + }; + }; + + environment.etc."electrs.toml".text = '' + network = "bitcoin" + db_dir = "/var/lib/electrs/db" + daemon_dir = "/var/lib/bitcoind" + daemon_rpc_addr="127.0.0.1:8332" + daemon_p2p_addr="127.0.0.1:8333" + electrum_rpc_addr = "0.0.0.0:50001" + log_filters = "INFO" + cookie_file = "/var/lib/bitcoind/.cookie" + jsonrpc_timeout = "300s" + ''; +} diff --git a/modules/mempool.nix b/modules/mempool.nix new file mode 100644 index 0000000..adbe69d --- /dev/null +++ b/modules/mempool.nix @@ -0,0 +1,182 @@ +{ config, pkgs, lib, ...}: +{ + virtualisation.oci-containers = { + backend = "podman"; + + containers = { + mempool-db = { + image = "mariadb:11"; + autoStart = true; + environment = { + MARIADB_DATABASE = "mempool"; + MARIADB_USER = "mempool"; + MARIADB_PASSWORD = "mempool"; + MARIADB_ROOT_PASSWORD = "root"; + }; + volumes = [ + "/srv/mempool/mysql:/var/lib/mysql" + ]; + }; + + mempool-backend = { + image = "mempool/backend:latest"; + autoStart = true; + dependsOn = [ "mempool-db" ]; + ports = [ "127.0.0.1:8999:8999" ]; + extraOptions = [ + "--add-host=host.containers.internal:host-gateway" + ]; + environment = { + MEMPOOL_NETWORK = "mainnet"; + MEMPOOL_BACKEND = "electrum"; + MEMPOOL_HTTP_PORT = "8999"; + + ELECTRUM_HOST = "host.containers.internal"; + ELECTRUM_PORT = "50001"; + ELECTRUM_TLS_ENABLED = "false"; + + CORE_RPC_HOST = "host.containers.internal"; + CORE_RPC_PORT = "8332"; + + CORE_RPC_COOKIE = "true"; + CORE_RPC_COOKIE_PATH = "/bitcoin/.cookie"; + + DATABASE_ENABLED = "true"; + DATABASE_HOST = "mempool-db"; + DATABASE_PORT = "3306"; + DATABASE_DATABASE = "mempool"; + DATABASE_USERNAME = "mempool"; + DATABASE_PASSWORD = "mempool"; + + STATISTICS_ENABLED = "true"; + }; + volumes = [ + "/run/mempool/bitcoind.cookie:/bitcoin/.cookie:ro" + ]; + }; + + mempool-frontend = { + image = "mempool/frontend:latest"; + autoStart = true; + ports = [ "127.0.0.1:8998:8080" ]; + environment = { + NGINX_HOSTNAME = "mempool.tobiasostner.de"; + NGINX_PORT = "443"; + NGINX_PROTOCOL = "https"; + }; + }; + }; + }; + + systemd.tmpfiles.rules = lib.mkAfter [ + "d /run/mempool 0755 root root - -" + ]; + + # Copy cookie once + systemd.services.mempool-cookie = { + description = "Expose bitcoind cookie for mempool-backend"; + after = [ "bitcoind-mainnet.service" ]; + wants = [ "bitcoind-mainnet.service" ]; + + serviceConfig = { + Type = "oneshot"; + ExecStart = pkgs.writeShellScript "mempool-cookie" '' + set -euo pipefail + for i in $(seq 1 60); do + if [ -f /var/lib/bitcoind/.cookie ]; then + install -m 0644 -o root -g root /var/lib/bitcoind/.cookie /run/mempool/bitcoind.cookie + exit 0 + fi + sleep 1 + done + echo "Cookie not found after 60s" >&2 + exit 1 + ''; + }; + }; + + # Re-run the cookie copy whenever bitcoind rotates the cookie + systemd.paths.mempool-cookie = { + description = "Watch bitcoind cookie and refresh mempool copy"; + wantedBy = [ "multi-user.target" ]; + + pathConfig = { + PathChanged = "/var/lib/bitcoind/.cookie"; + }; + }; + + # When the path triggers, it starts the service + systemd.services.mempool-cookie.wantedBy = lib.mkForce [ ]; + systemd.paths.mempool-cookie.unitConfig = { + Unit = "mempool-cookie.service"; + }; + + systemd.services.podman-mempool-backend = { + after = [ "mempool-cookie.service" ]; + wants = [ "mempool-cookie.service" ]; + }; + + services.nginx = { + appendHttpConfig = '' + limit_req_zone $binary_remote_addr zone=mempool_api:10m rate=60r/m; + limit_conn_zone $binary_remote_addr zone=mempool_ws:10m; + ''; + + virtualHosts."mempool.tobiasostner.de" = { + enableACME = true; + forceSSL = true; + + locations."/" = { + proxyPass = "http://127.0.0.1:8998"; + extraConfig = '' + proxy_set_header Host $host; + 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; + ''; + }; + + locations."/api/" = { + proxyPass = "http://127.0.0.1:8999"; + extraConfig = '' + limit_req zone=mempool_api burst=30 nodelay; + + proxy_http_version 1.1; + proxy_set_header Host $host; + 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_connect_timeout 2s; + proxy_read_timeout 60s; + proxy_send_timeout 60s; + proxy_next_upstream error timeout http_502 http_503 http_504; + proxy_next_upstream_tries 3; + proxy_next_upstream_timeout 10s; + ''; + }; + + locations."/api/v1/ws" = { + proxyPass = "http://127.0.0.1:8999/api/v1/ws"; + proxyWebsockets = true; + extraConfig = '' + limit_conn mempool_ws 3; + + proxy_set_header Host $host; + 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_connect_timeout 2s; + proxy_read_timeout 60s; + proxy_send_timeout 60s; + proxy_next_upstream error timeout http_502 http_503 http_504; + proxy_next_upstream_tries 3; + proxy_next_upstream_timeout 10s; + ''; + }; + }; + }; + + networking.firewall.interfaces."podman0".allowedTCPPorts = [ 8332 50001 ]; +} diff --git a/modules/nextcloud.nix b/modules/nextcloud.nix new file mode 100644 index 0000000..0fc307d --- /dev/null +++ b/modules/nextcloud.nix @@ -0,0 +1,120 @@ +{ config, pkgs, lib, ... }: + +{ + services = { + + nextcloud = { + enable = true; + hostName = "cloud.tobiasostner.de"; + https = true; + + database.createLocally = true; + configureRedis = true; + + config = { + dbtype = "mysql"; + adminuser = "tobi"; + adminpassFile = "/var/lib/nextcloud/admin-pass"; + }; + + autoUpdateApps.enable = true; + datadir = "/nextcloud-data"; + + settings = { + default_phone_region = "DE"; + log_type = "file"; + logfile = "/var/log/nextcloud/nextcloud.log"; + maintenance_window_start = 2; + + overwritewebroot = ""; + overwrite.cli.url = "https://cloud.tobiasostner.de"; + overwritehost = "cloud.tobiasostner.de"; + overwriteprotocol = "https"; + + ## Email settings + mail_smtpmode = "smtp"; + mail_smtphost = "smtp.protonmail.ch"; + mail_smtpport = 587; + mail_smtpauth = true; + mail_from_address = "tobias"; + mail_domain = "ostner.name"; + + enabledPreviewProviders = [ + "OC\\Preview\\PNG" + "OC\\Preview\\JPEG" + "OC\\Preview\\GIF" + "OC\\Preview\\Movie" + ]; + + preview_ffmpeg_path = "${pkgs.ffmpeg}/bin/ffmpeg"; + preview_ffprobe_path = "${pkgs.ffmpeg}/bin/ffprobe"; + }; + + phpOptions = lib.mkForce { + "memory_limit" = "4096M"; + "upload_max_filesize" = "20G"; + "post_max_size" = "20G"; + "date.timezone" = "Europe/Berlin"; + "output_buffering" = "Off"; + "opcache.enable" = "1"; + "opcache.enable_cli" = "1"; + "opcache.memory_consumption" = "512"; + "opcache.interned_strings_buffer" = "64"; + "opcache.save_comments" = "1"; + "opcache.max_accelerated_files" = "30000"; + }; + + secretFile = "/run/secrets/nextcloud-mail.json"; + }; + + restic.backups.nextcloud = { + initialize = true; + repository = "b2:tobcloud-backup:nextcloud"; + passwordFile = "/var/lib/nextcloud/restic-password"; + environmentFile = "/var/lib/nextcloud/backblaze-env"; + paths = [ + "/nextcloud-data" + "/var/backup/nextcloud" + ]; + backupPrepareCommand = '' + set -euo pipefail + install -d -m 0750 -o root -g root /var/backup/nextcloud + ${pkgs.mariadb}/bin/mysqldump --single-transaction --databases nextcloud \ + > /var/backup/nextcloud/nextcloud.sql + ''; + timerConfig = { + OnCalendar = "daily"; + }; + pruneOpts = [ + "--keep-daily 7" + "--keep-weekly 4" + "--keep-monthly 12" + ]; + }; + + nginx = { + enable = true; + virtualHosts."cloud.tobiasostner.de" = { + forceSSL = true; + enableACME = true; + }; + }; + + cron.systemCronJobs = [ + "0 */6 * * * root nextcloud-occ preview:pre-generate" + ]; + }; + + environment.shellAliases = { + occ = "sudo nextcloud-occ"; + }; + + users.users.nextcloud.extraGroups = [ "podman" ]; + + systemd.services.phpfpm-nextcloud.path = [ pkgs.ffmpeg ]; + + systemd.tmpfiles.rules = [ + "d /var/log/nextcloud 0750 nextcloud nextcloud - -" + "d /var/backup/nextcloud 0750 root root - -" + ]; +} diff --git a/modules/totap.nix b/modules/totap.nix new file mode 100644 index 0000000..cf57f3f --- /dev/null +++ b/modules/totap.nix @@ -0,0 +1,27 @@ +{ config, pkgs, lib, ... }: + +{ + services.nginx.virtualHosts."totap.de" = { + serverAliases = [ "www.totap.de" ]; + enableACME = true; + forceSSL = true; + root = "/var/www/totap"; + }; + + users.users.deploy-totap = { + isNormalUser = true; + description = "Deploy user for totap.de"; + createHome = true; + home = "/home/deploy-totap"; + group = "deploy-totap"; + openssh.authorizedKeys.keys = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMBJeuS1n+MjSy9NuQ4z6Z5jzr59kUVQgj627o0Vuc+Q github-actions-totap-deploy" + ]; + }; + + users.groups.deploy-totap = { }; + + systemd.tmpfiles.rules = [ + "d /var/www/totap 0755 deploy-totap deploy-totap -" + ]; +}