diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9fdb9f2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Nix build artifacts +result +result-* + +# secrets — НЕ коммитим незашифрованные +# (зашифрованные через SOPS — коммитятся) +*.plain.yaml +*.plain.yml + +# editor backups +*.swp +*.bak +*~ +.DS_Store + +# nix tooling +.direnv/ +devenv.nix +devenv.yaml + +# age key (НИКОГДА не коммитить!) +*.age-key +*age-keys.txt +*age-keys.json \ No newline at end of file diff --git a/.sops.yaml b/.sops.yaml new file mode 100644 index 0000000..e4176b3 --- /dev/null +++ b/.sops.yaml @@ -0,0 +1,13 @@ +# SOPS config — шифрование secrets/.yaml age-ключом mikl +# Публичный age-ключ вставляется ниже после генерации: +# age-keygen -o ~/.config/sops/age/mikl.txt +# cat ~/.config/sops/age/mikl.txt # последняя строка = public key + +keys: + - &admin_key age1placeholder_replace_with_real_key_after_age_keygen + +creation_rules: + - path_regex: secrets/.*\.ya?ml$ + key_groups: + - age: + - *admin_key \ No newline at end of file diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..c55d575 --- /dev/null +++ b/flake.lock @@ -0,0 +1,48 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1782188297, + "narHash": "sha256-imdcA5fgbHK259bhHlyiiSr7bMY1AZ6onOWDdAcydc4=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "9a1a7dbb18f0eb31c49b031babb8def7eab0af54", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-25.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs", + "sops-nix": "sops-nix" + } + }, + "sops-nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1782165805, + "narHash": "sha256-478kKQBvK6SYTOdN2h9jhKJv94nbXRbFMfuL1WshErg=", + "owner": "Mic92", + "repo": "sops-nix", + "rev": "56b24064fdcaedca53553b1a6d607fd23b613a24", + "type": "github" + }, + "original": { + "owner": "Mic92", + "repo": "sops-nix", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..f80ecf8 --- /dev/null +++ b/flake.nix @@ -0,0 +1,38 @@ +{ + description = "iscg NixOS configurations (muscari k3s + future rosemary backup)"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; + + sops-nix = { + url = "github:Mic92/sops-nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = { self, nixpkgs, sops-nix, ... }: + let + system = "x86_64-linux"; + + mkHost = hostname: nixpkgs.lib.nixosSystem { + inherit system; + modules = [ + ./hosts/common + ./hosts/${hostname} + sops-nix.nixosModules.sops + { + # SOPS для каждого хоста: secrets/.yaml + # приватный age-ключ лежит в /etc/sops/age/keys.txt (chmod 600) + sops.defaultSopsFile = ./secrets/${hostname}.yaml; + sops.age.sshKeyPaths = [ "/etc/sops/age/keys.txt" ]; + } + ]; + }; + in + { + nixosConfigurations = { + muscari = mkHost "muscari"; + # rosemary = mkHost "rosemary"; # раскомментировать когда будет backup VM + }; + }; +} \ No newline at end of file diff --git a/hosts/common/default.nix b/hosts/common/default.nix new file mode 100644 index 0000000..5d43689 --- /dev/null +++ b/hosts/common/default.nix @@ -0,0 +1,43 @@ +# Common config for ALL NixOS hosts (muscari, future rosemary, ...) +# Только то что реально нужно везде. Специфичное для k3s/forgejo — в modules/services/ + +{ pkgs, ... }: +{ + # Time + time.timeZone = "Europe/Moscow"; + + # Locale + i18n.defaultLocale = "en_US.UTF-8"; + + # SSH — единые правила для всех хостов + services.openssh = { + enable = true; + settings = { + PermitRootLogin = "prohibit-password"; + PasswordAuthentication = false; + }; + }; + + # Базовые пакеты — есть на каждом хосте + environment.systemPackages = with pkgs; [ + # shell + tools + git + neovim + htop + tmux + curl + wget + rsync + dnsutils # dig, nslookup + + # secrets + sops + age + ssh-to-age + + # nix tooling + nh # nix-helper от vlaci, удобнее чем nixos-rebuild switch + shellcheck + shfmt + ]; +} \ No newline at end of file diff --git a/hosts/muscari/default.nix b/hosts/muscari/default.nix new file mode 100644 index 0000000..64bbe9b --- /dev/null +++ b/hosts/muscari/default.nix @@ -0,0 +1,26 @@ +# muscari — k3s single-node cluster на YC (Debian 12 → NixOS через nixos-anywhere) +# https://git.iscg.dev/mikl/nixos-iscg + +{ config, pkgs, lib, ... }: +{ + imports = [ + ./hardware-configuration.nix + ../../modules/users/mikl.nix + ../../modules/services/k3s.nix + ]; + + networking = { + hostName = "muscari"; + useDHCP = true; + firewall.allowedTCPPorts = [ + 22 # SSH + 80 # HTTP (Ingress через Traefik) + 443 # HTTPS (Ingress через Traefik) + # 6443 не открываем — kubectl через SSH-туннель + ]; + }; + + # После nixos-anywhere первая установка может быть 26.05, + # потом не трогаем (NixOS convention) + system.stateVersion = "25.11"; +} \ No newline at end of file diff --git a/hosts/muscari/hardware-configuration.nix b/hosts/muscari/hardware-configuration.nix new file mode 100644 index 0000000..98fbdd9 --- /dev/null +++ b/hosts/muscari/hardware-configuration.nix @@ -0,0 +1,26 @@ +# Hardware configuration для muscari (Yandex Cloud VM) +# PLACEHOLDER — будет заменён реальным после создания VM в YC +# (см. ../NIXOS-SETUP.md, шаг 4-5) + +# Временный placeholder чтобы flake.nix компилировался +# После создания VM в YC с Debian 12: +# 1. SSH в VM как debian +# 2. Сгенерировать реальный hardware-config через nixos-generate-config +# 3. Заменить этот файл +# 4. Перезапустить nixos-anywhere + +{ ... }: +{ + # NixOS ругается без fileSystems и boot.loader, поэтому минимальный placeholder + # ПОМЕНЯТЬ после первого старта VM + fileSystems."/" = { + device = "/dev/vda1"; + fsType = "ext4"; + }; + + boot.loader.grub.enable = false; + boot.loader.generic-extlinux-compatible.enable = true; + + # Для YC: virtio scsi + boot.initrd.availableKernelModules = [ "virtio_pci" "virtio_mmio" "virtio_blk" "virtio_scsi" ]; +} \ No newline at end of file diff --git a/modules/services/k3s.nix b/modules/services/k3s.nix new file mode 100644 index 0000000..d201a5b --- /dev/null +++ b/modules/services/k3s.nix @@ -0,0 +1,36 @@ +# k3s daemon для muscari (single-node + Cilium CNI) +# Traefik и servicelb остаются включены (встроенные в k3s) +# Cilium ставится отдельно через Helm после старта k3s + +{ config, pkgs, lib, ... }: +{ + services.k3s = { + enable = true; + extraFlags = [ + # CNI: отключаем встроенный flannel, ставим Cilium отдельно + "--flannel-backend=none" + "--disable-network-policy" # Cilium сам делает network policy + + # CIDR для подов и сервисов (default для k3s, но фиксируем явно) + "--cluster-cidr=10.42.0.0/16" + "--service-cidr=10.43.0.0/16" + + # TLS SANs для kubectl (если подключаемся не только по IP) + "--tls-san=muscari.iscg.dev" + ]; + }; + + # Firewall: доверяем Cilium veth интерфейсам + networking.firewall = { + trustedInterfaces = [ "cali+" ]; + checkReversePath = lib.mkDefault "loose"; + }; + + # k3s-specific пакеты (НЕ в common — только для хостов с k3s) + environment.systemPackages = with pkgs; [ + kubectl + kubernetes-helm + kustomize + sops # для шифрования k8s secrets (если будем через SOPS) + ]; +} \ No newline at end of file diff --git a/modules/users/mikl.nix b/modules/users/mikl.nix new file mode 100644 index 0000000..62b374f --- /dev/null +++ b/modules/users/mikl.nix @@ -0,0 +1,28 @@ +# mikl — пользователь с SSH-доступом через forgejo .keys endpoint +# Live-ключи с git.iscg.dev/mikl.keys (sha256-pinned) +# Hardcoded fallback для восстановления если forgejo недоступен + +{ pkgs, ... }: +let + # Live-ключи с forgejo — обновляются автоматически при изменении на forgejo + # sha256 защищает от подмены: новое содержимое = явный коммит с новым хэшем + forgejoKeys = pkgs.fetchurl { + url = "https://git.iscg.dev/mikl.keys"; + sha256 = "sha256-4n6QLUPozdhHOmsRCuFn7HR9BU3nF0/GR2E7s5Lpz+Q="; + }; +in +{ + users.users.mikl = { + isNormalUser = true; + extraGroups = [ "wheel" ]; + uid = 1000; + + openssh.authorizedKeys.keyFiles = [ forgejoKeys ]; + + # Hardcoded fallback key — НЕ зависит ни от чего + # Вставить сюда свой recovery-ключ когда сгенерируешь (например, YubiKey) + # openssh.authorizedKeys = [ + # "ssh-ed25519 AAAA... my-recovery-key" + # ]; + }; +} \ No newline at end of file diff --git a/secrets/.gitkeep b/secrets/.gitkeep new file mode 100644 index 0000000..e69de29