diff --git a/hosts/web-arm/configuration.nix b/hosts/web-arm/configuration.nix index f0c0201..1f70c33 100644 --- a/hosts/web-arm/configuration.nix +++ b/hosts/web-arm/configuration.nix @@ -19,6 +19,7 @@ ./modules/victoriametrics.nix ./modules/blackbox-exporter.nix ./modules/updns.nix + ./modules/atticd.nix ./utils/modules/autoupgrade.nix ./utils/modules/promtail diff --git a/hosts/web-arm/modules/atticd.nix b/hosts/web-arm/modules/atticd.nix new file mode 100644 index 0000000..82ba8ee --- /dev/null +++ b/hosts/web-arm/modules/atticd.nix @@ -0,0 +1,207 @@ +{ config, lib, pkgs, ... }: + +let + atticHost = "attic.cloonar.com"; + atticPort = 8080; # Internal port for atticd +in { + # Declare required secrets + sops.secrets.atticd = { + # This should contain environment variables for atticd + # Format: KEY=value per line + # Required: + # ATTIC_SERVER_TOKEN_HS256_SECRET= + # Optional: + # ATTIC_SERVER_DATABASE_URL=postgresql://user:pass@localhost/attic + }; + + # Attic server service + services.atticd = { + enable = true; + + # Credentials file from sops + environmentFile = config.sops.secrets.atticd.path; + + settings = { + listen = "127.0.0.1:${toString atticPort}"; + + # API endpoint configuration + api-endpoint = "https://${atticHost}/"; + + # Allow automatic registration (set to false for production if you want to control access) + allow-registration = false; + + # Require tokens for all operations + require-proof-of-possession = true; + + # Chunking settings for large uploads + chunking = { + # Minimum chunk size: 16 MiB + min-size = 16 * 1024 * 1024; + # Average chunk size: 64 MiB + avg-size = 64 * 1024 * 1024; + # Maximum chunk size: 256 MiB + max-size = 256 * 1024 * 1024; + }; + + # Garbage collection + garbage-collection = { + # GC interval in seconds (12 hours) + interval = 12 * 60 * 60; + + # Delete unreferenced chunks after 7 days + default-retention-period = 7 * 24 * 60 * 60; + }; + + # Storage configuration + storage = { + # Use local filesystem storage + type = "local"; + # Store in /var/lib/atticd + path = "/var/lib/atticd/storage"; + }; + + # Optional: S3-compatible storage (commented out) + # storage = { + # type = "s3"; + # region = "eu-central-1"; + # bucket = "attic-cache"; + # endpoint = "https://s3.eu-central-1.amazonaws.com"; + # }; + + # Database configuration + database = { + url = "postgresql://atticd@/atticd?host=/run/postgresql"; + }; + + # Compression + compression = { + # Use zstd compression + type = "zstd"; + level = 3; # Balance between speed and compression + }; + }; + }; + + # Create state directory with proper permissions + systemd.services.atticd = { + serviceConfig = { + StateDirectory = "atticd"; + StateDirectoryMode = "0750"; + # Security hardening + PrivateTmp = true; + ProtectSystem = "strict"; + ProtectHome = true; + NoNewPrivileges = true; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + LockPersonality = true; + ProtectProc = "invisible"; + ProtectClock = true; + ProtectKernelLogs = true; + ProtectControlGroups = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectHostname = true; + SystemCallFilter = "@system-service"; + SystemCallErrorNumber = "EPERM"; + # Resource limits + LimitNOFILE = 65536; + }; + }; + + # Nginx reverse proxy configuration + services.nginx.virtualHosts."${atticHost}" = { + enableACME = true; + forceSSL = true; + acmeRoot = null; + + locations."/" = { + proxyPass = "http://127.0.0.1:${toString atticPort}"; + proxyWebsockets = true; + + extraConfig = '' + # Increase timeouts for large uploads + proxy_connect_timeout 300; + proxy_send_timeout 300; + proxy_read_timeout 300; + send_timeout 300; + + # Increase body size limit for large NAR uploads (500MB) + client_max_body_size 500M; + client_body_buffer_size 128k; + + # Proxy headers + 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; + + # Buffering settings for better performance + proxy_buffering on; + proxy_buffer_size 4k; + proxy_buffers 8 4k; + proxy_busy_buffers_size 8k; + proxy_temp_file_write_size 8k; + + # Cache settings for static content + proxy_cache_valid 200 302 10m; + proxy_cache_valid 404 1m; + ''; + }; + + # Health check endpoint + locations."/health" = { + proxyPass = "http://127.0.0.1:${toString atticPort}/health"; + }; + + # API endpoint with stricter rate limiting + locations."~ ^/api/" = { + proxyPass = "http://127.0.0.1:${toString atticPort}"; + + extraConfig = '' + # Rate limiting for API endpoints + limit_req zone=attic_api burst=10 nodelay; + + # Same proxy settings as above + 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; + + client_max_body_size 500M; + ''; + }; + }; + + # Configure Nginx rate limiting zones + services.nginx.appendHttpConfig = '' + # Rate limiting zones for Attic + limit_req_zone $binary_remote_addr zone=attic_api:10m rate=10r/s; + limit_req_zone $binary_remote_addr zone=attic_upload:10m rate=5r/s; + + # Connection limiting + limit_conn_zone $binary_remote_addr zone=attic_conn:10m; + ''; + + services.postgresql.ensureUsers = [ + { + name = "atticd"; + ensureDBOwnership = true; + } + ]; + services.postgresql.ensureDatabases = [ "atticd" ]; + services.postgresqlBackup.databases = [ "atticd" ]; + + services.borgbackup.jobs.default.exclude = [ + "/var/lib/atticd" + ]; + + # Monitoring with Prometheus (if you have it set up) + # services.prometheus.scrapeConfigs = [{ + # job_name = "atticd"; + # static_configs = [{ + # targets = [ "127.0.0.1:${toString atticPort}" ]; + # }]; + # }]; +}