Initial flake: muscari k3s host

- flake.nix: mkHost helper, SOPS-age integration
- hosts/common: locale, sshd, universal packages
- hosts/muscari: k3s host with Cilium flags
- modules/users/mikl: SSH keys via forgejo .keys + sha256 pinning
- modules/services/k3s: k3s daemon with Cilium-compatible flags
- hardware-configuration.nix: placeholder, will be regenerated on real VM

Stack:
- NixOS 25.11
- k3s single-node (Cilium CNI, Traefik ingress)
- SOPS + age for secrets
- Multi-host ready (rosemary planned)
This commit is contained in:
mikl 2026-06-25 14:47:38 +03:00 committed by isogonalconguate
parent 6f498d1da5
commit 2d023fc86d
10 changed files with 282 additions and 0 deletions

24
.gitignore vendored Normal file
View file

@ -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

13
.sops.yaml Normal file
View file

@ -0,0 +1,13 @@
# SOPS config — шифрование secrets/<hostname>.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

48
flake.lock Normal file
View file

@ -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
}

38
flake.nix Normal file
View file

@ -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/<hostname>.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
};
};
}

43
hosts/common/default.nix Normal file
View file

@ -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
];
}

26
hosts/muscari/default.nix Normal file
View file

@ -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";
}

View file

@ -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" ];
}

36
modules/services/k3s.nix Normal file
View file

@ -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)
];
}

28
modules/users/mikl.nix Normal file
View file

@ -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"
# ];
};
}

0
secrets/.gitkeep Normal file
View file