diff --git a/.gitignore b/.gitignore index 36de04e..e51ba08 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,13 @@ !hosts/ +!hosts/cube/ +!hosts/cube/matrix/ +!hosts/cube/nextcloud/ +!hosts/cube/nextcloud/*.gif +!hosts/cube/grafana/ +!hosts/cube/forgejo/ + !hosts/disk/ !hosts/nine/ diff --git a/hosts/cube/default.nix b/hosts/cube/default.nix new file mode 100644 index 0000000..184887e --- /dev/null +++ b/hosts/cube/default.nix @@ -0,0 +1,73 @@ +lib: lib.nixosSystem ({ config, keys, lib, ... }: let + inherit (lib) collectNix remove; +in { + imports = collectNix ./. |> remove ./default.nix; + + secrets.id.file = ./id.age; + services.openssh.hostKeys = [{ + type = "ed25519"; + path = config.secrets.id.path; + }]; + + services.openssh.banner = '' + _______________________________________ + / If God doesn't destroy San Francisco, \ + | He should apologize to Sodom and | + \ Gomorrah. / + --------------------------------------- + \ ^__^ + \ (oo)\_______ + (__)\ )\/\ + ||----w | + || || + ''; + + secrets.rgbPassword.file = ./password.rgb.age; + users.users = { + root.hashedPasswordFile = config.secrets.rgbPassword.path; + + rgb = { + description = "RGB"; + openssh.authorizedKeys.keys = keys.admins; + hashedPasswordFile = config.secrets.rgbPassword.path; + isNormalUser = true; + extraGroups = [ "wheel" ]; + }; + + backup = { + description = "Backup"; + openssh.authorizedKeys.keys = keys.all; + hashedPasswordFile = config.secrets.rgbPassword.path; + isNormalUser = true; + }; + }; + + home-manager.users = { + root = {}; + rgb = {}; + backup = {}; + }; + + networking = let + interface = "ens18"; + in { + hostName = "cube"; + + ipv4.address = "5.255.78.70"; + ipv4.prefixLength = 24; + + domain = "rgbcu.be"; + + defaultGateway = { + inherit interface; + + address = "5.255.78.1"; + }; + }; + + nixpkgs.hostPlatform = "x86_64-linux"; + system.stateVersion = "23.05"; + home-manager.sharedModules = [{ + home.stateVersion = "23.11"; + }]; +}) diff --git a/hosts/cube/forgejo/default.nix b/hosts/cube/forgejo/default.nix new file mode 100644 index 0000000..c8dabdf --- /dev/null +++ b/hosts/cube/forgejo/default.nix @@ -0,0 +1,161 @@ +{ self, config, lib, pkgs, ... }: let + inherit (config.networking) domain; + inherit (lib) const enabled genAttrs head merge mkForce; + + fqdn = "git.${domain}"; + + port = 8001; +in { + secrets.forgejoPasswordRunner = { + file = ./password.runner.age; + owner = "forgejo"; + }; + secrets.forgejoPasswordMail = { + file = self + /modules/mail/password.plain.age; + owner = "forgejo"; + }; + + services.postgresql = let + users = [ "forgejo" ]; + in { + ensureDatabases = users; + ensureUsers = map users (name: { + inherit name; + + ensureDBOwnership = true; + }); + }; + + services.restic.backups = genAttrs config.services.restic.hosts <| const { + paths = [ "/var/lib/gitea-runner" "/var/lib/forgejo" ]; + }; + + users.groups.gitea-runner = {}; + users.users.gitea-runner = { + extraGroups = [ "docker" ]; + group = "gitea-runner"; + home = "/var/lib/gitea-runner"; + isSystemUser = true; + }; + + services.gitea-actions-runner = { + package = pkgs.forgejo-actions-runner; + + instances.runner-01 = enabled { + name = "runner-01"; + url = fqdn; + + labels = [ + "debian-latest:docker://node:18-bullseye" + "ubuntu-latest:docker://node:18-bullseye" + "act:docker://ghcr.io/catthehacker/ubuntu:act-latest" + ]; + + tokenFile = config.secrets.forgejoPasswordRunner.path; + + settings = { + cache.enabled = true; + capacity = 4; + container.network = "host"; + }; + + hostPackages = [ + pkgs.bash + pkgs.uutils-coreutils-noprefix + pkgs.curl + pkgs.gitMinimal + pkgs.sudo + pkgs.wget + ]; + }; + }; + + services.openssh.settings.AcceptEnv = mkForce "SHELLS COLOTERM GIT_PROTOCOL"; + + services.forgejo = enabled { + lfs = enabled; + + secrets.mailer.PASSWD = config.secrets.forgejoPasswordMail.path; + + database = { + socket = "/run/postgresql"; + type = "postgres"; + }; + + settings = let + description = "RGBCube's Forge of Shitty Software"; + in { + default.APP_NAME = description; + + actions = { + ENABLED = true; + DEFAULT_ACTIONS_URL = "https://${fqdn}"; + }; + + attachment.ALLOWED_TYPES = "*/*"; + + cache.ENABLED = true; + + mailer = { + ENABLED = true; + + PROTOCOL = "smtps"; + SMTP_ADDR = self.disk.mailserver.fqdn; + USER = "git@${domain}"; + }; + + other = { + SHOW_FOOTER_TEMPLATE_LOAD_TIME = false; + SHOW_FOOTER_VERSION = false; + }; + + packages.ENABLED = false; + + repository = { + DEFAULT_BRANCH = "master"; + DEFAULT_MERGE_STYLE = "rebase-merge"; + DEFAULT_REPO_UNITS = "repo.code, repo.issues, repo.pulls, repo.actions"; + + DEFAULT_PUSH_CREATE_PRIVATE = false; + ENABLE_PUSH_CREATE_ORG = true; + ENABLE_PUSH_CREATE_USER = true; + + DISABLE_STARS = true; + }; + + "repository.upload" = { + FILE_MAX_SIZE = 100; + MAX_FILES = 10; + }; + + server = { + DOMAIN = domain; + ROOT_URL = "https://${fqdn}/"; + LANDING_PAGE = "/explore"; + + HTTP_ADDR = "::1"; + HTTP_PORT = port; + + SSH_PORT = head config.services.openssh.ports; + + DISABLE_ROUTER_LOG = true; + }; + + service.DISABLE_REGISTRATION = true; + + session = { + COOKIE_SECURE = true; + SAME_SITE = "strict"; + }; + + "ui.meta" = { + AUTHOR = description; + DESCRIPTION = description; + }; + }; + }; + + services.nginx.virtualHosts.${fqdn} = merge config.nginx.sslTemplate { + locations."/".proxyPass = "http://[::1]:${toString port}"; + }; +} diff --git a/hosts/cube/forgejo/password.runner.age b/hosts/cube/forgejo/password.runner.age new file mode 100644 index 0000000..8d48852 --- /dev/null +++ b/hosts/cube/forgejo/password.runner.age @@ -0,0 +1,8 @@ +age-encryption.org/v1 +-> ssh-ed25519 +rZ0Tw w2DgM8xAXzEYvEPHwf2SKgG6UThGCsC9O5GJtBEmESA +ID5KL9YemWr93wy66VQzgzoidMSR9TQKZYgNJp6WAoc +-> ssh-ed25519 CzqbPQ U1lCAlt/cyCwbo4VaEv7edcwGdEGzdHRSoDjvYyWkxI +JWVcb5a3iNeJv75AHNHr/9nU6pE6SnmS/5N+oZbl/cg +--- qBV+LTrheqYtuHtzeDpZTPa96P1Q91wcf7pMcIKBzFM +5=ghS+aߪċݍ_YeE +iB5UWo"JZ#(r`LZV \ No newline at end of file diff --git a/hosts/cube/grafana/default.nix b/hosts/cube/grafana/default.nix new file mode 100644 index 0000000..f3ff58e --- /dev/null +++ b/hosts/cube/grafana/default.nix @@ -0,0 +1,86 @@ +{ self, config, lib, ... }: let + inherit (config.networking) domain; + inherit (lib) const enabled genAttrs merge; + + fqdn = "metrics.${domain}"; + + port = 8000; +in { + secrets.grafanaPassword = { + file = ./password.age; + owner = "grafana"; + }; + secrets.grafanaPasswordMail = { + file = self + /modules/mail/password.plain.age; + owner = "grafana"; + }; + + services.postgresql = let + users = [ "grafana" ]; + in { + ensureDatabases = users; + ensureUsers = map users (name: { + inherit name; + + ensureDBOwnership = true; + }); + }; + + services.restic.backups = genAttrs config.services.restic.hosts <| const { + paths = [ "/var/lib/grafana" ]; + }; + + systemd.services.grafana = { + after = [ "postgresql.service" ]; + requires = [ "postgresql.service" ]; + }; + + services.grafana = enabled { + provision = enabled; + + settings = { + analytics.reporting_enabled = false; + + database.host = "/run/postgresql"; + database.type = "postgres"; + database.user = "grafana"; + + server.domain = fqdn; + server.http_addr = "[::1]"; + server.http_port = port; + + users.default_theme = "system"; + }; + + settings.security = { + admin_email = "metrics@${domain}"; + admin_password = "$__file{${config.secrets.grafanaPassword.path}}"; + admin_user = "admin"; + + cookie_secure = true; + disable_gravatar = true; + + disable_initial_admin_creation = true; # Just in case. + }; + + settings.smtp = { + enabled = true; + + password = "$__file{${config.secrets.grafanaPasswordMail.path}}"; + startTLS_policy = "MandatoryStartTLS"; + + ehlo_identity = "metrics@${domain}"; + from_address = "metrics@${domain}"; + from_name = "Metrics"; + host = "${self.disk.mailserver.fqdn}:${toString self.disk.services.postfix.relayPort}"; + }; + }; + + services.nginx.virtualHosts.${fqdn} = merge config.nginx.sslTemplate { + locations."/" = { + proxyPass = "http://[::1]:${toString port}"; + proxyWebsockets = true; + }; + }; +} + diff --git a/hosts/cube/grafana/password.age b/hosts/cube/grafana/password.age new file mode 100644 index 0000000..5085c24 Binary files /dev/null and b/hosts/cube/grafana/password.age differ diff --git a/hosts/cube/hardware.nix b/hosts/cube/hardware.nix new file mode 100644 index 0000000..5984ea7 --- /dev/null +++ b/hosts/cube/hardware.nix @@ -0,0 +1,23 @@ +{ lib, modulesPath, ... }: let + inherit (lib) enabled; +in { + imports = [(modulesPath + "/profiles/qemu-guest.nix")]; + + boot.loader.grub = enabled { + device = "/dev/vda"; + }; + + boot.initrd.availableKernelModules = [ + "ata_piix" + "sr_mod" + "uhci_hcd" + "virtio_blk" + "virtio_pci" + ]; + + fileSystems."/" = { + device = "/dev/disk/by-label/root"; + fsType = "ext4"; + options = [ "noatime" ]; + }; +} diff --git a/hosts/cube/id.age b/hosts/cube/id.age new file mode 100644 index 0000000..524f542 Binary files /dev/null and b/hosts/cube/id.age differ diff --git a/hosts/cube/matrix/default.nix b/hosts/cube/matrix/default.nix new file mode 100644 index 0000000..59174c1 --- /dev/null +++ b/hosts/cube/matrix/default.nix @@ -0,0 +1,136 @@ +{ config, lib, ... }: let + inherit (config.networking) domain; + inherit (lib) const enabled genAttrs merge strings; + + pathSite = "/var/www/site"; + + domainChat = "chat.${domain}"; + domainSync = "sync.${domain}"; + + wellKnownResponse = data: '' + default_type application/json; + add_header Access-Control-Allow-Origin *; + return 200 '${strings.toJSON data}'; + ''; + + configClient."m.homeserver".base_url = "https://${domainChat}"; + configClient."org.matrix.msc3575.proxy".url = "https://${domainSync}"; + + configServer."m.server" = "${domainChat}:443"; + + configWellKnownResponse.locations = { + "= /.well-known/matrix/client".extraConfig = wellKnownResponse configClient; + "= /.well-known/matrix/server".extraConfig = wellKnownResponse configServer; + }; + + configNotFoundLocation = { + locations."/".extraConfig = "return 404;"; + + extraConfig = "error_page 404 /404.html;"; + locations."/404".extraConfig = "internal;"; + + locations."/assets/".extraConfig = "return 301 https://${domain}$request_uri;"; + }; + + portSynapse = 8002; + portSync = 8003; +in { + secrets.matrixSecret = { + file = ./password.secret.age; + owner = "matrix-synapse"; + }; + secrets.matrixSyncPassword = { + file = ./password.sync.age; + owner = "matrix-synapse"; + }; + + services.postgresql = let + users = [ "matrix-synapse" "matrix-sliding-sync" ]; + in { + ensureDatabases = users; + ensureUsers = map users (name: { + inherit name; + + ensureDBOwnership = true; + }); + }; + + services.restic.backups = genAttrs config.services.restic.hosts <| const { + paths = [ "/var/lib/matrix-synapse" "/var/lib/matrix-sliding-sync" ]; + }; + + services.matrix-synapse = enabled { + withJemalloc = true; + + configureRedisLocally = true; + settings.redis.enabled = true; + + extras = [ "postgres" "url-preview" "user-search" ]; + + log.root.level = "WARNING"; # Shut the fuck up. + + settings = { + server_name = domain; + # We are not setting web_client_location since the root is not accessible + # from the outside web at all. Only /_matrix is reverse proxied to. + + database.name = "psycopg2"; + + report_stats = false; + + enable_metrics = true; + metrics_flags.known_servers = true; + + expire_access_token = true; + url_preview_enabled = true; + + # Trusting Matrix.org. + suppress_key_server_warning = true; + }; + + # Sets registration_shared_secret. + extraConfigFiles = [ config.secrets.matrixSecret.path ]; + + settings.listeners = [{ + port = portSynapse; + + bind_addresses = [ "::1" ]; + tls = false; + type = "http"; + x_forwarded = true; + + resources = [{ + compress = false; + names = [ "client" "federation" ]; + }]; + }]; + }; + + services.nginx.virtualHosts.${domain} = configWellKnownResponse; + + services.nginx.virtualHosts.${domainChat} = merge config.nginx.sslTemplate configWellKnownResponse configNotFoundLocation { + root = "${pathSite}"; + + locations."/_matrix".proxyPass = "http://[::1]:${toString portSynapse}"; + locations."/_synapse/client".proxyPass = "http://[::1]:${toString portSynapse}"; + }; + + services.matrix-sliding-sync = enabled { + environmentFile = config.age.secrets.matrixSyncPassword.path; + settings = { + SYNCV3_SERVER = "https://${domainChat}"; + SYNCV3_DB = "postgresql:///matrix-sliding-sync?host=/run/postgresql"; + SYNCV3_BINDADDR = "[::1]:${toString portSync}"; + }; + }; + + services.nginx.virtualHosts.${domainSync} = merge config.nginx.sslTemplate configNotFoundLocation { + root = pathSite; + + locations."~ ^/(client/|_matrix/client/unstable/org.matrix.msc3575/sync)" + .proxyPass = "http://[::1]:${toString portSynapse}"; + + locations."~ ^(\\/_matrix|\\/_synapse\\/client)" + .proxyPass = "http://[::1]:${toString portSync}"; + }; +} diff --git a/hosts/cube/matrix/password.secret.age b/hosts/cube/matrix/password.secret.age new file mode 100644 index 0000000..220b2f8 --- /dev/null +++ b/hosts/cube/matrix/password.secret.age @@ -0,0 +1,7 @@ +age-encryption.org/v1 +-> ssh-ed25519 +rZ0Tw JrLJgFemnQUQpZ+SYnEs0gVfPYmLyVM0ibPzLa9UKU0 +rZ9DtsOexcUGuPc4Rlfj9u0cbqU4z5R4M9MuKPyL/Zk +-> ssh-ed25519 CzqbPQ 5NibbW1CERCv8QTAuz5zeKxue1j23FyvQJn1ppjkXWg +oN12GPJsxJzRMrzUp34oLO0SvwT+Ed8CLCRqCtU0LrY +--- Y+OUAHyQ0irtOVcLDx1WvyIwp5VdkM2wqqhmeCMok6A +\{Z_?q74X˦+d^$4`B3 (^Ri)$WѭyX=-"<$X$:@̴XHXgq ޹ʤO v  <[o&CUkW|e_̺ \ No newline at end of file diff --git a/hosts/cube/matrix/password.sync.age b/hosts/cube/matrix/password.sync.age new file mode 100644 index 0000000..bf794d3 --- /dev/null +++ b/hosts/cube/matrix/password.sync.age @@ -0,0 +1,8 @@ +age-encryption.org/v1 +-> ssh-ed25519 +rZ0Tw txU8aL7ixRa9bTrV+FR5Vs8UrZ/JCNNblezCm/NJLWE +9ucwZW0qvCAc5EilV1B9XC+OA5Ro3FS6KBLGKT6NArI +-> ssh-ed25519 CzqbPQ 5DDlTXg4RIYcTcusedLRkuK3dbtccfQ0HiVFUH5B5XQ +6dW9qE5UMpOSohXIu559wBnPnOrTG/mqtrWvsy5CqYw +--- UO+CkdYt44UO16Yv3+sCJ5IoM2D+Pus7jEPbRFwGyKU +bsX +Pt \Twݟq{:l9%(Bd]hǹ|SMQ}^/qu]oXE@"ܑݟ|H}\pmd \ No newline at end of file diff --git a/hosts/cube/nextcloud/default.nix b/hosts/cube/nextcloud/default.nix new file mode 100644 index 0000000..158b11a --- /dev/null +++ b/hosts/cube/nextcloud/default.nix @@ -0,0 +1,118 @@ + { config, lib, pkgs, ... }: let + inherit (config.networking) domain; + inherit (lib) const enabled genAttrs mkAfter; + + fqdn = "cloud.${domain}"; + + packageNextcloud = pkgs.nextcloud29; +in { + secrets.nextcloudPassword = { + file = ./password.age; + owner = "nextcloud"; + }; + secrets.nextcloudPasswordExporter = { + file = ./password.age; + owner = "nextcloud-exporter"; + }; + + services.prometheus.exporters.nextcloud = enabled { + listenAddress = "[::]"; + + username = "admin"; + url = "https://${fqdn}"; + passwordFile = config.secrets.nextcloudPasswordExporter.path; + }; + + services.postgresql = let + users = [ "nextcloud" ]; + in { + ensureDatabases = users; + ensureUsers = map users (name: { + inherit name; + + ensureDBOwnership = true; + }); + }; + + services.restic.backups = genAttrs config.services.restic.hosts <| const { + paths = [ "/var/lib/nextcloud" ]; + }; + + systemd.services.nextcloud-setup = { + after = [ "postgresql.service" ]; + requires = [ "postgresql.service" ]; + + script = mkAfter '' + nextcloud-occ theming:config name "RGBCube's Depot" + nextcloud-occ theming:config slogan "RGBCube's storage of insignificant data." + + nextcloud-occ theming:config color "#000000" + nextcloud-occ theming:config background backgroundColor + + nextcloud-occ theming:config logo ${./icon.gif} + ''; + }; + + services.nextcloud = enabled { + package = packageNextcloud; + + hostName = fqdn; + https = true; + + configureRedis = true; + + config.adminuser = "admin"; + config.adminpassFile = config.secrets.nextcloudPassword.path; + + config.dbhost = "/run/postgresql"; + config.dbtype = "pgsql"; + + settings = { + default_phone_region = "TR"; + + # Even with manual SMTP configuration, Nextcloud fails to communicate properly + # and fails to send mail. PHP moment? + # mail_smtphost = "::1"; # FIXME: Will need to use SMTP. + # mail_smtpmode = "sendmail"; + # mail_from_address = "cloud"; + + maintenance_window_start = 1; + + # No clue why it was syslog. + # What are the NixOS module authors on? + log_type = "file"; + }; + + settings.enabledPreviewProviders = [ + "OC\\Preview\\BMP" + "OC\\Preview\\GIF" + "OC\\Preview\\JPEG" + "OC\\Preview\\Krita" + "OC\\Preview\\MarkDown" + "OC\\Preview\\MP3" + "OC\\Preview\\OpenDocument" + "OC\\Preview\\PNG" + "OC\\Preview\\TXT" + "OC\\Preview\\XBitmap" + "OC\\Preview\\HEIC" + ]; + + phpOptions = { + "opcache.interned_strings_buffer" = "16"; + output_buffering = "off"; + }; + + extraAppsEnable = true; + extraApps = { + inherit (packageNextcloud.packages.apps) + bookmarks calendar contacts deck + forms impersonate mail # groupfolders impersonate mail + maps notes polls previewgenerator; # tasks; + # Add: files_markdown files_texteditor memories news + }; + + nginx.recommendedHttpHeaders = true; + }; + + services.nginx.virtualHosts.${fqdn} = config.ngnixSslTemplate; +} diff --git a/hosts/cube/nextcloud/icon.gif b/hosts/cube/nextcloud/icon.gif new file mode 100644 index 0000000..7449097 Binary files /dev/null and b/hosts/cube/nextcloud/icon.gif differ diff --git a/hosts/cube/nextcloud/password.age b/hosts/cube/nextcloud/password.age new file mode 100644 index 0000000..51bd118 --- /dev/null +++ b/hosts/cube/nextcloud/password.age @@ -0,0 +1,7 @@ +age-encryption.org/v1 +-> ssh-ed25519 +rZ0Tw B5yow6s83Ij7ymJ4DVisL/Q58+tewqii8SLknj+WDCI +lkD9VftG4FA8sIztdrlCfaiaaSiKIEThT8gvGPuYvto +-> ssh-ed25519 CzqbPQ eHxowar9M2Zw7DheC9Cj2jwtCFKuPHJiNTGNtJOc2Ek +2LMp4JqjbKbW1Sglf06h7ZtPTA0+qxNj8QJbz7drH1k +--- kCW5Y/0JPRigRjj3CNpr4ruLsG1f5tBXoEX1eVKjB7Q +MpX1& rCx 3N5ޑ65 >W9W \ No newline at end of file diff --git a/hosts/cube/password.rgb.age b/hosts/cube/password.rgb.age new file mode 100644 index 0000000..ae896ef Binary files /dev/null and b/hosts/cube/password.rgb.age differ diff --git a/hosts/cube/podman.nix b/hosts/cube/podman.nix new file mode 100644 index 0000000..a798fbc --- /dev/null +++ b/hosts/cube/podman.nix @@ -0,0 +1,15 @@ +{ lib, ... }: let + inherit (lib) enabled; +in { + virtualisation.podman = enabled { + dockerCompat = true; + dockerSocket = enabled; + + defaultNetwork.settings.dns_enabled = true; + + autoPrune = enabled { + dates = "weekly"; + flags = [ "--all" ]; + }; + }; +} diff --git a/hosts/cube/postgresql.nix b/hosts/cube/postgresql.nix new file mode 100644 index 0000000..1b39eef --- /dev/null +++ b/hosts/cube/postgresql.nix @@ -0,0 +1,114 @@ +{ config, lib, pkgs, ... }: let + inherit (lib) const enabled genAttrs mkForce mkOverride; +in { + environment.systemPackages = [ + config.services.postgresql.package + ]; + + services.prometheus.exporters.postgres = enabled { + listenAddress = "[::]"; + runAsLocalSuperUser = true; + }; + + services.restic.backups = genAttrs config.services.restic.hosts <| const { + paths = [ "/tmp/postgresql-dump.sql.gz" ]; + + backupPrepareCommand = '' + ${config.services.postgresql.package}/bin/pg_dumpall --clean \ + | ${lib.getExe pkgs.gzip} --rsyncable \ + > /tmp/postgresql-dump.sql.gz + ''; + + backupCleanupCommand = '' + rm /tmp/postgresql-dump.sql.gz + ''; + }; + + services.postgresql = enabled { + package = pkgs.postgresql_14; + + enableJIT = true; + + initdbArgs = [ "--locale=C" "--encoding=UTF8" ]; + initialScript = pkgs.writeText "grant-root-perms" '' + GRANT pg_read_all_data TO root; + GRANT pg_write_all_data TO root; + ''; + + authentication = mkOverride 10 '' + # Type Database DBUser Authentication + local all all peer + ''; + + ensureUsers = map [ "postgres" "root" ] (name: { + inherit name; + + ensureClauses = { + createdb = true; + createrole = true; + login = true; + replication = true; + superuser = true; + }; + }); + + settings = { + listen_addresses = mkForce ""; + + # Generated by + max_connections = 100; + superuser_reserved_connections = 3; + + # Memory Settings + shared_buffers = "1024 MB"; + work_mem = "32 MB"; + maintenance_work_mem = "320 MB"; + huge_pages = "off"; + effective_cache_size = "3 GB"; + effective_io_concurrency = 1; # Concurrent IO only really activated if OS supports posix_fadvise function. + random_page_cost = 4; # Speed of random disk access relative to sequential access (1.0). + + # Monitoring + shared_preload_libraries = "pg_stat_statements"; # Per statement resource usage stats. + track_io_timing = "on"; # Measure exact block IO times. + track_functions = "pl"; # Track execution times of pl-language procedures if any. + + # Replication + wal_level = "replica"; + max_wal_senders = 0; + synchronous_commit = "on"; + + # Checkpointing + checkpoint_timeout = "15 min"; + checkpoint_completion_target = 0.9; + max_wal_size = "1024 MB"; + min_wal_size = "512 MB"; + + # WAL writing + wal_compression = "on"; + wal_buffers = -1; # auto-tuned by Postgres till maximum of segment size (16MB by default). + wal_writer_delay = "200ms"; + wal_writer_flush_after = "1MB"; + + # Background writer + bgwriter_delay = "200ms"; + bgwriter_lru_maxpages = 100; + bgwriter_lru_multiplier = 2.0; + bgwriter_flush_after = 0; + + # Parallel queries + max_worker_processes = 2; + max_parallel_workers_per_gather = 1; + max_parallel_maintenance_workers = 1; + max_parallel_workers = 2; + parallel_leader_participation = "on"; + + # Advanced features + enable_partitionwise_join = "on"; + enable_partitionwise_aggregate = "on"; + jit = "on"; + max_slot_wal_keep_size = "1000 MB"; + track_wal_io_timing = "on"; + }; + }; +} diff --git a/hosts/cube/prometheus.nix b/hosts/cube/prometheus.nix new file mode 100644 index 0000000..66c9d60 --- /dev/null +++ b/hosts/cube/prometheus.nix @@ -0,0 +1,42 @@ +{ self, config, lib, ... }: let + inherit (lib) enabled filterAttrs flatten mapAttrsToList; +in { + services.grafana.provision.datasources.settings = { + datasources = [{ + name = "Prometheus"; + type = "prometheus"; + url = "http://[::1]:${toString config.services.prometheus.port}"; + + orgId = 1; + }]; + + deleteDatasources = [{ + name = "Prometheus"; + orgId = 1; + }]; + }; + + services.prometheus = enabled { + listenAddress = "[::]"; + retentionTime = "1w"; + + scrapeConfigs = let + configToScrapeConfig = hostName: { hostConfig, ... }: + hostConfig.services.prometheus.exporters + |> filterAttrs (exporterName: exporterConfig: + exporterName != "minio" && + exporterName != "unifi-poller" && + exporterConfig.enable or false) + |> mapAttrsToList (exporterName: exporterConfig: { + job_name = "${exporterName}-${hostName}"; + + static_configs = [{ + targets = [ "${hostName}:${toString exporterConfig.port}" ]; + }]; + }); + + in self.nixosConfigurations + |> mapAttrsToList configToScrapeConfig + |> flatten; + }; +} diff --git a/hosts/disk/default.nix b/hosts/disk/default.nix index abd05e9..87d6683 100644 --- a/hosts/disk/default.nix +++ b/hosts/disk/default.nix @@ -40,8 +40,8 @@ in { in { hostName = "disk"; - ipv4 = "23.164.232.40"; - ipv6 = "2602:f9f7::40"; + ipv4.address = "23.164.232.40"; + ipv6.address = "2602:f9f7::40"; domain = "rgbcu.be"; diff --git a/hosts/disk/id.age b/hosts/disk/id.age index 4ff9d0a..1b3df6e 100644 Binary files a/hosts/disk/id.age and b/hosts/disk/id.age differ diff --git a/hosts/disk/password.floppy.age b/hosts/disk/password.floppy.age index 22d78fe..ff3b412 100644 --- a/hosts/disk/password.floppy.age +++ b/hosts/disk/password.floppy.age @@ -1,7 +1,7 @@ age-encryption.org/v1 --> ssh-ed25519 spFFQA pJguGLlB7R7iXrGfwKabGxmryMrfY57yvfaCytZG/Fs -1USXbjiteoTrs7+KEFPTMVBNHpBWFXyHi/iLxFL7tls --> ssh-ed25519 CzqbPQ IbK7nvEUn324R2zHDJzfgMV/FDqwLCU/jGZLSjrG4FY -naDshlcyrpvgLQydqxAXg/hhfFAFov568p163F7wrZ4 ---- MTj/7Zs1N348gDK+G1p01d6EZ21JzpPJnlaUc1ChcBo -*luM=&Z0!A3e\B0VښR; \6ֹo^ZR}_%~›k o$O$^A* \ No newline at end of file +-> ssh-ed25519 spFFQA YsTLrDcLvotgvQQJCD6DBH33gYQ9AqYaGcr190cG3TI +3q4i1UfAG7EnjhIxZaeQWQY8vcJr6JK7/LM3yP2Gao0 +-> ssh-ed25519 CzqbPQ e8RuAwnyVxk0Yuh7xHcrGYhhBH7GOJkJMxrIzgIcny8 +RCoXTagYrg7uX/XG9UCY794a4d+ClM8vYbkZCw6Sw0Q +--- 62b4Fu14qheY/e7ffDWERVQIrLrxl9yKdVWmuMD+ZSA +|aלp6}[TU>{V#7" SӍMOF1)3@7 5֋>Ktu2$܏W"v \ No newline at end of file diff --git a/hosts/nine/default.nix b/hosts/nine/default.nix index a6de34f..97f53b3 100644 --- a/hosts/nine/default.nix +++ b/hosts/nine/default.nix @@ -40,8 +40,8 @@ in { in { hostName = "nine"; - ipv4 = "152.53.2.105"; - ipv6 = "2a0a:4cc0:0:12d9::"; + ipv4.address = "152.53.2.105"; + ipv6.address = "2a0a:4cc0:0:12d9::"; domain = "rgbcu.be"; diff --git a/hosts/nine/github2forgejo/environment.age b/hosts/nine/github2forgejo/environment.age index 1373ee0..8fc0e00 100644 Binary files a/hosts/nine/github2forgejo/environment.age and b/hosts/nine/github2forgejo/environment.age differ diff --git a/hosts/nine/id.age b/hosts/nine/id.age index 42f0b9f..acfc099 100644 Binary files a/hosts/nine/id.age and b/hosts/nine/id.age differ diff --git a/hosts/nine/password.seven.age b/hosts/nine/password.seven.age index 21078a4..f416141 100644 --- a/hosts/nine/password.seven.age +++ b/hosts/nine/password.seven.age @@ -1,7 +1,7 @@ age-encryption.org/v1 --> ssh-ed25519 dASlBQ jbiIxrysHZ21MCvz4L+lbmi3GMH32ZRy+q/pNBkSqW0 -j2j3uQhWnvrCRNg0+cSwCz3/NNhQ3845h2bzCWMv3kQ --> ssh-ed25519 CzqbPQ u1JW2+Ti8JtHDLgvDFcKwVPPzSSQvNru4OOEMdGGhlQ -4AWUfqUzfT35ijv7rlqdnm0CGWOC4zQIpCWI+JYVWY4 ---- CtfGHevr6FqzOYK/REhFlxAy08LOgt+3+DJz9tZPNp0 -CB1D˵QC"B< *1UpE6NoGNQ> ?U#59\pH\q+DYk`kχuOM \ No newline at end of file +-> ssh-ed25519 dASlBQ QgmiZTOwtv6WXw/1tqHg53H7lH/ZYSYN932+desFFQo +p/1V5QZ9vrCOSvoSBKIPoIKAUXjdYXhovZkvRam2Yb0 +-> ssh-ed25519 CzqbPQ 7AN7/Dm9Iv4hQEELZyxKk74kJuDzv/veAHcPj3P3a0g +miUBnlLKmwsbY/OirayxJO3VQrdn/6HDGwONlMoKWlQ +--- trEYZUoukCFgpo/LwUW+0/ggQ0X0Chyyx/0TXRgsF58 +0zUTWҪ*Lt0S֚X,1>5esdKEU'|A"Q{?ժj}}4^&(9} \ No newline at end of file diff --git a/lib/system.nix b/lib/system.nix index e0feaa5..0aed4cd 100644 --- a/lib/system.nix +++ b/lib/system.nix @@ -1,9 +1,9 @@ inputs: self: super: let inherit (self) attrValues filter getAttrFromPath hasAttrByPath collectNix; - commonModules = collectNix ../modules/common; - nixosModules = collectNix ../modules/linux; - darwinModules = collectNix ../modules/darwin; + modulesCommon = collectNix ../modules/common; + modulesLinux = collectNix ../modules/linux; + modulesDarwin = collectNix ../modules/darwin; collectInputs = let inputs' = attrValues inputs; @@ -11,8 +11,8 @@ inputs: self: super: let |> filter (hasAttrByPath path) |> map (getAttrFromPath path); - inputNixosModules = collectInputs [ "nixosModules" "default" ]; - inputDarwinModules = collectInputs [ "darwinModules" "default" ]; + inputModulesLinux = collectInputs [ "nixosModules" "default" ]; + inputModulesDarwin = collectInputs [ "darwinModules" "default" ]; inputOverlays = collectInputs [ "overlays" "default" ]; overlayModule = { nixpkgs.overlays = inputOverlays; }; @@ -30,9 +30,9 @@ in { modules = [ module overlayModule - ] ++ commonModules - ++ nixosModules - ++ inputNixosModules; + ] ++ modulesCommon + ++ modulesLinux + ++ inputModulesLinux; }; darwinSystem = module: super.darwinSystem { @@ -41,8 +41,8 @@ in { modules = [ module overlayModule - ] ++ commonModules - ++ darwinModules - ++ inputDarwinModules; + ] ++ modulesCommon + ++ modulesDarwin + ++ inputModulesDarwin; }; } diff --git a/modules/acme/default.nix b/modules/acme/default.nix index a802747..a1cc3ca 100644 --- a/modules/acme/default.nix +++ b/modules/acme/default.nix @@ -2,11 +2,11 @@ inherit (config.networking) domain; inherit (lib) mkValue; in { - options.acmeUsers = mkValue []; + options.security.acme.users = mkValue []; config.secrets.acmeEnvironment.file = ./environment.age; - config.users.groups.acme.members = config.acmeUsers; + config.users.groups.acme.members = config.security.acme.users; config.security.acme = { acceptTerms = true; diff --git a/modules/acme/environment.age b/modules/acme/environment.age index e894173..75501e3 100644 --- a/modules/acme/environment.age +++ b/modules/acme/environment.age @@ -1,11 +1,12 @@ age-encryption.org/v1 --> ssh-ed25519 +rZ0Tw DMMzxXSIPSsRLkIvKJAiE6OzV1z3EZ0T+od2iIxMiA0 -OHVLHmVzeiWlsVI+DQ5M+iNik+nsdiQBz4zcquygC0A --> ssh-ed25519 spFFQA TVqArtAoudQlrgAqshCP8ZU0YlVZoKwkvUVh968NqC8 -Cy7+Y1rTFiAoWp6Gw8a1cljCjWPHtNwXjlXWQyu8A8U --> ssh-ed25519 dASlBQ ui5a61Tg1JoJvR8okc8qKkDhrSE9dH84XZQWhLn7cCo -5ehK2bvVgLZSYr5AstV1dwW7/qaVGRxs8PdzAg7sk4w --> ssh-ed25519 CzqbPQ wgktFhPRIAwX8BNJu8svEHDrpz0ZCOw94nR+M3FJCTY -RAErTHg/g/voC7yPf2lB+ELmysNwQXre9jucw2y+ZVc ---- AB7oiyhts6riNlp5xuWsFTzIx2y7Axn0CU4uCXHfVLo -`8eߧJST'BězgK zꚉWcFݸ3ᇴGR}Rיq6]n0b <+  dԴ\ECMUͱ3 X{qjʁE0&M8xtʈF }/Oq_:ҟ0(I/hKHK\X\'(gbAܐ \ No newline at end of file +-> ssh-ed25519 +rZ0Tw 9CETPqa+HdfpR1kRho1QnotNGFsLCVh64oRzP6DoF3Q +nZ6qcscZQ3Auct11BaM0jsYLyGnseDQ7OwQgSvLqGDw +-> ssh-ed25519 spFFQA 3oJSVeOECqU6ZkxLWErgrfn/3pLEEaJGy64OiGLvsXM +dt7obdmMHz4rGJxqQuZm9ptbCbJmxk80s3ME0FNQWuI +-> ssh-ed25519 dASlBQ A0OQkTL89cmKOhFlHerq98XxdEqn/EaXB+DlTbaGG2c +X8sG3M7BEPrey204Bs2kLuiPI+r4LKvIVD+Xdz/Vfxk +-> ssh-ed25519 CzqbPQ ZU/Fb5/XYBD9RQjMC4IQwQPSouotxFNWVpKdCsetNQk +o97rTb0aofBUmjPyrY11NwE33az7+HxbYUlw6cjE9GU +--- 8wtobvBFTd1V1idugrE6xnI1/QW/StCrcO6IjrRl/cQ +}KzLD(џ_̲tV~N֥^'5\H.7Թh map (splitString "#") |> map head; diff --git a/modules/linux/endlessh-go.nix b/modules/linux/endlessh-go.nix index 3698c86..8aefb0c 100644 --- a/modules/linux/endlessh-go.nix +++ b/modules/linux/endlessh-go.nix @@ -1,7 +1,7 @@ { config, lib, pkgs, ... }: let inherit (lib) enabled mkEnableOption mkIf mkOption types; - fakeSSHPort = 22; + portFakeSSH = 22; in { config.services.prometheus.exporters.endlessh-go = mkIf config.isServer <| enabled { listenAddress = "[::]"; @@ -10,11 +10,11 @@ in { # `services.endlessh-go.openFirewall` exposes both the Prometheus # exporters port and the SSH port, and we don't want the metrics # to leak, so we manually expose this like so. - config.networking.firewall.allowedTCPPorts = mkIf config.isServer <| [ fakeSSHPort ]; + config.networking.firewall.allowedTCPPorts = mkIf config.isServer <| [ portFakeSSH ]; config.services.endlessh-go = mkIf config.isServer <| enabled { listenAddress = "[::]"; - port = fakeSSHPort; + port = portFakeSSH; extraOptions = [ "-alsologtostderr" diff --git a/modules/linux/hyprland/hyprland.nix b/modules/linux/hyprland/hyprland.nix index cfe6132..4a70e0c 100644 --- a/modules/linux/hyprland/hyprland.nix +++ b/modules/linux/hyprland/hyprland.nix @@ -8,12 +8,12 @@ in merge <| mkIf config.isDesktop { xdg.portal = enabled { config.common.default = "*"; - extraPortals = with pkgs; [ - xdg-desktop-portal-hyprland + extraPortals = [ + pkgs.xdg-desktop-portal-hyprland ]; - configPackages = with pkgs; [ - hyprland + configPackages = [ + pkgs.hyprland ]; }; @@ -50,7 +50,7 @@ in merge <| mkIf config.isDesktop { enableXdgAutostart = true; }; - # plugins = with pkgs; [ hyprcursors ]; + # plugins = [ pkgs.hyprcursors ]; # settings.plugin.dynamic-cursors = { # mode = "rotate"; diff --git a/modules/linux/ip.nix b/modules/linux/ip.nix index 15dac4a..bd39671 100644 --- a/modules/linux/ip.nix +++ b/modules/linux/ip.nix @@ -3,14 +3,12 @@ inherit (lib) optionals; in { networking.interfaces.${interface} = { - ipv4.addresses = optionals (config.networking.ipv4 != null) [{ - address = config.networking.ipv4; - prefixLength = 22; + ipv4.addresses = optionals (config.networking.ipv4.address != null) [{ + inherit (config.networking.ipv4) address prefixLength; }]; - ipv6.addresses = optionals (config.networking.ipv4 != null) [{ - address = config.networking.ipv6; - prefixLength = 64; + ipv6.addresses = optionals (config.networking.ipv4.address != null) [{ + inherit (config.networking.ipv6) address prefixLength; }]; }; } diff --git a/modules/linux/resolved.nix b/modules/linux/resolved.nix index 85f8c61..c736bee 100644 --- a/modules/linux/resolved.nix +++ b/modules/linux/resolved.nix @@ -1,14 +1,14 @@ { config, lib, ... }: let - inherit (lib) enabled concatStringsSep map; + inherit (lib) enabled concatStringsSep; in { services.resolved = enabled { dnssec = "true"; dnsovertls = "true"; - extraConfig = config.dnsServers + extraConfig = config.networking.dns.servers |> map (server: "DNS=${server}") |> concatStringsSep "\n"; - fallbackDns = config.fallbackDnsServers; + fallbackDns = config.networking.dns.serversFallback; }; } diff --git a/modules/linux/restic/default.nix b/modules/linux/restic/default.nix index c2801a7..4c9fd4a 100644 --- a/modules/linux/restic/default.nix +++ b/modules/linux/restic/default.nix @@ -1,11 +1,11 @@ { config, lib, ... }: let inherit (lib) genAttrs mkConst mkIf remove; in{ - options.resticHosts = mkConst <| remove config.networking.hostName [ "cube" "disk" "nine" ]; + options.services.restic.hosts = mkConst <| remove config.networking.hostName [ "cube" "disk" "nine" ]; config.secrets.resticPassword.file = mkIf config.isServer ./password.age; - config.services.restic.backups = mkIf config.isServer <| genAttrs config.resticHosts (host: { + config.services.restic.backups = mkIf config.isServer <| genAttrs config.services.restic.hosts (host: { repository = "sftp:backup@${host}:${config.networking.hostName}-backup"; passwordFile = config.secrets.resticPassword.path; initialize = true; diff --git a/modules/linux/restic/password.age b/modules/linux/restic/password.age index dfe39fc..cc78dce 100644 Binary files a/modules/linux/restic/password.age and b/modules/linux/restic/password.age differ diff --git a/modules/mail/default.nix b/modules/mail/default.nix index 2d68cdc..d9538f8 100644 --- a/modules/mail/default.nix +++ b/modules/mail/default.nix @@ -10,11 +10,11 @@ in { listenAddress = "[::]"; }; - services.restic.backups = genAttrs config.resticHosts <| const { + services.restic.backups = genAttrs config.services.restic.hosts <| const { paths = [ config.mailserver.dkimKeyDirectory config.mailserver.mailDirectory ]; }; - acmeUsers = [ "mail" ]; + security.acme.users = [ "mail" ]; mailserver = enabled { domains = mkDefault [ domain ]; diff --git a/modules/mail/password.hash.age b/modules/mail/password.hash.age index 8347272..6a1d85e 100644 Binary files a/modules/mail/password.hash.age and b/modules/mail/password.hash.age differ diff --git a/modules/mail/password.plain.age b/modules/mail/password.plain.age index 917472f..39151da 100644 Binary files a/modules/mail/password.plain.age and b/modules/mail/password.plain.age differ diff --git a/modules/nginx.nix b/modules/nginx.nix index 09e4487..f5aa742 100644 --- a/modules/nginx.nix +++ b/modules/nginx.nix @@ -2,13 +2,13 @@ inherit (config.networking) domain; inherit (lib) enabled mkConst; in { - options.nginxSslTemplate = mkConst { + options.nginx.sslTemplate = mkConst { forceSSL = true; quic = true; useACMEHost = config.networking.domain; }; - options.nginxHeaders = mkConst '' + options.nginx.headers = mkConst '' # TODO: Not working for some reason. add_header Access-Control-Allow-Origin $allow_origin; add_header Access-Control-Allow-Methods $allow_methods; @@ -33,7 +33,7 @@ in { listenAddress = "[::]"; }; - config.acmeUsers = [ "nginx" ]; + config.security.acme.users = [ "nginx" ]; config.services.nginx = enabled { package = pkgs.nginxQuic; @@ -61,7 +61,7 @@ in { ~^https://.+\.${domain}$ "GET, HEAD, OPTIONS"; } - ${config.nginxHeaders} + ${config.nginx.headers} proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict"; ''; diff --git a/modules/site.nix b/modules/site.nix index 6705a04..55c2d9e 100644 --- a/modules/site.nix +++ b/modules/site.nix @@ -2,22 +2,22 @@ inherit (config.networking) domain; inherit (lib) enabled merge; - sitePath = "/var/www/site"; + pathSite = "/var/www/site"; - notFoundLocationConfig = { + configNotFoundLocation = { extraConfig = "error_page 404 /404.html;"; locations."/404".extraConfig = "internal;"; }; in { services.nginx = enabled { - virtualHosts.${domain} = merge config.nginxSslTemplate notFoundLocationConfig { - root = sitePath; + virtualHosts.${domain} = merge config.nginx.sslTemplate configNotFoundLocation { + root = pathSite; locations."/".tryFiles = "$uri $uri.html $uri/index.html =404"; locations."/assets/".extraConfig = '' if ($request_method = OPTIONS) { - ${config.nginxHeaders} + ${config.nginx.headers} add_header Content-Type text/plain; add_header Content-Length 0; return 204; @@ -27,12 +27,12 @@ in { ''; }; - virtualHosts."www.${domain}" = merge config.nginxSslTemplate { + virtualHosts."www.${domain}" = merge config.nginx.sslTemplate { locations."/".extraConfig = "return 301 https://${domain}$request_uri;"; }; - virtualHosts._ = merge config.nginxSslTemplate notFoundLocationConfig { - root = sitePath; + virtualHosts._ = merge config.nginx.sslTemplate configNotFoundLocation { + root = pathSite; locations."/".extraConfig = "return 404;"; locations."/assets/".extraConfig = "return 301 https://${domain}$request_uri;"; diff --git a/secrets.nix b/secrets.nix index 1476caa..53ac6cf 100644 --- a/secrets.nix +++ b/secrets.nix @@ -1,14 +1,23 @@ let - inherit (import ./keys.nix) disk nine admins all; + inherit (import ./keys.nix) cube disk nine admins all; in { + # cube + "hosts/cube/forgejo/password.runner.age".publicKeys = [ cube ] ++ admins; + "hosts/cube/grafana/password.age".publicKeys = [ cube ] ++ admins; + "hosts/cube/id.age".publicKeys = [ cube ] ++ admins; + "hosts/cube/matrix/password.secret.age".publicKeys = [ cube ] ++ admins; + "hosts/cube/matrix/password.sync.age".publicKeys = [ cube ] ++ admins; + "hosts/cube/nextcloud/password.age".publicKeys = [ cube ] ++ admins; + "hosts/cube/password.rgb.age".publicKeys = [ cube ] ++ admins; + # disk - "hosts/disk/password.floppy.age".publicKeys = [ disk ] ++ admins; "hosts/disk/id.age".publicKeys = [ disk ] ++ admins; + "hosts/disk/password.floppy.age".publicKeys = [ disk ] ++ admins; # nine + "hosts/nine/github2forgejo/environment.age".publicKeys = [ nine ] ++ admins; "hosts/nine/id.age".publicKeys = [ nine ] ++ admins; "hosts/nine/password.seven.age".publicKeys = [ nine ] ++ admins; - "hosts/nine/github2forgejo/environment.age".publicKeys = [ nine ] ++ admins; # shared "modules/common/ssh/config.age".publicKeys = all;