commit c53d997d075236f6d8c2a8e9db0238e46391735a Author: Xavier Lagraula Date: Sun May 10 19:06:58 2026 +0200 Initial framework: reusable modules, lib, pkgs, overlays, scripts, sample environment diff --git a/README.md b/README.md new file mode 100644 index 0000000..c5f508b --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +# NixOS Infrastructure Framework + +Reusable, environment-agnostic components for building and managing +NixOS-based infrastructure. + +## Contents + +| Directory | Purpose | +|-----------------|-------------------------------------------------| +| `modules/` | NixOS modules (machine types, services, users) | +| `lib/` | Nix utility functions | +| `pkgs/` | Custom packages not in Nixpkgs | +| `overlays/` | Nixpkgs overlays | +| `scripts/` | Operational scripts (deploy, create LXC, etc.) | +| `environments/` | Example environments to get started | + +## Getting Started + +1. Clone this repository. +2. Create your own environment directory (see `environments/sample/`). +3. Reference framework modules via relative paths or `fetchGit`. +4. Add your host configurations, network layout, and secrets. + +## Creating Your Own Environment + +``` +environments/your-env/ +├── configuration.nix # Environment entry point +├── hosts/ +│ ├── servers/ +│ │ └── myhost/ +│ │ └── configuration.nix +│ └── workstations/ +├── network/ # Subnets, VLANs, host assignments +├── secrets/ # agenix-encrypted secrets +└── users/ # User configurations +``` + +## License + +See [LICENSE](LICENSE). \ No newline at end of file diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..eae36a6 --- /dev/null +++ b/default.nix @@ -0,0 +1,34 @@ +# NixOS Infrastructure Framework +# +# Exports all reusable technical components. Use from your environment: +# +# framework = import /path/to/framework { }; +# framework.modules.machine-types.lxc +# framework.modules.services.dns +# +{ ... }: { + modules = { + machine-types = { + lxc = import ./modules/machine-types/lxc; + hypervisor = import ./modules/machine-types/hypervisor; + vm = import ./modules/machine-types/vm; + workstation = import ./modules/machine-types/workstation; + }; + services = { + dns = import ./modules/services/dns; + git-forge = import ./modules/services/git-forge; + password-manager = import ./modules/services/password-manager; + reverse-proxy = import ./modules/services/reverse-proxy; + }; + user-profiles = { + admin = import ./modules/user-profiles/admin; + dev = import ./modules/user-profiles/dev; + standard = import ./modules/user-profiles/standard; + guest = import ./modules/user-profiles/guest; + cam = import ./modules/user-profiles/cam; + }; + }; + lib = import ./lib; + pkgs = import ./pkgs; + overlays = import ./overlays; +} \ No newline at end of file diff --git a/environments/sample/hosts/dns01/configuration.nix b/environments/sample/hosts/dns01/configuration.nix new file mode 100644 index 0000000..351ea9c --- /dev/null +++ b/environments/sample/hosts/dns01/configuration.nix @@ -0,0 +1,26 @@ +{ config, pkgs, lib, ... }: + +{ + imports = [ + # Import the LXC machine type and DNS service from the framework + ../../../../modules/machine-types/lxc + ../../../../modules/services/dns/default.nix + ]; + + # Enable LXC machine type + lxc.enable = true; + + # Host identity — replace with your own hostname and IP + networking.hostName = "dns01"; + networking.useDHCP = true; + + # DNS service configuration — adapt to your network + services.dns = { + enable = true; + recursion = "AllowOnlyForPrivateNetworks"; + forwarders = [ "1.1.1.1" "8.8.8.8" ]; + listenAddresses = [ "10.0.0.10" "127.0.0.1" "::1" ]; + }; + + system.stateVersion = "25.11"; +} \ No newline at end of file diff --git a/environments/sample/users/alice/configuration.nix b/environments/sample/users/alice/configuration.nix new file mode 100644 index 0000000..7277d60 --- /dev/null +++ b/environments/sample/users/alice/configuration.nix @@ -0,0 +1,14 @@ +{ config, pkgs, lib, ... }: + +{ + # Sample user configuration — adapt to your needs + users.users.alice = { + isNormalUser = true; + description = "Alice (sample user)"; + extraGroups = [ "wheel" ]; # Enable sudo access + openssh.authorizedKeys.keys = [ + # Replace with your own SSH public key + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM7... alice@example" + ]; + }; +} \ No newline at end of file diff --git a/lib/README.md b/lib/README.md new file mode 100644 index 0000000..14e6f14 --- /dev/null +++ b/lib/README.md @@ -0,0 +1,4 @@ +# Lib +Custom Nix utility functions and helpers. +Used across the configuration to factorize code. +Simplifies data and string manipulation. \ No newline at end of file diff --git a/modules/README.md b/modules/README.md new file mode 100644 index 0000000..b001121 --- /dev/null +++ b/modules/README.md @@ -0,0 +1,2 @@ +# Modules +Reusable NixOS modules for the infrastructure. diff --git a/modules/machine-types/hypervisor/default.nix b/modules/machine-types/hypervisor/default.nix new file mode 100644 index 0000000..3e579ec --- /dev/null +++ b/modules/machine-types/hypervisor/default.nix @@ -0,0 +1,4 @@ +{ config, pkgs, lib, ... }: +{ + # TODO: Add hypervisor-specific configuration +} \ No newline at end of file diff --git a/modules/machine-types/lxc/default.nix b/modules/machine-types/lxc/default.nix new file mode 100644 index 0000000..cb6076e --- /dev/null +++ b/modules/machine-types/lxc/default.nix @@ -0,0 +1,48 @@ +{ config, modulesPath, pkgs, lib, ... }: +{ + imports = [ (modulesPath + "/virtualisation/proxmox-lxc.nix") ]; + nix.settings = { sandbox = false; }; + proxmoxLXC = { + manageNetwork = false; + privileged = true; + }; + + # Enable LXC specific options + options.lxc = { + enable = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Enable LXC machine type"; + }; + }; + + services.fstrim.enable = false; # Let Proxmox host handle fstrim + + # Cache DNS lookups to improve performance + services.resolved = { + extraConfig = '' + Cache=true + CacheFromLocalhost=true + ''; + }; + + # Default configuration for a LXC container + config = lib.mkIf config.lxc.enable { + # Disabling useless services + services.avahi.daemon.enable = false; # TODO : review the need for avahi in a container + services.bluetooth.enable = false; + services.printing.enable = false; + + # Optimzing for conainters + boot.kernelModules = [ ]; # TODO : review the disabling of all kernelModules in a container + powerManagement.enable = false; + + # Limiter les ressources si nécessaire + # TODO : review the need to limit ZFS pools in the LXC container configuration, in my ZFSless context + boot.zfs.extraPools = [ ]; + + }; + + system.stateVersion = "25.11"; + +} \ No newline at end of file diff --git a/modules/machine-types/vm/default.nix b/modules/machine-types/vm/default.nix new file mode 100644 index 0000000..d8c8687 --- /dev/null +++ b/modules/machine-types/vm/default.nix @@ -0,0 +1,4 @@ +{ config, pkgs, lib, ... }: +{ + # TODO: Add VM-specific configuration +} \ No newline at end of file diff --git a/modules/machine-types/workstation/default.nix b/modules/machine-types/workstation/default.nix new file mode 100644 index 0000000..726ba3f --- /dev/null +++ b/modules/machine-types/workstation/default.nix @@ -0,0 +1,4 @@ +{ config, pkgs, lib, ... }: +{ + # TODO: Add workstation-specific configuration +} \ No newline at end of file diff --git a/modules/secrets/default.nix b/modules/secrets/default.nix new file mode 100644 index 0000000..f12a23a --- /dev/null +++ b/modules/secrets/default.nix @@ -0,0 +1,22 @@ +{ config, pkgs, lib, ... }: + +{ + options.secrets = { + enable = lib.mkEnableOption "agenix secret management"; + + identity = lib.mkOption { + type = lib.types.path; + default = "/etc/ssh/ssh_host_ed25519_key"; + description = "Path to the SSH host private key used for age decryption."; + }; + }; + + config = lib.mkIf config.secrets.enable { + age = { + identityPaths = [ config.secrets.identity ]; + secrets = { }; + }; + + environment.systemPackages = with pkgs; [ agenix ]; + }; +} \ No newline at end of file diff --git a/modules/services/dns/default.nix b/modules/services/dns/default.nix new file mode 100644 index 0000000..1014050 --- /dev/null +++ b/modules/services/dns/default.nix @@ -0,0 +1,116 @@ +{ config, pkgs, lib, ... }: + +let + cfg = config.services.dns; + dnsPkg = if cfg.package != null then cfg.package else pkgs.technitium-dns-server; + + # Build the config.json for Technitium DNS Server. + # The server reads this file on startup from its config directory. + configJson = { + WebServicePort = cfg.webPort; + DNSListenerPort = cfg.dnsPort; + Recursion = cfg.recursion; + Forwarders = cfg.forwarders; + Log = false; + CachePrefetch = false; + AllowTtlOverride = true; + } // lib.optionalAttrs (cfg.adminPasswordFile != null) { + # Password hash will be set by the activation script on first run + # using the value from adminPasswordFile. + } // lib.optionalAttrs (cfg.listenAddresses != [ ]) { + ListenAddresses = cfg.listenAddresses; + } // lib.optionalAttrs (cfg.allowZoneTransfer != [ ]) { + AllowZoneTransfer = cfg.allowZoneTransfer; + } // cfg.extraConfig; + + configFile = pkgs.writeText "technitium-dns-config.json" + (builtins.toJSON configJson); +in + +{ + imports = [ + ./options.nix + ]; + + config = lib.mkIf cfg.enable { + + environment.systemPackages = [ dnsPkg ]; + + # Create the config directory and deploy initial config.json + systemd.tmpfiles.rules = [ + "d ${cfg.configDir} 0750 dns dns - -" + ]; + + systemd.services.technitium-dns-server = { + description = "Technitium DNS Server"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + wants = [ "network-online.target" ]; + + # Generate a password hash if adminPasswordFile is provided. + # The server is stopped on first run if no password hash exists, + # so we pre-seed the config with the hashed password. + preStart = '' + if [ -f "${cfg.configDir}/config.json" ]; then + # Config already exists, do not overwrite + true + else + install -m 0640 ${configFile} ${cfg.configDir}/config.json + ${lib.optionalString (cfg.adminPasswordFile != null) '' + if [ -f "${cfg.adminPasswordFile}" ]; then + # .NET-compatible SHA256 hash of the password + PASSWORD=$(cat "${cfg.adminPasswordFile}" | tr -d '\n') + HASH=$(echo -n "$PASSWORD" | ${pkgs.openssl}/bin/openssl dgst -sha256 -hex | cut -d' ' -f2) + ${pkgs.jq}/bin/jq \ + ".AdminPassword = \"$HASH\" | .Pbkdf2Iterations = 600000" \ + ${cfg.configDir}/config.json > ${cfg.configDir}/config.json.tmp + mv ${cfg.configDir}/config.json.tmp ${cfg.configDir}/config.json + fi + ''} + fi + ''; + + serviceConfig = { + Type = "simple"; + ExecStart = "${dnsPkg}/bin/technitium-dns-server ${cfg.configDir}"; + User = "dns"; + Group = "dns"; + Restart = "on-failure"; + RestartSec = "5s"; + LimitNOFILE = 1048576; + # Protect the system + ProtectSystem = "full"; + ProtectHome = true; + PrivateTmp = true; + NoNewPrivileges = true; + ReadWritePaths = [ cfg.configDir ]; + }; + }; + + # Create the dns system user and group + users.users.dns = { + description = "Technitium DNS Server daemon user"; + group = "dns"; + isSystemUser = true; + home = cfg.configDir; + createHome = true; + }; + + users.groups.dns = { }; + + # Open firewall ports for DNS (UDP/TCP 53) and optionally the web interface + networking.firewall = lib.mkMerge [ + { + allowedTCPPorts = [ cfg.dnsPort ]; + allowedUDPPorts = [ cfg.dnsPort ]; + } + # Allow web admin access only if listenAddresses restricts it to localhost + (lib.mkIf (cfg.listenAddresses == [ ] || builtins.elem "127.0.0.1" cfg.listenAddresses) { + allowedTCPPorts = [ cfg.webPort ]; + }) + ]; + + # Ensure DNS resolution is available locally before starting + networking.nameservers = lib.mkAfter [ "127.0.0.1" ]; + }; +} \ No newline at end of file diff --git a/modules/services/dns/options.nix b/modules/services/dns/options.nix new file mode 100644 index 0000000..8d57cc2 --- /dev/null +++ b/modules/services/dns/options.nix @@ -0,0 +1,79 @@ +{ config, lib, ... }: + +let + inherit (lib) types mkOption; +in + +{ + options.services.dns = { + enable = mkOption { + type = types.bool; + default = false; + description = "Enable the Technitium DNS Server"; + }; + + package = mkOption { + type = types.package; + default = null; + description = "Technitium DNS Server package to use. Defaults to pkgs.technitium-dns-server."; + }; + + webPort = mkOption { + type = types.port; + default = 5380; + description = "HTTP port for the Technitium web administration interface"; + }; + + dnsPort = mkOption { + type = types.port; + default = 53; + description = "DNS server port (both TCP and UDP)"; + }; + + recursion = mkOption { + type = types.enum [ "AllowOnlyForPrivateNetworks" "AllowAll" "DenyAll" ]; + default = "AllowOnlyForPrivateNetworks"; + description = "Recursion policy for DNS queries"; + }; + + forwarders = mkOption { + type = types.listOf types.str; + default = [ ]; + description = "Upstream DNS forwarders (e.g. [ \"1.1.1.1\" \"8.8.8.8\" ]). Empty means use root hints"; + }; + + configDir = mkOption { + type = types.str; + default = "/etc/dns"; + description = "Directory for persistent Technitium DNS configuration and zone data"; + }; + + adminPasswordFile = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Path to a file containing the admin password for the web interface. + If not set, the default credentials (admin/admin) are used. + Use agenix or sops-nix to provide this file securely. + ''; + }; + + listenAddresses = mkOption { + type = types.listOf types.str; + default = [ ]; + description = "IP addresses to listen on. Empty means listen on all interfaces"; + }; + + allowZoneTransfer = mkOption { + type = types.listOf types.str; + default = [ ]; + description = "IP addresses or subnets allowed to request zone transfers (AXFR/IXFR)"; + }; + + extraConfig = mkOption { + type = types.attrsOf types.anything; + default = { }; + description = "Additional Technitium DNS configuration options as an attribute set"; + }; + }; +} \ No newline at end of file diff --git a/modules/services/git-forge/default.nix b/modules/services/git-forge/default.nix new file mode 100644 index 0000000..7e4e4cd --- /dev/null +++ b/modules/services/git-forge/default.nix @@ -0,0 +1,90 @@ +{ config, pkgs, lib, ... }: + +let + cfg = config.services.git-forge; + inherit (lib) mkIf mkOption types; +in + +{ + options.services.git-forge = { + enable = mkOption { + type = types.bool; + default = false; + description = "Enable the git forge service (Forgejo)"; + }; + + domain = mkOption { + type = types.str; + default = "git.lagraula.fr"; + description = "Domain name for the Forgejo instance"; + }; + + sshPort = mkOption { + type = types.port; + default = 2222; + description = "SSH port for Git operations (avoid conflict with host SSH on 22)"; + }; + + httpPort = mkOption { + type = types.port; + default = 3000; + description = "HTTP port for the Forgejo web interface"; + }; + + dataDir = mkOption { + type = types.str; + default = "/var/lib/forgejo"; + description = "Data directory for Forgejo repositories and database"; + }; + + databaseType = mkOption { + type = types.enum [ "sqlite3" "postgres" "mysql" ]; + default = "sqlite3"; + description = "Database backend type"; + }; + + settings = mkOption { + type = types.attrsOf types.anything; + default = { }; + description = "Additional Forgejo settings (merged into services.forgejo.settings)"; + }; + }; + + config = mkIf cfg.enable { + # Use the built-in NixOS forgejo module + services.forgejo = { + enable = true; + package = pkgs.forgejo; + settings = lib.recursiveUpdate { + server = { + DOMAIN = cfg.domain; + HTTP_PORT = cfg.httpPort; + HTTP_ADDR = "0.0.0.0"; + ROOT_URL = "https://${cfg.domain}"; + SSH_PORT = cfg.sshPort; + SSH_LISTEN_PORT = cfg.sshPort; + }; + service = { + DISABLE_REGISTRATION = false; + }; + "repository".ROOT = "${cfg.dataDir}/repos"; + } (lib.mapAttrs (section: values: lib.mapAttrs (key: value: lib.mkDefault value) values) cfg.settings); + + database = { + type = cfg.databaseType; + }; + + dump = { + type = "tar.zst"; + }; + + # LXC container specifics - use the existing forgejo user + stateDir = cfg.dataDir; + }; + + # Open firewall ports for HTTP and SSH (git protocol) + networking.firewall = lib.mkIf config.services.forgejo.enable { + allowedTCPPorts = [ cfg.httpPort cfg.sshPort ]; + }; + }; +} \ No newline at end of file diff --git a/modules/services/password-manager/default.nix b/modules/services/password-manager/default.nix new file mode 100644 index 0000000..3cf690f --- /dev/null +++ b/modules/services/password-manager/default.nix @@ -0,0 +1,90 @@ +{ config, pkgs, lib, ... }: + +let + cfg = config.services.password-manager; + inherit (lib) mkIf mkOption types; +in + +{ + options.services.password-manager = { + enable = mkOption { + type = types.bool; + default = false; + description = "Enable the password manager service (Vaultwarden)"; + }; + + domain = mkOption { + type = types.str; + default = "pass.lagraula.fr"; + description = "Domain name for the Vaultwarden instance"; + }; + + port = mkOption { + type = types.port; + default = 8080; + description = "HTTP port for the Vaultwarden web interface"; + }; + + dataDir = mkOption { + type = types.str; + default = "/var/lib/vaultwarden"; + description = "Data directory for Vaultwarden persistent state"; + }; + + dbBackend = mkOption { + type = types.enum [ "sqlite" "mysql" "postgresql" ]; + default = "sqlite"; + description = "Database backend type"; + }; + + adminTokenFile = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Path to a file containing the admin token for the /admin panel. + Use agenix or sops-nix to provide this file securely. + ''; + }; + + signupsAllowed = mkOption { + type = types.bool; + default = true; + description = "Allow new user registration"; + }; + + extraConfig = mkOption { + type = types.attrsOf (types.nullOr (types.oneOf [ types.bool types.str types.int types.port ])); + default = { }; + description = "Additional Vaultwarden config options as attribute set (mapped to env vars)"; + }; + }; + + config = mkIf cfg.enable { + # Use the built-in NixOS vaultwarden module + services.vaultwarden = { + enable = true; + package = pkgs.vaultwarden; + webVaultPackage = pkgs.vaultwarden-webvault; + inherit (cfg) dbBackend; + config = { + DOMAIN = "https://${cfg.domain}"; + PORT = cfg.port; + SIGNUPS_ALLOWED = cfg.signupsAllowed; + } // (lib.mapAttrs (name: value: + if value == true then "true" + else if value == false then "false" + else toString value + ) cfg.extraConfig); + } // lib.optionalAttrs (cfg.adminTokenFile != null) { + environmentFile = cfg.adminTokenFile; + config = { + ADMIN_TOKEN = null; # Will be read from environmentFile + }; + }; + + # Open firewall port + networking.firewall = mkIf config.services.vaultwarden.enable { + allowedTCPPorts = [ cfg.port ]; + }; + }; +} \ No newline at end of file diff --git a/modules/services/reverse-proxy/default.nix b/modules/services/reverse-proxy/default.nix new file mode 100644 index 0000000..2786b41 --- /dev/null +++ b/modules/services/reverse-proxy/default.nix @@ -0,0 +1,56 @@ +{ config, pkgs, lib, ... }: + +let + # Récupère la liste des services depuis la configuration + publicServices = config.services.reverse-proxy.publicServices or []; +in +{ + # Options pour le module reverse-proxy + options.services.reverse-proxy = { + publicServices = lib.mkOption { + type = lib.types.listOf (lib.types.submodule { + options = { + host = lib.mkOption { type = lib.types.str; }; + internalHost = lib.mkOption { type = lib.types.str; }; + port = lib.mkOption { type = lib.types.int; default = 80; }; + }; + }); + default = []; + description = "Liste des services à exposer via le reverse proxy"; + }; + }; + + # Configuration de Caddy + config = lib.mkIf (config.services.reverse-proxy.publicServices or []) != [] { + services.caddy = { + enable = true; + virtualHosts = map (service: { + host = "${service.host}.lagraula.fr"; + reverseProxy = "http://${service.internalHost}.lagraula.fr:${toString service.port}"; + tls = { + email = config.services.caddy.email or "xavier@lagraula.fr"; + }; + }) (config.services.reverse-proxy.publicServices or []); + + # Configuration globale pour Caddy + extraConfig = '' + { + # Rate limiting global (optionnel) + rate_limit { + requests 100 + burst 200 + interval 1m + } + # Logging + log { + output file /var/log/caddy/access.log + } + } + ''; + }; + + # Ouvrir les ports firewall pour HTTP/HTTPS + networking.firewall.allowedTCPPorts = [ 80 443 ]; + networking.firewall.allowedUDPPorts = []; + }; +} \ No newline at end of file diff --git a/modules/user-profiles/admin.nix b/modules/user-profiles/admin.nix new file mode 100644 index 0000000..e69de29 diff --git a/modules/user-profiles/cam.nix b/modules/user-profiles/cam.nix new file mode 100644 index 0000000..e69de29 diff --git a/modules/user-profiles/dev.nix b/modules/user-profiles/dev.nix new file mode 100644 index 0000000..e69de29 diff --git a/modules/user-profiles/guest.nix b/modules/user-profiles/guest.nix new file mode 100644 index 0000000..e69de29 diff --git a/modules/user-profiles/standard.nix b/modules/user-profiles/standard.nix new file mode 100644 index 0000000..e69de29 diff --git a/overlays/README.md b/overlays/README.md new file mode 100644 index 0000000..bd78723 --- /dev/null +++ b/overlays/README.md @@ -0,0 +1,4 @@ +# Overlays +Custom modifications and extensions to Nixpkgs. +Applies patches or version overrides to existing packages. +Applied globally across the infrastructure. \ No newline at end of file diff --git a/overlays/custom-pkgs.nix b/overlays/custom-pkgs.nix new file mode 100644 index 0000000..974bce8 --- /dev/null +++ b/overlays/custom-pkgs.nix @@ -0,0 +1,4 @@ +self: super: { + # Custom packages and overrides for nixos-infra + # agenix is already available in nixpkgs — no custom overlay needed. +} \ No newline at end of file diff --git a/pkgs/README.md b/pkgs/README.md new file mode 100644 index 0000000..3e6f505 --- /dev/null +++ b/pkgs/README.md @@ -0,0 +1,4 @@ +# Packages +Custom software packages not found in upstream Nixpkgs. +Contains project-specific derivations (default.nix). +Can be referenced via overlays or directly by hosts. \ No newline at end of file diff --git a/pkgs/default.nix b/pkgs/default.nix new file mode 100644 index 0000000..eac1464 --- /dev/null +++ b/pkgs/default.nix @@ -0,0 +1,8 @@ +let + spec = builtins.fromJSON (builtins.readFile ./nixpkgs.json); +in +import (builtins.fetchTarball { + url = "https://github.com/NixOS/nixpkgs/archive/${spec.rev}.tar.gz"; + sha256 = spec.sha256; +}) {} +# TODO: add a nixos-infra module \ No newline at end of file diff --git a/pkgs/nixpkgs.json b/pkgs/nixpkgs.json new file mode 100644 index 0000000..2e98baa --- /dev/null +++ b/pkgs/nixpkgs.json @@ -0,0 +1,5 @@ +{ + "url": "https://github.com/NixOS/nixpkgs/archive/755f5aa91337890c432639c60b6064bb7fe67769.tar.gz", + "rev": "755f5aa91337890c432639c60b6064bb7fe67769", + "sha256": "1lmn8dicfwmsfdaiw18xjjys78bal6yjy3a41j02my7kw0wlb76a" +} diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..9bcb7e0 --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,107 @@ +# Scripts + +Utility scripts for infrastructure management. +Covers deployment, LXC container creation and bootstrap, +initial configuration of new NixOS machines, and age key generation. + +## Scripts Overview + +### `create-lxc-nixos.sh` — Create and deploy a NixOS LXC container + +Creates a NixOS LXC container on a remote Proxmox VE hypervisor, then +bootstraps it with the initial NixOS configuration and runs `deploy.sh` +to apply the host-specific configuration. + +```bash +# Usage +./create-lxc-nixos.sh [options] + +# Example: create dns01 with static IPv4 and IPv6 token +./create-lxc-nixos.sh dns01 \ + --ip 10.40.0.10/24 \ + --ip6 ::a:b:c:d \ + --pve-host pve01.prod.lagraula.fr + +# Dry run to preview the commands +./create-lxc-nixos.sh dns01 --dry-run +``` + +**Bootstrap process:** +1. `pct create` — create the container from the NixOS template +2. `pct start ` — start the container +3. Wait for the container to be ready (polling `pct exec`) +4. `pct push initial-lxc-configuration.nix` → `/etc/nixos/configuration.nix` +5. `pct push deploy.sh` → `/usr/local/bin/deploy-nixos` +6. `pct exec nixos-rebuild switch` — apply initial config (SSH, git, curl) +7. `pct exec deploy-nixos` — clone repo and apply host-specific config + +### `deploy.sh` — Deploy NixOS configuration from Git repository + +Clones or updates the nixos-infra repository, detects the hostname, +finds the corresponding configuration file, and applies it with +`nixos-rebuild switch`. + +```bash +# Usage +./deploy.sh [options] + +# Options +-u, --repo-url URL Git repository URL (default: https://gitea.lagraula.fr/...) +-d, --repo-dir DIR Local directory (default: /etc/nixos-infra) +-b, --branch BRANCH Git branch (default: main) +-n, --dry-run Simulate without making changes +``` + +**Configuration lookup order:** +1. `hosts/servers//configuration.nix` +2. `hosts/workstations//configuration.nix` + +### `initial-lxc-configuration.nix` — Bootstrap NixOS configuration (LXC) + +Minimal NixOS configuration pushed to a new LXC container during the +bootstrap phase. Installs SSH, git, and curl so the container can +clone the repository and apply its specific configuration. + +**Pushed to `/etc/nixos/configuration.nix` by `create-lxc-nixos.sh`.** + +### `gen-secrets-keys.sh` — Generate age public keys for agenix + +Connects to each host in the infrastructure, retrieves its SSH host +key via `ssh-keyscan`, converts it to an age public key with +`ssh-to-age`, and stores it in `secrets/pubkeys/.age`. + +```bash +# Usage +./gen-secrets-keys.sh + +# Prerequisites +# nix-shell -p ssh-to-age +``` + +**After generating keys, encrypt secrets with:** +```bash +age -r $(cat secrets/pubkeys/.age) -o secrets/.age +agenix -e secrets/.age +``` + +### `update-nixpkgs.sh` — Update the nixpkgs pin + +Updates `pkgs/nixpkgs.json` with the latest commit from nixpkgs stable. + +## Deployment workflow (LXC containers) + +``` +create-lxc-nixos.sh # Step 1: Create + bootstrap + └─ pct create + └─ pct push initial-lxc-configuration.nix + └─ pct push deploy.sh + └─ pct exec nixos-rebuild switch + └─ pct exec deploy.sh # Step 2: Clone repo + apply config + └─ git clone + └─ nixos-rebuild switch (host-specific) +``` + +For subsequent updates on an already-deployed container: +```bash +ssh +sudo /usr/local/bin/deploy-nixos \ No newline at end of file diff --git a/scripts/create-lxc-nixos.sh b/scripts/create-lxc-nixos.sh new file mode 100755 index 0000000..8b95a85 --- /dev/null +++ b/scripts/create-lxc-nixos.sh @@ -0,0 +1,325 @@ +#!/usr/bin/env bash +set -euo pipefail + +# --- Dependencies --- +# Check if docopts is installed (for Bash) +if ! command -v docopts &> /dev/null; then + echo "❌ Error: 'docopts' is required for Bash." >&2 + echo "See https://github.com/docopt/docopts to install it." >&2 + exit 1 +fi + +# --- Usage and Documentation --- +usage="Create, bootstrap and deploy a NixOS LXC container on a remote Proxmox VE 9 server. + +Usage: + $0 [options] + $0 -h|--help + +Options: + -h, --help Show this message. + -t, --template TEMPLATE LXC template (e.g. local:vztmpl/nixos-unstable). + -r, --rootfs-size SIZE Root filesystem size (e.g. 8G). + -c, --cores CORES Number of CPU cores. + -m, --memory MEMORY RAM in MiB. + -s, --swap SWAP Swap in MiB. + -p, --password PASSWORD Root password for the container. + -b, --bridge BRIDGE Network bridge (e.g. vmbr0). + -v, --vlan VLAN VLAN tag (e.g. tag=10). + -d, --domain DOMAIN DNS domain. + -u, --unprivileged UNPRIV Unprivileged container (0 or 1). + -i, --ip IP Static IPv4 address (e.g. 192.168.1.100/24). + --ip6 TOKEN IPv6 token for SLAAC (e.g. ::1:2:3:4). + -C, --cmode CMODE Console mode (console or tty). Default: console. + -T, --tags TAGS Tags for the container (optional). + -k, --ssh-public-keys KEYS SSH public keys for the container. + --pve-host HOST Proxmox host (e.g. pve). + --pve-user USER Proxmox user (default: admin). + --pve-port PORT SSH port for Proxmox (default: 22). + --pve-ssh-key KEY SSH key file for authentication. + --initial-config FILE Initial NixOS configuration file to push + [default: ./initial-lxc-configuration.nix]. + --repo-url URL Git repository URL for deploy.sh + [default: (none, must be provided)]. + --branch BRANCH Git branch for deploy.sh [default: main]. + --environment ENV Environment name (production, dev, etc.) + [default: (none, must be provided or set via config)]. + --skip-deploy Skip the post-creation bootstrap (push + nixos-rebuild). + --dry-run Simulate container creation without execution. + +Optional configuration files (loaded in order, later overrides earlier): + /etc/nixos-infra/hosts/config + \${XDG_CONFIG_HOME}/nixos-infra/hosts/config + ./config +" + +# --- Default Parameters (Environment Variables) --- +# Proxmox Server +PVE_HOST="${PVE_HOST:-}" +PVE_USER="${PVE_USER:-admin}" +PVE_PORT="${PVE_PORT:-22}" +PVE_SSH_KEY="${PVE_SSH_KEY:-}" +DRY_RUN="${DRY_RUN:-false}" + +# LXC Container +TEMPLATE="${TEMPLATE:-local:vztmpl/nixos-unstable-amd64-default_20260428}" +ROOTFS_SIZE="${ROOTFS_SIZE:-8G}" +CORES="${CORES:-2}" +MEMORY="${MEMORY:-2048}" +SWAP="${SWAP:-1024}" +PASSWORD="${PASSWORD:-changeme}" +BRIDGE="${BRIDGE:-vmbr0}" +VLAN="${VLAN:-}" +DOMAIN="${DOMAIN:-}" +UNPRIVILEGED="${UNPRIVILEGED:-0}" +IP="${IP:-}" +IP6="${IP6:-}" +CMODE="${CMODE:-console}" +TAGS="${TAGS:-}" +SSH_PUBLIC_KEYS="${SSH_PUBLIC_KEYS:-}" + +# Bootstrap +INITIAL_CONFIG="${INITIAL_CONFIG:-./initial-lxc-configuration.nix}" +REPO_URL="${REPO_URL:-}" +BRANCH="${BRANCH:-main}" +SKIP_DEPLOY="${SKIP_DEPLOY:-false}" +ENVIRONMENT="${ENVIRONMENT:-}" + +# --- Parse Arguments with docopts (Highest priority) --- +# set +e is to prevent set -e from eating the error message from docopts. +# This code is up here to prevent useless error messages to be printed +# in case the "-h" or "--help" argument is used. +set +e +args=$(docopts -h "$usage" : "$@") +eval "$args" +set -e + +# --- Apply Configuration Files (by increasing priority) --- +XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}" +CONFIG_FILES=(\ + "/etc/nixos-infra/hosts/config" \ + "$XDG_CONFIG_HOME/nixos-infra/hosts/config" \ + "./config") +for conffile in ${CONFIG_FILES[*]}; do + if [ -f "$conffile" ]; then + echo "📄 Applying parameters from $conffile..." + set -a + source "$conffile" + set +a + else + echo "ℹ️ $conffile not found (optional)." + fi +done + +# Override with CLI arguments (have priority over config files) +PVE_HOST="${pve_host:-$PVE_HOST}" +PVE_USER="${pve_user:-$PVE_USER}" +PVE_PORT="${pve_port:-$PVE_PORT}" +PVE_SSH_KEY="${pve_ssh_key:-$PVE_SSH_KEY}" +DRY_RUN="${dry_run:-$DRY_RUN}" + +TEMPLATE="${template:-$TEMPLATE}" +ROOTFS_SIZE="${rootfs_size:-$ROOTFS_SIZE}" +CORES="${cores:-$CORES}" +MEMORY="${memory:-$MEMORY}" +SWAP="${swap:-$SWAP}" +PASSWORD="${password:-$PASSWORD}" +BRIDGE="${bridge:-$BRIDGE}" +VLAN="${vlan:-$VLAN}" +DOMAIN="${domain:-$DOMAIN}" +UNPRIVILEGED="${unprivileged:-$UNPRIVILEGED}" +IP="${ip:-$IP}" +IP6="${ip6:-$IP6}" +CMODE="${cmode:-$CMODE}" +TAGS="${tags:-$TAGS}" +SSH_PUBLIC_KEYS="${ssh_public_keys:-$SSH_PUBLIC_KEYS}" + +INITIAL_CONFIG="${initial_config:-$INITIAL_CONFIG}" +REPO_URL="${repo_url:-$REPO_URL}" +BRANCH="${branch:-$BRANCH}" +SKIP_DEPLOY="${skip_deploy:-$SKIP_DEPLOY}" +ENVIRONMENT="${environment:-$ENVIRONMENT}" + +# --- SSH Key Default Logic --- +if [ "$PVE_SSH_KEY" = "default" ]; then + PVE_SSH_KEY="${HOME}/.ssh/id_${PVE_USER}" +fi + +# --- Critical Parameters Validation --- +mandatory_params=( + "TEMPLATE" + "ROOTFS_SIZE" + "CORES" + "MEMORY" + "SWAP" + "PASSWORD" + "BRIDGE" + "DOMAIN" + "UNPRIVILEGED" + "CMODE" + "SSH_PUBLIC_KEYS" + "PVE_HOST" + "PVE_USER" + "PVE_PORT" +) +missing_params=() +for param in ${mandatory_params[*]}; do + if [ -z "${!param}" ]; then missing_params+=("$param"); fi +done +if [ ${#missing_params[@]} -gt 0 ]; then + echo "❌ Error: The following necessary parameters are missing: ${missing_params[*]}" >&2 + echo "❌ Error: Please provide them through a config file or the command line." >&2 + exit 1 +fi + +# Authentication Validation +if [ ! -f "$PVE_SSH_KEY" ]; then + echo "❌ Error: SSH key file '$PVE_SSH_KEY' does not exist." >&2 + exit 1 +fi + +# Validate initial-config.nix exists (unless --skip-deploy) +if [ "$SKIP_DEPLOY" != "true" ] && [ ! -f "$INITIAL_CONFIG" ]; then + echo "❌ Error: Initial NixOS configuration '$INITIAL_CONFIG' not found." >&2 + echo " Provide a valid path with --initial-config or use --skip-deploy." >&2 + exit 1 +fi + +# Validate deploy.sh exists (unless --skip-deploy) +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DEPLOY_SCRIPT="${SCRIPT_DIR}/deploy.sh" +if [ "$SKIP_DEPLOY" != "true" ] && [ ! -f "$DEPLOY_SCRIPT" ]; then + echo "❌ Error: deploy.sh not found at '$DEPLOY_SCRIPT'." >&2 + echo " The bootstrap phase requires deploy.sh to be present." >&2 + exit 1 +fi + +# --- SSH Connection to Proxmox Server --- +run_proxmox() { + local ssh_cmd="ssh -p $PVE_PORT" + if [ -n "$PVE_SSH_KEY" ] && [ -f "$PVE_SSH_KEY" ]; then + ssh_cmd="$ssh_cmd -i $PVE_SSH_KEY " + else + ssh_cmd="$ssh_cmd -o PreferredAuthentications=password " + fi + $ssh_cmd "$PVE_USER@$PVE_HOST" "$1" +} + +# --- Network Options Construction --- +NET_OPTS="name=eth0,bridge=$BRIDGE" +if [ -n "$VLAN" ]; then + NET_OPTS="$NET_OPTS,$VLAN" +fi +if [ -n "$IP" ]; then + NET_OPTS="$NET_OPTS,ip=$IP" +fi +# IPv6: use SLAAC with a token if provided, otherwise DHCP +if [ -n "$IP6" ]; then + NET_OPTS="$NET_OPTS,ip6=auto,token6=${IP6}" +else + NET_OPTS="$NET_OPTS,ip6=dhcp" +fi + +# --- Container Creation --- +echo "🚀 Creating LXC container $short_name on $PVE_HOST (domain: $DOMAIN)..." +CREATE_CMD="pct create $ROOTFS_SIZE $TEMPLATE --cores $CORES \ +--memory $MEMORY --swap $SWAP --hostname $short_name.$DOMAIN \ +--password $PASSWORD --unprivileged $UNPRIVILEGED --net0 $NET_OPTS \ +--onboot 0 --cmode $CMODE --ssh-public-keys $SSH_PUBLIC_KEYS" +if [ -n "$TAGS" ]; then + CREATE_CMD="$CREATE_CMD --tags $TAGS" +fi + +# Display the command (with password masked) +DISPLAY_CMD=$(echo "$CREATE_CMD" | + sed "s/--password [^ ]*/--password \*\*\*\*\*/g") +echo "🔧 Command to execute on $PVE_HOST: $DISPLAY_CMD" + +# Execute or simulate +if [ "$DRY_RUN" = "true" ]; then + echo "🧪 Dry run mode:" + echo " - Container creation skipped" + echo " - Bootstrap phase skipped" + exit 0 +fi + +LXC_ID=$(run_proxmox "$CREATE_CMD" | grep -oP '\d+') +if [ -z "$LXC_ID" ]; then + echo "❌ Error: Failed to create the container." >&2 + exit 1 +fi +echo "✅ LXC container $short_name created successfully (ID: $LXC_ID)." + +# --- Bootstrap Phase (unless --skip-deploy) --- +if [ "$SKIP_DEPLOY" = "true" ]; then + echo "⏭️ --skip-deploy set. Container created but not bootstrapped." + echo " Start it manually with: pct start $LXC_ID" + echo " Then apply a configuration manually." + exit 0 +fi + +echo "" +echo "🚀 Starting bootstrap phase for CT $LXC_ID..." + +# 1. Start the container +echo "▶️ Starting container $LXC_ID..." +run_proxmox "pct start $LXC_ID" || { + echo "❌ Error: Failed to start container $LXC_ID." >&2 + exit 1 +} + +# 2. Wait for the container to be ready (SSH or pct exec available) +echo "⏳ Waiting for container to be ready..." +for i in $(seq 1 30); do + if run_proxmox "pct exec $LXC_ID -- true" 2>/dev/null; then + echo "✅ Container $LXC_ID is ready." + break + fi + if [ "$i" -eq 30 ]; then + echo "❌ Error: Container $LXC_ID did not become ready in time." >&2 + echo " You can retry bootstrap manually." >&2 + exit 1 + fi + sleep 2 +done + +# 3. Push initial-lxc-configuration.nix +echo "📄 Pushing initial NixOS configuration..." +run_proxmox "pct push $LXC_ID '$INITIAL_CONFIG' /etc/nixos/configuration.nix" || { + echo "❌ Error: Failed to push initial configuration." >&2 + exit 1 +} + +# 4. Push deploy.sh +echo "📄 Pushing deploy script..." +run_proxmox "pct push $LXC_ID '$DEPLOY_SCRIPT' /usr/local/bin/deploy-nixos" || { + echo "❌ Error: Failed to push deploy script." >&2 + exit 1 +} +run_proxmox "pct exec $LXC_ID -- chmod +x /usr/local/bin/deploy-nixos" || { + echo "❌ Error: Failed to make deploy script executable." >&2 + exit 1 +} + +# 5. Apply initial configuration (nixos-rebuild switch) +echo "⚙️ Applying initial NixOS configuration (this may take a while)..." +if ! run_proxmox "pct exec $LXC_ID -- nixos-rebuild switch" 2>&1; then + echo "⚠️ Warning: Initial nixos-rebuild may have issues." >&2 + echo " Check the container manually: pct exec $LXC_ID -- nixos-rebuild switch" >&2 + # Continue anyway — deploy.sh might still work +fi +echo "✅ Initial configuration applied." + +# 6. Run deploy.sh to clone the repo and apply the specific configuration +echo "🌐 Running deploy.sh to clone repo and apply specific configuration..." +# Pass REPO_URL, BRANCH and ENVIRONMENT as env vars to deploy.sh inside the container +DEPLOY_CMD="REPO_URL='$REPO_URL' BRANCH='$BRANCH' ENVIRONMENT='$ENVIRONMENT' /usr/local/bin/deploy-nixos" +if ! run_proxmox "pct exec $LXC_ID -- bash -c '$DEPLOY_CMD'" 2>&1; then + echo "⚠️ Warning: deploy.sh encountered issues." >&2 + echo " You can retry manually: pct exec $LXC_ID -- /usr/local/bin/deploy-nixos" >&2 + exit 1 +fi + +echo "" +echo "🎉 Container $short_name (CT $LXC_ID) successfully created and deployed!" +echo " Connect with: ssh root@${IP%%/*} (or use pct exec $LXC_ID)" \ No newline at end of file diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100644 index 0000000..ef994d8 --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,116 @@ +#!/usr/bin/env bash +set -euo pipefail + +# --- Default values (can be overridden by environment variables) --- +REPO_URL="${REPO_URL:-}" +REPO_DIR="${REPO_DIR:-/etc/nixos-infra}" +BRANCH="${BRANCH:-main}" +DRY_RUN="${DRY_RUN:-false}" + +# --- Usage --- +usage() { + cat <&2; usage ;; + esac +done + +HOSTNAME=$(hostname) + +if [ "$(id -u)" -ne 0 ]; then + echo "❌ This script must be run as root." >&2 + exit 1 +fi + +# --- Dry run mode --- +if [ "$DRY_RUN" = "true" ]; then + echo "🧪 Dry run mode:" + echo " - Repository URL: $REPO_URL" + echo " - Repository dir: $REPO_DIR" + echo " - Branch: $BRANCH" + echo " - Hostname: $HOSTNAME" + echo " - Expected config: $REPO_DIR/environments//hosts/servers/$HOSTNAME/configuration.nix" + echo "" + echo " Would execute:" + echo " git clone --branch $BRANCH $REPO_URL $REPO_DIR" + echo " nixos-rebuild switch -I nixos-config=...$HOSTNAME/configuration.nix" + exit 0 +fi + +# --- Clone or update the repository --- +if [ -d "$REPO_DIR/.git" ]; then + echo "🔄 Mise à jour du dépôt dans $REPO_DIR..." + cd "$REPO_DIR" + git fetch origin + git checkout "$BRANCH" + git pull origin "$BRANCH" +else + echo "📥 Clonage du dépôt dans $REPO_DIR..." + mkdir -p "$REPO_DIR" + git clone --branch "$BRANCH" "$REPO_URL" "$REPO_DIR" +fi + +# --- Find the configuration for this machine --- +ENVIRONMENT="${ENVIRONMENT:-}" +if [ -z "$ENVIRONMENT" ]; then + # Try common environment names + for env in production prod staging stage dev development; do + if [ -d "$REPO_DIR/environments/$env" ]; then + ENVIRONMENT="$env" + break + fi + done +fi + +if [ -z "$ENVIRONMENT" ]; then + echo "❌ Error: No environment specified and none detected." >&2 + echo " Set ENVIRONMENT environment variable or use --environment flag." >&2 + exit 1 +fi + +CONFIG_PATH="$REPO_DIR/environments/$ENVIRONMENT/hosts/servers/$HOSTNAME/configuration.nix" +if [ ! -f "$CONFIG_PATH" ]; then + CONFIG_PATH="$REPO_DIR/environments/$ENVIRONMENT/hosts/workstations/$HOSTNAME/configuration.nix" +fi + +if [ ! -f "$CONFIG_PATH" ]; then + echo "❌ Error : No configuration found for $HOSTNAME in environment '$ENVIRONMENT'" >&2 + echo " Checked paths :" >&2 + echo " - $REPO_DIR/environments/$ENVIRONMENT/hosts/servers/$HOSTNAME/configuration.nix" >&2 + echo " - $REPO_DIR/environments/$ENVIRONMENT/hosts/workstations/$HOSTNAME/configuration.nix" >&2 + exit 1 +fi + +# --- Apply the configuration --- +echo "🚀 Deploying the configuration for $HOSTNAME..." +nixos-rebuild switch -I nixos-config="$CONFIG_PATH" + +echo "✅ Deployment was successful !" \ No newline at end of file diff --git a/scripts/gen-secrets-keys.sh b/scripts/gen-secrets-keys.sh new file mode 100644 index 0000000..f8a603e --- /dev/null +++ b/scripts/gen-secrets-keys.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +set -euo pipefail + +# --- gen-secrets-keys.sh --- +# Generate age public keys from SSH host keys for all known hosts. +# +# This script retrieves each host's SSH host key, converts it to an +# age public key using ssh-to-age, and stores it in +# secrets/pubkeys/.age for use with agenix. + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +PUBKEYS_DIR="${PROJECT_DIR}/secrets/pubkeys" + +# Ensure ssh-to-age is available +if ! command -v ssh-to-age &> /dev/null; then + echo "❌ Error: 'ssh-to-age' is required." + echo " Install it with: nix-shell -p ssh-to-age" + exit 1 +fi + +mkdir -p "$PUBKEYS_DIR" + +echo "🔑 Generating age public keys from SSH host keys..." +echo " Output directory: $PUBKEYS_DIR" +echo "" + +# Known hosts (hostname, user@host, ssh port) +# Add entries as hosts are deployed in the infrastructure +HOSTS=( + # Hypervisors + # "pve01:root@pve01.prod.lagraula.fr:22" + # "pve02:root@pve02.prod.lagraula.fr:22" + # LXC containers (once deployed) + # "dns01:root@dns01.lagraula.fr:22" + # "gitea01:root@gitea01.lagraula.fr:22" + # "vault01:root@vault01.lagraula.fr:22" + # "rp01:root@rp01.lagraula.fr:22" + # Workstations + # "sting:root@sting.lagraula.fr:22" +) + +if [ ${#HOSTS[@]} -eq 0 ]; then + echo "⚠️ No hosts configured. Edit the HOSTS array in this script first." + echo "" + echo "For a single host, you can also run manually:" + echo " ssh-keyscan 2>/dev/null | grep ed25519 | awk '{print \$3}' | ssh-to-age > $PUBKEYS_DIR/.age" + exit 0 +fi + +for entry in "${HOSTS[@]}"; do + IFS=':' read -r hostname ssh_user_port <<< "$entry" + IFS='@' read -r ssh_user ssh_host <<< "$ssh_user_port" + + echo "🖥️ Processing $hostname ($ssh_user@$ssh_host)..." + + age_key=$(ssh-keyscan -t ed25519 "$ssh_host" 2>/dev/null | \ + grep "ed25519" | \ + awk '{print $3}' | \ + ssh-to-age 2>/dev/null || true) + + if [ -z "$age_key" ]; then + echo " ⚠️ Could not retrieve age key for $hostname. Skipping." + continue + fi + + echo "$age_key" > "$PUBKEYS_DIR/$hostname.age" + echo " ✅ Saved age public key: $age_key" +done + +echo "" +echo "🎉 Done! Generated $(ls -1 "$PUBKEYS_DIR"/*.age 2>/dev/null | wc -l) key(s)." +echo "" +echo "To encrypt a secret for specific hosts:" +echo " age -r \$(cat $PUBKEYS_DIR/.age) -o secrets/.age" +echo "" +echo "Or with agenix:" +echo " agenix -e secrets/.age" \ No newline at end of file diff --git a/scripts/initial-lxc-configuration.nix b/scripts/initial-lxc-configuration.nix new file mode 100644 index 0000000..82ce6af --- /dev/null +++ b/scripts/initial-lxc-configuration.nix @@ -0,0 +1,27 @@ +{ config, pkgs, ... }: + +{ + # Install Git, curl, and other required tools + environment.systemPackages = with pkgs; [ git curl ]; + + # Enable SSH for initial deployment + services.openssh = { + enable = true; + permitRootLogin = "yes"; + passwordAuthentication = true; + }; + + # Clone the repository so deploy.sh can use it + system.activationScripts.setup-deploy = '' + #!${pkgs.bash}/bin/bash + set -euo pipefail + + # Create the target directory + mkdir -p /etc/nixos-infra + + # The deploy script has already been pushed to /usr/local/bin/deploy-nixos + # by create-lxc-nixos.sh; it will clone the repo and apply the config. + ''; + + system.stateVersion = "25.11"; +} \ No newline at end of file diff --git a/scripts/update-nixpkgs.sh b/scripts/update-nixpkgs.sh new file mode 100755 index 0000000..e0c6081 --- /dev/null +++ b/scripts/update-nixpkgs.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# Met à jour le commit de référence pour Nixpkgs + +BRANCH="nixos-25.11" +REPO="https://github.com/NixOS/nixpkgs" +JSON_FILE="$(dirname "$0")/../pkgs/nixpkgs.json" + +echo "Récupération du dernier commit sur $BRANCH..." +REV=$(git ls-remote $REPO refs/heads/$BRANCH | cut -f1) + +if [ -z "$REV" ]; then + echo "Erreur : Impossible de récupérer le commit." + exit 1 +fi + +echo "Calcul du hash (cela peut prendre un moment)..." +SHA256=$(nix-prefetch-url --unpack "https://github.com/NixOS/nixpkgs/archive/$REV.tar.gz") + +cat < "$JSON_FILE" +{ + "url": "https://github.com/NixOS/nixpkgs/archive/$REV.tar.gz", + "rev": "$REV", + "sha256": "$SHA256" +} +EOF + +echo "Succès ! Nixpkgs est maintenant épinglé au commit : $REV" \ No newline at end of file