Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e1672d7cbe | ||
|
|
9e44cb4c99 |
No files matched your search
@@ -0,0 +1,7 @@
|
||||
keys:
|
||||
- &kirill age1tx6jdr6535xxeygpwkkgvdwc24tulr420cm7gk56xzfp0slzeqks2nwpyg
|
||||
creation_rules:
|
||||
- path_regex: host/[^/]+/secrets/.+$
|
||||
key_groups:
|
||||
- age:
|
||||
- *kirill
|
||||
@@ -11,6 +11,7 @@
|
||||
./modules/grab.nix
|
||||
./modules/thinkpad.nix
|
||||
./modules/fingerprint.nix
|
||||
./secrets/secrets.nix
|
||||
|
||||
(import ../modules/common.nix {
|
||||
inherit lib;
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
sops = {
|
||||
age.keyFile = "/root/age.key";
|
||||
|
||||
secrets = {
|
||||
wallhaven = {
|
||||
format = "binary";
|
||||
sopsFile = ./wallhaven.pem;
|
||||
owner = "kirill";
|
||||
mode = "0400";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"data": "ENC[AES256_GCM,data:0Dw6uMFnVNZKg2ANaHRcMicOPQ7U+mi5V6dia47GDUMM,iv:ScI+ESgBXXcu5hlDkP6m50FL8lp7S3E2cLbf1M9ZAbQ=,tag:VBwdRDyWV3unNUhSVguu2g==,type:str]",
|
||||
"sops": {
|
||||
"age": [
|
||||
{
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3bWFwMUlwMmxJenZib2Qy\nRk5TMkVtQS9kLzgyWlFNbFVFbzJmUVNXZFRBCkVKQldtcTd5ZElzcVpienhDM3dh\nTmFQZTN0KytZVnZQMmNZNGFQSUlIbXMKLS0tIERBVmhGMmgwZmNwTHB6eEZ0bFJX\nWFN2cVE0WmFna2V5UWE2NXJ6OU9YM2cKWitmQY994raC3Kz/QYXVmiONTomW/JYB\n0PADqEp32xsw/gFnTwi7zbfFPD63ME2PYJ4I9wNKcgR/cFoW2Sv8Jg==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||
"recipient": "age1tx6jdr6535xxeygpwkkgvdwc24tulr420cm7gk56xzfp0slzeqks2nwpyg"
|
||||
}
|
||||
],
|
||||
"lastmodified": "2026-10-16T17:19:39Z",
|
||||
"mac": "ENC[AES256_GCM,data:+CSe7gGwiSANibzDJ4DVTq1OXykiR1vv49szqlxCZyAeKqnxbEjaNFCdslxPAa8SGttDqDxM9aO59DSO3AKxzR9J4kfKqZ7eA8/cNC0DKltzS4AkLkPcGjFpctdUiYENXaPrtk1tV8e/GeW1//f3R61hzAEama8jjkoeAYaDZIM=,iv:aD4c8y2a8whXyG0ig05bryZPfh/NrETeth+O5D4/bPE=,tag:r1EbYa0lsr/u2Rw6ZFjYsQ==,type:str]",
|
||||
"version": "3.13.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
# Секреты (sops-nix)
|
||||
|
||||
Кратко: как всё устроено, как добавить новый секрет и как поднять рабочий sops на новом устройстве.
|
||||
|
||||
## Как устроено
|
||||
|
||||
- `.sops.yaml` в корне репозитория — список age-реципиентов и правила выбора ключа по пути файла.
|
||||
- Зашифрованные секреты хоста: `host/<Host>/secrets/<файл>`.
|
||||
- Метаданные секретов: `host/<Host>/secrets/secrets.nix`, подключается из `host/<Host>/configuration.nix` через `imports`.
|
||||
- При активации sops-nix расшифровывает секреты в `/run/secrets/<имя>` (runtime, в `/nix/store` ничего не попадает).
|
||||
- age-ключ выводится из существующего SSH-ключа.
|
||||
|
||||
Текущее правило в `.sops.yaml`:
|
||||
|
||||
```yaml
|
||||
creation_rules:
|
||||
- path_regex: host/[^/]+/secrets/.+$
|
||||
```
|
||||
|
||||
То есть правило выбирается по пути файла: всё, что лежит в `host/<Host>/secrets/`, шифруется на перечисленных реципиентов.
|
||||
|
||||
## Добавить новый секрет
|
||||
|
||||
1. Придумай имя, дальше для примера — `mysecret`.
|
||||
|
||||
2. Положи открытое значение во временный файл **внутри** `host/<Host>/secrets/` (путь важен — см. `path_regex`):
|
||||
|
||||
```bash
|
||||
printf '%s' 'СЕКРЕТНОЕ_ЗНАЧЕНИЕ' > host/ThinkPad/secrets/mysecret.plain
|
||||
```
|
||||
|
||||
3. Зашифруй (приватный ключ не нужен — sops берёт публичного реципиента из `.sops.yaml`):
|
||||
|
||||
```bash
|
||||
nix-shell -p sops --run \
|
||||
"sops --input-type binary --output-type binary \
|
||||
--encrypt host/ThinkPad/secrets/mysecret.plain \
|
||||
--output host/ThinkPad/secrets/mysecret.enc"
|
||||
rm host/ThinkPad/secrets/mysecret.plain
|
||||
```
|
||||
|
||||
`--input-type`/`--output-type` обязательны для бинарных данных, иначе sops пытается определить тип по расширению.
|
||||
|
||||
4. Опиши секрет в `host/<Host>/secrets/secrets.nix`:
|
||||
|
||||
```nix
|
||||
{
|
||||
sops = {
|
||||
age.keyFile = "/root/age.key";
|
||||
|
||||
secrets = {
|
||||
mysecret = {
|
||||
format = "binary"; # yaml | json | binary | dotenv | ini
|
||||
sopsFile = ./mysecret.enc;
|
||||
owner = "kirill"; # владелец расшифрованного файла
|
||||
mode = "0400";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
`format = "binary"` — для произвольных байтов (`.pem`, токены). Для структурированных данных используй `yaml`/`json`.
|
||||
`owner`/`mode` нужны, если секрет читает пользовательский процесс (а не root-сервис).
|
||||
|
||||
5. `git add` — **Nix видит только файлы, добавленные в git** — и ребилд:
|
||||
|
||||
```bash
|
||||
git add host/ThinkPad/secrets/
|
||||
sudo nixos-rebuild switch --flake .#ThinkPad
|
||||
```
|
||||
|
||||
6. Проверка:
|
||||
|
||||
```bash
|
||||
sudo ls -l /run/secrets/mysecret
|
||||
```
|
||||
|
||||
## Изменить или посмотреть существующий секрет
|
||||
|
||||
Используется приватный ключ (`/root/age.key`), поэтому нужен root:
|
||||
|
||||
```bash
|
||||
sudo SOPS_AGE_KEY_FILE=/root/age.key nix-shell -p sops --run \
|
||||
"sops host/ThinkPad/secrets/wallhaven.pem"
|
||||
```
|
||||
|
||||
`sops` сам не ищет `/root/age.key`, путь передаётся через `SOPS_AGE_KEY_FILE`.
|
||||
|
||||
## Новое устройство
|
||||
|
||||
1. Убедись, что на устройстве есть приватный SSH-ключ, из которого выводится age-ключ. Ключ должен быть **без пароля** — иначе расшифровка при активации будет требовать ввода.
|
||||
|
||||
2. Создай age-ключ:
|
||||
|
||||
```bash
|
||||
sudo nix-shell -p ssh-to-age --run \
|
||||
"ssh-to-age -private-key -i /home/kirill/.ssh/<ваш_ключ> -o /root/age.key"
|
||||
sudo chmod 600 /root/age.key
|
||||
```
|
||||
|
||||
3. Проверь, что публичный age-реципиент устройства совпадает с тем, что в `.sops.yaml`:
|
||||
|
||||
```bash
|
||||
nix-shell -p ssh-to-age --run \
|
||||
"ssh-to-age -i /home/kirill/.ssh/<ваш_ключ>.pub"
|
||||
```
|
||||
|
||||
Должно вывести:
|
||||
|
||||
```
|
||||
age1tx6jdr6535xxeygpwkkgvdwc24tulr420cm7gk56xzfp0slzeqks2nwpyg
|
||||
```
|
||||
|
||||
Если вывод другой — добавь новый реципиент (см. следующий раздел).
|
||||
|
||||
4. Создай структуру секретов хоста:
|
||||
|
||||
```
|
||||
host/<Host>/secrets/secrets.nix
|
||||
host/<Host>/secrets/<файлы секретов>
|
||||
```
|
||||
|
||||
Минимальный `secrets.nix`:
|
||||
|
||||
```nix
|
||||
{
|
||||
sops.age.keyFile = "/root/age.key";
|
||||
secrets = {
|
||||
# ...
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
И добавь `./secrets/secrets.nix` в `imports` в `host/<Host>/configuration.nix`.
|
||||
|
||||
5. `git add` + ребилд:
|
||||
|
||||
```bash
|
||||
git add host/<Host>/secrets/
|
||||
sudo nixos-rebuild switch --flake .#<Host>
|
||||
```
|
||||
|
||||
## Добавить устройство к существующим секретам
|
||||
|
||||
1. Получи age-реципиент устройства (шаг 3 выше).
|
||||
|
||||
2. Добавь его в `.sops.yaml`:
|
||||
|
||||
```yaml
|
||||
keys:
|
||||
- &kirill age1tx6jdr6535xxeygpwkkgvdwc24tulr420cm7gk56xzfp0slzeqks2nwpyg
|
||||
- &newdevice age1...новый_реципиент... # замени на реальный
|
||||
|
||||
creation_rules:
|
||||
- path_regex: host/[^/]+/secrets/.+$
|
||||
key_groups:
|
||||
- age:
|
||||
- *kirill
|
||||
- *newdevice
|
||||
```
|
||||
|
||||
3. Перешифруй существующие секреты под новый список реципиентов (нужен приватный ключ, значит root):
|
||||
|
||||
```bash
|
||||
sudo SOPS_AGE_KEY_FILE=/root/age.key nix-shell -p sops --run \
|
||||
"sops updatekeys host/ThinkPad/secrets/wallhaven.pem"
|
||||
```
|
||||
|
||||
4. `git add` + ребилд на этом устройстве.
|
||||
@@ -1,12 +1,17 @@
|
||||
{ pkgs, lib, config, inputs, host, ... }:
|
||||
{ pkgs, lib, config, inputs, host, osConfig, ... }:
|
||||
let
|
||||
secretPath =
|
||||
if osConfig ? sops.secrets.wallhaven
|
||||
then osConfig.sops.secrets.wallhaven.path
|
||||
else "";
|
||||
|
||||
wallpaper_changer = pkgs.writers.writePython3Bin "wallpaper_changer" {
|
||||
libraries = [ pkgs.python3Packages.requests ];
|
||||
flakeIgnore = [ "E111" "E121" "E241" "E501" "E701" "E731" ];
|
||||
} /*py*/ ''
|
||||
import requests as requests
|
||||
from random import choice
|
||||
from os import listdir, makedirs, remove, replace, system
|
||||
from os import environ, getuid, listdir, makedirs, remove, replace, system
|
||||
from os.path import exists, getmtime, isfile
|
||||
from subprocess import run
|
||||
|
||||
@@ -88,18 +93,61 @@ let
|
||||
remove(f"{folder}/{name}")
|
||||
|
||||
|
||||
def read_history(path):
|
||||
try:
|
||||
with open(path) as file:
|
||||
return [line.strip() for line in file if line.strip()]
|
||||
except OSError:
|
||||
return []
|
||||
|
||||
|
||||
def write_history(path, ids):
|
||||
try:
|
||||
with open(path, "w") as file:
|
||||
file.write("\n".join(dict.fromkeys(ids)) + "\n")
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
folder = "${config.home.homeDirectory}/wallpapers"
|
||||
url = "https://wallhaven.cc/api/v1/collections/BlackRaven21/2284568"
|
||||
runtime = environ.get("XDG_RUNTIME_DIR") or f"/run/user/{getuid()}"
|
||||
history = f"{runtime}/wallpaper_changer_history"
|
||||
|
||||
token = None
|
||||
try:
|
||||
with open("${secretPath}") as file:
|
||||
token = file.read().strip() or None
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
filename = None
|
||||
notify("Updating wallpaper...", 0)
|
||||
|
||||
try:
|
||||
response = requests.get(url, timeout=10)
|
||||
response.raise_for_status()
|
||||
json = response.json()
|
||||
params = {}
|
||||
if token:
|
||||
params["apikey"] = token
|
||||
|
||||
wallpaper = choice(json['data'])
|
||||
response = requests.get(url, params=params, timeout=10)
|
||||
response.raise_for_status()
|
||||
page_json = response.json()
|
||||
|
||||
walls = list(page_json['data'])
|
||||
for page in range(2, page_json['meta']['last_page'] + 1):
|
||||
params['page'] = page
|
||||
response = requests.get(url, params=params, timeout=10)
|
||||
response.raise_for_status()
|
||||
walls.extend(response.json()['data'])
|
||||
|
||||
seen = read_history(history)
|
||||
candidates = [wall for wall in walls if wall['id'] not in seen]
|
||||
|
||||
if not candidates:
|
||||
seen = []
|
||||
candidates = walls
|
||||
|
||||
wallpaper = choice(candidates)
|
||||
link = wallpaper['path']
|
||||
format = wallpaper['file_type']
|
||||
id = wallpaper['id']
|
||||
@@ -119,6 +167,8 @@ let
|
||||
else:
|
||||
notify("Using cached wallpaper", 100)
|
||||
|
||||
write_history(history, seen + [id])
|
||||
|
||||
except requests.exceptions.RequestException:
|
||||
notify("Offline mode")
|
||||
try:
|
||||
|
||||
Reference in new issue
Block a user