commit 9383d615d7a9a7b45f645e9b7e0f91fc5ac1bd2b Author: Eli Fadi Date: Tue Mar 3 11:21:32 2026 +0100 Initial NixOS infra flake skeleton diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..8d3b30f --- /dev/null +++ b/flake.nix @@ -0,0 +1,53 @@ +{ + description = "Flowback infra (NixOS): Nextcloud + Forgejo, staging + production"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + + # Optional, but strongly recommended for secrets: + sops-nix.url = "github:Mic92/sops-nix"; + sops-nix.inputs.nixpkgs.follows = "nixpkgs"; + }; + + outputs = { self, nixpkgs, sops-nix, ... }: + let + mkHost = { name, system, modules }: + nixpkgs.lib.nixosSystem { + inherit system; + modules = + modules ++ [ + sops-nix.nixosModules.sops + ]; + specialArgs = { inherit name; }; + }; + in + { + # Staging host (x86_64 for VM/cloud) + nixosConfigurations.staging = mkHost { + name = "staging"; + system = "x86_64-linux"; + modules = [ + ./hosts/staging/configuration.nix + ]; + }; + + # Production host (x86_64 for VM/cloud) + nixosConfigurations.production = mkHost { + name = "production"; + system = "x86_64-linux"; + modules = [ + ./hosts/production/configuration.nix + ]; + }; + + # Raspberry Pi example (aarch64) — use when you’re ready + # nixosConfigurations.rpi-staging = mkHost { + # name = "rpi-staging"; + # system = "aarch64-linux"; + # modules = [ + # ./hosts/staging/configuration.nix + # ./hosts/staging/rpi-hardware.nix + # ]; + # }; + }; +} diff --git a/hosts/production/configuration.nix b/hosts/production/configuration.nix new file mode 100644 index 0000000..9fb1bf5 --- /dev/null +++ b/hosts/production/configuration.nix @@ -0,0 +1,33 @@ +{ config, pkgs, lib, name, ... }: + +{ + imports = [ + ../../modules/common.nix + ../../modules/sops.nix + ../../modules/nextcloud.nix + ../../modules/forgejo.nix + ]; + + networking.hostName = name; + + # Prod: open ports for web + networking.firewall.allowedTCPPorts = [ 80 443 22 ]; + + services.flowback = { + nextcloudHost = "cloud.example.com"; + forgejoHost = "git.example.com"; + }; + + # Production should enforce stronger auth; you can refine later. + users.users.elifa = { + isNormalUser = true; + extraGroups = [ "wheel" ]; + openssh.authorizedKeys.keys = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGI1L2SZzAfxkdHPsgJe0cx9s0owlMPyS6LnAURzXyad eli@wsl" + ]; + }; + + security.sudo.wheelNeedsPassword = true; + + system.stateVersion = "25.11"; +} diff --git a/hosts/staging/configuration.nix b/hosts/staging/configuration.nix new file mode 100644 index 0000000..1cc8bce --- /dev/null +++ b/hosts/staging/configuration.nix @@ -0,0 +1,34 @@ +{ config, pkgs, lib, name, ... }: + +{ + imports = [ + ../../modules/common.nix + ../../modules/sops.nix + ../../modules/nextcloud.nix + ../../modules/forgejo.nix + ]; + + networking.hostName = name; + + # Staging: open ports for web + networking.firewall.allowedTCPPorts = [ 80 443 22 ]; + + # Put placeholders for domains now; you can change later + services.flowback = { + nextcloudHost = "cloud-staging.example.com"; + forgejoHost = "git-staging.example.com"; + }; + + # Minimal user (replace with your SSH key) + users.users.elifa = { + isNormalUser = true; + extraGroups = [ "wheel" ]; + openssh.authorizedKeys.keys = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGI1L2SZzAfxkdHPsgJe0cx9s0owlMPyS6LnAURzXyad eli@wsl" + ]; + }; + + security.sudo.wheelNeedsPassword = false; + + system.stateVersion = "25.11"; +} diff --git a/modules/common.nix b/modules/common.nix new file mode 100644 index 0000000..94a4b55 --- /dev/null +++ b/modules/common.nix @@ -0,0 +1,31 @@ +{ config, pkgs, lib, ... }: + +{ + # Basic sane defaults + time.timeZone = "Europe/Stockholm"; + + # SSH access (you’ll tweak users later) + services.openssh.enable = true; + services.openssh.settings = { + PasswordAuthentication = false; + KbdInteractiveAuthentication = false; + PermitRootLogin = "no"; + }; + + # Helpful tools on the server + environment.systemPackages = with pkgs; [ + git + curl + jq + vim + ]; + + # Firewall on by default + networking.firewall.enable = true; + + # Nix settings (good defaults) + nix.settings = { + experimental-features = [ "nix-command" "flakes" ]; + auto-optimise-store = true; + }; +} diff --git a/modules/flowback-options.nix b/modules/flowback-options.nix new file mode 100644 index 0000000..44d9351 --- /dev/null +++ b/modules/flowback-options.nix @@ -0,0 +1,16 @@ +{ lib, ... }: +let + inherit (lib) mkOption types; +in +{ + options.services.flowback = { + nextcloudHost = mkOption { + type = types.str; + description = "Hostname for Nextcloud"; + }; + forgejoHost = mkOption { + type = types.str; + description = "Hostname for Forgejo"; + }; + }; +} diff --git a/modules/forgejo.nix b/modules/forgejo.nix new file mode 100644 index 0000000..dde99c3 --- /dev/null +++ b/modules/forgejo.nix @@ -0,0 +1,36 @@ +{ config, pkgs, lib, ... }: + +{ + imports = [ ./flowback-options.nix ]; + + services.nginx.enable = true; + + security.acme.acceptTerms = true; + security.acme.defaults.email = "me@example.com"; + + services.forgejo = { + enable = true; + settings = { + server = { + DOMAIN = config.services.flowback.forgejoHost; + ROOT_URL = "https://${config.services.flowback.forgejoHost}/"; + HTTP_PORT = 3000; + }; + service = { + DISABLE_REGISTRATION = true; + }; + }; + # Database defaults are okay to start; you can move to Postgres if desired + }; + + services.nginx.virtualHosts.${config.services.flowback.forgejoHost} = { + forceSSL = true; + enableACME = true; + locations."/" = { + proxyPass = "http://127.0.0.1:3000"; + proxyWebsockets = true; + }; + }; + + networking.firewall.allowedTCPPorts = [ 22 ]; +} diff --git a/modules/nextcloud.nix b/modules/nextcloud.nix new file mode 100644 index 0000000..2f8a818 --- /dev/null +++ b/modules/nextcloud.nix @@ -0,0 +1,38 @@ +{ config, pkgs, lib, ... }: + +{ + imports = [ ./flowback-options.nix ]; + + # Enable nginx + ACME (TLS). Works when DNS points correctly. + services.nginx.enable = true; + + security.acme.acceptTerms = true; + # I will change this email later + security.acme.defaults.email = "me@example.com"; + + services.nextcloud = { + enable = true; + hostName = config.services.flowback.nextcloudHost; + https = true; + + # Performance / reliability + configureRedis = true; + + # DB locally (Postgres) — production-ready baseline + database.createLocally = true; + + # Admin bootstrap secret will be wired via sops later. + # For now, placeholder: you’ll set adminpassFile via sops secret. + config = { + adminuser = "admin"; + adminpassFile = "/var/lib/sops-nix/nextcloud-adminpass"; + dbtype = "pgsql"; + dbpassFile = "/var/lib/sops-nix/nextcloud-dbpass"; + }; + }; + + services.nginx.virtualHosts.${config.services.flowback.nextcloudHost} = { + forceSSL = true; + enableACME = true; + }; +} diff --git a/modules/sops.nix b/modules/sops.nix new file mode 100644 index 0000000..6aeffb4 --- /dev/null +++ b/modules/sops.nix @@ -0,0 +1,15 @@ +{ config, lib, ... }: + +{ + # sops-nix reads secrets from YAML files in ./secrets + # You will create these later. + sops.defaultSopsFile = ../secrets/secrets.yaml; + + # Where the age key lives on target machines + sops.age.keyFile = "/var/lib/sops-nix/key.txt"; + + # Good to have a dedicated secrets mount/dir + systemd.tmpfiles.rules = [ + "d /var/lib/sops-nix 0700 root root - -" + ]; +} diff --git a/secrets/README.md b/secrets/README.md new file mode 100644 index 0000000..5660a8d --- /dev/null +++ b/secrets/README.md @@ -0,0 +1,11 @@ +Secrets are managed using sops + age. + +This repo expects an encrypted file: + secrets/secrets.yaml + +It should contain: +- nextcloud-adminpass +- nextcloud-dbpass + +On a target server you also need: + /var/lib/sops-nix/key.txt (age private key)