{ config, pkgs, lib, ...}: { sops.secrets."mempool/mariadb-password" = { owner = "root"; group = "root"; mode = "0400"; }; sops.secrets."mempool/mariadb-root-password" = { owner = "root"; group = "root"; mode = "0400"; }; sops.templates."mempool-db.env" = { owner = "root"; group = "root"; mode = "0400"; content = '' MARIADB_DATABASE=mempool MARIADB_USER=mempool MARIADB_PASSWORD=${config.sops.placeholder."mempool/mariadb-password"} MARIADB_ROOT_PASSWORD=${config.sops.placeholder."mempool/mariadb-root-password"} ''; }; sops.templates."mempool-backend.env" = { owner = "root"; group = "root"; mode = "0400"; content = '' 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=${config.sops.placeholder."mempool/mariadb-password"} STATISTICS_ENABLED=true ''; }; virtualisation.oci-containers = { backend = "podman"; containers = { mempool-db = { image = "mariadb:11"; autoStart = true; environmentFiles = [ config.sops.templates."mempool-db.env".path ]; 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" ]; environmentFiles = [ config.sops.templates."mempool-backend.env".path ]; 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 ]; }