Initial framework: reusable modules, lib, pkgs, overlays, scripts, sample environment
This commit is contained in:
@@ -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).
|
||||||
+34
@@ -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;
|
||||||
|
}
|
||||||
@@ -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";
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# Lib
|
||||||
|
Custom Nix utility functions and helpers.
|
||||||
|
Used across the configuration to factorize code.
|
||||||
|
Simplifies data and string manipulation.
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# Modules
|
||||||
|
Reusable NixOS modules for the infrastructure.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
{
|
||||||
|
# TODO: Add hypervisor-specific configuration
|
||||||
|
}
|
||||||
@@ -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";
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
{
|
||||||
|
# TODO: Add VM-specific configuration
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
{
|
||||||
|
# TODO: Add workstation-specific configuration
|
||||||
|
}
|
||||||
@@ -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 ];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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" ];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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 ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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 ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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 = [];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# Overlays
|
||||||
|
Custom modifications and extensions to Nixpkgs.
|
||||||
|
Applies patches or version overrides to existing packages.
|
||||||
|
Applied globally across the infrastructure.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
self: super: {
|
||||||
|
# Custom packages and overrides for nixos-infra
|
||||||
|
# agenix is already available in nixpkgs — no custom overlay needed.
|
||||||
|
}
|
||||||
@@ -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.
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"url": "https://github.com/NixOS/nixpkgs/archive/755f5aa91337890c432639c60b6064bb7fe67769.tar.gz",
|
||||||
|
"rev": "755f5aa91337890c432639c60b6064bb7fe67769",
|
||||||
|
"sha256": "1lmn8dicfwmsfdaiw18xjjys78bal6yjy3a41j02my7kw0wlb76a"
|
||||||
|
}
|
||||||
@@ -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 <short_name> [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 <CT_ID>` — 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/<hostname>/configuration.nix`
|
||||||
|
2. `hosts/workstations/<hostname>/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/<hostname>.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/<hostname>.age) -o secrets/<name>.age
|
||||||
|
agenix -e secrets/<name>.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 <hostname>
|
||||||
|
sudo /usr/local/bin/deploy-nixos
|
||||||
Executable
+325
@@ -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 <short_name> [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)"
|
||||||
@@ -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 <<EOF
|
||||||
|
Deploy NixOS configuration from the nixos-infra repository.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
$0 [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-u, --repo-url URL Git repository URL
|
||||||
|
[default: ${REPO_URL}]
|
||||||
|
-d, --repo-dir DIR Local directory for the repository
|
||||||
|
[default: ${REPO_DIR}]
|
||||||
|
-b, --branch BRANCH Git branch to deploy
|
||||||
|
[default: ${BRANCH}]
|
||||||
|
-n, --dry-run Simulate deployment without making changes.
|
||||||
|
-h, --help Show this help message.
|
||||||
|
|
||||||
|
Environment variables:
|
||||||
|
REPO_URL, REPO_DIR, BRANCH, DRY_RUN (same as options above).
|
||||||
|
EOF
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Parse arguments ---
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
-u|--repo-url) REPO_URL="$2"; shift 2 ;;
|
||||||
|
-d|--repo-dir) REPO_DIR="$2"; shift 2 ;;
|
||||||
|
-b|--branch) BRANCH="$2"; shift 2 ;;
|
||||||
|
-n|--dry-run) DRY_RUN="true"; shift ;;
|
||||||
|
-h|--help) usage ;;
|
||||||
|
*) echo "❌ Unknown option: $1" >&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/<env>/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 !"
|
||||||
@@ -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/<hostname>.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 <host> 2>/dev/null | grep ed25519 | awk '{print \$3}' | ssh-to-age > $PUBKEYS_DIR/<hostname>.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/<hostname>.age) -o secrets/<name>.age"
|
||||||
|
echo ""
|
||||||
|
echo "Or with agenix:"
|
||||||
|
echo " agenix -e secrets/<name>.age"
|
||||||
@@ -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";
|
||||||
|
}
|
||||||
Executable
+27
@@ -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 <<EOF > "$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"
|
||||||
Reference in New Issue
Block a user