Compare commits
62 Commits
a912c4dc55
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 5f300d9e7b | |||
| 99ac2ea3b0 | |||
| 2caa36c0ab | |||
| 55c15c790d | |||
| 450d9d6457 | |||
| e08bf42eaa | |||
| 8e0e5c0d16 | |||
| ada9db7942 | |||
| 5995612407 | |||
| 5762916970 | |||
| dd456eab69 | |||
| 18a8fde66e | |||
| f97c9185c1 | |||
| 8bf4b185a1 | |||
| 8424d771f6 | |||
| 840f99a7e9 | |||
| 1b27bafd41 | |||
| 4770d671c0 | |||
| 28a7bed3b9 | |||
| 170becceb0 | |||
| 6e8f530537 | |||
| 209bafd70f | |||
| 1d182437db | |||
| 6c046a549e | |||
| 0a30a2ac23 | |||
| 82c15e8d26 | |||
| f277d089bd | |||
| 7ed345b8e8 | |||
| bd6b15b617 | |||
| 3282b7d634 | |||
| 21ed381d18 | |||
| 4500f41983 | |||
| 1d30eeb939 | |||
| 537f144885 | |||
| dbada3c509 | |||
| c8be707420 | |||
| fdba2c75c7 | |||
| 55d600c0c0 | |||
| 71c5bd5e6c | |||
| 998f04713f | |||
| 6935fbea8b | |||
| 301e090251 | |||
| 58d8ef050c | |||
| 41cb2ec791 | |||
| 1faec5b2d1 | |||
| 111b8cec97 | |||
| 3aaebdb1c4 | |||
| 3e7b8c93e3 | |||
| 3e2f46377e | |||
| 38bead3dc8 | |||
| 351d36b217 | |||
| 59a37c9b46 | |||
| d7d3722ce7 | |||
| 6475524d23 | |||
| 1a70ca9564 | |||
| d6f206f0bb | |||
| b3c5366f31 | |||
| fab06ca4d5 | |||
| bd1d04943d | |||
| 8305d1b0c5 | |||
| 2d812c03eb | |||
| 156e63fd6c |
8
.mcp.json
Normal file
8
.mcp.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"nixos": {
|
||||||
|
"command": "uvx",
|
||||||
|
"args": ["mcp-nixos"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
.sops.yaml
11
.sops.yaml
@@ -16,6 +16,7 @@ keys:
|
|||||||
- &gpd-win4 age1ceg548u5ma6rgu3xgvd254y5xefqrdqfqhcjsjp3255q976fgd2qaua53d
|
- &gpd-win4 age1ceg548u5ma6rgu3xgvd254y5xefqrdqfqhcjsjp3255q976fgd2qaua53d
|
||||||
- &nb age1exny8unxynaw03yu8ppahu5z28uermghr8ag34e7kdqnaduq9stsyettzz
|
- &nb age1exny8unxynaw03yu8ppahu5z28uermghr8ag34e7kdqnaduq9stsyettzz
|
||||||
- &amzebs-01 age1xcgc6u7fmc2trgxtdtf5nhrd7axzweuxlg0ya9jre3sdrg6c6easecue9w
|
- &amzebs-01 age1xcgc6u7fmc2trgxtdtf5nhrd7axzweuxlg0ya9jre3sdrg6c6easecue9w
|
||||||
|
- &nas age1x3elhtccp4u8ha5ry32juj9fkpg0qg7qqx4gduuehgwwnnhcxp8s892hek
|
||||||
|
|
||||||
creation_rules:
|
creation_rules:
|
||||||
- path_regex: ^[^/]+\.yaml$
|
- path_regex: ^[^/]+\.yaml$
|
||||||
@@ -80,6 +81,14 @@ creation_rules:
|
|||||||
- *dominik2
|
- *dominik2
|
||||||
- *nb
|
- *nb
|
||||||
- *amzebs-01
|
- *amzebs-01
|
||||||
|
- path_regex: hosts/nas/[^/]+\.yaml$
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- *bitwarden
|
||||||
|
- *dominik
|
||||||
|
- *dominik2
|
||||||
|
- *nb
|
||||||
|
- *nas
|
||||||
- path_regex: hosts/mail/[^/]+\.yaml$
|
- path_regex: hosts/mail/[^/]+\.yaml$
|
||||||
key_groups:
|
key_groups:
|
||||||
- age:
|
- age:
|
||||||
@@ -129,6 +138,7 @@ creation_rules:
|
|||||||
- *netboot
|
- *netboot
|
||||||
- *fw
|
- *fw
|
||||||
- *fw-new
|
- *fw-new
|
||||||
|
- *nas
|
||||||
- *amzebs-01
|
- *amzebs-01
|
||||||
- path_regex: utils/modules/victoriametrics/[^/]+\.yaml$
|
- path_regex: utils/modules/victoriametrics/[^/]+\.yaml$
|
||||||
key_groups:
|
key_groups:
|
||||||
@@ -142,4 +152,5 @@ creation_rules:
|
|||||||
- *netboot
|
- *netboot
|
||||||
- *fw
|
- *fw
|
||||||
- *fw-new
|
- *fw-new
|
||||||
|
- *nas
|
||||||
- *amzebs-01
|
- *amzebs-01
|
||||||
|
|||||||
100
CLAUDE.md
Normal file
100
CLAUDE.md
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Repository Overview
|
||||||
|
|
||||||
|
This is a NixOS infrastructure repository managing multiple hosts (servers and personal machines) using a modular Nix configuration approach with SOPS for secrets management and Bento for deployment.
|
||||||
|
|
||||||
|
## Build and Test Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Enter development shell (sets up MCP configs)
|
||||||
|
nix-shell
|
||||||
|
|
||||||
|
# Test configuration before deployment (required before PRs)
|
||||||
|
./scripts/test-configuration <hostname>
|
||||||
|
./scripts/test-configuration -v <hostname> # with --show-trace
|
||||||
|
|
||||||
|
# Edit encrypted secrets
|
||||||
|
nix-shell -p sops --run 'sops hosts/<hostname>/secrets.yaml'
|
||||||
|
|
||||||
|
# Update secrets keys after adding new age keys
|
||||||
|
./scripts/update-secrets-keys
|
||||||
|
|
||||||
|
# Format Nix files
|
||||||
|
nix run nixpkgs#nixpkgs-fmt .
|
||||||
|
|
||||||
|
# Compute hash for new packages
|
||||||
|
nix hash to-sri --type sha256 $(nix-prefetch-url https://example.com/file.tar.gz)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Host Structure
|
||||||
|
Each host in `hosts/<hostname>/` contains:
|
||||||
|
- `configuration.nix` - Main entry point importing modules
|
||||||
|
- `hardware-configuration.nix` - Machine-specific hardware config
|
||||||
|
- `secrets.yaml` - SOPS-encrypted secrets
|
||||||
|
- `modules/` - Host-specific service configurations
|
||||||
|
- `fleet.nix` → symlink to root `fleet.nix` (SFTP user provisioning)
|
||||||
|
- `utils/` → symlink to root `utils/` (shared modules)
|
||||||
|
|
||||||
|
Current hosts: `fw` (firewall/router), `nb` (notebook), `web-arm`, `mail`, `amzebs-01`, `nas`
|
||||||
|
|
||||||
|
### Shared Components (`utils/`)
|
||||||
|
- `modules/` - Reusable NixOS modules (nginx, sops, borgbackup, lego, promtail, etc.)
|
||||||
|
- `overlays/` - Nixpkgs overlays
|
||||||
|
- `pkgs/` - Custom package derivations
|
||||||
|
- `bento.nix` - Deployment helper module
|
||||||
|
|
||||||
|
### Secrets Management
|
||||||
|
- SOPS with age encryption; keys defined in `.sops.yaml`
|
||||||
|
- Each host has its own age key derived from SSH host key
|
||||||
|
- Host secrets in `hosts/<hostname>/secrets.yaml`
|
||||||
|
- Shared module secrets in `utils/modules/<module>/secrets.yaml`
|
||||||
|
|
||||||
|
**IMPORTANT: Never modify secrets files directly.** Instead, tell the user which secrets need to be added and where, so they can edit the encrypted files themselves using:
|
||||||
|
```bash
|
||||||
|
nix-shell -p sops --run 'sops hosts/<hostname>/secrets.yaml'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deployment
|
||||||
|
The Git runner handles deployment automatically when changes merge to main. A successful `./scripts/test-configuration <host>` dry-build is the gate before pushing.
|
||||||
|
|
||||||
|
## Custom Packages
|
||||||
|
|
||||||
|
When creating a new package in `utils/pkgs/`, always include an `update.sh` script to automate version updates. See `utils/pkgs/claude-code/update.sh` for the pattern:
|
||||||
|
|
||||||
|
1. Fetch latest version from upstream (npm, GitHub, etc.)
|
||||||
|
2. Update version string in `default.nix`
|
||||||
|
3. Update source hash using `nix-prefetch-url`
|
||||||
|
4. Update dependency hashes (e.g., `npmDepsHash`) by triggering a build with a fake hash
|
||||||
|
5. Verify the final build succeeds
|
||||||
|
|
||||||
|
Example structure:
|
||||||
|
```
|
||||||
|
utils/pkgs/<package-name>/
|
||||||
|
├── default.nix
|
||||||
|
├── update.sh # Always include this
|
||||||
|
└── (other files like patches, lock files)
|
||||||
|
```
|
||||||
|
|
||||||
|
**IMPORTANT: When modifying a custom package** (patches, version updates, etc.), always test by building the package directly, not just running `test-configuration`. The configuration test only checks that the Nix expression evaluates, but doesn't verify the package actually builds:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build a custom package directly to verify it works
|
||||||
|
nix-build -E 'with import <nixpkgs> { overlays = [ (import ./utils/overlays/packages.nix) ]; config.allowUnfree = true; }; <package-name>'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
**IMPORTANT: Always run `./scripts/test-configuration <hostname>` after making any changes** to verify the NixOS configuration builds successfully. This is required before committing.
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
- Nix files: two-space indentation, lower kebab-case naming
|
||||||
|
- Commits: Conventional Commits format (`fix:`, `feat:`, `chore:`), scope by host when relevant (`fix(mail):`). Do not add "Generated with Claude Code" or "Co-Authored-By: Claude" footers.
|
||||||
|
- Modules import via explicit paths, not wildcards
|
||||||
|
- Comments explain non-obvious decisions (open ports, unusual service options)
|
||||||
|
- **Never update `system.stateVersion`** - it should remain at the original installation version. To upgrade NixOS, update the `channel` file instead.
|
||||||
@@ -39,7 +39,7 @@ cat ~/.ssh/id_rsa.pub | ssh -p23 u149513-subx@u149513-subx.your-backup.de instal
|
|||||||
|
|
||||||
# 4. Add new Host
|
# 4. Add new Host
|
||||||
```console
|
```console
|
||||||
sftp host.cloonar.com@git.cloonar.com:/config/bootstrap.sh ./
|
sftp host@git.cloonar.com:/config/bootstrap.sh ./
|
||||||
```
|
```
|
||||||
|
|
||||||
# 5. Yubikey
|
# 5. Yubikey
|
||||||
|
|||||||
@@ -47,6 +47,11 @@
|
|||||||
username = "gpd-win4";
|
username = "gpd-win4";
|
||||||
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILjfS2DtS8PQgkf86dU+EVu5t+r/QlCWmY7+RPYprQrO";
|
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILjfS2DtS8PQgkf86dU+EVu5t+r/QlCWmY7+RPYprQrO";
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
username = "nas";
|
||||||
|
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICS6b97LPUpr7/kWvOcI40s5e+gfbfz0I2/hAPL6zTmU";
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
username = "amzebs-01";
|
username = "amzebs-01";
|
||||||
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINMkFZ60SPl8pzEtGrFq1+n6ZkDuNe3xJaccJMjr3y/q";
|
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINMkFZ60SPl8pzEtGrFq1+n6ZkDuNe3xJaccJMjr3y/q";
|
||||||
|
|||||||
471
hosts/amzebs-01/EMAIL_SETUP.md
Normal file
471
hosts/amzebs-01/EMAIL_SETUP.md
Normal file
@@ -0,0 +1,471 @@
|
|||||||
|
# Email Setup for amzebs-01 (amz.at)
|
||||||
|
|
||||||
|
This host is configured to send emails via Laravel with DKIM signing.
|
||||||
|
|
||||||
|
## Configuration Overview
|
||||||
|
|
||||||
|
- **Postfix**: Localhost-only SMTP server (no external access)
|
||||||
|
- **Rspamd**: DKIM signing with host-specific key
|
||||||
|
- **Domain**: amz.at
|
||||||
|
- **DKIM Selector**: amzebs-01
|
||||||
|
- **Secret Management**: DKIM private key stored in sops
|
||||||
|
|
||||||
|
## Initial Setup (Before First Deployment)
|
||||||
|
|
||||||
|
### 1. Generate DKIM Key Pair
|
||||||
|
|
||||||
|
You need to generate a DKIM key pair locally first. You'll need `rspamd` package installed.
|
||||||
|
|
||||||
|
#### Option A: Using rspamd (if installed locally)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create a temporary directory
|
||||||
|
mkdir -p /tmp/dkim-gen
|
||||||
|
|
||||||
|
# Generate the key pair
|
||||||
|
rspamadm dkim_keygen -s amzebs-01 -d amz.at -k /tmp/dkim-gen/amz.at.amzebs-01.key
|
||||||
|
```
|
||||||
|
|
||||||
|
This will output:
|
||||||
|
- **Private key** saved to `/tmp/dkim-gen/amz.at.amzebs-01.key`
|
||||||
|
- **Public key** printed to stdout (starts with `v=DKIM1; k=rsa; p=...`)
|
||||||
|
|
||||||
|
#### Option B: Using OpenSSL (alternative)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create temporary directory
|
||||||
|
mkdir -p /tmp/dkim-gen
|
||||||
|
|
||||||
|
# Generate private key (2048-bit RSA)
|
||||||
|
openssl genrsa -out /tmp/dkim-gen/amz.at.amzebs-01.key 2048
|
||||||
|
|
||||||
|
# Extract public key in the correct format for DNS
|
||||||
|
openssl rsa -in /tmp/dkim-gen/amz.at.amzebs-01.key -pubout -outform PEM | \
|
||||||
|
grep -v '^-----' | tr -d '\n' > /tmp/dkim-gen/public.txt
|
||||||
|
|
||||||
|
# Display the DNS record value
|
||||||
|
echo "v=DKIM1; k=rsa; p=$(cat /tmp/dkim-gen/public.txt)"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Save the public key output!** You'll need it for DNS configuration later.
|
||||||
|
|
||||||
|
### 2. Add DKIM Private Key to Sops Secrets
|
||||||
|
|
||||||
|
Now you need to encrypt and add the private key to your secrets file.
|
||||||
|
|
||||||
|
#### Step 1: View the private key
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat /tmp/dkim-gen/amz.at.amzebs-01.key
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 2: Edit the secrets file
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/dominik/projects/cloonar/cloonar-nixos/hosts/amzebs-01
|
||||||
|
sops secrets.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 3: Add the key to secrets.yaml
|
||||||
|
|
||||||
|
In the sops editor, add a new key called `rspamd-dkim-key` with the **entire private key content** including the BEGIN/END markers:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
rspamd-dkim-key: |
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC...
|
||||||
|
(paste the entire key content here)
|
||||||
|
...
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important:**
|
||||||
|
- Make sure to use the pipe `|` character for multiline content
|
||||||
|
- Keep the proper indentation (2 spaces before each line of the key)
|
||||||
|
- Include the full BEGIN/END markers
|
||||||
|
|
||||||
|
#### Step 4: Save and exit
|
||||||
|
|
||||||
|
Save the file in sops (it will be encrypted automatically).
|
||||||
|
|
||||||
|
#### Step 5: Clean up temporary files
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rm -rf /tmp/dkim-gen
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Verify Secret is Encrypted
|
||||||
|
|
||||||
|
Check that the secret is properly encrypted:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat hosts/amzebs-01/secrets.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see encrypted content, not the plain private key.
|
||||||
|
|
||||||
|
### 4. Extract Public Key for DNS (if needed later)
|
||||||
|
|
||||||
|
If you didn't save the public key earlier, you can extract it after deployment:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# On the server after deployment
|
||||||
|
sudo cat /var/lib/rspamd/dkim/amz.at.amzebs-01.key | \
|
||||||
|
openssl rsa -pubout -outform PEM 2>/dev/null | \
|
||||||
|
grep -v '^-----' | tr -d '\n'
|
||||||
|
```
|
||||||
|
|
||||||
|
Then format it as:
|
||||||
|
```
|
||||||
|
v=DKIM1; k=rsa; p=<output_from_above>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
### 1. Deploy Configuration
|
||||||
|
|
||||||
|
After adding the DKIM private key to sops, deploy the configuration:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build and switch on the remote host
|
||||||
|
nixos-rebuild switch --flake .#amzebs-01 --target-host amzebs-01 --use-remote-sudo
|
||||||
|
```
|
||||||
|
|
||||||
|
Or if deploying locally on the server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo nixos-rebuild switch
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Verify Deployment
|
||||||
|
|
||||||
|
Check that the services are running:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check rspamd-dkim-setup service
|
||||||
|
systemctl status rspamd-dkim-setup
|
||||||
|
|
||||||
|
# Check that rspamd is running
|
||||||
|
systemctl status rspamd
|
||||||
|
|
||||||
|
# Check that postfix is running
|
||||||
|
systemctl status postfix
|
||||||
|
|
||||||
|
# Verify DKIM key was deployed
|
||||||
|
ls -la /var/lib/rspamd/dkim/amz.at.amzebs-01.key
|
||||||
|
```
|
||||||
|
|
||||||
|
## DNS Configuration
|
||||||
|
|
||||||
|
Add the following DNS records to ensure proper email delivery and avoid spam classification.
|
||||||
|
|
||||||
|
### Critical: PTR Record (Reverse DNS)
|
||||||
|
|
||||||
|
**This is CRITICAL for email deliverability!** Without a proper PTR record, most mail servers will reject or spam your emails.
|
||||||
|
|
||||||
|
#### What is a PTR Record?
|
||||||
|
A PTR (pointer) record is a reverse DNS entry that maps your IP address back to your hostname. Mail servers use this to verify you're a legitimate mail server.
|
||||||
|
|
||||||
|
#### Required PTR Record
|
||||||
|
```
|
||||||
|
IP Address: 23.88.38.1
|
||||||
|
Points to: amzebs-01.amz.at
|
||||||
|
```
|
||||||
|
|
||||||
|
#### How to Configure PTR Record
|
||||||
|
|
||||||
|
**Step 1: Contact Your Hosting Provider**
|
||||||
|
|
||||||
|
PTR records MUST be configured through your hosting provider (e.g., Hetzner, OVH, AWS, etc.). You cannot set PTR records through your domain registrar.
|
||||||
|
|
||||||
|
1. Log into your hosting provider's control panel
|
||||||
|
2. Find the "Reverse DNS" or "PTR Record" section
|
||||||
|
3. Set the PTR record for IP `23.88.38.1` to point to `amzebs-01.amz.at`
|
||||||
|
|
||||||
|
**Common Provider Links:**
|
||||||
|
- **Hetzner**: Robot panel → IPs → Edit reverse DNS
|
||||||
|
- **OVH**: Network → IP → ... → Modify reverse
|
||||||
|
- **AWS EC2**: Select instance → Networking → Request reverse DNS
|
||||||
|
|
||||||
|
**Step 2: Verify Forward DNS First**
|
||||||
|
|
||||||
|
Before setting the PTR record, ensure your forward DNS is correct:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# This should return 23.88.38.1
|
||||||
|
dig +short amzebs-01.amz.at A
|
||||||
|
host amzebs-01.amz.at
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 3: Verify PTR Record**
|
||||||
|
|
||||||
|
After configuring, verify the PTR record is working:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Method 1: Using dig
|
||||||
|
dig +short -x 23.88.38.1
|
||||||
|
|
||||||
|
# Method 2: Using host
|
||||||
|
host 23.88.38.1
|
||||||
|
|
||||||
|
# Method 3: Using nslookup
|
||||||
|
nslookup 23.88.38.1
|
||||||
|
```
|
||||||
|
|
||||||
|
All commands should return: `amzebs-01.amz.at`
|
||||||
|
|
||||||
|
**Step 4: Verify FCrDNS (Forward-Confirmed Reverse DNS)**
|
||||||
|
|
||||||
|
This ensures forward and reverse DNS match properly:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Forward lookup
|
||||||
|
dig +short amzebs-01.amz.at
|
||||||
|
# Should output: 23.88.38.1
|
||||||
|
|
||||||
|
# Reverse lookup
|
||||||
|
dig +short -x 23.88.38.1
|
||||||
|
# Should output: amzebs-01.amz.at.
|
||||||
|
```
|
||||||
|
|
||||||
|
If both work correctly, FCrDNS passes! ✓
|
||||||
|
|
||||||
|
**Why PTR Records Matter:**
|
||||||
|
- Gmail, Microsoft, Yahoo require valid PTR records
|
||||||
|
- Missing PTR = automatic spam classification or rejection
|
||||||
|
- Can add 5-10 points to spam score alone
|
||||||
|
- Required for professional email delivery
|
||||||
|
|
||||||
|
### Domain DNS Records (amz.at)
|
||||||
|
|
||||||
|
Add these records through your domain registrar's DNS management:
|
||||||
|
|
||||||
|
#### SPF Record
|
||||||
|
```
|
||||||
|
Type: TXT
|
||||||
|
Name: @
|
||||||
|
Value: v=spf1 mx a:amzebs-01.amz.at ~all
|
||||||
|
```
|
||||||
|
|
||||||
|
#### DKIM Record
|
||||||
|
```
|
||||||
|
Type: TXT
|
||||||
|
Name: amzebs-01._domainkey
|
||||||
|
Value: [Your public key from step 1 above]
|
||||||
|
```
|
||||||
|
|
||||||
|
The DKIM record will look something like:
|
||||||
|
```
|
||||||
|
v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
|
||||||
|
```
|
||||||
|
|
||||||
|
#### DMARC Record
|
||||||
|
```
|
||||||
|
Type: TXT
|
||||||
|
Name: _dmarc
|
||||||
|
Value: v=DMARC1; p=quarantine; rua=mailto:postmaster@amz.at; ruf=mailto:postmaster@amz.at; fo=1
|
||||||
|
```
|
||||||
|
|
||||||
|
**Explanation:**
|
||||||
|
- `p=quarantine`: Failed messages should be quarantined (you can change to `p=reject` after testing)
|
||||||
|
- `rua=mailto:...`: Aggregate reports sent to this address
|
||||||
|
- `ruf=mailto:...`: Forensic reports sent to this address
|
||||||
|
- `fo=1`: Generate forensic reports for any failure
|
||||||
|
|
||||||
|
## Laravel Configuration
|
||||||
|
|
||||||
|
Update your Laravel application's `.env` file:
|
||||||
|
|
||||||
|
#### Option A: Using sendmail (Recommended)
|
||||||
|
```env
|
||||||
|
MAIL_MAILER=sendmail
|
||||||
|
MAIL_FROM_ADDRESS=noreply@amz.at
|
||||||
|
MAIL_FROM_NAME="${APP_NAME}"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Option B: Using SMTP
|
||||||
|
```env
|
||||||
|
MAIL_MAILER=smtp
|
||||||
|
MAIL_HOST=127.0.0.1
|
||||||
|
MAIL_PORT=25
|
||||||
|
MAIL_USERNAME=null
|
||||||
|
MAIL_PASSWORD=null
|
||||||
|
MAIL_ENCRYPTION=null
|
||||||
|
MAIL_FROM_ADDRESS=noreply@amz.at
|
||||||
|
MAIL_FROM_NAME="${APP_NAME}"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note**: Laravel can use ANY email address with @amz.at domain. All will be DKIM signed automatically.
|
||||||
|
|
||||||
|
## Testing Email
|
||||||
|
|
||||||
|
### Test from Command Line
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Send a test email
|
||||||
|
echo "Test email body" | mail -s "Test Subject" test@example.com -aFrom:test@amz.at
|
||||||
|
```
|
||||||
|
|
||||||
|
### Check Postfix Queue
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# View mail queue
|
||||||
|
mailq
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
journalctl -u postfix -f
|
||||||
|
```
|
||||||
|
|
||||||
|
### Check Rspamd Logs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# View rspamd logs
|
||||||
|
journalctl -u rspamd -f
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test DKIM Signature and Deliverability
|
||||||
|
|
||||||
|
Send an email to test your complete email configuration:
|
||||||
|
|
||||||
|
#### Email Testing Services
|
||||||
|
1. **Mail Tester** (https://www.mail-tester.com/)
|
||||||
|
- Provides a temporary email address
|
||||||
|
- Shows comprehensive spam score (0-10, higher is better)
|
||||||
|
- Checks DKIM, SPF, DMARC, PTR, blacklists, content
|
||||||
|
- **Target: 9/10 or higher**
|
||||||
|
|
||||||
|
2. **MXToolbox Email Health** (https://mxtoolbox.com/emailhealth/)
|
||||||
|
- Comprehensive deliverability check
|
||||||
|
- Checks DNS records, blacklists, configuration
|
||||||
|
|
||||||
|
3. **Google Admin Toolbox** (https://toolbox.googleapps.com/apps/messageheader/)
|
||||||
|
- Paste email headers to see how Gmail scored your email
|
||||||
|
- Shows SPF, DKIM, DMARC results
|
||||||
|
|
||||||
|
#### What to Check
|
||||||
|
- ✓ DKIM signature is valid
|
||||||
|
- ✓ SPF passes
|
||||||
|
- ✓ DMARC passes
|
||||||
|
- ✓ PTR record (reverse DNS) matches
|
||||||
|
- ✓ Not on any blacklists
|
||||||
|
- ✓ Spam score < 2.0 (lower is better)
|
||||||
|
|
||||||
|
#### Common Issues & Fixes
|
||||||
|
|
||||||
|
**High Spam Score (> 5.0)**
|
||||||
|
- Check: PTR record configured correctly? (Critical!)
|
||||||
|
- Check: HELO name matches hostname?
|
||||||
|
- Check: All headers present (To:, From:, Subject:)?
|
||||||
|
- Check: IP not blacklisted?
|
||||||
|
|
||||||
|
**Missing "To:" Header**
|
||||||
|
Your Laravel app must set a recipient. In your code:
|
||||||
|
```php
|
||||||
|
Mail::to('recipient@example.com')
|
||||||
|
->send(new YourMailable());
|
||||||
|
```
|
||||||
|
|
||||||
|
**HELO/EHLO Mismatch**
|
||||||
|
After applying this configuration, HELO should be `amzebs-01.amz.at`, not `localhost`
|
||||||
|
|
||||||
|
**Check Current HELO Name**
|
||||||
|
```bash
|
||||||
|
# On the server
|
||||||
|
echo "HELO test" | nc localhost 25
|
||||||
|
# Should see: 250 amzebs-01.amz.at
|
||||||
|
```
|
||||||
|
|
||||||
|
## Verification Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check if Postfix is running
|
||||||
|
systemctl status postfix
|
||||||
|
|
||||||
|
# Check if Rspamd is running
|
||||||
|
systemctl status rspamd
|
||||||
|
|
||||||
|
# Check if Postfix is listening on localhost only
|
||||||
|
ss -tlnp | grep master
|
||||||
|
|
||||||
|
# View DKIM public key again
|
||||||
|
systemctl start rspamd-show-dkim
|
||||||
|
journalctl -u rspamd-show-dkim
|
||||||
|
|
||||||
|
# Check if DKIM key exists
|
||||||
|
ls -la /var/lib/rspamd/dkim/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Notes
|
||||||
|
|
||||||
|
1. **Localhost-only**: Postfix is configured to listen ONLY on 127.0.0.1
|
||||||
|
2. **No authentication**: Not needed since only local processes can connect
|
||||||
|
3. **No firewall changes**: No external ports opened for email
|
||||||
|
4. **DKIM signing**: All outgoing emails are automatically signed with DKIM
|
||||||
|
5. **Host-specific key**: Using selector "amzebs-01" allows multiple hosts to send for amz.at
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Email not being sent
|
||||||
|
|
||||||
|
1. Check Postfix status: `systemctl status postfix`
|
||||||
|
2. Check queue: `mailq`
|
||||||
|
3. Check logs: `journalctl -u postfix -n 100`
|
||||||
|
|
||||||
|
### DKIM not signing
|
||||||
|
|
||||||
|
1. Check Rspamd status: `systemctl status rspamd`
|
||||||
|
2. Check if key exists: `ls -la /var/lib/rspamd/dkim/amz.at.amzebs-01.key`
|
||||||
|
3. Check Rspamd logs: `journalctl -u rspamd -n 100`
|
||||||
|
|
||||||
|
### Permission errors
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Ensure proper ownership
|
||||||
|
chown -R rspamd:rspamd /var/lib/rspamd/dkim/
|
||||||
|
chmod 600 /var/lib/rspamd/dkim/*.key
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rotate DKIM key
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Generate new key pair locally (follow "Initial Setup" steps)
|
||||||
|
# 2. Update the rspamd-dkim-key in secrets.yaml with new key
|
||||||
|
# 3. Deploy the configuration
|
||||||
|
nixos-rebuild switch
|
||||||
|
|
||||||
|
# 4. Restart the setup service to copy new key
|
||||||
|
systemctl restart rspamd-dkim-setup
|
||||||
|
|
||||||
|
# 5. Restart rspamd to use new key
|
||||||
|
systemctl restart rspamd
|
||||||
|
|
||||||
|
# 6. Update DNS with new public key
|
||||||
|
# 7. Wait for DNS propagation before removing old DNS record
|
||||||
|
```
|
||||||
|
|
||||||
|
## Related Files
|
||||||
|
|
||||||
|
- Postfix config: `hosts/amzebs-01/modules/postfix.nix`
|
||||||
|
- Rspamd config: `hosts/amzebs-01/modules/rspamd.nix`
|
||||||
|
- Main config: `hosts/amzebs-01/configuration.nix`
|
||||||
|
- Secrets file: `hosts/amzebs-01/secrets.yaml` (encrypted)
|
||||||
|
|
||||||
|
## Sops Secret Configuration
|
||||||
|
|
||||||
|
The DKIM private key is stored as a sops secret with the following configuration:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
sops.secrets.rspamd-dkim-key = {
|
||||||
|
owner = "rspamd";
|
||||||
|
group = "rspamd";
|
||||||
|
mode = "0400";
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
This ensures:
|
||||||
|
- Only the rspamd user can read the key
|
||||||
|
- The key is decrypted at boot time by sops-nix
|
||||||
|
- The key is encrypted in version control
|
||||||
|
- The key persists across rebuilds
|
||||||
|
|
||||||
|
The key is automatically copied from the sops secret path to `/var/lib/rspamd/dkim/amz.at.amzebs-01.key` by the `rspamd-dkim-setup.service` on every boot.
|
||||||
1
hosts/amzebs-01/channel
Normal file
1
hosts/amzebs-01/channel
Normal file
@@ -0,0 +1 @@
|
|||||||
|
https://channels.nixos.org/nixos-25.11
|
||||||
@@ -3,11 +3,15 @@
|
|||||||
./utils/bento.nix
|
./utils/bento.nix
|
||||||
./utils/modules/sops.nix
|
./utils/modules/sops.nix
|
||||||
./utils/modules/nginx.nix
|
./utils/modules/nginx.nix
|
||||||
|
./utils/modules/set-nix-channel.nix
|
||||||
|
|
||||||
./modules/mysql.nix
|
./modules/mysql.nix
|
||||||
./modules/web/stack.nix
|
./modules/web/stack.nix
|
||||||
./modules/laravel-storage.nix
|
./modules/laravel-storage.nix
|
||||||
|
./modules/laravel-scheduler.nix
|
||||||
./modules/blackbox-exporter.nix
|
./modules/blackbox-exporter.nix
|
||||||
|
./modules/postfix.nix
|
||||||
|
./modules/rspamd.nix
|
||||||
|
|
||||||
./utils/modules/autoupgrade.nix
|
./utils/modules/autoupgrade.nix
|
||||||
./utils/modules/promtail
|
./utils/modules/promtail
|
||||||
@@ -65,7 +69,7 @@
|
|||||||
|
|
||||||
networking.firewall = {
|
networking.firewall = {
|
||||||
enable = true;
|
enable = true;
|
||||||
allowedTCPPorts = [ 22 80 443 ];
|
allowedTCPPorts = [ 22 80 443 3306 ];
|
||||||
|
|
||||||
# Allow MariaDB access only from specific IP
|
# Allow MariaDB access only from specific IP
|
||||||
extraCommands = ''
|
extraCommands = ''
|
||||||
@@ -73,5 +77,5 @@
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
system.stateVersion = "23.11";
|
system.stateVersion = "25.11";
|
||||||
}
|
}
|
||||||
|
|||||||
51
hosts/amzebs-01/modules/laravel-scheduler.nix
Normal file
51
hosts/amzebs-01/modules/laravel-scheduler.nix
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
# Daily scheduled Laravel artisan jobs
|
||||||
|
# Runs artisan finish:reports at 01:00 for production and staging APIs
|
||||||
|
|
||||||
|
let
|
||||||
|
php = pkgs.php82;
|
||||||
|
|
||||||
|
sites = [
|
||||||
|
{
|
||||||
|
domain = "api.ebs.amz.at";
|
||||||
|
user = "api_ebs_amz_at";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
domain = "api.stage.ebs.amz.at";
|
||||||
|
user = "api_stage_ebs_amz_at";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
mkArtisanService = site: {
|
||||||
|
name = "artisan-finish-reports-${site.domain}";
|
||||||
|
value = {
|
||||||
|
description = "Laravel artisan finish:reports for ${site.domain}";
|
||||||
|
after = [ "network.target" "mysql.service" "phpfpm-${site.domain}.service" ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
User = site.user;
|
||||||
|
Group = "nginx";
|
||||||
|
WorkingDirectory = "/var/www/${site.domain}";
|
||||||
|
ExecStart = "${php}/bin/php artisan finish:reports";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
mkArtisanTimer = site: {
|
||||||
|
name = "artisan-finish-reports-${site.domain}";
|
||||||
|
value = {
|
||||||
|
description = "Daily timer for artisan finish:reports on ${site.domain}";
|
||||||
|
wantedBy = [ "timers.target" ];
|
||||||
|
timerConfig = {
|
||||||
|
OnCalendar = "*-*-* 01:00:00";
|
||||||
|
Persistent = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
in
|
||||||
|
{
|
||||||
|
systemd.services = builtins.listToAttrs (map mkArtisanService sites);
|
||||||
|
systemd.timers = builtins.listToAttrs (map mkArtisanTimer sites);
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
systemd.tmpfiles.rules = [
|
systemd.tmpfiles.rules = [
|
||||||
# api.ebs.cloonar.dev
|
# api.ebs.cloonar.dev
|
||||||
"d /var/www/api.ebs.cloonar.dev/storage/framework/cache 0775 api_ebs_cloonar_dev nginx -"
|
"d /var/www/api.ebs.cloonar.dev/storage/framework/cache 0775 api_ebs_cloonar_dev nginx -"
|
||||||
|
"d /var/www/api.ebs.cloonar.dev/storage/framework/testing 0775 api_ebs_cloonar_dev nginx -"
|
||||||
"d /var/www/api.ebs.cloonar.dev/storage/framework/sessions 0775 api_ebs_cloonar_dev nginx -"
|
"d /var/www/api.ebs.cloonar.dev/storage/framework/sessions 0775 api_ebs_cloonar_dev nginx -"
|
||||||
"d /var/www/api.ebs.cloonar.dev/storage/framework/views 0775 api_ebs_cloonar_dev nginx -"
|
"d /var/www/api.ebs.cloonar.dev/storage/framework/views 0775 api_ebs_cloonar_dev nginx -"
|
||||||
"d /var/www/api.ebs.cloonar.dev/storage/logs 0775 api_ebs_cloonar_dev nginx -"
|
"d /var/www/api.ebs.cloonar.dev/storage/logs 0775 api_ebs_cloonar_dev nginx -"
|
||||||
@@ -12,6 +13,7 @@
|
|||||||
|
|
||||||
# api.ebs.amz.at
|
# api.ebs.amz.at
|
||||||
"d /var/www/api.ebs.amz.at/storage/framework/cache 0775 api_ebs_amz_at nginx -"
|
"d /var/www/api.ebs.amz.at/storage/framework/cache 0775 api_ebs_amz_at nginx -"
|
||||||
|
"d /var/www/api.ebs.amz.at/storage/framework/testing 0775 api_ebs_amz_at nginx -"
|
||||||
"d /var/www/api.ebs.amz.at/storage/framework/sessions 0775 api_ebs_amz_at nginx -"
|
"d /var/www/api.ebs.amz.at/storage/framework/sessions 0775 api_ebs_amz_at nginx -"
|
||||||
"d /var/www/api.ebs.amz.at/storage/framework/views 0775 api_ebs_amz_at nginx -"
|
"d /var/www/api.ebs.amz.at/storage/framework/views 0775 api_ebs_amz_at nginx -"
|
||||||
"d /var/www/api.ebs.amz.at/storage/logs 0775 api_ebs_amz_at nginx -"
|
"d /var/www/api.ebs.amz.at/storage/logs 0775 api_ebs_amz_at nginx -"
|
||||||
@@ -19,6 +21,7 @@
|
|||||||
|
|
||||||
# api.stage.ebs.amz.at
|
# api.stage.ebs.amz.at
|
||||||
"d /var/www/api.stage.ebs.amz.at/storage/framework/cache 0775 api_stage_ebs_amz_at nginx -"
|
"d /var/www/api.stage.ebs.amz.at/storage/framework/cache 0775 api_stage_ebs_amz_at nginx -"
|
||||||
|
"d /var/www/api.stage.ebs.amz.at/storage/framework/testing 0775 api_stage_ebs_amz_at nginx -"
|
||||||
"d /var/www/api.stage.ebs.amz.at/storage/framework/sessions 0775 api_stage_ebs_amz_at nginx -"
|
"d /var/www/api.stage.ebs.amz.at/storage/framework/sessions 0775 api_stage_ebs_amz_at nginx -"
|
||||||
"d /var/www/api.stage.ebs.amz.at/storage/framework/views 0775 api_stage_ebs_amz_at nginx -"
|
"d /var/www/api.stage.ebs.amz.at/storage/framework/views 0775 api_stage_ebs_amz_at nginx -"
|
||||||
"d /var/www/api.stage.ebs.amz.at/storage/logs 0775 api_stage_ebs_amz_at nginx -"
|
"d /var/www/api.stage.ebs.amz.at/storage/logs 0775 api_stage_ebs_amz_at nginx -"
|
||||||
|
|||||||
56
hosts/amzebs-01/modules/postfix.nix
Normal file
56
hosts/amzebs-01/modules/postfix.nix
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
{ pkgs
|
||||||
|
, lib
|
||||||
|
, config
|
||||||
|
, ...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
headerChecksFile = pkgs.writeText "header_checks" ''
|
||||||
|
# Warn about missing critical headers (but don't reject from localhost)
|
||||||
|
# These help identify misconfigured applications
|
||||||
|
/^$/ WARN Missing headers detected
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
{
|
||||||
|
services.postfix = {
|
||||||
|
mapFiles."header_checks" = headerChecksFile;
|
||||||
|
enable = true;
|
||||||
|
hostname = "amzebs-01.amz.at";
|
||||||
|
domain = "amz.at";
|
||||||
|
|
||||||
|
config = {
|
||||||
|
# Explicitly set hostname to prevent "localhost" HELO issues
|
||||||
|
myhostname = "amzebs-01.amz.at";
|
||||||
|
|
||||||
|
# Set proper HELO name for outgoing SMTP connections
|
||||||
|
smtp_helo_name = "amzebs-01.amz.at";
|
||||||
|
|
||||||
|
# Professional SMTP banner (prevents appearing as default/misconfigured)
|
||||||
|
smtpd_banner = "$myhostname ESMTP";
|
||||||
|
|
||||||
|
# Listen only on localhost for security
|
||||||
|
# Laravel will send via localhost, no external access needed
|
||||||
|
inet_interfaces = "loopback-only";
|
||||||
|
|
||||||
|
# Compatibility
|
||||||
|
compatibility_level = "2";
|
||||||
|
|
||||||
|
# Only accept mail from localhost
|
||||||
|
mynetworks = [ "127.0.0.0/8" "[::1]/128" ];
|
||||||
|
|
||||||
|
# Larger message size limits for attachments
|
||||||
|
mailbox_size_limit = 202400000; # ~200MB
|
||||||
|
message_size_limit = 51200000; # ~50MB
|
||||||
|
|
||||||
|
# Ensure proper header handling
|
||||||
|
# Reject mail that's missing critical headers
|
||||||
|
header_checks = "regexp:/var/lib/postfix/conf/header_checks";
|
||||||
|
|
||||||
|
# Rate limiting to prevent spam-like behavior
|
||||||
|
# Allow reasonable sending rates for applications
|
||||||
|
smtpd_client_message_rate_limit = 100;
|
||||||
|
smtpd_client_recipient_rate_limit = 200;
|
||||||
|
|
||||||
|
# Milter configuration is handled automatically by rspamd.postfix.enable
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
84
hosts/amzebs-01/modules/rspamd.nix
Normal file
84
hosts/amzebs-01/modules/rspamd.nix
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
{ pkgs
|
||||||
|
, config
|
||||||
|
, ...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
domain = "amz.at";
|
||||||
|
selector = "amzebs-01";
|
||||||
|
|
||||||
|
localConfig = pkgs.writeText "local.conf" ''
|
||||||
|
logging {
|
||||||
|
level = "notice";
|
||||||
|
}
|
||||||
|
|
||||||
|
# DKIM signing configuration with host-specific selector
|
||||||
|
dkim_signing {
|
||||||
|
path = "/var/lib/rspamd/dkim/${domain}.${selector}.key";
|
||||||
|
selector = "${selector}";
|
||||||
|
allow_username_mismatch = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
# ARC signing (Authenticated Received Chain)
|
||||||
|
arc {
|
||||||
|
path = "/var/lib/rspamd/dkim/${domain}.${selector}.key";
|
||||||
|
selector = "${selector}";
|
||||||
|
allow_username_mismatch = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add authentication results to headers
|
||||||
|
milter_headers {
|
||||||
|
use = ["authentication-results"];
|
||||||
|
authenticated_headers = ["authentication-results"];
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
{
|
||||||
|
services.rspamd = {
|
||||||
|
enable = true;
|
||||||
|
extraConfig = ''
|
||||||
|
.include(priority=1,duplicate=merge) "${localConfig}"
|
||||||
|
'';
|
||||||
|
|
||||||
|
# Enable Postfix milter integration
|
||||||
|
postfix.enable = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Copy DKIM key from sops secret to rspamd directory
|
||||||
|
systemd.services.rspamd-dkim-setup = {
|
||||||
|
description = "Setup DKIM key from sops secret for ${domain}";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
before = [ "rspamd.service" ];
|
||||||
|
after = [ "sops-nix.service" ];
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
RemainAfterExit = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
script = ''
|
||||||
|
DKIM_DIR="/var/lib/rspamd/dkim"
|
||||||
|
DKIM_KEY="$DKIM_DIR/${domain}.${selector}.key"
|
||||||
|
|
||||||
|
# Create directory if it doesn't exist
|
||||||
|
mkdir -p "$DKIM_DIR"
|
||||||
|
|
||||||
|
# Copy key from sops secret
|
||||||
|
if [ -f "${config.sops.secrets.rspamd-dkim-key.path}" ]; then
|
||||||
|
cp "${config.sops.secrets.rspamd-dkim-key.path}" "$DKIM_KEY"
|
||||||
|
chown rspamd:rspamd "$DKIM_KEY"
|
||||||
|
chmod 600 "$DKIM_KEY"
|
||||||
|
echo "DKIM key deployed successfully from sops secret"
|
||||||
|
else
|
||||||
|
echo "ERROR: DKIM key not found in sops secrets!"
|
||||||
|
echo "Please ensure rspamd-dkim-key is defined in secrets.yaml"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
sops.secrets.rspamd-dkim-key = {
|
||||||
|
owner = "rspamd";
|
||||||
|
group = "rspamd";
|
||||||
|
mode = "0400";
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,45 +1,46 @@
|
|||||||
borg-passphrase: ENC[AES256_GCM,data:6T00Em+a5TcrmQNvtoCoij5aks6KIZkCAAaPXLirkQlZ6x1p1bX9KXU2ZvBAtVPrUuTeZLPTKqT/iL5Io+WKGw==,iv:gB9cktzKa8khmZZ8xwLS6oEX+Ag3APmf2jIQNLa1g/Y=,tag:sWfVbKQHgaaSRWuqYdpKTQ==,type:str]
|
borg-passphrase: ENC[AES256_GCM,data:Q2GvEat5EHmshFiya3yNqFTVS+oJv0al+bYMRwysb0yu7F2gCJd000Y3ibA+tUPSL9iSlMSy0cTkesGVEGBt9w==,iv:/kUJXgibF1cyaCPB55/0nKYq9sSva6psxu2P/l7iRN4=,tag:velr9LTfoj7gEWhUmvPtQg==,type:str]
|
||||||
borg-ssh-key: ENC[AES256_GCM,data:TrfaVOWlk8NXMEm6xr5+9pv2j8qPQ2dd6jAhHw8uw25ijhiA+eNtQh9YuQj40zw7hj7cQKctZ6pptdGS1OvS0Zoq1r6IJWLJ2UZcYLXDhOX2/TJQbRcooawG5+JiYCMBe6+T1bzgQaDGWKM7l09lq/saycci/ICe6KKQE8d8i0RaKCsCTf6auNiSMgjYBUMezYP0tTMTIZT6lqeHCUqiEkQ32aE7fdFCAF4hU9JkzhUad3BYasJfDKWksPGc2/GrCpx5JHAD4Tp6GbUpTFVCFf0JVFWaAIyYfKsIShO+yks2TIEOkcQbg8VZADbTtAhhEBcvOCS6mVcgpqQRiEfJI3OvG07KhujJa66EKHlaOM2K71RDDVG+KrlPTW2/1Zaz03FDo7QxiOae9u6KkF+GCIMXAiOL6XYAyDMkxssUA6JodFXImWWwqDJe5Af5jIUAIAAGJTDBSl6S+TWP3pJSM+Pfq6OxRJYFeCVrIE2P4aC37x2vi8V7iTPk0EJSm9TmrLbw+Ia5OvrarwzwtZ2u,iv:IVyeqEGhWUamXw8HPwqyvrHcmTcyEOZmm2NRaTdK+qw=,tag:hQb7wk0YeeQxrPFWuMlfGg==,type:str]
|
borg-ssh-key: ENC[AES256_GCM,data:0YEvv7QDmGsur0PFMmz5HqDgDCEk0kRaOu1n7GGWAwmmr0K0bVbpKzHw5wMiMnXEBrvj0izo4P4LYGAlAAYn22Bhgi2eN/vdvSO5V5uDV1ep2dV/TN2m3oYgTIgot47YgwBhcNunIUtEsbZuhAsTGL6LFBPJ3OCLKvhXTNTDaajgH/e4CvyxHHl63MBzr0i1ajigl1IKCk2hhZF4Kd1YGBCVZRoNyyNXywihlcFeskNfldW/sd5Qn2nowVf1MEV9n6Il6Zc1FX69WUVy1k+kOT7HJZGq3uDmgwXQgQhqKm1wh5uOlLkGUX6fz/nz+YFzLFMuUVvs34CzbbEFuWmGU+aNQrfCfI1hqwB5s6wVNdpUmigX9AQMQklu85tHFJg1AaRvhA24Cp/GrptggrTThcjwVFoe9NSQouNYn+ImTvlsE4HuDRRFE6YUounGd2lpRd40LsEjwKiLtwBwqG94u4ZOI91+LG6ZqHftRehE9r/CtedLyqtluNyyQyUNKPraUOm9Rrapewsj0ZCZgGQU,iv:xdRUBQlZlwVIog5KgZRmGNxdmhFE9HgnK3Ahfo+zT9k=,tag:McsJKUEGnKXxiv8Tg5zA4A==,type:str]
|
||||||
mysql-readonly-password: ENC[AES256_GCM,data:KQiL0ZJGkJEqX7wADmY2YucT79Grt+tCQA/aER7llHgqUIvjJHO8C2yw+VI=,iv:M3QchAeKXp7BjP2FfaWgUNiGPs0qQHe9P5lttxO5+Fg=,tag:TPGUWXYQsf40hlVu7PGEEg==,type:str]
|
mysql-readonly-password: ENC[AES256_GCM,data:k2RplkUZPGZlh29KXXdtwe+MCqKzTI/bLdyuEeicdkbGlBk1SGyLF8vW4t8=,iv:a14IrXYVCDqPKGfJSEPP8g19sPvRTx5NT8IVJJeL48s=,tag:GWgU3oa/+u21/L2y3+vOsw==,type:str]
|
||||||
|
rspamd-dkim-key: ENC[AES256_GCM,data:maOnsx8AQUIjXqHEzHLxtSvAkr9+YCZid9xWaflkffS0gHd/hoHrozHy+rHSjU7Mz7QHYhjUjFY7Hp7wdKQnHpQLJRV96iNPXTXXYtBr7oDL51cq8ozd094FuMeLNSPitV89OHDcM+9h1F4dsdDWPUiw7eoijQeZ8vx1/VCVAp4FVxTFX3qhoMhXlFabiyM85eKMwJG4BdSwqS624f2Z4tvECRp0pBGtd/3r4/EVRDV1qNsiFvH8mi8eyg9xiWDLDrePq4TuWSu1Xc7z0qpDy0o8iAwGhPu9egIyzHEPk07j9U7PpK56C2UCSY0JBm0hkBGbqLXyRklSMytxoKgw4GJykMwNPNXmA9yuLPanxagJB/z8b7X4HTuYhExzQcC6ke/y8xKcxU4qGt8Ayy5v+QoNpdqXIPsZkIuw9uWm6RIgDt2dCaOdI06lesZKjqU/T6EhDfGoGZX7DwQ7uV9xNDM4NW2jsKpUdFKnzPCCe7/jO/ck4P4i8V+6NWDjj4+/BXDNnKJbMcHIHoSvckCGZiginJsbGvSWd0HfbpR7GQAnL3uKB5/HuFAaUkx+dPHmmP2tOBv6vNt+tq+V9i4kQmwAdl8a9KI456tw9vLwXcBDZOO7n4X5H0jc4afoYCnvLxahvbIXm2QNcBYVKxkqBCvoYEMrBjrnujwbQdEfDKQf3g5p8LQwAfCQ3ng+XH/BDF3qMBdsN1u5Di0FQpCDaGKX8pJ1gg+il76fJgSU8ftoaT32hJnLAjal4cgNIxbta2UYQLixUqaWZ8xqvxrSopkWYrlBBUyQh9jMEoTzpxwCsEPQ72qgVcQfJYlMl4WUBwcasfJnySR+qZ22g3fhStpAQ2HuTGhLjTG1QOewYdwDXDhNmcbqZ478Sp1t7qbBx0R7vWFSyYCMlbmLmvzPm6Z3ET7lkfCrMjMNXaQ8cWSF19QaHAfqRwQooLL93yx7U0KHCilEg4bUjsw8MLQNa4A0ohpq6CG4s5O1+di7W4/h71/moggIebFb2eGLJ8BvbkwiVozXI9L77IGd9RswlEjZed18u7fqetS7dyDthhVG2pvya4zZI/cxIq6oJkNr2RIt3NgYChOh0I/17DuSJJ1jAmPB0Evj8QtCCo49ENnyO5cGWn12DZWybwYkg2jQC4aDFA/u5ajTo3wOKdwHj5hgMz/z05Bn2vAdhCGl6uWWNzcNnDiu3/rjqsjOkfkp0hCP7Q==,iv:FORxJ8htcoLIEJihUN7im3dN4jhnigB70InTohtpWwU=,tag:e2DHBd2dn3piCkEdkbHdoA==,type:str]
|
||||||
sops:
|
sops:
|
||||||
age:
|
age:
|
||||||
- recipient: age14grjcxaq4h55yfnjxvnqhtswxhj9sfdcvyas4lwvpa8py27pjy2sv3g6v7
|
- recipient: age14grjcxaq4h55yfnjxvnqhtswxhj9sfdcvyas4lwvpa8py27pjy2sv3g6v7
|
||||||
enc: |
|
enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAvWjd4K3BueGVxNGs2OC9t
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhQUpWNUgxVnhuTXd2TkF0
|
||||||
YURtcTIzNytCMDdYd29KenZOdm4vMk9mWEZFCkJrZlVZdkVJeW4rNzc0N3NBY3hM
|
SVVHemFKRWlYczZ0TnBESVNRczhuRUNnUG1BCmJKQ2JZbHhFcXJidHJzci9OaFBm
|
||||||
dE1ORzRHRHlONEQ2dW83R051aE45QVEKLS0tIE0zOVpVbWphNitPaUg0cmxUbW5m
|
ZTd0MGhsaVBic3dMb3psUHRCRnR3ODQKLS0tIERrSG1GVTRHdkJpVWpqdTZ4Yytq
|
||||||
ZHViVHJrOWREb0pYR3hOTm0zSHZ3N0EKeNcZOM+H0XZN3Ji1ubBoHMgycuJFX3+C
|
OHhlZjV6MjRVbXFsWjlQSU03ZDNwYm8KAswHRSdV0BW/oJyZx63iZRHsF7SZ6PO+
|
||||||
YvJ795wSwtXMU+mCDB04tcYPSAI0RC82wGT9r3XLNZgbF/xP0Er3nw==
|
hajQqmEyfcVfEu39zZzxQ2mtWlOr69I++irOhE3NeiFeJ1yIRQDJEQ==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
- recipient: age1exny8unxynaw03yu8ppahu5z28uermghr8ag34e7kdqnaduq9stsyettzz
|
- recipient: age1exny8unxynaw03yu8ppahu5z28uermghr8ag34e7kdqnaduq9stsyettzz
|
||||||
enc: |
|
enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWTnpIdHBmMGdBQzk2N0xW
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBUaUNnY0hpdDAzMTNIUS9D
|
||||||
SGlNMUxjYU5SZHJLK0wvWnZyYkEzK04vdWd3CmVGWW50RUQ2K0lLRC90WW9KY1hj
|
RmdKbmplUk9DRXlLRXEvSnVjT05sQjcvTnpJCkd6bGRINm5yYUZOUTVzWEdjRmtG
|
||||||
NGVpMEdzaHUyTUVBaDcxb2w5MC9BUjAKLS0tIFdSSjlFTHl1Z3NYNVhxaSs0WkJE
|
Mmx0ci93N2wvTWV5MzlRVnlYdUxoUWsKLS0tIEVHUlNWYStWTG01RzRrVnNXc3BW
|
||||||
eGVWZHdnMkhaNzlDNlhCa24wZzlvNmsK7pLzsxtlMevP2o9nJOjVgDAjrYdEgRUu
|
VkRkUXROU3plNmwvTUVhYmhCS2syQkEKKgC0EmUu1u2vZ/SZTnam+h846gZSyY4V
|
||||||
NlJHfO0m9U7fJfeu6XSWQgGYRJm7tSmTZKvsJgTS+pKcynHz8B9rkQ==
|
JyMzkws8O5TY9juWdDzXJIU67mIgc4qrWWN3uh8k28JBZGc078b5bg==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
- recipient: age1v6p8dan2t3w9h94fz4flldl32082j3s9x6zqq7u5j66keth9aphsd6pvch
|
- recipient: age1v6p8dan2t3w9h94fz4flldl32082j3s9x6zqq7u5j66keth9aphsd6pvch
|
||||||
enc: |
|
enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBNMk9VL2s0ZmoxSzY3NmFQ
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBoK2JUNVdYTzkvM1BBWXRm
|
||||||
MTZEaml5Q0kxRDFwSEN5dG90SzZmcXZLUjJBClpVNUJEZEdaa09hM1BQUU1jVVQz
|
citCNlE4Z1NLdEZ2R0tNZTVSMlFSeGxGOURnClJnYURYa0JZaVprQWdBcmVnOWVj
|
||||||
M2w4QXdmZnJya2pCTEV1QW9keXgyTWMKLS0tIFA1VWl3RzF5V2FMUE1mZ2NYRnBU
|
TGVCK1JWMVlueHJUaTZZYmROM0E5aDAKLS0tIEJxYkdadGtZM250d2d6Ujl2UU9C
|
||||||
OURWSFZnM0lEMXJEcjVPL3hnZ0pIQ1kKVvoCVQuayH/XRfddMKq2d8TssXOS5e1o
|
YUpkVll2S2RpT0I1UVZiZFRKS1prMEEKp/bGImanJ/58vTQG/gUun/Y2QdmOEi3h
|
||||||
bIL6F+tRBle2UgVuXSMkyggCnvLePA8OxfAdMMg5npSFkPgTZrAYYQ==
|
hVS0V2QcfuGgi0/YofLOM3+M6k6ViXw07XfXmR+puvLIHKr2y11x1Q==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
- recipient: age1xcgc6u7fmc2trgxtdtf5nhrd7axzweuxlg0ya9jre3sdrg6c6easecue9w
|
- recipient: age1xcgc6u7fmc2trgxtdtf5nhrd7axzweuxlg0ya9jre3sdrg6c6easecue9w
|
||||||
enc: |
|
enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3QmR3T3FYN2RpR1JrV0w0
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBDSGdEZnZEaDRpWUJVcnds
|
||||||
aVMycUJUN1NKME1UKzVsZmpNNTAzTmxUUUZNClhmdUF5N0Q4K09IeVhNOWhNNEc2
|
VGFSQklvczBZdEdEbXhodW8vME9wMUpVRENjClFZcnVqYkJxdlBiZFhma0tmZjgz
|
||||||
UTNzeGJ4NlpxMUtEaHZDWDZOeHdvSU0KLS0tIFNTaThWbklXeE85c3hSMWZwNTNN
|
YXlIdlRDTDU4MHg1dzhGVDRJb2FGYVUKLS0tIDBXSWZ2NkxzdEk0ZlFRM00ybFNy
|
||||||
RWFUVXVXWjdsSHM5ZGljd3YyQW5ja2MKvAhwHL5PcLFxuU7MfV/cWtNfzTb9yoqR
|
M0doaWl5R2cwU2RxQm5DbWxXeTZ5S2MKwrB3SysmgzCThQOhEVx18dxIfko0+oZY
|
||||||
3iD4UJsDDagCIkpvjKods4ydlzh3agOyLHswDSX/WmUur9J5pd4PAg==
|
9BSZOoFbfuwiLbtpL4J8bzxDvxn6sXxB8EBJH1hbpID53AquWDsxSw==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
lastmodified: "2025-11-14T11:33:59Z"
|
lastmodified: "2025-11-19T11:16:25Z"
|
||||||
mac: ENC[AES256_GCM,data:AnEs3yzpOJ5/wyCL/sHV6U5V7FBhZZlBQeA+mCGfZ25JZAL3Yb6yD6xJhmGC8AqIFS6PIFSWa2r0suDRQAoVO2AwFVwd9Y/TEwjPGnXvWfwB82+mnyLIakyzM/pcLjiMePUqr5nnJ8tWoKzuqs/jQHuMOGkItqwjkVDr9/hx3lc=,iv:kc08z63phDfs7gzruHjnQA9bXAvWMGkE14/0Kyfhuds=,tag:9wedcRCTAv0HMtSap55JOw==,type:str]
|
mac: ENC[AES256_GCM,data:x4yor9G+QirceSYSX1K9GdfyGellT4JCkE09Tl9/mOX8HMOKFAQGknuwwU6SNGg+ciBFk4TdjQnDmVai4T8JQo9W/DLiZ+GKnWO3s+ZLDX30sEF0aMjKa43R5CCPO/Fl2XH96TaPC+8itTJQ6TpBSg51QLPcpqrMljiBNWvEoTU=,iv:Zi9rglAwgsejUmIpLN/1QlL80BSp3HP32k1xkWt2b+o=,tag:2ADk8d2G4OezkQjcV3CZuA==,type:str]
|
||||||
unencrypted_suffix: _unencrypted
|
unencrypted_suffix: _unencrypted
|
||||||
version: 3.11.0
|
version: 3.11.0
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ in {
|
|||||||
# React client-side routing support
|
# React client-side routing support
|
||||||
locations."/".extraConfig = ''
|
locations."/".extraConfig = ''
|
||||||
index index.html;
|
index index.html;
|
||||||
try_files $uri $uri/ /index.html;
|
try_files $uri $uri/ /index.html$is_args$args;
|
||||||
'';
|
'';
|
||||||
|
|
||||||
# Cache static assets
|
# Cache static assets
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ in {
|
|||||||
# React client-side routing support
|
# React client-side routing support
|
||||||
locations."/".extraConfig = ''
|
locations."/".extraConfig = ''
|
||||||
index index.html;
|
index index.html;
|
||||||
try_files $uri $uri/ /index.html;
|
try_files $uri $uri/ /index.html$is_args$args;
|
||||||
'';
|
'';
|
||||||
|
|
||||||
# Cache static assets
|
# Cache static assets
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ in {
|
|||||||
# React client-side routing support
|
# React client-side routing support
|
||||||
locations."/".extraConfig = ''
|
locations."/".extraConfig = ''
|
||||||
index index.html;
|
index index.html;
|
||||||
try_files $uri $uri/ /index.html;
|
try_files $uri $uri/ /index.html$is_args$args;
|
||||||
'';
|
'';
|
||||||
|
|
||||||
# Cache static assets
|
# Cache static assets
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ in {
|
|||||||
# React client-side routing support
|
# React client-side routing support
|
||||||
locations."/".extraConfig = ''
|
locations."/".extraConfig = ''
|
||||||
index index.html;
|
index index.html;
|
||||||
try_files $uri $uri/ /index.html;
|
try_files $uri $uri/ /index.html$is_args$args;
|
||||||
'';
|
'';
|
||||||
|
|
||||||
# Cache static assets
|
# Cache static assets
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
https://channels.nixos.org/nixos-25.05
|
https://channels.nixos.org/nixos-25.11
|
||||||
|
|||||||
@@ -7,8 +7,10 @@
|
|||||||
./utils/modules/nginx.nix
|
./utils/modules/nginx.nix
|
||||||
|
|
||||||
./utils/modules/autoupgrade.nix
|
./utils/modules/autoupgrade.nix
|
||||||
|
./utils/modules/victoriametrics
|
||||||
./utils/modules/promtail
|
./utils/modules/promtail
|
||||||
./utils/modules/borgbackup.nix
|
./utils/modules/borgbackup.nix
|
||||||
|
./utils/modules/set-nix-channel.nix
|
||||||
|
|
||||||
# fw
|
# fw
|
||||||
./modules/network-prefix.nix
|
./modules/network-prefix.nix
|
||||||
@@ -25,7 +27,6 @@
|
|||||||
./modules/podman.nix
|
./modules/podman.nix
|
||||||
./modules/omada.nix
|
./modules/omada.nix
|
||||||
./modules/ddclient.nix
|
./modules/ddclient.nix
|
||||||
./utils/modules/victoriametrics
|
|
||||||
# ./modules/wol.nix
|
# ./modules/wol.nix
|
||||||
|
|
||||||
|
|
||||||
@@ -49,6 +50,7 @@
|
|||||||
|
|
||||||
./modules/firefox-sync.nix
|
./modules/firefox-sync.nix
|
||||||
./modules/fivefilters.nix
|
./modules/fivefilters.nix
|
||||||
|
# ./modules/pyload
|
||||||
|
|
||||||
# home assistant
|
# home assistant
|
||||||
./modules/home-assistant
|
./modules/home-assistant
|
||||||
@@ -85,11 +87,24 @@
|
|||||||
nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [
|
nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [
|
||||||
"mongodb"
|
"mongodb"
|
||||||
"ai-mailer"
|
"ai-mailer"
|
||||||
|
"filebot"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
# Intel N100 Graphics Support for hardware transcoding
|
||||||
|
hardware.graphics = {
|
||||||
|
enable = true;
|
||||||
|
extraPackages = with pkgs; [
|
||||||
|
intel-media-driver # VAAPI driver (iHD) for modern Intel GPUs
|
||||||
|
vpl-gpu-rt # Intel VPL/QSV runtime for Gen 12+ (N100)
|
||||||
|
intel-compute-runtime # OpenCL support for tone-mapping
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
hardware.enableRedistributableFirmware = true;
|
||||||
|
|
||||||
time.timeZone = "Europe/Vienna";
|
time.timeZone = "Europe/Vienna";
|
||||||
|
|
||||||
services.logind.extraConfig = "RuntimeDirectorySize=2G";
|
services.logind.settings.Login.RuntimeDirectorySize = "2G";
|
||||||
|
|
||||||
sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
|
sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
|
||||||
sops.defaultSopsFile = ./secrets.yaml;
|
sops.defaultSopsFile = ./secrets.yaml;
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
|
|
||||||
context:
|
context:
|
||||||
urls:
|
urls:
|
||||||
|
- "https://paraclub.cloonar.dev/de/tandemfallschirmspringen/faq/"
|
||||||
- "https://paraclub.at/de/"
|
- "https://paraclub.at/de/"
|
||||||
- "https://paraclub.at/de/tandemfallschirmspringen/alle-infos/"
|
- "https://paraclub.at/de/tandemfallschirmspringen/alle-infos/"
|
||||||
- "https://paraclub.at/de/tandemfallschirmspringen/kosten-tandemsprung/"
|
- "https://paraclub.at/de/tandemfallschirmspringen/kosten-tandemsprung/"
|
||||||
@@ -103,6 +104,8 @@
|
|||||||
|
|
||||||
restartTriggers = [
|
restartTriggers = [
|
||||||
"/etc/ai-mailer/config.yaml"
|
"/etc/ai-mailer/config.yaml"
|
||||||
|
config.sops.secrets.ai-mailer-imap-password.path
|
||||||
|
config.sops.secrets.ai-mailer-openrouter-key.path
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,21 +2,33 @@
|
|||||||
virtualisation = {
|
virtualisation = {
|
||||||
oci-containers.containers = {
|
oci-containers.containers = {
|
||||||
deconz = {
|
deconz = {
|
||||||
autoStart = false;
|
autoStart = true;
|
||||||
image = "marthoc/deconz";
|
image = "marthoc/deconz";
|
||||||
volumes = [
|
volumes = [
|
||||||
"/etc/localtime:/etc/localtime:ro"
|
"/etc/localtime:/etc/localtime:ro"
|
||||||
"/var/lib/deconz:/root/.local/share/dresden-elektronik/deCONZ"
|
"/var/lib/deconz:/root/.local/share/dresden-elektronik/deCONZ"
|
||||||
|
"/dev/bus/usb:/dev/bus/usb:ro"
|
||||||
|
"/run/udev:/run/udev:ro"
|
||||||
];
|
];
|
||||||
environment = {
|
environment = {
|
||||||
DECONZ_DEVICE = "/dev/ttyACM0";
|
DECONZ_DEVICE = "/dev/ttyACM0";
|
||||||
TZ = "Europe/Vienna";
|
TZ = "Europe/Vienna";
|
||||||
|
DECONZ_UID = "0";
|
||||||
|
DECONZ_GID = "0";
|
||||||
|
DECONZ_START_VERBOSE = "1";
|
||||||
};
|
};
|
||||||
extraOptions = [
|
extraOptions = [
|
||||||
"--network=server"
|
"--network=server"
|
||||||
"--ip=${config.networkPrefix}.97.22"
|
"--ip=${config.networkPrefix}.97.22"
|
||||||
"--device=/dev/ttyACM0"
|
"--device=/dev/ttyACM0"
|
||||||
"--hostname=deconz"
|
"--hostname=deconz"
|
||||||
|
"--mac-address=1a:c4:04:6e:29:bd"
|
||||||
|
"--cap-add=CAP_MKNOD"
|
||||||
|
"--cap-add=CAP_NET_RAW"
|
||||||
|
"--cap-add=CAP_NET_ADMIN"
|
||||||
|
"--device-cgroup-rule=c 166:* rmw"
|
||||||
|
"--device-cgroup-rule=c 188:* rmw"
|
||||||
|
"--security-opt=label=disable"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -73,6 +73,7 @@
|
|||||||
"02:00:00:00:00:04,${config.networkPrefix}.97.6,matrix"
|
"02:00:00:00:00:04,${config.networkPrefix}.97.6,matrix"
|
||||||
"ea:db:d4:c1:18:ba,${config.networkPrefix}.97.50,git"
|
"ea:db:d4:c1:18:ba,${config.networkPrefix}.97.50,git"
|
||||||
"c2:4f:64:dd:13:0c,${config.networkPrefix}.97.20,home-assistant"
|
"c2:4f:64:dd:13:0c,${config.networkPrefix}.97.20,home-assistant"
|
||||||
|
"6c:1f:f7:8e:a9:86,${config.networkPrefix}.97.11,nas"
|
||||||
"1a:c4:04:6e:29:02,${config.networkPrefix}.101.25,deconz"
|
"1a:c4:04:6e:29:02,${config.networkPrefix}.101.25,deconz"
|
||||||
|
|
||||||
"c4:a7:2b:c7:ea:30,${config.networkPrefix}.99.10,metz"
|
"c4:a7:2b:c7:ea:30,${config.networkPrefix}.99.10,metz"
|
||||||
@@ -133,6 +134,10 @@
|
|||||||
"/foundry-vtt.cloonar.com/${config.networkPrefix}.97.5"
|
"/foundry-vtt.cloonar.com/${config.networkPrefix}.97.5"
|
||||||
"/sync.cloonar.com/${config.networkPrefix}.97.5"
|
"/sync.cloonar.com/${config.networkPrefix}.97.5"
|
||||||
|
|
||||||
|
# multimedia
|
||||||
|
"/dl.cloonar.com/${config.networkPrefix}.97.5"
|
||||||
|
"/jellyfin.cloonar.com/${config.networkPrefix}.97.5"
|
||||||
|
|
||||||
"/deconz.cloonar.multimedia/${config.networkPrefix}.97.22"
|
"/deconz.cloonar.multimedia/${config.networkPrefix}.97.22"
|
||||||
|
|
||||||
"/ddl-warez.to/172.67.184.30"
|
"/ddl-warez.to/172.67.184.30"
|
||||||
|
|||||||
@@ -396,12 +396,13 @@
|
|||||||
all = true;
|
all = true;
|
||||||
entities = [
|
entities = [
|
||||||
"light.livingroom_switch"
|
"light.livingroom_switch"
|
||||||
"light.livingroom_bulb_1_rgbcw_bulb"
|
"light.living_bulb_1"
|
||||||
"light.livingroom_bulb_2_rgbcw_bulb"
|
"light.living_bulb_2"
|
||||||
"light.livingroom_bulb_3_rgbcw_bulb"
|
"light.living_bulb_3"
|
||||||
"light.livingroom_bulb_4_rgbcw_bulb"
|
"light.living_bulb_4"
|
||||||
"light.livingroom_bulb_5_rgbcw_bulb"
|
"light.living_bulb_5"
|
||||||
"light.livingroom_bulb_6_rgbcw_bulb"
|
"light.living_bulb_6"
|
||||||
|
# "light.living_room"
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
@@ -415,6 +416,7 @@
|
|||||||
all = true;
|
all = true;
|
||||||
entities = [
|
entities = [
|
||||||
"light.kitchen_switch"
|
"light.kitchen_switch"
|
||||||
|
"light.kitchen_bulb_1"
|
||||||
"light.kitchen"
|
"light.kitchen"
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
153
hosts/fw/modules/pyload/default.nix
Normal file
153
hosts/fw/modules/pyload/default.nix
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
{ config, pkgs, ... }:
|
||||||
|
let
|
||||||
|
cids = import ../staticids.nix;
|
||||||
|
networkPrefix = config.networkPrefix;
|
||||||
|
filebotScript = pkgs.callPackage ./filebot-process.nix {};
|
||||||
|
|
||||||
|
pyloadUser = {
|
||||||
|
isSystemUser = true;
|
||||||
|
uid = cids.uids.pyload;
|
||||||
|
group = "pyload";
|
||||||
|
home = "/var/lib/pyload";
|
||||||
|
createHome = true;
|
||||||
|
extraGroups = [ "jellyfin" ]; # Access to multimedia directories
|
||||||
|
};
|
||||||
|
pyloadGroup = {
|
||||||
|
gid = cids.gids.pyload;
|
||||||
|
};
|
||||||
|
|
||||||
|
jellyfinUser = {
|
||||||
|
isSystemUser = true;
|
||||||
|
uid = cids.uids.jellyfin;
|
||||||
|
group = "jellyfin";
|
||||||
|
home = "/var/lib/jellyfin";
|
||||||
|
createHome = true;
|
||||||
|
extraGroups = [ "render" "video" ];
|
||||||
|
};
|
||||||
|
jellyfinGroup = {
|
||||||
|
gid = cids.gids.jellyfin;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
users.users.pyload = pyloadUser;
|
||||||
|
users.groups.pyload = pyloadGroup;
|
||||||
|
users.users.jellyfin = jellyfinUser;
|
||||||
|
users.groups.jellyfin = jellyfinGroup;
|
||||||
|
|
||||||
|
# Create the directory structure on the host
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
"d /var/lib/downloads 0755 pyload pyload - -"
|
||||||
|
"d /var/lib/multimedia 0775 root jellyfin - -"
|
||||||
|
"d /var/lib/multimedia/movies 0775 jellyfin jellyfin - -"
|
||||||
|
"d /var/lib/multimedia/tv-shows 0775 jellyfin jellyfin - -"
|
||||||
|
"d /var/lib/multimedia/music 0755 jellyfin jellyfin - -"
|
||||||
|
"d /var/lib/jellyfin 0755 jellyfin jellyfin - -"
|
||||||
|
|
||||||
|
# PyLoad hook scripts directory
|
||||||
|
"d /var/lib/pyload/config 0755 pyload pyload - -"
|
||||||
|
"d /var/lib/pyload/config/scripts 0755 pyload pyload - -"
|
||||||
|
"d /var/lib/pyload/config/scripts/package_extracted 0755 pyload pyload - -"
|
||||||
|
"L+ /var/lib/pyload/config/scripts/package_extracted/filebot-process.sh - - - - ${filebotScript}/bin/filebot-process"
|
||||||
|
];
|
||||||
|
|
||||||
|
# FileBot license secret
|
||||||
|
sops.secrets.filebot-license = {
|
||||||
|
mode = "0440";
|
||||||
|
owner = "pyload";
|
||||||
|
group = "pyload";
|
||||||
|
};
|
||||||
|
|
||||||
|
containers.pyload = {
|
||||||
|
autoStart = true;
|
||||||
|
ephemeral = false;
|
||||||
|
privateNetwork = true;
|
||||||
|
hostBridge = "server";
|
||||||
|
hostAddress = "${networkPrefix}.97.1";
|
||||||
|
localAddress = "${networkPrefix}.97.11/24";
|
||||||
|
|
||||||
|
# GPU device passthrough for hardware transcoding
|
||||||
|
allowedDevices = [
|
||||||
|
{
|
||||||
|
modifier = "rwm";
|
||||||
|
node = "/dev/dri/card0";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
modifier = "rwm";
|
||||||
|
node = "/dev/dri/renderD128";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
bindMounts = {
|
||||||
|
"/dev/dri" = {
|
||||||
|
hostPath = "/dev/dri";
|
||||||
|
isReadOnly = false;
|
||||||
|
};
|
||||||
|
"/run/opengl-driver" = {
|
||||||
|
hostPath = "/run/opengl-driver";
|
||||||
|
isReadOnly = true;
|
||||||
|
};
|
||||||
|
"/nix/store" = {
|
||||||
|
hostPath = "/nix/store";
|
||||||
|
isReadOnly = true;
|
||||||
|
};
|
||||||
|
"/var/lib/pyload" = {
|
||||||
|
hostPath = "/var/lib/pyload";
|
||||||
|
isReadOnly = false;
|
||||||
|
};
|
||||||
|
"/var/lib/jellyfin" = {
|
||||||
|
hostPath = "/var/lib/jellyfin";
|
||||||
|
isReadOnly = false;
|
||||||
|
};
|
||||||
|
"/downloads" = {
|
||||||
|
hostPath = "/var/lib/downloads";
|
||||||
|
isReadOnly = false;
|
||||||
|
};
|
||||||
|
"/multimedia" = {
|
||||||
|
hostPath = "/var/lib/multimedia";
|
||||||
|
isReadOnly = false;
|
||||||
|
};
|
||||||
|
"/var/lib/pyload/filebot-license.psm" = {
|
||||||
|
hostPath = config.sops.secrets.filebot-license.path;
|
||||||
|
isReadOnly = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = { lib, config, pkgs, ... }: {
|
||||||
|
nixpkgs.overlays = [
|
||||||
|
(import ../../utils/overlays/packages.nix)
|
||||||
|
];
|
||||||
|
|
||||||
|
imports = [
|
||||||
|
./pyload.nix
|
||||||
|
./jellyfin.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [
|
||||||
|
"unrar"
|
||||||
|
"filebot"
|
||||||
|
];
|
||||||
|
|
||||||
|
networking = {
|
||||||
|
hostName = "pyload";
|
||||||
|
useHostResolvConf = false;
|
||||||
|
defaultGateway = {
|
||||||
|
address = "${networkPrefix}.97.1";
|
||||||
|
interface = "eth0";
|
||||||
|
};
|
||||||
|
nameservers = [ "${networkPrefix}.97.1" ];
|
||||||
|
firewall.enable = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Ensure render/video groups exist with consistent GIDs for GPU access
|
||||||
|
users.groups.render = { gid = 303; };
|
||||||
|
users.groups.video = { gid = 26; };
|
||||||
|
|
||||||
|
users.users.pyload = pyloadUser;
|
||||||
|
users.groups.pyload = pyloadGroup;
|
||||||
|
users.users.jellyfin = jellyfinUser;
|
||||||
|
users.groups.jellyfin = jellyfinGroup;
|
||||||
|
|
||||||
|
system.stateVersion = "24.05";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
89
hosts/fw/modules/pyload/filebot-process.nix
Normal file
89
hosts/fw/modules/pyload/filebot-process.nix
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
{ pkgs }:
|
||||||
|
|
||||||
|
pkgs.writeShellScriptBin "filebot-process" ''
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# FileBot AMC script for automated media organization
|
||||||
|
# Called by PyLoad's package_extracted hook with parameters:
|
||||||
|
# $1 = package_id
|
||||||
|
# $2 = package_name
|
||||||
|
# $3 = download_folder (actual path to extracted files)
|
||||||
|
# $4 = password (optional)
|
||||||
|
|
||||||
|
PACKAGE_ID="''${1:-}"
|
||||||
|
PACKAGE_NAME="''${2:-unknown}"
|
||||||
|
DOWNLOAD_DIR="''${3:-/downloads}"
|
||||||
|
PASSWORD="''${4:-}"
|
||||||
|
|
||||||
|
OUTPUT_DIR="/multimedia"
|
||||||
|
LOG_FILE="/var/lib/pyload/filebot-amc.log"
|
||||||
|
EXCLUDE_LIST="/var/lib/pyload/filebot-exclude-list.txt"
|
||||||
|
|
||||||
|
# Ensure FileBot data directory exists
|
||||||
|
mkdir -p /var/lib/pyload/.local/share/filebot/data
|
||||||
|
mkdir -p "$(dirname "$LOG_FILE")"
|
||||||
|
touch "$EXCLUDE_LIST"
|
||||||
|
|
||||||
|
# Install FileBot license if not already installed
|
||||||
|
if [ ! -f /var/lib/pyload/.local/share/filebot/data/.license ]; then
|
||||||
|
echo "$(date): Installing FileBot license..." >> "$LOG_FILE"
|
||||||
|
${pkgs.filebot}/bin/filebot --license /var/lib/pyload/filebot-license.psm || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "===========================================" >> "$LOG_FILE"
|
||||||
|
echo "$(date): PyLoad package extracted hook triggered" >> "$LOG_FILE"
|
||||||
|
echo "Package ID: $PACKAGE_ID" >> "$LOG_FILE"
|
||||||
|
echo "Package Name: $PACKAGE_NAME" >> "$LOG_FILE"
|
||||||
|
echo "Download Directory: $DOWNLOAD_DIR" >> "$LOG_FILE"
|
||||||
|
echo "===========================================" >> "$LOG_FILE"
|
||||||
|
|
||||||
|
# Check if download directory exists and has media files
|
||||||
|
if [ ! -d "$DOWNLOAD_DIR" ]; then
|
||||||
|
echo "$(date): Download directory does not exist: $DOWNLOAD_DIR" >> "$LOG_FILE"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if there are any video/media files to process
|
||||||
|
if ! find "$DOWNLOAD_DIR" -type f \( -iname "*.mkv" -o -iname "*.mp4" -o -iname "*.avi" -o -iname "*.m4v" -o -iname "*.mov" \) -print -quit | grep -q .; then
|
||||||
|
echo "$(date): No media files found in: $DOWNLOAD_DIR" >> "$LOG_FILE"
|
||||||
|
echo "$(date): Skipping FileBot processing" >> "$LOG_FILE"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$(date): Starting FileBot processing" >> "$LOG_FILE"
|
||||||
|
|
||||||
|
# Run FileBot AMC script
|
||||||
|
set +e # Temporarily disable exit on error to capture exit code
|
||||||
|
${pkgs.filebot}/bin/filebot \
|
||||||
|
-script fn:amc \
|
||||||
|
--output "$OUTPUT_DIR" \
|
||||||
|
--action move \
|
||||||
|
--conflict auto \
|
||||||
|
-non-strict \
|
||||||
|
--log-file "$LOG_FILE" \
|
||||||
|
--def \
|
||||||
|
excludeList="$EXCLUDE_LIST" \
|
||||||
|
movieFormat="$OUTPUT_DIR/movies/{n} ({y})/{n} ({y}) - {vf}" \
|
||||||
|
seriesFormat="$OUTPUT_DIR/tv-shows/{n}/Season {s.pad(2)}/{n} - {s00e00} - {t}" \
|
||||||
|
ut_dir="$DOWNLOAD_DIR" \
|
||||||
|
ut_kind=multi \
|
||||||
|
clean=y \
|
||||||
|
skipExtract=y
|
||||||
|
|
||||||
|
FILEBOT_EXIT_CODE=$?
|
||||||
|
set -e # Re-enable exit on error
|
||||||
|
|
||||||
|
if [ $FILEBOT_EXIT_CODE -ne 0 ]; then
|
||||||
|
echo "$(date): FileBot processing failed with exit code $FILEBOT_EXIT_CODE" >> "$LOG_FILE"
|
||||||
|
exit 0 # Don't fail the hook even if FileBot fails
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$(date): FileBot processing completed successfully" >> "$LOG_FILE"
|
||||||
|
|
||||||
|
# Clean up any remaining empty directories
|
||||||
|
find "$DOWNLOAD_DIR" -type d -empty -delete 2>/dev/null || true
|
||||||
|
|
||||||
|
echo "$(date): All processing completed" >> "$LOG_FILE"
|
||||||
|
exit 0
|
||||||
|
''
|
||||||
36
hosts/fw/modules/pyload/jellyfin.nix
Normal file
36
hosts/fw/modules/pyload/jellyfin.nix
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{ lib, pkgs, ... }: {
|
||||||
|
# Intel graphics support for hardware transcoding
|
||||||
|
hardware.graphics = {
|
||||||
|
enable = true;
|
||||||
|
extraPackages = with pkgs; [
|
||||||
|
intel-media-driver
|
||||||
|
vpl-gpu-rt
|
||||||
|
intel-compute-runtime
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
# Set VA-API driver to iHD (modern Intel driver for N100)
|
||||||
|
environment.sessionVariables = {
|
||||||
|
LIBVA_DRIVER_NAME = "iHD";
|
||||||
|
};
|
||||||
|
|
||||||
|
services.jellyfin = {
|
||||||
|
enable = true;
|
||||||
|
openFirewall = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Override systemd hardening for GPU access
|
||||||
|
systemd.services.jellyfin = {
|
||||||
|
serviceConfig = {
|
||||||
|
PrivateUsers = lib.mkForce false; # Disable user namespacing - breaks GPU device access
|
||||||
|
DeviceAllow = [
|
||||||
|
"/dev/dri/card0 rw"
|
||||||
|
"/dev/dri/renderD128 rw"
|
||||||
|
];
|
||||||
|
SupplementaryGroups = [ "render" "video" ]; # Critical: Explicit group membership for GPU access
|
||||||
|
};
|
||||||
|
environment = {
|
||||||
|
LIBVA_DRIVER_NAME = "iHD"; # Ensure service sees this variable
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
69
hosts/fw/modules/pyload/pyload.nix
Normal file
69
hosts/fw/modules/pyload/pyload.nix
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{ pkgs, lib, ... }:
|
||||||
|
{
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
unrar # Required for RAR archive extraction
|
||||||
|
p7zip # Required for 7z and other archive formats
|
||||||
|
];
|
||||||
|
|
||||||
|
services.pyload = {
|
||||||
|
enable = true;
|
||||||
|
downloadDirectory = "/downloads";
|
||||||
|
listenAddress = "0.0.0.0";
|
||||||
|
port = 8000;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Configure pyload service
|
||||||
|
systemd.services.pyload = {
|
||||||
|
# Add extraction tools to service PATH
|
||||||
|
path = with pkgs; [
|
||||||
|
unrar # For RAR extraction
|
||||||
|
p7zip # For 7z extraction
|
||||||
|
];
|
||||||
|
|
||||||
|
environment = {
|
||||||
|
# Disable SSL certificate verification
|
||||||
|
PYLOAD__GENERAL__SSL_VERIFY = "0";
|
||||||
|
|
||||||
|
# Download speed limiting (150 Mbit/s = 19200 KiB/s)
|
||||||
|
PYLOAD__DOWNLOAD__LIMIT_SPEED = "1";
|
||||||
|
PYLOAD__DOWNLOAD__MAX_SPEED = "19200";
|
||||||
|
|
||||||
|
# Enable ExtractArchive plugin
|
||||||
|
PYLOAD__EXTRACTARCHIVE__ENABLED = "1";
|
||||||
|
PYLOAD__EXTRACTARCHIVE__DELETE = "1";
|
||||||
|
PYLOAD__EXTRACTARCHIVE__DELTOTRASH = "0";
|
||||||
|
PYLOAD__EXTRACTARCHIVE__REPAIR = "1";
|
||||||
|
PYLOAD__EXTRACTARCHIVE__RECURSIVE = "1";
|
||||||
|
PYLOAD__EXTRACTARCHIVE__FULLPATH = "1";
|
||||||
|
|
||||||
|
# Enable ExternalScripts plugin for hooks
|
||||||
|
PYLOAD__EXTERNALSCRIPTS__ENABLED = "1";
|
||||||
|
PYLOAD__EXTERNALSCRIPTS__UNLOCK = "1"; # Run hooks asynchronously
|
||||||
|
};
|
||||||
|
|
||||||
|
# Bind-mount DNS configuration files into the chroot
|
||||||
|
serviceConfig = {
|
||||||
|
BindReadOnlyPaths = [
|
||||||
|
"/etc/resolv.conf"
|
||||||
|
"/etc/nsswitch.conf"
|
||||||
|
"/etc/hosts"
|
||||||
|
"/etc/ssl"
|
||||||
|
"/etc/static/ssl"
|
||||||
|
];
|
||||||
|
# Bind mount multimedia directory as writable for FileBot hook scripts
|
||||||
|
BindPaths = [ "/multimedia" ];
|
||||||
|
|
||||||
|
# Override SystemCallFilter to allow @resources syscalls
|
||||||
|
# FileBot (Java) needs resource management syscalls like setpriority
|
||||||
|
# during cleanup operations. Still block privileged syscalls for security.
|
||||||
|
# Use mkForce to completely replace the NixOS module's default filter.
|
||||||
|
SystemCallFilter = lib.mkForce [
|
||||||
|
"@system-service"
|
||||||
|
"@resources" # Explicitly allow resource management syscalls
|
||||||
|
"~@privileged" # Still block privileged operations
|
||||||
|
"fchown" # Re-allow fchown for FileBot file operations
|
||||||
|
"fchown32" # 32-bit compatibility
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -5,6 +5,9 @@
|
|||||||
gitea-runner = 10003;
|
gitea-runner = 10003;
|
||||||
podman = 10004;
|
podman = 10004;
|
||||||
foundry-vtt = 10005;
|
foundry-vtt = 10005;
|
||||||
|
pyload = 10006;
|
||||||
|
jellyfin = 10007;
|
||||||
|
filebot = 10008;
|
||||||
};
|
};
|
||||||
gids = {
|
gids = {
|
||||||
unbound = 10001;
|
unbound = 10001;
|
||||||
@@ -12,5 +15,8 @@
|
|||||||
gitea-runner = 10003;
|
gitea-runner = 10003;
|
||||||
podman = 10004;
|
podman = 10004;
|
||||||
foundry-vtt = 10005;
|
foundry-vtt = 10005;
|
||||||
|
pyload = 10006;
|
||||||
|
jellyfin = 10007;
|
||||||
|
filebot = 10008;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,21 +19,19 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
# n8n service configuration
|
# n8n service configuration
|
||||||
services.n8n = {
|
services.n8n.enable = true;
|
||||||
enable = true;
|
|
||||||
settings = {
|
|
||||||
database.type = "postgresdb";
|
|
||||||
database.postgresdb.host = "/run/postgresql";
|
|
||||||
database.postgresdb.database = "n8n";
|
|
||||||
database.postgresdb.user = "n8n";
|
|
||||||
executions.pruneData = true;
|
|
||||||
executions.pruneDataMaxAge = 168; # 7 days
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# Configure git integration via environment variables
|
# Configure n8n via environment variables
|
||||||
systemd.services.n8n = {
|
systemd.services.n8n = {
|
||||||
environment = lib.mkForce {
|
environment = lib.mkForce {
|
||||||
|
# Database configuration (migrated from services.n8n.settings)
|
||||||
|
DB_TYPE = "postgresdb";
|
||||||
|
DB_POSTGRESDB_HOST = "/run/postgresql";
|
||||||
|
DB_POSTGRESDB_DATABASE = "n8n";
|
||||||
|
DB_POSTGRESDB_USER = "n8n";
|
||||||
|
EXECUTIONS_DATA_PRUNE = "true";
|
||||||
|
EXECUTIONS_DATA_MAX_AGE = "168"; # 7 days
|
||||||
|
# Other settings
|
||||||
N8N_ENCRYPTION_KEY = ""; # Will be set via environmentFile
|
N8N_ENCRYPTION_KEY = ""; # Will be set via environmentFile
|
||||||
N8N_VERSION_NOTIFICATIONS_ENABLED = "false";
|
N8N_VERSION_NOTIFICATIONS_ENABLED = "false";
|
||||||
N8N_DIAGNOSTICS_ENABLED = "false";
|
N8N_DIAGNOSTICS_ENABLED = "false";
|
||||||
|
|||||||
@@ -33,4 +33,53 @@
|
|||||||
proxyPass = "http://${config.networkPrefix}.97.10";
|
proxyPass = "http://${config.networkPrefix}.97.10";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
services.nginx.virtualHosts."dl.cloonar.com" = {
|
||||||
|
forceSSL = true;
|
||||||
|
enableACME = true;
|
||||||
|
acmeRoot = null;
|
||||||
|
|
||||||
|
# Restrict to internal LAN only
|
||||||
|
extraConfig = ''
|
||||||
|
allow ${config.networkPrefix}.96.0/24;
|
||||||
|
allow ${config.networkPrefix}.97.0/24;
|
||||||
|
allow ${config.networkPrefix}.98.0/24;
|
||||||
|
deny all;
|
||||||
|
'';
|
||||||
|
|
||||||
|
locations."/" = {
|
||||||
|
proxyPass = "http://${config.networkPrefix}.97.11:8000";
|
||||||
|
proxyWebsockets = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
services.nginx.virtualHosts."jellyfin.cloonar.com" = {
|
||||||
|
forceSSL = true;
|
||||||
|
enableACME = true;
|
||||||
|
acmeRoot = null;
|
||||||
|
|
||||||
|
# Restrict to internal LAN only
|
||||||
|
extraConfig = ''
|
||||||
|
allow ${config.networkPrefix}.96.0/24;
|
||||||
|
allow ${config.networkPrefix}.97.0/24;
|
||||||
|
allow ${config.networkPrefix}.98.0/24;
|
||||||
|
allow ${config.networkPrefix}.99.0/24;
|
||||||
|
deny all;
|
||||||
|
'';
|
||||||
|
|
||||||
|
locations."/" = {
|
||||||
|
proxyPass = "http://${config.networkPrefix}.97.11:8096";
|
||||||
|
proxyWebsockets = true;
|
||||||
|
|
||||||
|
extraConfig = ''
|
||||||
|
# Jellyfin-specific headers for proper streaming
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-Host $http_host;
|
||||||
|
|
||||||
|
# Disable buffering for better streaming performance
|
||||||
|
proxy_buffering off;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,68 +1,69 @@
|
|||||||
ai-mailer-imap-password: ENC[AES256_GCM,data:gDjqNlI7AN+z7MsQONxn7/ZSxw==,iv:SHOAtBe3iEYzg3+3wzMgej8mWFDD9EzdT5ie0wDDx+o=,tag:aoHbnJoGABFx00+eD7qYsQ==,type:str]
|
ai-mailer-imap-password: ENC[AES256_GCM,data:q9eJ9Tom+X6KxQJhWQTUB61k5A==,iv:FH+IUWi2yZBBgMiL/kNW470GEVHEG3fImf0bel9og/c=,tag:RSlcpXwmNyLB8Oc/K2Epvw==,type:str]
|
||||||
ai-mailer-openrouter-key: ENC[AES256_GCM,data:USt+zJkS+8fF5PTzwT9R1z9oeBYqOrP5pGtmtrnZbJV2gXsCzyTjYXKqI18GrFAAokJdwnOHZ9tNO6w8hGTIVt8gNX4kp7BDBg==,iv:FMZPORB9UzsWHFQqP120z6byVNbjKMSGBRDaL+AvyCE=,tag:x2sNElJK7tgM7FXOVRykiA==,type:str]
|
ai-mailer-openrouter-key: ENC[AES256_GCM,data:EvI0BuCBA1uYOderjAVcB8RSk7un7tiKmgsSe70KQcmfu3CxmQerP/2kQsRTJ0/6pWf4QqNpaes691O3nf+UG1qgG2CUcIaYRQ==,iv:OYEy0xMs+vkGa0qMtY4UP/iol5JPQ0eFVyPpPXLAmUE=,tag:5PeXZcI8TRSUOyuKs0STWg==,type:str]
|
||||||
borg-passphrase: ENC[AES256_GCM,data:Dn5+JCnIHIr7jYA2nVqemWRXZqUpPgk9h4pAP4frdaVAZL1sod4GatXI1VHnH/idOR/FgBEPdiCgqbQ650EsGxLfCuU=,iv:dZUlH0FnNh5GBUXEdFGA0K2xwbR6mgajumtzm2jWxpg=,tag:oJ1WVnC6BZzmx5coLBVp+w==,type:str]
|
borg-passphrase: ENC[AES256_GCM,data:GGmf09zX5wQ8Fih1EyP1p3up9ckFjVKsktU6ZFwvuZnG/O2OyOod66qXc/IXx8GQordubZ3TgisOeMLNnSowp2qylh8=,iv:fFgw/x8Ww9cInkNlPIoE3stUfISbfk46PBj7aimuXNA=,tag:hnNYrkLgt1qJc+gN5s9L2Q==,type:str]
|
||||||
borg-ssh-key: ENC[AES256_GCM,data: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,iv:2aDMP3t558cKzNSLb7j0slhABQRjzPtrq8QwvKRwKSY=,tag:mF3LVq7/RgabYyXfhe0nog==,type:str]
|
borg-ssh-key: ENC[AES256_GCM,data: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,iv:f7rBK8aNqX8dGyzjoeRX6yl20XsnLU8b4gitaw9+O+0=,tag:WvfUw1JgFBAtS3vsVIvM6Q==,type:str]
|
||||||
ddclient: ENC[AES256_GCM,data:YpHkMyuGBR9mANOslg+Nyy9ZJeNsS/tv8sXZ3RwhONo=,iv:L18QQIn1te6rjrel8dS+89IomuvTl8AVlfjzf8aU1UI=,tag:V/lj0focF9F/2Njlf0krBw==,type:str]
|
ddclient: ENC[AES256_GCM,data:dS6TVVNb6R7EE1JVMDfSnRYCZyHHqEPvwaYpkTSj+VA=,iv:9uMo+9X7dFdVW4wuSgrqIAaQelXuA4cek2oif0GRHow=,tag:ncQq4UeUzWtjPNxEUOlqNA==,type:str]
|
||||||
gitea-mailer-password: ENC[AES256_GCM,data:kZyWqGYgSgHUuHDHwip0YoumgvrfwLrPMhKBJdkTXo7Ej71YAEfYwTA2l2dVPytypxMgO+6Yvb4vsG9P6k9tf1y3,iv:d2JGAjTfQx7r2QX4WQ6mWTdb3pg0RYGa7s57ZQc6DpM=,tag:iQp6AVPXtQYKeKAomBhGfw==,type:str]
|
filebot-license: ENC[AES256_GCM,data:jY7E29fFJ/h9NIgIjuX++WBhnLk6Mm4iRfMh4P0pUDdqH231gXDsTZ6pJ1rpFXdEHSuNN4LfznDTKgZ2azKid4WprDUzGkN0uJD6CfSR8gTIx5Rq0M8vkRah51LC36bop4hTMzECYQd1YA47hOBV/gfyg3RIw95coWamV9FebnQjIBgWYxE+wTvO5iRvWpiCHd6VZQfkiiR0KF1DrkYkuxlX0piGEKmIgyYCiKMFZ4nrrIe58x5lEQA9uPVjE7vmq3c3ge6tJzjVVaaNocbJhxhA18GLMqTSHfnBsOLRlA8qSQ3xX/VRzKQmaYQHIM77Ylb9ZQsvFt6EDlzQMl5NqT7OJZUW/0jwNaEXHURjeTOC3Hr1HugiDGm+uLXEraaJ6Na2AbFDn28o+3J22p9xNg6vWL0FElzKuaz5TFDzdZLZsD9HOPQm95/ZM8JymDjN4qxkkd2o9rEKY6to1MVDarj+lDxIHhf4pL23YhZsn3esNlEbFswzHQiH7nMsu9Jg6a0rPu7IYylDnH/soBjxSKmf2dhH1LLsDm8It9K/7NnXwmvncFXaqNBqm/e7JzCDBCCyVUf/BXbBc3xwLwZf5MiirZ/iYiYnRtUssveh7BV7ICigRj5Ewtr+n97+IGI+FyonkvOgM0bn8nHf79ZzJCKMuntcw3FlGd1nIkmcehkC79PlKIS95oV/wypl1OmU0CVel+D8hsMuONmF9NPHgFk/ztp4GF+XXRO4ExNotX1XrUlvLOccoHDsl1TedUOISzgAK71edxfI8y110shIe9OfsCEUAbmWMmjGVWH2fKu/IrYYQTry6pYFOjG2bIEUXMaiIP0lALbq/QNgleqMNPY8wGzFbP+/jaYzTbw9KXH4bwYQCl1hSI8THfV7lLE=,iv:4ik/aQqi/hIqH8ix3ejgUiXGY7ycw0ymdVrV+CEQe1o=,tag:7ymc4QZEezJVPlYTlU4H/g==,type:str]
|
||||||
gitea-runner: ENC[AES256_GCM,data:TH05utTgZDpv9AjPnN9AUA0DUXWQvS6D9vg/6f9tfRiqqKIFBL+V98VOSTjiPw5x92aru0v2Y+XBYhbw8alYnOuk/I5c954qotXNxCKwLW1i74eA4VOf6t/gY7PjqaTC17aG7UIxTtZI6rJYLm7vBonQkaJxxE5umu069Nf67cyeVU/RtvIzFKLKOxOC8qej70j9vj0/QX+QMhsX8D948ia4oH2a1jVENZO119rIlvI4byh+494X5MTVPatMzR4SVlgHLBC9joXP6yxOOrZa4AI=,iv:ooNmpw84JDuPmezQBbhRx3cDI3+3ds/5i746WIwtOU4=,tag:ksnnPCj1/0Mhdv5JnjjElg==,type:str]
|
gitea-mailer-password: ENC[AES256_GCM,data:lEv5euTCHG6pyNqrVtKK7oE8wLvk+q8ABXOzFSizQ2TVFi35lyGPzOTel/dCCC0Je5GAHE1KQQ4Y4/iHghZgb5Ft,iv:gt/mCzLbDrHFNqW+Lkd2dy9nRIBKO+rqsVuXM45zJ8k=,tag:gCxTSzY7GZ+jQP9SCsdUtw==,type:str]
|
||||||
gitea-runner-token: ENC[AES256_GCM,data:0UcHRYJLNEbtm15SEOsuHbZnVvNBdE4cP0LU3A+e64c+Y6KgIrrDbmz5n+YTuK0=,iv:eJjMSYld586EdNf98PGl/t2rw28mw4LIUWCZODorAGw=,tag:+O3UVRyt+1vUMZXimGjXSA==,type:str]
|
gitea-runner: ENC[AES256_GCM,data:HLjSETmu2C2ROf6kqUuIzQl/t4Fe5EOVkMqdTeLNnb6AJ95l6M/WUk//dnPMrWVvEq7rV07awUiyvyJcYQzMgPNddCrfcn2Xr0dYK4XFenz/sdhknVex9uS/RhK8fOqdYJ6djpynikMKddZMQr9AOVfpF5mea//87+Az9rOrlzLdgNtf5HyBEAFKaOFbkZboAsP+jlxyyYurGHPr8LxxikewDVxnpB+XzMc6RAnesrZPOTDQlkMiPZ2t2o0klhD/4VomgiHEklULxCCmIAHaqDo=,iv:1FwTespqVTnKFbyf9Unbbod08D36MKsVbDhIBNGBkHg=,tag:rgVvyxUCwzYB2CqWm2fwgg==,type:str]
|
||||||
home-assistant-ldap: ENC[AES256_GCM,data:hdut/WfbLVFGYZqx2rmOGNndt+K99LkyIaRVPLyqUesCG7gaRWrWH1XqU8Cnf7OLHP82uHL4JEMrU8T3mlPC5g==,iv:pNmFTlMKIzoDvodYN0WC0ZZhIEgNsdx6Am4SZdFz1tc=,tag:DLwIb7T2MTTjMiyMuqX6Nw==,type:str]
|
gitea-runner-token: ENC[AES256_GCM,data:pzJp7j1Ktz+27oU+qtESk7D32w7+BSEUkPSX4xuFml0i10z12Gzu0QHXL9s3734=,iv:U77b5515H1URfz5BCdzuY03zVkhSRsL9d+HdHUJFx9U=,tag:QvooaT4TS/X5R5KGdaVpVQ==,type:str]
|
||||||
home-assistant-secrets.yaml: ENC[AES256_GCM,data:+l1eSLm3nmujtaxt4h/1tbwxS0HkmcZPpxiGMxdKBFksZYc87EEXCnqHNxML2vGW6QDF/ygL73z913BczWchwIySBnXpvNAWdSZ/Ba35MwKVc9xc8Kt8CwpqK0o+rZDrC+B6pkcd0pwo5Byrho+kxwobUaCBn3R5nyDtnB5H+xg/ZPElGLdB0Pv+bceTIUjEXTgqx4MtuNVIsbYZW73optovcTHqaYUDWA+rf3lKGdB2iBRb1Wmdd9jvtB2Bl0PcRHKPlorxfW0ENw==,iv:NMk979DIMl7xNTe6CQyWCRjAe/Ru3qTBdGf4Tv6hGo4=,tag:4LMh63y0hXbDO0k+fHNYgA==,type:str]
|
home-assistant-ldap: ENC[AES256_GCM,data:4kofJzPbiLXILxjuAZWiTb9hu2Gver/IHBCXDnrmrKuCSII6SJ9FrSi67nl7SHdoA6xe22GSMfmPrKzy5sGiow==,iv:F8mIHhWHpaI6kzRV9du6uW/Fj07PbEIU1goSDmeSD5E=,tag:6NIC6sN8OclinribZhrLLw==,type:str]
|
||||||
piped-db-password: ENC[AES256_GCM,data:OYxFR6WOfhm0wy9ooyXkDaNkSIdkW7HN5HOc01Ug7vmp2XhJIF+y8zJzhas=,iv:+8L82xp492lkFwHV5Cc7EAZFBMIlYoHcmuvHfSBwOXA=,tag:ZpJN4Q86dh6gx9A2tRurdQ==,type:str]
|
home-assistant-secrets.yaml: ENC[AES256_GCM,data:rns9heAmVMxB6WWlGMXvF/ianFUnja3FObiLTEKJmodePNsJ8ah3OhuCAX5jON+/7NZ+3JN/hIJjXsORC5WYhr01DvO9meykf0aMpbmAnYI+cmPEPvcunF4NNInl96rpcI519nMiHDSh5J7pD74CxHZcXSV4c9ZR5UBymchrwmHyZMF6dVrD9Jbr9yph1r7iq6S5wlI2ZImWRjaoGDZ1x+ZU8XnsUmYcP4pa1Yt8JBxSnyUw5gxgBkVCh4eSZBsUCt0cd9P0i7qWVg==,iv:YXQsawXZsQb9ZUt1/lkpfTa4tfKIQrLkkyShFtBRaIQ=,tag:/vSnipGiMntdMqHLePSEQw==,type:str]
|
||||||
pushover-api-token: ENC[AES256_GCM,data:jtf5QSHPteh8WuH6kd364ft66RfOe5itZxKxX9ek,iv:B0r7d59BMt1/OZ1dpADojxCS3R5y+g9begBqY+PQE+k=,tag:RbtViJdaPyN9AYDt8F4Huw==,type:str]
|
piped-db-password: ENC[AES256_GCM,data:5atQccdHYDEf638bpiON9VO14jqNDtzZ8nnXVW0/cqtWkZJc8RYn9N7QhAw=,iv:Gwyf1R+mpmX+TFuoYLPHjXwSDwzJhSEpnj5ZsJgmrtk=,tag:zm4zNkzbqbCyTN6o3lQQfg==,type:str]
|
||||||
pushover-user-key: ENC[AES256_GCM,data:AKbD7zPcus1/pTvc1oLRYqOgRWUuMRTqgS1vK8u/,iv:DF00iWC1wGSIYnyC3aw1HUQ9O0kO7rxwiOJ/XGT93WY=,tag:bRCwxx0GmbBpRZAXm35SDg==,type:str]
|
pushover-api-token: ENC[AES256_GCM,data:cMBDdySEBQ7vS7FUC2DsCcSvEMpapWvMFmnuCsY6,iv:SVDrrDm2pcAfwUVAC5j47YwF4s/FWNARlZdIZ1Wgwgw=,tag:w7ZeNMPXWc9j+zVaSxq1cQ==,type:str]
|
||||||
wrwks_vpn_key: ENC[AES256_GCM,data:OjZA6MeIdYP7adTf/F8kp+5igdj2dpK/ze2n1BDzaRz5RyXhGuQgdp3CLec7JITwTORmJ2fgNjBLO6K66aFG3A==,iv:vU4jBV3uaG3+ZgJhJgJDWk/j7Y4azDT1TUyah0AszWQ=,tag:ZD11XKdLNB771+mnDBp58A==,type:str]
|
pushover-user-key: ENC[AES256_GCM,data:fjoA2YQxmeWEbSKWWE5iyi+CUh1vtW9usVCm5EGk,iv:p4YwYIhpgn/bY9t61//CDrDmZrsj9B/naZit62lCpwo=,tag:pqEw3pDlX7i87tE0Nsy0/Q==,type:str]
|
||||||
wg_cloonar_key: ENC[AES256_GCM,data:c65a3mo4ACHzZHvUFEEYjR0op3i8LycJh/RTTeBhNDRRN8A6jGJgyPMfA2c=,iv:vADTTX5wTP/F3x/v4HqjZkXGlVDMnU4XRLmONdp9/IY=,tag:mRgweOD1hS1GZxXt+HTK7g==,type:str]
|
wrwks_vpn_key: ENC[AES256_GCM,data:VEHqnr/bDtmyLzs0wnmZ0jCWS0BGJWu6Wjq0ZHJuEz8PH3j/E54S9NUe6WRIo+BJCsh1PlRqw/PD9xSqlW5uPg==,iv:OMP0s8Lc2CmFgwRuwB3UWJVuQFqvpy+BiyhnIKbVIb8=,tag:x1LvSf6i8khd8jKgv/284g==,type:str]
|
||||||
wg_epicenter_works_key: ENC[AES256_GCM,data:fDBZK1JK19CfCgihRP3iSlMnJc2SoGFtgnfhHgWI4Mpze9+u+Yn/PYjJl3Y=,iv:a+2HrB4rgWbTseohJgmpWIgcOfLqnOcIQcPFhawQrXo=,tag:7TYOVSEUGznnEjYyd6RbQQ==,type:str]
|
wg_cloonar_key: ENC[AES256_GCM,data:1OfHD8yX+pgCXqqxn7cddnnCA9HBjGra4eht7uLxdcbdG9vDvxUoE1x6aWg=,iv:/NBEbmA3wP/zwrqCeBKDzaoSMqz3f4ZeMlWbu81R5Pg=,tag:Apt8x/j0qiJAKR4UEVSkrA==,type:str]
|
||||||
wg_epicenter_works_psk: ENC[AES256_GCM,data:kaBPIzjINEWQ+9iHJO1Gm9WqLwfR4pwzEgfXcEqZd1a8G6AMW1CxcBMsPHU=,iv:oex6RJjMmd/6RQ6I0NdpVP3b8u5jyLkwQfFsVRZoty4=,tag:dIocYsfpddTfQfyF3KjHdg==,type:str]
|
wg_epicenter_works_key: ENC[AES256_GCM,data:CTZkVGEVRlCdt6W0BGPmX0SZbuBBH5IIlUsi44SGXi7gdmrZNwv2zDv6zjA=,iv:4ZDDKqR6pBq8cjX763tBxOvWFaS2IiGaBxJu6L2JYig=,tag:H8p63BvXSx1SKPFw5gnptw==,type:str]
|
||||||
wg_ghetto_at_key: ENC[AES256_GCM,data:DBoVoKRjA5Q/uA5pG+sky0WTr+MiiE1YuFD7ucY+uHydufCP8UdLsl6TvN8=,iv:mWMYw7naZ9u1FS8fNswnXucrDxkgTFLnmfAvQUg2cQQ=,tag:jQnBhBRGy2yD2yr7ZYRjvA==,type:str]
|
wg_epicenter_works_psk: ENC[AES256_GCM,data:K0SDlDWfUk9vIGP5U1j8p6TJ9GsydJTuKPb4kMgde1CILOia0S9/+4AkMWY=,iv:ITwLoWZXR6NxRFF3eBvOogiWHLmXnf7S1e2FW0ofr/M=,tag:2OVi3OBFYT0nlCx8gf2AdA==,type:str]
|
||||||
matrix-shared-secret: ENC[AES256_GCM,data:nV6HLSdXi0BjBfeIReVNGk/3D2bKPC19+Rxfq68aSNAXMGfNlFj1++bQbglTC3ometwAXprkzrIpX1Yz0Q==,iv:n9cXQTpY7GDyHcoVF9qdY5COOkw0CbKErRgiGEZFUUU=,tag:laXNDFDe6yCy8KBGrcVw6g==,type:str]
|
wg_ghetto_at_key: ENC[AES256_GCM,data:+bonpVjV1hxwaqtR7ywshmoDxCnFPD11q0OiNLzxUJIaYrDeS1srpyo6rlE=,iv:Djn16kuXTWqJZy/AT77GpH8RcNtUMZ6zcIdKIMHv+PM=,tag:LP2JCaPKpzeOKvBc2bMr4w==,type:str]
|
||||||
phpldapadmin: ENC[AES256_GCM,data:l6pT7JaHbPiqMzVceuuko5kxgjTWaODKG99YMVBT604epjF2k+hg97EQYZOJEJ/xoeEFq+UisTG37M79HmVVmBjaJcPW0xDgwoGjx1e4BdPV0aSnYxcuX+W/bTmKaakhU1Lsz2W4QSSCpWGIQkUdCm9li1C1MHFdC5xgfdK17ejvI6u9Xi254Id1TrOMe1Ea1ZYf/eAJCQipP2srRY09q5eXM271ZxGY7axqUiGBVkYI87daoJovAKL/Un3qQh6X6f7A3vg0isNLs2bCCWBd0j9bIQXyUkoC2Jg1Ymv+Du02U80TVpxwXlfSJv3I1KaI9LRnFmLEpd7mWZlZpFdKCf++RY7xMHk3,iv:jJ/fq6+MIyOlskGU3mLLi29BDZ90mZ2RiC3a59IA3z4=,tag:wOYWjSHYM8YfvNJDb7qNuw==,type:str]
|
matrix-shared-secret: ENC[AES256_GCM,data:nVSHwPa8xYUaDCxL+5neFtzc11DDNzJtoDCSHYXZ+bZXVAAbp6/Pjx6UkTdAA8B2GOM09nFAsBuLnQfJ3w==,iv:WU3hnRlWVwx7Qin3ejw7V4VhAmYLf6oXzVk6xQgZPgA=,tag:O2hJ2q8XDxYF+rHPNgATgA==,type:str]
|
||||||
palworld: ENC[AES256_GCM,data:R8lcg+qhrSqpmopN3/GJ6aE2nej966Y0DsS3RNHP3iPJSP9UOiy1mgdHqUObSE1s76gMrmmKPp1FyJaWx5eXL2JrtBllwSQad6GHxuyqnp3LavyOZzR+dgik/2qcOrzHzL/yXcEmwCttA+U3Be+so3/y2Y21x0uEOlQgnOfcGYkk6Uptv8t19fWMMJxFMGbaWAlLFbCIvqyjhfcXwZc7CR6g4pcVNsNfdQFKlruzoHaEQCzq5bTu4oqlWFNqk25ajRJk/rNc4IAFlSiCV0Uw7KXLkUHFodKA8nvWwN5eaZ3FYabMFh4lo/JqA3bYXzQiuxaTPuQOYWRJ3sWGn5NooaNL2FaVu4/FTxrcVK/X1OzG+SILxfyIK5aD3pS28tvKxRpXpF3e4jChFToxfYTzsiwP5WwmBowKNedX+yrTeUaLOarKUEdeD4LbBekZdOpXRFMGlwhJ4vkxARHMxz5ls7QMen1kTOUhrQEO/ny25rkN7X4+UDiIaqPibTi1oHdsLrUetIhwkUw+3WGhTtSayE2sEA/EgORJKqYEiyNAFJ3rF2F4nYnMK5apSACfnYSomTmiAiuwF5IhybvI37c4C1g3OdmTznsX2mpDgZwH68fd083TQR3oLeiqRxct0XEm7JCG2zBfCvbV9bU1zhaGhYuHg1f+V5a3hetvvd4aZ92qNMjpNz36/QGvQHBE5fZOVosgwHPeBYqrSTjwxA==,iv:b+qj+gOd5tGNNTrZ/ySph7ynlcBNbG7+wNt3DKhFtLY=,tag:sVMyuB8dk6OMamy5lel4HA==,type:str]
|
phpldapadmin: ENC[AES256_GCM,data:94jCcgGJ89Er5ENLqhFZ1qY44Qp709SuUhBUuED6v/a7mPPjrJGDmi0Gm3r1Hb4CDPGkWf+x4NStY7LSQ2bHEzjyMPMS23wvSLTmC5b2TVca1UI8vZRTD1R7OvdWo8d1oNweSpYEnAXGv3USYF0NZo8DrPLM5G8lG5Tk/rKS/mxU5ZRhPyA60rbmIiy3Mk4yNcs1tvTEckxU/zMVl7zUPAsOOlmYGuwJrHmmh9p7YIWHGIgZNiLs3U0BvSKzN7WktmlwqjfWpeLn4dusqgov4SSQ2otAkxLHIH8mGhyotd1wgXJDZc6tilMe+WPHQDz9db7FT0VdeKggQ94FD+8rP0OsIjR4AdjZ,iv:C8X10wtA9jPgS41pxasaZJTO/XFcRymOyTDZCWJlhmg=,tag:xkMJsGubny+Di+GucAqypQ==,type:str]
|
||||||
ark: ENC[AES256_GCM,data:RABtQqMaRTpu0rn6H3O33GwoMcQpwoNj9kN3tmSYA+TtJlW6vxgqvwCB3/nNRwZ5E7szHynDP6/uRZGQkhw/NZDnA1WU9CAhncr8ad4Al+pmMqBs+EN3gTQPQUZ4JxJZKuVMr14i+5ct3dj09tddQQFbfLVFsEg8WhGPIKxdacvXFfV54bNUbTa3gVvUvY7qucyXJJHtuBRbaISmcQ5lsH7tREhx9jfJZVt87wb1VpNpMTRNAvxhrE7V/SM/DGvwj3XBfVypAeq5oXry,iv:7TfB3UW9ey8BsdPJQvkHoXQkOw8fA7hgeiYG89uK4Fc=,tag:Td/W1/2IdTCPwo7A/vZnaA==,type:str]
|
palworld: ENC[AES256_GCM,data:iR9nceVotLKrFHnPIVskCYVLev9OzGLLlmfCGQq5hqB1HveXjhjkfm/NMmqnSi9o776+Ezy7l3kkS0R+0cFJ2B9kaWGsdJtdYDwQevmf6Nq5eaBYmvu8kTnaatqZ5e/1BQzcF3to6MA061XL54YGqsAV5FpnDVLhyyzIvaR3gMvMqJ748NL7K+hbBqMFuWcSH3hKXwxtDK7SLtcgx93W5ZgXkZMMumtlH9hSSlZL4yxuQDAQUwHrEBL+rdphA0m27dyS87DA3Av5ZL1MZ+Vlm4uAHM68T+rtVYXTakNImDTc0WrhIP8FZD/UKTAhVYAbA9oz6cbeC574vchuEY1z19SY9+2HshZZBOiPDMqdvrqyszMQCo5I9dUzAJCemQQTlYG8ekREQ0wxARnBYi3iy5PbmgDQWdM3+ff4yhMmGiHtiMQLHzrquKy8nvS9lDp9uT7njkaI0QAt3eNWa2DAQRqXQAtmuRVob5+GS2Nt6XMTWRkbeEb1phwbTqZD5mH4p2TiyMKCn6KOXsgQTxqGr35Izbe+bfptCmUeyscTKq01IZg77w/dvg3AX4iHAcMNgJ9LIHDLibGHQzu9fGN6alpeyy788GDwRY4glYyKxPhCasKkBSj/uhcDAtdg+c63vDTBhqRjNr6+v1NeRW6lVBzrgq+f9QvO5RVKrvdsVHnTA3CMGQzUPAaluNZMpzV5KqxqIrpAAPXnN0ktig==,iv:kkcm/alLHwC84IKK//OJpa36ec9ddOARTIM+KJlOHHs=,tag:jV1DjfNzRgNaCGgJTKIy5g==,type:str]
|
||||||
firefox-sync: ENC[AES256_GCM,data:d9pagfRy2H3gl2NlCt2+LT6/lSXk+A2EjIx2Jv7IHafYGu4Euhfay7Ku1wCdnkM3x95b7x45uLR3X7r/TDnl+kVVc7xSp7wUVJAo1EnUadYOVf61hIUf/+8odRUNfdrozOONPSFKT2va5jggx9r7ahWFf1xAyEHRtLBNkrHxNMYlwjo8x7q5Z00oyILH/6g=,iv:ZXCQ2loWUJXtXwMwxHvy6sOPWi0yLr5l8gbLxFOtmsM=,tag:iPKbePsZkCu76yoJn0218g==,type:str]
|
ark: ENC[AES256_GCM,data:TRTwxqkeUGbtgrWuj1YEFr73+nxCXmt/fR5vVnYR+k4FpNBB2FoY/gXl0kqeFKPDcajwn8nYBs8YE9vmYtAX/Qs4g5OyU9qC/pkmSV7/gbGfqLLqcbIlbWrZzeM8gRW0fp6h1TMPsGO8/iYdF4bmInfuZW+fKr0i7ZRgrtOpPiRCOI/ztPGkFaduuwGIy+yVoS64b9r7ZLRnOZT7ghVv80GKorJuuOQIipNAJMzEqtSA2IqaxWeb13v8wdQoKuMNcD6dCYVJnvgwf4R+,iv:+F9+yJUZBzPSSIt4uLHxjjXAjzRojLxKAyrd8grMXkk=,tag:VrIr4FFbIGTq9RBJMz8/Ig==,type:str]
|
||||||
knot-tsig-key: ENC[AES256_GCM,data:1WH0uQ399Mgm9k5dvVzs4DvB8oF6RLg87vQ8EnxZ/FGK4kSWxfJ9fi98KOo=,iv:KsIWp2qxQ3gb/wx0DOfK1X5o1Joy1m1PJ3EfCzVXe7Y=,tag:5suSs5FalS5yKRcHC+uvAw==,type:str]
|
firefox-sync: ENC[AES256_GCM,data:guNgEVi9n8uJuLkkX2Z3tMY/NVqzQ2tdIutZAqleah9qBri0/3dzVHF2xvztLeAgm/59tN7TtAlAH2SMK6gcfAZDasAWOJ/rGEASxLi6VRjqCe25glDMp2YrA0/mcqZVYMCg+QZ5OPA56b55WDqPHPoBJkPDuTm9axwm6AOxdNi5BkDzMw12fVBxlJL/Rm8=,iv:yD+MkZK5vvZ85vYGd9X2Dv6KkSvMUsMGLrwlJ1pRqlk=,tag:YA379QupHh7aJZKcQxB7bA==,type:str]
|
||||||
mopidy-spotify: ENC[AES256_GCM,data:bhNuJ06YbL07eM/FgAWgxbAqSEI736F5PCM8zBdSisffDk0eU9pVAJMFd48626iS9QvgNsnm1ljpCjGsQWWBqrbv2LeFgB/nGUxOiKOHURBa0aJU2pwLMYbv9WKtytdlYlQ4bBfZ5jSTkhgM1kFP2n6qGZPu9CKK,iv:F9TL2l2mmALwhKyShw1ju4YOfHXav/WwqAsEXFEVxac=,tag:FH5h1xoFjQngaZGy8g/TyQ==,type:str]
|
knot-tsig-key: ENC[AES256_GCM,data:CBFaRKPr+HRVM01fA9/OLWeD1O33axQKEKJuqDRfcGmuDeP3oXf+ccEJhQE=,iv:2O5y24YenpiMc9txPx8kz8x0aO37LpLjIcwlNywPEak=,tag:J4bVZ7RNSR9fiOBQ2HKpnQ==,type:str]
|
||||||
lms-spotify: ENC[AES256_GCM,data:isjXS3VLWC9qF0ssgUXKOFL62WZBZSuzVI53lxo3d6bmsMdYPzXMtgIYh0B4FKIuXmhYH9ZWAXLZCKnPLGcpQJp3ooR03xHxSa4WZ/TcYJkQtQvgYBCHvK+PT5W0W/DHnhviKlzEsDg/cUtmeJpD4932q8y6k7hK+0M0N+2zDuyApCLGgxpA+F2PwNaG4fifWSCADHIMZdPb3+qT3Z2DmIm1tzBq2AJcHc4TcwzT8sbvwUbngxxqaFFHuTDA2R9gu+K7FUHRXg==,iv:bMpkLKDK+uc0YgFZVbJfUvvJ+MvtHbHOeemxp7fZwf0=,tag:aelZoqjfMFTE8cvEywm2YA==,type:str]
|
mopidy-spotify: ENC[AES256_GCM,data:irBeIh2FieNkdf6Hls/Oj+qYxj1U7R7/Ffq6dx+JCS0PdOiFWIHXtccY+PXPKP7RhhaQOgZtIcgPyqTiML52P0c8AwN6UHMl7kgUcKnk60AI0IUZNWorCBZluHhEpf2e2OISlFzDGjSHk+zAzh2eDS1lJ9lCRYEC,iv:r6aZmlVHdRsA9DxkelcIVVpwwm32jaOgP429h61NL/U=,tag:FvPIr0HX/V7+G9kal4nO8w==,type:str]
|
||||||
|
lms-spotify: ENC[AES256_GCM,data:E53aUSNxE30SSrG6Y6SWKVzmsv0lu8aZvjk1RBgSj3q4m65dPLwGM9HcagN3BPoVTc0tKJaccrjoL2k5FOMnwcTXIz3qgiZGbnB6hVCoOhMrrkoFRN2JzSIA5WxKOT8VuMoC4/a6WaWbY8SWAdhgRQb9uq1hUxdkMCoNRLNJnPqR/0w07lCDVHvkj8XuBV4rGl93VVT3rCzjVTL+Vigv38WZ2il2aANkCz3joNeN8Uod3K/HA5uXLw3cLFmD7eI7LBDSTHpMEg==,iv:iRKrij3TRaufB5BXy7Xhiu3asClZ6hpkbMV14aod7jk=,tag:hpUwP/OHygqfgI6j6q2sKQ==,type:str]
|
||||||
sops:
|
sops:
|
||||||
age:
|
age:
|
||||||
- recipient: age14grjcxaq4h55yfnjxvnqhtswxhj9sfdcvyas4lwvpa8py27pjy2sv3g6v7
|
- recipient: age14grjcxaq4h55yfnjxvnqhtswxhj9sfdcvyas4lwvpa8py27pjy2sv3g6v7
|
||||||
enc: |
|
enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQWFJ6V0NwY2tWV3JmMitQ
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBrTDFvM2l3Tm5lU0paWXpF
|
||||||
a1NVMUtIMk9HeGVoTklXaWxFbVE0S3JVN0NzCkRPOUE5ZjhhQXFMYjJrajlmeHAv
|
cjVBSFhENW5mNG9DSFM1NXh3UHdaKzlKMGlZCnRmNFBFVWY4N0FqLzF1bUMyUDdL
|
||||||
NnpPRFlGYzYvSEl0U0EyNWc3eXYyWkUKLS0tIDRWOXFhaTVtbmx3ZjhSdXgxWkI3
|
U091VENiVFhYeEJ5K0xodXlHVkhHKzgKLS0tIGxta3A2TjJiMUtiR2RzcU02Rys5
|
||||||
M04zU2ZZcUNIOGN1cFBCdy9LUC9lRFkK4opoeV03v2v/Zspie9KL3+JG2McNqA07
|
U1c0SjRKK2UwbTVIQUMrT1pOOVFmOVkKY3UyGNIPZJLE8GG124y0pLgqGub9SMCq
|
||||||
jlN9honxmFK+aWQBwV8o0OetMcx4rwVYBquaq1QaYgMniY/0Lbc/IQ==
|
plK5H+kASOB1X6pK+3PBFuDYT1AbsRxXvWgAEMvVI7eBcxQlSrrB4Q==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
- recipient: age1exny8unxynaw03yu8ppahu5z28uermghr8ag34e7kdqnaduq9stsyettzz
|
- recipient: age1exny8unxynaw03yu8ppahu5z28uermghr8ag34e7kdqnaduq9stsyettzz
|
||||||
enc: |
|
enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGUW1pVGpZZ3d4YnpMYzJv
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBVaXBqMGl1UytNL3BkZEhQ
|
||||||
TmdEbnBPQkhoSDZNQjhUZGpUSkVWc1EvUno4CkxCR1ZPSnhHRkFpR0M0UkZpQ0dp
|
S3RFL3lZRVZKTGVRTGFMNlFlWFRCNDNvRTM4CnpWZWovSDZaclQvN2Vwa0dWZGgz
|
||||||
dU5qekhPa3FwYW1KcWNVcGtCdmhybXcKLS0tIGtwRlIrRDdCSUZvZzJkUFVTYWVF
|
Q1ZLM0sveXBxOVpvNHkycWJWWXdmVE0KLS0tIHl2bFk3RE03N01IdDJPWk5HT1Np
|
||||||
ODJFMUFaUldtNnZwNXVhWDdZMnRldVkKqB7wzQTBep1tHGrVfSPzUiOJ4XTX0/jG
|
Qm82Sit3Q0haaDdnbzFjendMUm04Wk0KYp09dxXjzvC4IlH6Ilip8YjTz0mFeu/0
|
||||||
CGdx7b28c0Cw74uLpr4cJ4cdbNiwB42EdxRLCWfoI/uel1o9JAjVLA==
|
5IDMYjT1BuW5YiKgIJVd+UgOd6ysZLFFwk+Us2AcV7z110xk/askqQ==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
- recipient: age1v6p8dan2t3w9h94fz4flldl32082j3s9x6zqq7u5j66keth9aphsd6pvch
|
- recipient: age1v6p8dan2t3w9h94fz4flldl32082j3s9x6zqq7u5j66keth9aphsd6pvch
|
||||||
enc: |
|
enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBaZzU2NHdoRmZSSWV6eko0
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4Wkd0YnBQRnExeVdUTGFu
|
||||||
ZVdReHhCQ3YrN0JIdEF4d2F6Y0EvZnYreFZzCjRnR2F4a3NZek1XSnZxTXBRelht
|
N3o3MnF2aTY2NlBmdDJYT01zRytWZ2w1dFg0ClAzcnJ0NFYrVWlBM2JQU1B0SEJi
|
||||||
Wi85R2V0bjJFK01LNzg0VVhYVDdNM0kKLS0tIC9wQy9EK0VzeG00R1N0NXdtSEYv
|
MGE5aVh6KzNmaEoxaHFOTW90K0VmMGsKLS0tIDNkOGZyVmMzME80TlBWMzI5UVR2
|
||||||
MkhsNVMzZTF4Z3o4ei9oMlc2c0Vsd1UKB8CerMKKPOJlScRQs9+jrR2ZjGJiFlRh
|
djB3Y2FIRDFKWlEwTnRBUnRIT3M2OXcK+SIt/7DRdQi6H1AZooJN2Pt2g1EwVTZe
|
||||||
mDl5FHHI2Dq1lbhZ2skuXSVJMEVlhNLUxgzUoyZ7tEijpbc9Xp+BZA==
|
Q14cEt0sLyVYzLJugfz2JWRHDZX6wPueYcTSEs7w3wAPVwvJWju8bg==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
- recipient: age1wq82xjyj80htz33x7agxddjfumr3wkwh3r24tasagepxw7ka893sau68df
|
- recipient: age1wq82xjyj80htz33x7agxddjfumr3wkwh3r24tasagepxw7ka893sau68df
|
||||||
enc: |
|
enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBRVWkvdzNXWW5xYXI2WnNE
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAzQXhNSFBnNUtMdkpwR0th
|
||||||
QjZnWTFlMG5EK0VRR2E0U2pScXQ5SEhrL1JjCm13WXZTVDNpeHlqdTd5L0R6ZlA5
|
M1NmOVorcUdlZTFDM3dVRHZlYWpJcDZiakVnCit6eTFOeW92SzhPYzJxR0VTem9r
|
||||||
MU83SzFZVk0yY0dnNkFDMEFDL0dNQTAKLS0tIHZ3TjZIVVArVDFlbkNZVTlxT2Rj
|
MSs4cWxRbzVBQmlWaHIwMjB5RUlJMXcKLS0tIHNSVTloOEVVVndDWkVrWmQrYXlD
|
||||||
WDg3QkxpV0wzVEVjcVE3TzJ0eTBud3MK6LdWJ7LD0c1mlf95X7G7ZAUu+hTCAZJd
|
NTd1WGFJWHVLTnFNT3hYbDdtSnMzTTAKBmJOayZLbjmBejwVzVtUSYPki+qPkYwG
|
||||||
EA8kJE+JzqsCiH7MJJmTBgr2fbzxMZ+mUV6CHysNP1ax4Mvd7uz/WQ==
|
xdO3L7n0Z8Cv/kVYZpkuG5GqOUL+nCJuYDjF0g4PaLb6WWd0W8ZGFA==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
lastmodified: "2025-11-02T12:53:43Z"
|
lastmodified: "2025-12-01T11:01:54Z"
|
||||||
mac: ENC[AES256_GCM,data:7a7iVyM5ZXNLJYFw4+8kyRYQZOg7FQ6SvhnsWebdEsWlcZjqjbGYMLROoH6lxWYp7nunaewlsLpPrRqzAFbCnMs1QKZDJ/yWccXKYrSmSQVyh48PdlNDUu5w2e4HQdxBAvWdkEyKkJZ11q6eDncb7gm4ri4M1/NrMdQKZ+saVC4=,iv:Mmm/wkLCdfNVpjkvUJHUGAHX7wOM23K3HcJZ66HKnHI=,tag:5YPnp2CVWgGC/kPyXPlvhQ==,type:str]
|
mac: ENC[AES256_GCM,data:taGX5HHZCL7Zo4taS2Jz/5WxhvpBNNKZ13ZCtS3x/P17tC1Nrk2UDcxbOZ1pPVbVvvaAHJtDb3owFvBOM4nr2Eve0M9zT4HbXh3hke7AviQ6U7CT1ru6LjY7W8lBjbQ6uCt+Ldxd1PRPPGiyKdK5GAUPKg6avFjpJbhEikh8Gww=,iv:NNs5usVJ5izYvHKnNm1IgjSt4dg0QFQ7cClJ6zh+3wM=,tag:sYYbEWIUgOWthEItdy5PFg==,type:str]
|
||||||
unencrypted_suffix: _unencrypted
|
unencrypted_suffix: _unencrypted
|
||||||
version: 3.11.0
|
version: 3.11.0
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
https://channels.nixos.org/nixos-25.05
|
https://channels.nixos.org/nixos-25.11
|
||||||
|
|||||||
@@ -240,11 +240,11 @@ in
|
|||||||
|
|
||||||
sops.secrets.dovecot-ldap-password = { };
|
sops.secrets.dovecot-ldap-password = { };
|
||||||
|
|
||||||
systemd.services.dovecot2.preStart = ''
|
systemd.services.dovecot.preStart = ''
|
||||||
sed -e "s/@ldap-password@/$(cat ${config.sops.secrets.dovecot-ldap-password.path})/" ${ldapConfig} > /run/dovecot2/ldap.conf
|
sed -e "s/@ldap-password@/$(cat ${config.sops.secrets.dovecot-ldap-password.path})/" ${ldapConfig} > /run/dovecot2/ldap.conf
|
||||||
'';
|
'';
|
||||||
|
|
||||||
systemd.services.dovecot2 = {
|
systemd.services.dovecot = {
|
||||||
wants = [ "acme-imap.${domain}.service" ];
|
wants = [ "acme-imap.${domain}.service" ];
|
||||||
after = [ "acme-imap.${domain}.service" ];
|
after = [ "acme-imap.${domain}.service" ];
|
||||||
};
|
};
|
||||||
@@ -257,7 +257,7 @@ in
|
|||||||
"imap-test.${domain}"
|
"imap-test.${domain}"
|
||||||
"imap-02.${domain}"
|
"imap-02.${domain}"
|
||||||
];
|
];
|
||||||
postRun = "systemctl --no-block restart dovecot2.service";
|
postRun = "systemctl --no-block restart dovecot.service";
|
||||||
};
|
};
|
||||||
|
|
||||||
networking.firewall.allowedTCPPorts = [
|
networking.firewall.allowedTCPPorts = [
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ in {
|
|||||||
olcTLSCACertificateFile = "/var/lib/acme/ldap.${domain}/full.pem";
|
olcTLSCACertificateFile = "/var/lib/acme/ldap.${domain}/full.pem";
|
||||||
olcTLSCertificateFile = "/var/lib/acme/ldap.${domain}/cert.pem";
|
olcTLSCertificateFile = "/var/lib/acme/ldap.${domain}/cert.pem";
|
||||||
olcTLSCertificateKeyFile = "/var/lib/acme/ldap.${domain}/key.pem";
|
olcTLSCertificateKeyFile = "/var/lib/acme/ldap.${domain}/key.pem";
|
||||||
olcTLSCipherSuite = "HIGH:MEDIUM:+3DES:+RC4:+aNULL";
|
olcTLSCipherSuite = "HIGH:!aNULL:!MD5:!3DES:!RC4";
|
||||||
olcTLSCRLCheck = "none";
|
olcTLSCRLCheck = "none";
|
||||||
olcTLSVerifyClient = "never";
|
olcTLSVerifyClient = "never";
|
||||||
olcTLSProtocolMin = "3.1";
|
olcTLSProtocolMin = "3.3";
|
||||||
olcSecurity = "tls=1";
|
olcSecurity = "tls=1";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -128,16 +128,16 @@ in
|
|||||||
compatibility_level = "2";
|
compatibility_level = "2";
|
||||||
|
|
||||||
# bigger attachement size
|
# bigger attachement size
|
||||||
mailbox_size_limit = "202400000";
|
mailbox_size_limit = 202400000;
|
||||||
message_size_limit = "51200000";
|
message_size_limit = 51200000;
|
||||||
smtpd_helo_required = "yes";
|
smtpd_helo_required = "yes";
|
||||||
smtpd_delay_reject = "yes";
|
smtpd_delay_reject = "yes";
|
||||||
strict_rfc821_envelopes = "yes";
|
strict_rfc821_envelopes = "yes";
|
||||||
|
|
||||||
# send Limit
|
# send Limit
|
||||||
smtpd_error_sleep_time = "1s";
|
smtpd_error_sleep_time = "1s";
|
||||||
smtpd_soft_error_limit = "10";
|
smtpd_soft_error_limit = 10;
|
||||||
smtpd_hard_error_limit = "20";
|
smtpd_hard_error_limit = 20;
|
||||||
|
|
||||||
smtpd_use_tls = "yes";
|
smtpd_use_tls = "yes";
|
||||||
smtp_tls_note_starttls_offer = "yes";
|
smtp_tls_note_starttls_offer = "yes";
|
||||||
@@ -151,14 +151,13 @@ in
|
|||||||
smtpd_tls_key_file = "/var/lib/acme/mail.cloonar.com/key.pem";
|
smtpd_tls_key_file = "/var/lib/acme/mail.cloonar.com/key.pem";
|
||||||
smtpd_tls_CAfile = "/var/lib/acme/mail.cloonar.com/fullchain.pem";
|
smtpd_tls_CAfile = "/var/lib/acme/mail.cloonar.com/fullchain.pem";
|
||||||
|
|
||||||
smtpd_tls_dh512_param_file = config.security.dhparams.params.postfix512.path;
|
|
||||||
smtpd_tls_dh1024_param_file = config.security.dhparams.params.postfix2048.path;
|
smtpd_tls_dh1024_param_file = config.security.dhparams.params.postfix2048.path;
|
||||||
|
|
||||||
smtpd_tls_session_cache_database = ''btree:''${data_directory}/smtpd_scache'';
|
smtpd_tls_session_cache_database = ''btree:''${data_directory}/smtpd_scache'';
|
||||||
smtpd_tls_mandatory_protocols = "!SSLv2,!SSLv3,!TLSv1,!TLSv1.1";
|
smtpd_tls_mandatory_protocols = "!SSLv2,!SSLv3,!TLSv1,!TLSv1.1";
|
||||||
smtpd_tls_protocols = "!SSLv2,!SSLv3,!TLSv1,!TLSv1.1";
|
smtpd_tls_protocols = "!SSLv2,!SSLv3,!TLSv1,!TLSv1.1";
|
||||||
smtpd_tls_mandatory_ciphers = "medium";
|
smtpd_tls_mandatory_ciphers = "medium";
|
||||||
tls_medium_cipherlist = "AES128+EECDH:AES128+EDH";
|
tls_medium_cipherlist = "ECDHE+AESGCM:DHE+AESGCM:ECDHE+CHACHA20:DHE+CHACHA20";
|
||||||
|
|
||||||
# authentication
|
# authentication
|
||||||
smtpd_sasl_auth_enable = "yes";
|
smtpd_sasl_auth_enable = "yes";
|
||||||
@@ -225,8 +224,7 @@ in
|
|||||||
|
|
||||||
security.dhparams = {
|
security.dhparams = {
|
||||||
enable = true;
|
enable = true;
|
||||||
params.postfix512.bits = 512;
|
params.postfix2048.bits = 2048;
|
||||||
params.postfix2048.bits = 1024;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
security.acme.certs."mail.${domain}" = {
|
security.acme.certs."mail.${domain}" = {
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ in
|
|||||||
|
|
||||||
# systemd.services.rspamd.serviceConfig.SupplementaryGroups = [ "redis-rspamd" ];
|
# systemd.services.rspamd.serviceConfig.SupplementaryGroups = [ "redis-rspamd" ];
|
||||||
|
|
||||||
systemd.services.dovecot2.preStart = ''
|
systemd.services.dovecot.preStart = ''
|
||||||
mkdir -p /var/lib/dovecot/sieve/
|
mkdir -p /var/lib/dovecot/sieve/
|
||||||
for i in ${sieve-spam-filter}/share/sieve-rspamd-filter/*.sieve; do
|
for i in ${sieve-spam-filter}/share/sieve-rspamd-filter/*.sieve; do
|
||||||
dest="/var/lib/dovecot/sieve/$(basename $i)"
|
dest="/var/lib/dovecot/sieve/$(basename $i)"
|
||||||
|
|||||||
60
hosts/nas/STORAGE.md
Normal file
60
hosts/nas/STORAGE.md
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# NAS Storage Notes
|
||||||
|
|
||||||
|
## Current Issue: XFS Metadata Overhead
|
||||||
|
|
||||||
|
The XFS filesystem on `/var/lib/multimedia` uses ~100GB more than the actual file data due to metadata overhead.
|
||||||
|
|
||||||
|
### Root Cause
|
||||||
|
|
||||||
|
The filesystem was created with advanced features enabled:
|
||||||
|
|
||||||
|
```
|
||||||
|
rmapbt=1 # Reverse mapping btree - tracks block ownership
|
||||||
|
reflink=1 # Copy-on-write support
|
||||||
|
```
|
||||||
|
|
||||||
|
These features add metadata that scales with **filesystem size**, not file count. On a 5TB filesystem with 700GB of data, this results in ~100GB (~2%) overhead.
|
||||||
|
|
||||||
|
### Diagnostic Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Compare file data vs filesystem usage
|
||||||
|
du -sh /var/lib/multimedia # Actual file data
|
||||||
|
df -h /var/lib/multimedia # Filesystem reports
|
||||||
|
|
||||||
|
# Check XFS features
|
||||||
|
xfs_info /var/lib/multimedia
|
||||||
|
|
||||||
|
# Verify block allocation
|
||||||
|
xfs_db -r -c "freesp -s" /dev/mapper/vg--data-lv--multimedia
|
||||||
|
```
|
||||||
|
|
||||||
|
## Recommendation: LVM + ext4
|
||||||
|
|
||||||
|
For media storage (write-once, read-many), ext4 with minimal reserved space offers the lowest overhead:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create filesystem with 0% reserved blocks
|
||||||
|
mkfs.ext4 -m 0 /dev/vg/lv
|
||||||
|
|
||||||
|
# Or adjust existing ext4
|
||||||
|
tune2fs -m 0 /dev/vg/lv
|
||||||
|
```
|
||||||
|
|
||||||
|
### Why ext4 over XFS for this use case
|
||||||
|
|
||||||
|
| Consideration | ext4 | XFS (current) |
|
||||||
|
|---------------|------|---------------|
|
||||||
|
| Reserved space | 0% with `-m 0` | N/A |
|
||||||
|
| Metadata overhead | ~0.5% | ~2% (with rmapbt) |
|
||||||
|
| Shrink support | Yes | No |
|
||||||
|
| Performance for 4K stream | Identical | Identical |
|
||||||
|
|
||||||
|
A single 4K remux stream requires ~12 MB/s. Any filesystem handles this trivially.
|
||||||
|
|
||||||
|
## Migration Path
|
||||||
|
|
||||||
|
1. Backup data from XFS volumes
|
||||||
|
2. Recreate LVs with ext4 (`mkfs.ext4 -m 0`)
|
||||||
|
3. Restore data
|
||||||
|
4. Update `/etc/fstab` or NixOS `fileSystems` config
|
||||||
1
hosts/nas/channel
Normal file
1
hosts/nas/channel
Normal file
@@ -0,0 +1 @@
|
|||||||
|
https://channels.nixos.org/nixos-25.11
|
||||||
100
hosts/nas/configuration.nix
Normal file
100
hosts/nas/configuration.nix
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
# NAS host configuration
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
let
|
||||||
|
impermanence = builtins.fetchTarball "https://github.com/nix-community/impermanence/archive/master.tar.gz";
|
||||||
|
in {
|
||||||
|
nixpkgs.config.allowUnfree = true;
|
||||||
|
|
||||||
|
imports = [
|
||||||
|
"${impermanence}/nixos.nix"
|
||||||
|
./utils/bento.nix
|
||||||
|
./utils/modules/sops.nix
|
||||||
|
./utils/modules/set-nix-channel.nix
|
||||||
|
./utils/modules/victoriametrics
|
||||||
|
./utils/modules/promtail
|
||||||
|
|
||||||
|
# ./modules/cyberghost.nix
|
||||||
|
./modules/pyload.nix
|
||||||
|
./modules/jellyfin.nix
|
||||||
|
./modules/power-management.nix
|
||||||
|
./modules/disk-monitoring.nix
|
||||||
|
./modules/ugreen-leds.nix
|
||||||
|
|
||||||
|
./hardware-configuration.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
nixpkgs.overlays = [
|
||||||
|
(import ./utils/overlays/packages.nix)
|
||||||
|
];
|
||||||
|
|
||||||
|
# Hostname
|
||||||
|
networking.hostName = "nas";
|
||||||
|
|
||||||
|
# Timezone
|
||||||
|
time.timeZone = "Europe/Vienna";
|
||||||
|
console.keyMap = "de";
|
||||||
|
|
||||||
|
# SSH server
|
||||||
|
services.openssh.enable = true;
|
||||||
|
users.users.root.openssh.authorizedKeys.keys = [
|
||||||
|
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDN/2SAFm50kraB1fepAizox/QRXxB7WbqVbH+5OPalDT47VIJGNKOKhixQoqhABHxEoLxdf/C83wxlCVlPV9poLfDgVkA3Lyt5r3tSFQ6QjjOJAgchWamMsxxyGBedhKvhiEzcr/Lxytnoz3kjDG8fqQJwEpdqMmJoMUfyL2Rqp16u+FQ7d5aJtwO8EUqovhMaNO7rggjPpV/uMOg+tBxxmscliN7DLuP4EMTA/FwXVzcFNbOx3K9BdpMRAaSJt4SWcJO2cS2KHA5n/H+PQI7nz5KN3Yr/upJN5fROhi/SHvK39QOx12Pv7FCuWlc+oR68vLaoCKYhnkl3DnCfc7A7"
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIRQuPqH5fdX3KEw7DXzWEdO3AlUn1oSmtJtHB71ICoH Generated By Termius"
|
||||||
|
];
|
||||||
|
|
||||||
|
# Firewall
|
||||||
|
networking.firewall.enable = true;
|
||||||
|
networking.firewall.allowedTCPPorts = [ 22 ];
|
||||||
|
|
||||||
|
# SOPS configuration
|
||||||
|
sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
|
||||||
|
sops.defaultSopsFile = ./secrets.yaml;
|
||||||
|
|
||||||
|
# Btrfs maintenance
|
||||||
|
services.btrfs.autoScrub = {
|
||||||
|
enable = true;
|
||||||
|
interval = "monthly";
|
||||||
|
fileSystems = [ "/nix" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
# Impermanence - persist important directories
|
||||||
|
# Note: /var/lib/downloads and /var/lib/multimedia are mounted from LVM on RAID
|
||||||
|
environment.persistence."/nix/persist/system" = {
|
||||||
|
hideMounts = true;
|
||||||
|
directories = [
|
||||||
|
"/var/lib/pyload"
|
||||||
|
"/var/lib/jellyfin"
|
||||||
|
"/var/log"
|
||||||
|
"/var/lib/nixos"
|
||||||
|
"/var/bento"
|
||||||
|
"/root/.ssh"
|
||||||
|
];
|
||||||
|
files = [
|
||||||
|
"/etc/machine-id"
|
||||||
|
{ file = "/etc/ssh/ssh_host_ed25519_key"; parentDirectory = { mode = "u=rwx,g=,o="; }; }
|
||||||
|
{ file = "/etc/ssh/ssh_host_ed25519_key.pub"; parentDirectory = { mode = "u=rwx,g=,o="; }; }
|
||||||
|
{ file = "/etc/ssh/ssh_host_rsa_key"; parentDirectory = { mode = "u=rwx,g=,o="; }; }
|
||||||
|
{ file = "/etc/ssh/ssh_host_rsa_key.pub"; parentDirectory = { mode = "u=rwx,g=,o="; }; }
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
# System packages
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
vim
|
||||||
|
screen
|
||||||
|
];
|
||||||
|
|
||||||
|
# Nix settings
|
||||||
|
nix = {
|
||||||
|
settings = {
|
||||||
|
auto-optimise-store = true;
|
||||||
|
experimental-features = [ "nix-command" "flakes" ];
|
||||||
|
};
|
||||||
|
gc = {
|
||||||
|
automatic = true;
|
||||||
|
dates = "weekly";
|
||||||
|
options = "--delete-older-than 14d";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
system.stateVersion = "24.05";
|
||||||
|
}
|
||||||
1
hosts/nas/fleet.nix
Symbolic link
1
hosts/nas/fleet.nix
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../fleet.nix
|
||||||
103
hosts/nas/hardware-configuration.nix
Normal file
103
hosts/nas/hardware-configuration.nix
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
# Hardware configuration for NAS
|
||||||
|
# TODO: Update disk labels/UUIDs after installation
|
||||||
|
{ config, lib, pkgs, modulesPath, ... }:
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
(modulesPath + "/installer/scan/not-detected.nix")
|
||||||
|
];
|
||||||
|
|
||||||
|
boot.loader.systemd-boot = {
|
||||||
|
enable = true;
|
||||||
|
configurationLimit = 5;
|
||||||
|
};
|
||||||
|
boot.loader.efi.canTouchEfiVariables = true;
|
||||||
|
boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "nvme" "usb_storage" "sd_mod" ];
|
||||||
|
boot.initrd.kernelModules = [ "dm-snapshot" ];
|
||||||
|
boot.kernelModules = [ "kvm-intel" ];
|
||||||
|
boot.extraModulePackages = [ ];
|
||||||
|
|
||||||
|
# Power management kernel parameters
|
||||||
|
boot.kernelParams = [
|
||||||
|
"intel_pstate=passive" # Better with powersave governor
|
||||||
|
"i915.enable_rc6=1" # GPU deep sleep states
|
||||||
|
"i915.enable_dc=2" # Display C-states (deepest)
|
||||||
|
"i915.enable_fbc=1" # Frame buffer compression
|
||||||
|
];
|
||||||
|
|
||||||
|
# RAID 1 arrays for data storage
|
||||||
|
boot.swraid = {
|
||||||
|
enable = true;
|
||||||
|
mdadmConf = ''
|
||||||
|
DEVICE /dev/disk/by-id/nvme-KIOXIA-EXCERIA_PLUS_G3_SSD_7FJKS1MAZ0E7-part1
|
||||||
|
DEVICE /dev/disk/by-id/nvme-KIOXIA-EXCERIA_PLUS_G3_SSD_7FJKS1M9Z0E7-part1
|
||||||
|
DEVICE /dev/disk/by-id/ata-TOSHIBA_MG10ACA20TE_8582A01SF4MJ-part1
|
||||||
|
DEVICE /dev/disk/by-id/ata-TOSHIBA_MG10ACA20TE_75V2A0H3F4MJ-part1
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# Tmpfs root filesystem (ephemeral - resets on reboot)
|
||||||
|
fileSystems."/" = {
|
||||||
|
device = "none";
|
||||||
|
fsType = "tmpfs";
|
||||||
|
options = [ "size=8G" "mode=755" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
# Boot partition - EFI
|
||||||
|
fileSystems."/boot" = {
|
||||||
|
device = "/dev/disk/by-partlabel/BOOT";
|
||||||
|
fsType = "vfat";
|
||||||
|
};
|
||||||
|
|
||||||
|
fileSystems."/nix" = {
|
||||||
|
device = "/dev/disk/by-partlabel/NIXOS";
|
||||||
|
fsType = "btrfs";
|
||||||
|
neededForBoot = true;
|
||||||
|
options = [
|
||||||
|
"subvol=@"
|
||||||
|
"compress=zstd:1"
|
||||||
|
"noatime"
|
||||||
|
"commit=120"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
fileSystems."/nix/store" = {
|
||||||
|
device = "/dev/disk/by-partlabel/NIXOS";
|
||||||
|
fsType = "btrfs";
|
||||||
|
neededForBoot = true;
|
||||||
|
options = [
|
||||||
|
"subvol=@nix-store"
|
||||||
|
"compress=zstd:1"
|
||||||
|
"noatime"
|
||||||
|
"commit=120"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
fileSystems."/nix/persist" = {
|
||||||
|
device = "/dev/disk/by-partlabel/NIXOS";
|
||||||
|
fsType = "btrfs";
|
||||||
|
neededForBoot = true;
|
||||||
|
options = [
|
||||||
|
"subvol=@nix-persist"
|
||||||
|
"compress=zstd:1"
|
||||||
|
"noatime"
|
||||||
|
"commit=120"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
# LVM volumes on RAID array
|
||||||
|
fileSystems."/var/lib/downloads" = {
|
||||||
|
device = "/dev/vg-data-fast/downloads";
|
||||||
|
fsType = "ext4";
|
||||||
|
};
|
||||||
|
|
||||||
|
fileSystems."/var/lib/multimedia" = {
|
||||||
|
device = "/dev/vg-data-slow/multimedia";
|
||||||
|
fsType = "ext4";
|
||||||
|
options = [ "noatime" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
# DHCP networking
|
||||||
|
networking.useDHCP = lib.mkDefault true;
|
||||||
|
|
||||||
|
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
|
||||||
|
}
|
||||||
103
hosts/nas/modules/cyberghost.nix
Normal file
103
hosts/nas/modules/cyberghost.nix
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
{ config, pkgs, ... }:
|
||||||
|
let
|
||||||
|
localNetwork = "10.42.96.0/20";
|
||||||
|
in
|
||||||
|
{
|
||||||
|
# SOPS secrets for CyberGhost credentials
|
||||||
|
sops.secrets.cyberghost-auth = {
|
||||||
|
mode = "0400";
|
||||||
|
owner = "root";
|
||||||
|
};
|
||||||
|
sops.secrets.cyberghost-ca = {
|
||||||
|
mode = "0400";
|
||||||
|
owner = "root";
|
||||||
|
};
|
||||||
|
sops.secrets.cyberghost-cert = {
|
||||||
|
mode = "0400";
|
||||||
|
owner = "root";
|
||||||
|
};
|
||||||
|
sops.secrets.cyberghost-key = {
|
||||||
|
mode = "0400";
|
||||||
|
owner = "root";
|
||||||
|
};
|
||||||
|
|
||||||
|
environment.systemPackages = [ pkgs.openvpn ];
|
||||||
|
|
||||||
|
# OpenVPN client service
|
||||||
|
services.openvpn.servers.cyberghost = {
|
||||||
|
autoStart = true;
|
||||||
|
updateResolvConf = true;
|
||||||
|
config = ''
|
||||||
|
client
|
||||||
|
dev tun
|
||||||
|
proto udp
|
||||||
|
remote 87-1-hu.cg-dialup.net 443
|
||||||
|
resolv-retry infinite
|
||||||
|
nobind
|
||||||
|
persist-key
|
||||||
|
persist-tun
|
||||||
|
|
||||||
|
# Authentication
|
||||||
|
auth-user-pass ${config.sops.secrets.cyberghost-auth.path}
|
||||||
|
ca ${config.sops.secrets.cyberghost-ca.path}
|
||||||
|
cert ${config.sops.secrets.cyberghost-cert.path}
|
||||||
|
key ${config.sops.secrets.cyberghost-key.path}
|
||||||
|
|
||||||
|
# Security
|
||||||
|
data-ciphers AES-256-GCM:AES-128-GCM:AES-256-CBC
|
||||||
|
data-ciphers-fallback AES-256-CBC
|
||||||
|
auth SHA256
|
||||||
|
remote-cert-tls server
|
||||||
|
script-security 2
|
||||||
|
|
||||||
|
# Connection
|
||||||
|
ping 5
|
||||||
|
explicit-exit-notify 2
|
||||||
|
route-delay 5
|
||||||
|
|
||||||
|
# Split tunnel: Don't pull routes from server, we'll set our own
|
||||||
|
route-nopull
|
||||||
|
|
||||||
|
# Route all traffic through VPN except local network
|
||||||
|
route 0.0.0.0 128.0.0.0 vpn_gateway
|
||||||
|
route 128.0.0.0 128.0.0.0 vpn_gateway
|
||||||
|
|
||||||
|
# Keep local network route direct
|
||||||
|
route ${localNetwork} net_gateway
|
||||||
|
|
||||||
|
verb 4
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# Kill switch: Block outgoing traffic if VPN is down
|
||||||
|
networking.firewall = {
|
||||||
|
extraCommands = ''
|
||||||
|
# Allow traffic to local network
|
||||||
|
iptables -A OUTPUT -d ${localNetwork} -j ACCEPT
|
||||||
|
|
||||||
|
# Allow traffic through VPN tunnel
|
||||||
|
iptables -A OUTPUT -o tun+ -j ACCEPT
|
||||||
|
|
||||||
|
# Allow loopback
|
||||||
|
iptables -A OUTPUT -o lo -j ACCEPT
|
||||||
|
|
||||||
|
# Allow established connections (for responses)
|
||||||
|
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
|
||||||
|
|
||||||
|
# Allow OpenVPN to establish connection (UDP 443)
|
||||||
|
iptables -A OUTPUT -p udp --dport 443 -j ACCEPT
|
||||||
|
|
||||||
|
# Drop all other outgoing internet traffic (kill switch)
|
||||||
|
iptables -A OUTPUT ! -d ${localNetwork} -j DROP
|
||||||
|
'';
|
||||||
|
|
||||||
|
extraStopCommands = ''
|
||||||
|
iptables -D OUTPUT -d ${localNetwork} -j ACCEPT 2>/dev/null || true
|
||||||
|
iptables -D OUTPUT -o tun+ -j ACCEPT 2>/dev/null || true
|
||||||
|
iptables -D OUTPUT -o lo -j ACCEPT 2>/dev/null || true
|
||||||
|
iptables -D OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT 2>/dev/null || true
|
||||||
|
iptables -D OUTPUT -p udp --dport 443 -j ACCEPT 2>/dev/null || true
|
||||||
|
iptables -D OUTPUT ! -d ${localNetwork} -j DROP 2>/dev/null || true
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}
|
||||||
199
hosts/nas/modules/disk-monitoring.nix
Normal file
199
hosts/nas/modules/disk-monitoring.nix
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
# Disk monitoring for NAS
|
||||||
|
# - S.M.A.R.T. metrics collection (respects disk spindown)
|
||||||
|
# - mdadm RAID array status
|
||||||
|
# - Exports metrics via node_exporter textfile collector
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
# Disk identifiers from hardware-configuration.nix
|
||||||
|
disks = [
|
||||||
|
"/dev/disk/by-id/ata-ST18000NM000J-2TV103_ZR52TBSB"
|
||||||
|
"/dev/disk/by-id/ata-ST18000NM000J-2TV103_ZR52V9QX"
|
||||||
|
"/dev/disk/by-id/ata-TOSHIBA_MG10ACA20TE_8582A01SF4MJ"
|
||||||
|
"/dev/disk/by-id/ata-TOSHIBA_MG10ACA20TE_75V2A0H3F4MJ"
|
||||||
|
"/dev/disk/by-id/nvme-KIOXIA-EXCERIA_PLUS_G3_SSD_7FJKS1MAZ0E7"
|
||||||
|
"/dev/disk/by-id/nvme-KIOXIA-EXCERIA_PLUS_G3_SSD_7FJKS1M9Z0E7"
|
||||||
|
];
|
||||||
|
|
||||||
|
textfileDir = "/var/lib/prometheus-node-exporter";
|
||||||
|
|
||||||
|
# Script to collect S.M.A.R.T. and mdadm metrics
|
||||||
|
collectMetricsScript = pkgs.writeShellScript "collect-disk-metrics" ''
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
TEXTFILE_DIR="${textfileDir}"
|
||||||
|
METRICS_FILE="$TEXTFILE_DIR/disk_health.prom"
|
||||||
|
TEMP_FILE="$TEXTFILE_DIR/disk_health.prom.tmp"
|
||||||
|
|
||||||
|
mkdir -p "$TEXTFILE_DIR"
|
||||||
|
: > "$TEMP_FILE"
|
||||||
|
|
||||||
|
# Timestamp of collection
|
||||||
|
echo "# HELP disk_metrics_last_update Unix timestamp of last metrics collection" >> "$TEMP_FILE"
|
||||||
|
echo "# TYPE disk_metrics_last_update gauge" >> "$TEMP_FILE"
|
||||||
|
echo "disk_metrics_last_update $(date +%s)" >> "$TEMP_FILE"
|
||||||
|
|
||||||
|
echo "" >> "$TEMP_FILE"
|
||||||
|
echo "# HELP smart_device_active Whether the disk was active (1) or sleeping (0) when checked" >> "$TEMP_FILE"
|
||||||
|
echo "# TYPE smart_device_active gauge" >> "$TEMP_FILE"
|
||||||
|
|
||||||
|
# S.M.A.R.T. metrics for each disk
|
||||||
|
for disk in ${lib.concatStringsSep " " disks}; do
|
||||||
|
if [[ ! -e "$disk" ]]; then
|
||||||
|
echo "Warning: Disk $disk not found, skipping" >&2
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Resolve symlink to get actual device (needed for hdparm/smartctl)
|
||||||
|
device=$(readlink -f "$disk")
|
||||||
|
|
||||||
|
# Extract model+serial from disk-by-id path for stable labeling
|
||||||
|
# ata-ST18000NM000J-2TV103_ZR52TBSB → ST18000NM000J-2TV103-ZR52TBSB
|
||||||
|
# nvme-KIOXIA-EXCERIA_PLUS_G3_SSD_7FJKS1MAZ0E7 → KIOXIA-EXCERIA_PLUS_G3_SSD-7FJKS1MAZ0E7
|
||||||
|
disk_id=$(basename "$disk")
|
||||||
|
serial=$(echo "$disk_id" | sed 's/.*_//')
|
||||||
|
model=$(echo "$disk_id" | sed 's/^[^-]*-//; s/_[^_]*$//')
|
||||||
|
short_name="$model-$serial"
|
||||||
|
|
||||||
|
# Check power state without waking disk
|
||||||
|
power_state=$(${pkgs.hdparm}/bin/hdparm -C "$device" 2>/dev/null | grep -oP '(standby|active/idle|active|idle)' | head -1 || echo "unknown")
|
||||||
|
|
||||||
|
if [[ "$power_state" == "standby" ]]; then
|
||||||
|
# Disk is sleeping - don't wake it, report inactive
|
||||||
|
echo "smart_device_active{device=\"$short_name\",serial=\"$serial\"} 0" >> "$TEMP_FILE"
|
||||||
|
echo "Disk $short_name is in standby, skipping S.M.A.R.T. collection" >&2
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Disk is active - collect S.M.A.R.T. data
|
||||||
|
echo "smart_device_active{device=\"$short_name\",serial=\"$serial\"} 1" >> "$TEMP_FILE"
|
||||||
|
|
||||||
|
# Get S.M.A.R.T. health status
|
||||||
|
if ${pkgs.smartmontools}/bin/smartctl -H "$device" 2>/dev/null | grep -q "PASSED"; then
|
||||||
|
health=1
|
||||||
|
else
|
||||||
|
health=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get S.M.A.R.T. attributes
|
||||||
|
smartctl_output=$(${pkgs.smartmontools}/bin/smartctl -A "$device" 2>/dev/null || true)
|
||||||
|
|
||||||
|
# Parse key attributes
|
||||||
|
# Format: ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_FAILED RAW_VALUE
|
||||||
|
|
||||||
|
get_raw_value() {
|
||||||
|
local attr_id="$1"
|
||||||
|
echo "$smartctl_output" | awk -v id="$attr_id" '$1 == id { print $10 }' | head -1
|
||||||
|
}
|
||||||
|
|
||||||
|
reallocated=$(get_raw_value "5")
|
||||||
|
power_on_hours=$(get_raw_value "9")
|
||||||
|
temperature=$(get_raw_value "194")
|
||||||
|
reallocated_event=$(get_raw_value "196")
|
||||||
|
pending_sector=$(get_raw_value "197")
|
||||||
|
offline_uncorrectable=$(get_raw_value "198")
|
||||||
|
udma_crc_error=$(get_raw_value "199")
|
||||||
|
|
||||||
|
# Output metrics
|
||||||
|
cat >> "$TEMP_FILE" << EOF
|
||||||
|
|
||||||
|
# S.M.A.R.T. metrics for $short_name
|
||||||
|
smart_health_passed{device="$short_name",serial="$serial"} $health
|
||||||
|
EOF
|
||||||
|
|
||||||
|
[[ -n "$reallocated" ]] && echo "smart_reallocated_sector_ct{device=\"$short_name\",serial=\"$serial\"} $reallocated" >> "$TEMP_FILE"
|
||||||
|
[[ -n "$power_on_hours" ]] && echo "smart_power_on_hours{device=\"$short_name\",serial=\"$serial\"} $power_on_hours" >> "$TEMP_FILE"
|
||||||
|
[[ -n "$temperature" ]] && echo "smart_temperature_celsius{device=\"$short_name\",serial=\"$serial\"} $temperature" >> "$TEMP_FILE"
|
||||||
|
[[ -n "$reallocated_event" ]] && echo "smart_reallocated_event_count{device=\"$short_name\",serial=\"$serial\"} $reallocated_event" >> "$TEMP_FILE"
|
||||||
|
[[ -n "$pending_sector" ]] && echo "smart_current_pending_sector{device=\"$short_name\",serial=\"$serial\"} $pending_sector" >> "$TEMP_FILE"
|
||||||
|
[[ -n "$offline_uncorrectable" ]] && echo "smart_offline_uncorrectable{device=\"$short_name\",serial=\"$serial\"} $offline_uncorrectable" >> "$TEMP_FILE"
|
||||||
|
[[ -n "$udma_crc_error" ]] && echo "smart_udma_crc_error_count{device=\"$short_name\",serial=\"$serial\"} $udma_crc_error" >> "$TEMP_FILE"
|
||||||
|
done
|
||||||
|
|
||||||
|
# mdadm RAID array status (doesn't access disks)
|
||||||
|
echo "" >> "$TEMP_FILE"
|
||||||
|
echo "# HELP mdadm_array_state RAID array state (1=clean/active/resyncing, 0=degraded/other)" >> "$TEMP_FILE"
|
||||||
|
echo "# TYPE mdadm_array_state gauge" >> "$TEMP_FILE"
|
||||||
|
echo "# HELP mdadm_array_devices_total Total devices in RAID array" >> "$TEMP_FILE"
|
||||||
|
echo "# TYPE mdadm_array_devices_total gauge" >> "$TEMP_FILE"
|
||||||
|
echo "# HELP mdadm_array_devices_active Active devices in RAID array" >> "$TEMP_FILE"
|
||||||
|
echo "# TYPE mdadm_array_devices_active gauge" >> "$TEMP_FILE"
|
||||||
|
|
||||||
|
# Find RAID arrays
|
||||||
|
for md_device in /dev/md/*; do
|
||||||
|
[[ -e "$md_device" ]] || continue
|
||||||
|
|
||||||
|
array_name=$(basename "$md_device")
|
||||||
|
|
||||||
|
# Get array details
|
||||||
|
mdadm_output=$(${pkgs.mdadm}/bin/mdadm --detail "$md_device" 2>/dev/null || continue)
|
||||||
|
|
||||||
|
# Parse state
|
||||||
|
state=$(echo "$mdadm_output" | grep "State :" | sed 's/.*State : //' | tr -d ' ')
|
||||||
|
if [[ "$state" == *clean* ]] || [[ "$state" == *active* ]]; then
|
||||||
|
state_value=1
|
||||||
|
else
|
||||||
|
state_value=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Parse device counts
|
||||||
|
total_devices=$(echo "$mdadm_output" | grep "Raid Devices" | awk '{print $4}')
|
||||||
|
active_devices=$(echo "$mdadm_output" | grep "Active Devices" | awk '{print $4}')
|
||||||
|
|
||||||
|
echo "mdadm_array_state{array=\"$array_name\",state=\"$state\"} $state_value" >> "$TEMP_FILE"
|
||||||
|
[[ -n "$total_devices" ]] && echo "mdadm_array_devices_total{array=\"$array_name\"} $total_devices" >> "$TEMP_FILE"
|
||||||
|
[[ -n "$active_devices" ]] && echo "mdadm_array_devices_active{array=\"$array_name\"} $active_devices" >> "$TEMP_FILE"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Atomically replace the metrics file
|
||||||
|
mv "$TEMP_FILE" "$METRICS_FILE"
|
||||||
|
|
||||||
|
echo "Disk metrics collection complete"
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
{
|
||||||
|
# Required packages
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
smartmontools
|
||||||
|
hdparm
|
||||||
|
mdadm
|
||||||
|
];
|
||||||
|
|
||||||
|
# Node exporter with textfile collector
|
||||||
|
services.prometheus.exporters.node = {
|
||||||
|
enable = true;
|
||||||
|
enabledCollectors = [
|
||||||
|
"textfile"
|
||||||
|
];
|
||||||
|
extraFlags = [
|
||||||
|
"--collector.textfile.directory=${textfileDir}"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
# Systemd service to collect metrics
|
||||||
|
systemd.services.disk-metrics = {
|
||||||
|
description = "Collect S.M.A.R.T. and RAID metrics";
|
||||||
|
path = with pkgs; [ coreutils gawk gnugrep gnused ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
ExecStart = "${collectMetricsScript}";
|
||||||
|
# Run as root to access disk devices
|
||||||
|
User = "root";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Timer to run every 20 minutes (5min buffer for 15min spindown)
|
||||||
|
systemd.timers.disk-metrics = {
|
||||||
|
wantedBy = [ "timers.target" ];
|
||||||
|
timerConfig = {
|
||||||
|
OnCalendar = "*:0/20"; # Every 20 minutes
|
||||||
|
RandomizedDelaySec = "1min";
|
||||||
|
Persistent = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Ensure textfile directory exists and is persisted
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
"d ${textfileDir} 0755 root root -"
|
||||||
|
];
|
||||||
|
}
|
||||||
186
hosts/nas/modules/filebot-process.nix
Normal file
186
hosts/nas/modules/filebot-process.nix
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
{ pkgs }:
|
||||||
|
|
||||||
|
pkgs.writeShellScriptBin "filebot-process" ''
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# FileBot AMC script for automated media organization
|
||||||
|
# Called by PyLoad's package_extracted hook with parameters:
|
||||||
|
# $1 = package_id
|
||||||
|
# $2 = package_name
|
||||||
|
# $3 = download_folder (actual path to extracted files)
|
||||||
|
# $4 = password (optional)
|
||||||
|
|
||||||
|
PACKAGE_ID="''${1:-}"
|
||||||
|
PACKAGE_NAME="''${2:-unknown}"
|
||||||
|
DOWNLOAD_DIR="''${3:-/var/lib/downloads}"
|
||||||
|
PASSWORD="''${4:-}"
|
||||||
|
|
||||||
|
OUTPUT_DIR="/var/lib/multimedia"
|
||||||
|
LOG_FILE="/var/lib/pyload/filebot-amc.log"
|
||||||
|
EXCLUDE_LIST="/var/lib/pyload/filebot-exclude-list.txt"
|
||||||
|
PASSWORD_FILE="/var/lib/pyload/extraction-passwords.txt"
|
||||||
|
|
||||||
|
# Ensure FileBot data directory exists
|
||||||
|
mkdir -p /var/lib/pyload/.local/share/filebot/data
|
||||||
|
mkdir -p "$(dirname "$LOG_FILE")"
|
||||||
|
touch "$EXCLUDE_LIST"
|
||||||
|
|
||||||
|
# Install FileBot license if not already installed
|
||||||
|
if [ ! -f /var/lib/pyload/.local/share/filebot/data/.license ]; then
|
||||||
|
echo "$(date): Installing FileBot license..." >> "$LOG_FILE"
|
||||||
|
${pkgs.filebot}/bin/filebot --license /var/lib/pyload/filebot-license.psm || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "===========================================" >> "$LOG_FILE"
|
||||||
|
echo "$(date): PyLoad package hook triggered" >> "$LOG_FILE"
|
||||||
|
echo "Package ID: $PACKAGE_ID" >> "$LOG_FILE"
|
||||||
|
echo "Package Name: $PACKAGE_NAME" >> "$LOG_FILE"
|
||||||
|
echo "Download Directory: $DOWNLOAD_DIR" >> "$LOG_FILE"
|
||||||
|
echo "===========================================" >> "$LOG_FILE"
|
||||||
|
|
||||||
|
# Check if download directory exists
|
||||||
|
if [ ! -d "$DOWNLOAD_DIR" ]; then
|
||||||
|
echo "$(date): Download directory does not exist: $DOWNLOAD_DIR" >> "$LOG_FILE"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Archive Extraction ---
|
||||||
|
|
||||||
|
# Try extraction with passwords, then without
|
||||||
|
try_extract() {
|
||||||
|
local archive="$1"
|
||||||
|
local outdir
|
||||||
|
outdir="$(dirname "$archive")"
|
||||||
|
|
||||||
|
# Try each password from file
|
||||||
|
if [ -f "$PASSWORD_FILE" ]; then
|
||||||
|
while IFS= read -r pass || [ -n "$pass" ]; do
|
||||||
|
[ -z "$pass" ] && continue
|
||||||
|
case "$archive" in
|
||||||
|
*.rar) ${pkgs.unrar}/bin/unrar x -p"$pass" -o+ "$archive" "$outdir/" >/dev/null 2>&1 && return 0 ;;
|
||||||
|
*.7z) ${pkgs.p7zip}/bin/7z x -p"$pass" -aoa -o"$outdir" "$archive" >/dev/null 2>&1 && return 0 ;;
|
||||||
|
*.zip) ${pkgs.p7zip}/bin/7z x -p"$pass" -aoa -o"$outdir" "$archive" >/dev/null 2>&1 && return 0 ;;
|
||||||
|
esac
|
||||||
|
done < "$PASSWORD_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Try without password
|
||||||
|
case "$archive" in
|
||||||
|
*.rar) ${pkgs.unrar}/bin/unrar x -o+ "$archive" "$outdir/" >/dev/null 2>&1 && return 0 ;;
|
||||||
|
*.7z) ${pkgs.p7zip}/bin/7z x -aoa -o"$outdir" "$archive" >/dev/null 2>&1 && return 0 ;;
|
||||||
|
*.zip) ${pkgs.p7zip}/bin/7z x -aoa -o"$outdir" "$archive" >/dev/null 2>&1 && return 0 ;;
|
||||||
|
esac
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Delete all parts of a split RAR archive
|
||||||
|
delete_rar_parts() {
|
||||||
|
local first_part="$1"
|
||||||
|
local base dir
|
||||||
|
|
||||||
|
dir="$(dirname "$first_part")"
|
||||||
|
# Extract base name: "foo.part1.rar" -> "foo", "foo.part01.rar" -> "foo"
|
||||||
|
base="$(basename "$first_part" | ${pkgs.gnused}/bin/sed -E 's/\.part[0-9]+\.rar$//')"
|
||||||
|
|
||||||
|
# Delete all parts matching the pattern
|
||||||
|
find "$dir" -maxdepth 1 -type f -iname "''${base}.part*.rar" -delete
|
||||||
|
echo "$(date): Deleted all parts: ''${base}.part*.rar" >> "$LOG_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extract archives in directory
|
||||||
|
extract_archives() {
|
||||||
|
local dir="$1"
|
||||||
|
local extracted=0
|
||||||
|
|
||||||
|
# 1. Handle split RAR archives (*.part1.rar or *.part01.rar - first part only)
|
||||||
|
while IFS= read -r -d "" archive; do
|
||||||
|
echo "$(date): Extracting split RAR: $archive" >> "$LOG_FILE"
|
||||||
|
if try_extract "$archive"; then
|
||||||
|
echo "$(date): Extraction successful" >> "$LOG_FILE"
|
||||||
|
delete_rar_parts "$archive"
|
||||||
|
extracted=1
|
||||||
|
else
|
||||||
|
echo "$(date): Extraction FAILED: $archive" >> "$LOG_FILE"
|
||||||
|
fi
|
||||||
|
done < <(find "$dir" -type f \( -iname "*.part1.rar" -o -iname "*.part01.rar" \) -print0 2>/dev/null)
|
||||||
|
|
||||||
|
# 2. Handle single RAR files (not part of split archive)
|
||||||
|
while IFS= read -r -d "" archive; do
|
||||||
|
echo "$(date): Extracting RAR: $archive" >> "$LOG_FILE"
|
||||||
|
if try_extract "$archive"; then
|
||||||
|
echo "$(date): Extraction successful, deleting: $archive" >> "$LOG_FILE"
|
||||||
|
rm -f "$archive"
|
||||||
|
extracted=1
|
||||||
|
else
|
||||||
|
echo "$(date): Extraction FAILED: $archive" >> "$LOG_FILE"
|
||||||
|
fi
|
||||||
|
done < <(find "$dir" -type f -iname "*.rar" ! -iname "*.part*.rar" -print0 2>/dev/null)
|
||||||
|
|
||||||
|
# 3. Handle 7z and zip archives
|
||||||
|
while IFS= read -r -d "" archive; do
|
||||||
|
echo "$(date): Extracting: $archive" >> "$LOG_FILE"
|
||||||
|
if try_extract "$archive"; then
|
||||||
|
echo "$(date): Extraction successful, deleting: $archive" >> "$LOG_FILE"
|
||||||
|
rm -f "$archive"
|
||||||
|
extracted=1
|
||||||
|
else
|
||||||
|
echo "$(date): Extraction FAILED: $archive" >> "$LOG_FILE"
|
||||||
|
fi
|
||||||
|
done < <(find "$dir" -type f \( -iname "*.7z" -o -iname "*.zip" \) -print0 2>/dev/null)
|
||||||
|
|
||||||
|
[ "$extracted" -eq 1 ] && return 0 || return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run extraction (loop to handle nested archives)
|
||||||
|
echo "$(date): Starting archive extraction..." >> "$LOG_FILE"
|
||||||
|
for i in 1 2 3; do
|
||||||
|
extract_archives "$DOWNLOAD_DIR" || break
|
||||||
|
done
|
||||||
|
echo "$(date): Archive extraction complete" >> "$LOG_FILE"
|
||||||
|
|
||||||
|
# --- Media Processing ---
|
||||||
|
|
||||||
|
# Check if there are any video/media files to process
|
||||||
|
if ! find "$DOWNLOAD_DIR" -type f \( -iname "*.mkv" -o -iname "*.mp4" -o -iname "*.avi" -o -iname "*.m4v" -o -iname "*.mov" \) -print -quit | grep -q .; then
|
||||||
|
echo "$(date): No media files found in: $DOWNLOAD_DIR" >> "$LOG_FILE"
|
||||||
|
echo "$(date): Skipping FileBot processing" >> "$LOG_FILE"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$(date): Starting FileBot processing" >> "$LOG_FILE"
|
||||||
|
|
||||||
|
# Run FileBot AMC script
|
||||||
|
set +e # Temporarily disable exit on error to capture exit code
|
||||||
|
${pkgs.filebot}/bin/filebot \
|
||||||
|
-script fn:amc \
|
||||||
|
--output "$OUTPUT_DIR" \
|
||||||
|
--action move \
|
||||||
|
--conflict auto \
|
||||||
|
-non-strict \
|
||||||
|
--log-file "$LOG_FILE" \
|
||||||
|
--def \
|
||||||
|
excludeList="$EXCLUDE_LIST" \
|
||||||
|
movieFormat="$OUTPUT_DIR/movies/{n} ({y})/{n} ({y}) - {vf}" \
|
||||||
|
seriesFormat="$OUTPUT_DIR/tv-shows/{n}/Season {s.pad(2)}/{n} - {s00e00} - {t}" \
|
||||||
|
ut_dir="$DOWNLOAD_DIR" \
|
||||||
|
ut_kind=multi \
|
||||||
|
clean=y \
|
||||||
|
skipExtract=y
|
||||||
|
|
||||||
|
FILEBOT_EXIT_CODE=$?
|
||||||
|
set -e # Re-enable exit on error
|
||||||
|
|
||||||
|
if [ $FILEBOT_EXIT_CODE -ne 0 ]; then
|
||||||
|
echo "$(date): FileBot processing failed with exit code $FILEBOT_EXIT_CODE" >> "$LOG_FILE"
|
||||||
|
exit 0 # Don't fail the hook even if FileBot fails
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$(date): FileBot processing completed successfully" >> "$LOG_FILE"
|
||||||
|
|
||||||
|
# Clean up any remaining empty directories
|
||||||
|
find "$DOWNLOAD_DIR" -type d -empty -delete 2>/dev/null || true
|
||||||
|
|
||||||
|
echo "$(date): All processing completed" >> "$LOG_FILE"
|
||||||
|
exit 0
|
||||||
|
''
|
||||||
51
hosts/nas/modules/jellyfin.nix
Normal file
51
hosts/nas/modules/jellyfin.nix
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
{ lib, pkgs, ... }: {
|
||||||
|
# Intel graphics support for hardware transcoding
|
||||||
|
hardware.graphics = {
|
||||||
|
enable = true;
|
||||||
|
extraPackages = with pkgs; [
|
||||||
|
intel-media-driver
|
||||||
|
vpl-gpu-rt
|
||||||
|
intel-compute-runtime
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
# Set VA-API driver to iHD (modern Intel driver)
|
||||||
|
environment.sessionVariables = {
|
||||||
|
LIBVA_DRIVER_NAME = "iHD";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Jellyfin user with render/video groups for GPU access
|
||||||
|
users.users.jellyfin = {
|
||||||
|
isSystemUser = true;
|
||||||
|
group = "jellyfin";
|
||||||
|
home = "/var/lib/jellyfin";
|
||||||
|
createHome = true;
|
||||||
|
extraGroups = [ "render" "video" ];
|
||||||
|
};
|
||||||
|
users.groups.jellyfin = {};
|
||||||
|
|
||||||
|
# Create jellyfin directory
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
"d /var/lib/jellyfin 0755 jellyfin jellyfin - -"
|
||||||
|
];
|
||||||
|
|
||||||
|
services.jellyfin = {
|
||||||
|
enable = true;
|
||||||
|
openFirewall = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Override systemd hardening for GPU access
|
||||||
|
systemd.services.jellyfin = {
|
||||||
|
serviceConfig = {
|
||||||
|
PrivateUsers = lib.mkForce false; # Disable user namespacing - breaks GPU device access
|
||||||
|
DeviceAllow = [
|
||||||
|
"/dev/dri/card0 rw"
|
||||||
|
"/dev/dri/renderD128 rw"
|
||||||
|
];
|
||||||
|
SupplementaryGroups = [ "render" "video" ]; # Critical: Explicit group membership for GPU access
|
||||||
|
};
|
||||||
|
environment = {
|
||||||
|
LIBVA_DRIVER_NAME = "iHD"; # Ensure service sees this variable
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
24
hosts/nas/modules/power-management.nix
Normal file
24
hosts/nas/modules/power-management.nix
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Power management for NAS
|
||||||
|
# - CPU powersave governor (scales up on demand for transcoding)
|
||||||
|
# - Disk spindown after 15 minutes idle
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
# CPU Power Management - powersave scales up on demand for transcoding
|
||||||
|
powerManagement.cpuFreqGovernor = "powersave";
|
||||||
|
|
||||||
|
# Disk spindown - hdparm for Seagate 18TB drives
|
||||||
|
environment.systemPackages = [ pkgs.hdparm ];
|
||||||
|
|
||||||
|
services.udev.extraRules = ''
|
||||||
|
# Seagate 18TB NAS drives - APM 127 allows spindown, -S 180 = 15 min
|
||||||
|
ACTION=="add", KERNEL=="sd[a-z]", SUBSYSTEM=="block", \
|
||||||
|
ATTRS{model}=="ST18000NM000J*", \
|
||||||
|
RUN+="${pkgs.hdparm}/bin/hdparm -B 127 -S 180 /dev/%k"
|
||||||
|
|
||||||
|
# Toshiba 20TB NAS drives - same settings
|
||||||
|
ACTION=="add", KERNEL=="sd[a-z]", SUBSYSTEM=="block", \
|
||||||
|
ATTRS{model}=="TOSHIBA MG10ACA2*", \
|
||||||
|
RUN+="${pkgs.hdparm}/bin/hdparm -B 127 -S 180 /dev/%k"
|
||||||
|
'';
|
||||||
|
}
|
||||||
120
hosts/nas/modules/pyload.nix
Normal file
120
hosts/nas/modules/pyload.nix
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
let
|
||||||
|
filebotScript = pkgs.callPackage ./filebot-process.nix {};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [
|
||||||
|
"unrar"
|
||||||
|
"filebot"
|
||||||
|
];
|
||||||
|
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
unrar # Required for RAR archive extraction
|
||||||
|
p7zip # Required for 7z and other archive formats
|
||||||
|
];
|
||||||
|
|
||||||
|
# Create directory structure
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
"d /var/lib/downloads 0755 pyload pyload - -"
|
||||||
|
"d /var/lib/multimedia 0775 root jellyfin - -"
|
||||||
|
"d /var/lib/multimedia/movies 0775 jellyfin jellyfin - -"
|
||||||
|
"d /var/lib/multimedia/tv-shows 0775 jellyfin jellyfin - -"
|
||||||
|
"d /var/lib/multimedia/music 0755 jellyfin jellyfin - -"
|
||||||
|
"d /var/lib/multimedia/audiobooks 0775 jellyfin jellyfin - -"
|
||||||
|
|
||||||
|
# PyLoad hook scripts directory (package_finished triggers after download completes)
|
||||||
|
"d /var/lib/pyload/config 0755 pyload pyload - -"
|
||||||
|
"d /var/lib/pyload/config/scripts 0755 pyload pyload - -"
|
||||||
|
"d /var/lib/pyload/config/scripts/package_finished 0755 pyload pyload - -"
|
||||||
|
"L+ /var/lib/pyload/config/scripts/package_finished/filebot-process.sh - - - - ${filebotScript}/bin/filebot-process"
|
||||||
|
];
|
||||||
|
|
||||||
|
# FileBot license secret (only if secrets.yaml exists)
|
||||||
|
sops.secrets.filebot-license = {
|
||||||
|
mode = "0440";
|
||||||
|
owner = "pyload";
|
||||||
|
group = "pyload";
|
||||||
|
path = "/var/lib/pyload/filebot-license.psm";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Extraction passwords for filebot-process script (one password per line)
|
||||||
|
sops.secrets.pyload-extraction-passwords = {
|
||||||
|
mode = "0440";
|
||||||
|
owner = "pyload";
|
||||||
|
group = "pyload";
|
||||||
|
path = "/var/lib/pyload/extraction-passwords.txt";
|
||||||
|
};
|
||||||
|
|
||||||
|
# PyLoad user with jellyfin group membership for multimedia access
|
||||||
|
users.users.pyload = {
|
||||||
|
isSystemUser = true;
|
||||||
|
group = "pyload";
|
||||||
|
home = "/var/lib/pyload";
|
||||||
|
createHome = true;
|
||||||
|
extraGroups = [ "jellyfin" ];
|
||||||
|
};
|
||||||
|
users.groups.pyload = {};
|
||||||
|
|
||||||
|
services.pyload = {
|
||||||
|
enable = true;
|
||||||
|
downloadDirectory = "/var/lib/downloads";
|
||||||
|
listenAddress = "0.0.0.0";
|
||||||
|
port = 8000;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Configure pyload service
|
||||||
|
systemd.services.pyload = {
|
||||||
|
# Add extraction tools to service PATH
|
||||||
|
path = with pkgs; [
|
||||||
|
unrar # For RAR extraction
|
||||||
|
p7zip # For 7z extraction
|
||||||
|
];
|
||||||
|
|
||||||
|
environment = {
|
||||||
|
# Disable SSL certificate verification
|
||||||
|
PYLOAD__GENERAL__SSL_VERIFY = "0";
|
||||||
|
|
||||||
|
# Download speed limiting (150 Mbit/s = 19200 KiB/s)
|
||||||
|
PYLOAD__DOWNLOAD__LIMIT_SPEED = "1";
|
||||||
|
PYLOAD__DOWNLOAD__MAX_SPEED = "19200";
|
||||||
|
|
||||||
|
# Disable ExtractArchive plugin (extraction handled by filebot-process script)
|
||||||
|
PYLOAD__EXTRACTARCHIVE__ENABLED = "0";
|
||||||
|
|
||||||
|
# Enable ExternalScripts plugin for hooks
|
||||||
|
PYLOAD__EXTERNALSCRIPTS__ENABLED = "1";
|
||||||
|
PYLOAD__EXTERNALSCRIPTS__UNLOCK = "1"; # Run hooks asynchronously
|
||||||
|
|
||||||
|
# DdownloadCom plugin: don't fall back to free if premium fails
|
||||||
|
PYLOAD__DDOWNLOADCOM__FALLBACK = "0";
|
||||||
|
};
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
# Bind-mount DNS configuration files into the sandboxed service
|
||||||
|
BindReadOnlyPaths = [
|
||||||
|
"/etc/resolv.conf"
|
||||||
|
"/etc/nsswitch.conf"
|
||||||
|
"/etc/hosts"
|
||||||
|
"/etc/ssl"
|
||||||
|
"/etc/static/ssl"
|
||||||
|
"/run/secrets" # SOPS secrets access for FileBot license
|
||||||
|
];
|
||||||
|
# Bind mount multimedia directory as writable for FileBot hook scripts
|
||||||
|
BindPaths = [ "/var/lib/multimedia" ];
|
||||||
|
|
||||||
|
# Override SystemCallFilter to allow @resources syscalls
|
||||||
|
# FileBot (Java) needs resource management syscalls like setpriority
|
||||||
|
# during cleanup operations. Still block privileged syscalls for security.
|
||||||
|
SystemCallFilter = lib.mkForce [
|
||||||
|
"@system-service"
|
||||||
|
"@resources" # Explicitly allow resource management syscalls
|
||||||
|
"~@privileged" # Still block privileged operations
|
||||||
|
"fchown" # Re-allow fchown for FileBot file operations
|
||||||
|
"fchown32" # 32-bit compatibility
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Open firewall for PyLoad web interface
|
||||||
|
networking.firewall.allowedTCPPorts = [ 8000 ];
|
||||||
|
}
|
||||||
285
hosts/nas/modules/ugreen-leds.nix
Normal file
285
hosts/nas/modules/ugreen-leds.nix
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
# UGREEN DXP4800 LED control
|
||||||
|
# Based on https://github.com/miskcoo/ugreen_leds_controller
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
# Disk mapping: ata port -> LED name
|
||||||
|
# DXP4800 has bays 1-4, all populated
|
||||||
|
diskMapping = {
|
||||||
|
"1" = "disk1";
|
||||||
|
"2" = "disk2";
|
||||||
|
"3" = "disk3";
|
||||||
|
"4" = "disk4";
|
||||||
|
};
|
||||||
|
|
||||||
|
# LED colors (R G B)
|
||||||
|
colors = {
|
||||||
|
healthy = "0 255 0"; # Green
|
||||||
|
activity = "0 255 0"; # Green blink
|
||||||
|
standby = "0 0 255"; # Dim blue when sleeping
|
||||||
|
fail = "255 0 0"; # Red
|
||||||
|
network = "0 255 0"; # Green
|
||||||
|
power = "255 255 255"; # White
|
||||||
|
};
|
||||||
|
|
||||||
|
brightness = 255;
|
||||||
|
standbyBrightness = 30; # Dim when in standby
|
||||||
|
refreshInterval = "0.1"; # Seconds between activity checks
|
||||||
|
powerCheckInterval = 30; # Seconds between power state checks
|
||||||
|
|
||||||
|
# Script to initialize LEDs on boot
|
||||||
|
initLedsScript = pkgs.writeShellScript "ugreen-leds-init" ''
|
||||||
|
set -euo pipefail
|
||||||
|
PATH="${lib.makeBinPath [ pkgs.ugreen-leds-cli ]}:$PATH"
|
||||||
|
|
||||||
|
# Wait for i2c device to be available
|
||||||
|
for i in $(seq 1 30); do
|
||||||
|
if [ -e /dev/i2c-0 ]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
# Initialize power LED - solid white
|
||||||
|
ugreen_leds_cli power -on -color ${colors.power} -brightness ${toString brightness} || true
|
||||||
|
|
||||||
|
# Initialize network LED - will be controlled by netdevmon
|
||||||
|
ugreen_leds_cli netdev -off || true
|
||||||
|
|
||||||
|
# Initialize disk LEDs based on mapping
|
||||||
|
${lib.concatStringsSep "\n" (lib.mapAttrsToList (ata: led: ''
|
||||||
|
ugreen_leds_cli ${led} -off || true
|
||||||
|
'') diskMapping)}
|
||||||
|
|
||||||
|
echo "UGREEN LEDs initialized"
|
||||||
|
'';
|
||||||
|
|
||||||
|
# Disk activity monitoring script
|
||||||
|
diskMonitorScript = pkgs.writeShellScript "ugreen-diskiomon" ''
|
||||||
|
set -euo pipefail
|
||||||
|
PATH="${lib.makeBinPath [ pkgs.ugreen-leds-cli pkgs.coreutils pkgs.gnugrep pkgs.gawk pkgs.smartmontools pkgs.hdparm ]}:$PATH"
|
||||||
|
|
||||||
|
# Build device -> LED mapping by checking ata ports
|
||||||
|
declare -A devices
|
||||||
|
declare -A diskio_data
|
||||||
|
declare -A disk_healthy
|
||||||
|
declare -A disk_standby
|
||||||
|
|
||||||
|
# Discover disks based on ata port mapping
|
||||||
|
for path in /dev/disk/by-path/pci-*-ata-*; do
|
||||||
|
[ -e "$path" ] || continue
|
||||||
|
# Skip partitions
|
||||||
|
[[ "$path" == *-part* ]] && continue
|
||||||
|
|
||||||
|
# Extract ata port number (e.g., ata-2 -> 2)
|
||||||
|
ata_port=$(echo "$path" | grep -oP 'ata-\K[0-9]+' | head -1)
|
||||||
|
|
||||||
|
case "$ata_port" in
|
||||||
|
${lib.concatStringsSep "\n " (lib.mapAttrsToList (ata: led: ''
|
||||||
|
${ata})
|
||||||
|
device=$(readlink -f "$path")
|
||||||
|
short_name=$(basename "$device")
|
||||||
|
devices["${led}"]="$short_name"
|
||||||
|
echo "Mapped $short_name (ata-${ata}) -> ${led}"
|
||||||
|
;;'') diskMapping)}
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ''${#devices[@]} -eq 0 ]; then
|
||||||
|
echo "No disks found matching ATA ports, exiting"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set initial LED state for discovered disks
|
||||||
|
for led in "''${!devices[@]}"; do
|
||||||
|
device="''${devices[$led]}"
|
||||||
|
|
||||||
|
# Check SMART health (this will wake the disk at boot, which is acceptable)
|
||||||
|
if smartctl -H "/dev/$device" 2>/dev/null | grep -q "PASSED"; then
|
||||||
|
disk_healthy["$led"]=1
|
||||||
|
ugreen_leds_cli "$led" -on -color ${colors.healthy} -brightness ${toString brightness} || true
|
||||||
|
else
|
||||||
|
disk_healthy["$led"]=0
|
||||||
|
ugreen_leds_cli "$led" -on -color ${colors.fail} -brightness ${toString brightness} || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Initialize tracking
|
||||||
|
diskio_data["$led"]=""
|
||||||
|
disk_standby["$led"]=0
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Starting disk activity monitoring for ''${#devices[@]} disk(s)"
|
||||||
|
|
||||||
|
# Function to update LED based on current state
|
||||||
|
update_led() {
|
||||||
|
local led="$1"
|
||||||
|
local device="''${devices[$led]}"
|
||||||
|
|
||||||
|
# Check power state without waking disk
|
||||||
|
local power_state
|
||||||
|
power_state=$(hdparm -C "/dev/$device" 2>/dev/null | grep -oP '(standby|active/idle|active|idle)' | head -1 || echo "unknown")
|
||||||
|
|
||||||
|
if [[ "$power_state" == "standby" ]]; then
|
||||||
|
if [[ "''${disk_standby[$led]}" != "1" ]]; then
|
||||||
|
# Disk just went to standby - dim the LED
|
||||||
|
disk_standby["$led"]=1
|
||||||
|
ugreen_leds_cli "$led" -on -color ${colors.standby} -brightness ${toString standbyBrightness} || true
|
||||||
|
echo "Disk $device entered standby, dimming LED"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ "''${disk_standby[$led]}" == "1" ]]; then
|
||||||
|
# Disk woke up - restore health-based color
|
||||||
|
disk_standby["$led"]=0
|
||||||
|
if [[ "''${disk_healthy[$led]}" == "1" ]]; then
|
||||||
|
ugreen_leds_cli "$led" -on -color ${colors.healthy} -brightness ${toString brightness} || true
|
||||||
|
else
|
||||||
|
ugreen_leds_cli "$led" -on -color ${colors.fail} -brightness ${toString brightness} || true
|
||||||
|
fi
|
||||||
|
echo "Disk $device woke up, restoring LED"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Background power state checker
|
||||||
|
check_power_states() {
|
||||||
|
while true; do
|
||||||
|
sleep ${toString powerCheckInterval}
|
||||||
|
for led in "''${!devices[@]}"; do
|
||||||
|
update_led "$led"
|
||||||
|
done
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Start power state checker in background
|
||||||
|
check_power_states &
|
||||||
|
POWER_CHECK_PID=$!
|
||||||
|
trap "kill $POWER_CHECK_PID 2>/dev/null || true" EXIT
|
||||||
|
|
||||||
|
# Main activity monitoring loop
|
||||||
|
while true; do
|
||||||
|
for led in "''${!devices[@]}"; do
|
||||||
|
device="''${devices[$led]}"
|
||||||
|
stat_file="/sys/block/$device/stat"
|
||||||
|
|
||||||
|
if [ -f "$stat_file" ]; then
|
||||||
|
new_stat=$(cat "$stat_file" 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
if [ -n "$new_stat" ] && [ "''${diskio_data[$led]}" != "$new_stat" ]; then
|
||||||
|
# Activity detected - disk must be awake now
|
||||||
|
if [[ "''${disk_standby[$led]}" == "1" ]]; then
|
||||||
|
disk_standby["$led"]=0
|
||||||
|
if [[ "''${disk_healthy[$led]}" == "1" ]]; then
|
||||||
|
ugreen_leds_cli "$led" -on -color ${colors.healthy} -brightness ${toString brightness} || true
|
||||||
|
else
|
||||||
|
ugreen_leds_cli "$led" -on -color ${colors.fail} -brightness ${toString brightness} || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Trigger LED blink for activity
|
||||||
|
if [ -e "/sys/class/leds/$led/shot" ]; then
|
||||||
|
echo 1 > "/sys/class/leds/$led/shot" 2>/dev/null || true
|
||||||
|
else
|
||||||
|
ugreen_leds_cli "$led" -blink 100 100 2>/dev/null || true
|
||||||
|
sleep 0.05
|
||||||
|
ugreen_leds_cli "$led" -on 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
diskio_data["$led"]="$new_stat"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
sleep ${refreshInterval}
|
||||||
|
done
|
||||||
|
'';
|
||||||
|
|
||||||
|
# Network activity monitoring script
|
||||||
|
netMonitorScript = pkgs.writeShellScript "ugreen-netdevmon" ''
|
||||||
|
set -euo pipefail
|
||||||
|
PATH="${lib.makeBinPath [ pkgs.ugreen-leds-cli pkgs.coreutils pkgs.iproute2 ]}:$PATH"
|
||||||
|
|
||||||
|
INTERFACE="$1"
|
||||||
|
CHECK_INTERVAL=60
|
||||||
|
|
||||||
|
echo "Starting network monitoring on $INTERFACE"
|
||||||
|
|
||||||
|
# Configure LED to trigger on network activity
|
||||||
|
led_path="/sys/class/leds/netdev"
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
# Check if interface is up
|
||||||
|
if ip link show "$INTERFACE" 2>/dev/null | grep -q "state UP"; then
|
||||||
|
# Link is up - set green
|
||||||
|
ugreen_leds_cli netdev -on -color ${colors.network} -brightness ${toString brightness} || true
|
||||||
|
|
||||||
|
# Try to enable hardware trigger for activity indication
|
||||||
|
if [ -e "$led_path/device_name" ]; then
|
||||||
|
echo "$INTERFACE" > "$led_path/device_name" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
if [ -e "$led_path/rx" ]; then
|
||||||
|
echo 1 > "$led_path/rx" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
if [ -e "$led_path/tx" ]; then
|
||||||
|
echo 1 > "$led_path/tx" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Link is down - turn off
|
||||||
|
ugreen_leds_cli netdev -off || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep $CHECK_INTERVAL
|
||||||
|
done
|
||||||
|
'';
|
||||||
|
|
||||||
|
in
|
||||||
|
{
|
||||||
|
# Load i2c-dev kernel module for LED controller communication
|
||||||
|
boot.kernelModules = [ "i2c-dev" ];
|
||||||
|
|
||||||
|
# Install CLI tool
|
||||||
|
environment.systemPackages = [ pkgs.ugreen-leds-cli ];
|
||||||
|
|
||||||
|
# LED initialization service - runs once at boot
|
||||||
|
systemd.services.ugreen-leds-init = {
|
||||||
|
description = "Initialize UGREEN NAS LEDs";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
after = [ "local-fs.target" "systemd-modules-load.service" ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
RemainAfterExit = true;
|
||||||
|
ExecStart = "${initLedsScript}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Disk activity monitoring service
|
||||||
|
systemd.services.ugreen-diskiomon = {
|
||||||
|
description = "UGREEN disk activity LED monitor";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
after = [ "ugreen-leds-init.service" "local-fs.target" ];
|
||||||
|
requires = [ "ugreen-leds-init.service" ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "simple";
|
||||||
|
ExecStart = "${diskMonitorScript}";
|
||||||
|
Restart = "always";
|
||||||
|
RestartSec = "5s";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Network activity monitoring service (template for interface)
|
||||||
|
systemd.services."ugreen-netdevmon@" = {
|
||||||
|
description = "UGREEN network LED monitor for %i";
|
||||||
|
after = [ "ugreen-leds-init.service" "network-online.target" ];
|
||||||
|
requires = [ "ugreen-leds-init.service" ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "simple";
|
||||||
|
ExecStart = "${netMonitorScript} %i";
|
||||||
|
Restart = "always";
|
||||||
|
RestartSec = "10s";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Enable network monitoring for primary interface
|
||||||
|
systemd.services."ugreen-netdevmon@enp2s0" = {
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
};
|
||||||
|
}
|
||||||
48
hosts/nas/secrets.yaml
Normal file
48
hosts/nas/secrets.yaml
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
pyload-extraction-passwords: ENC[AES256_GCM,data:zOvPYcnvcg2OwJaCZovYQz87ZN9DdpKX1Re1/v24daw0WGBG3sGeJn1q+LDfjPIMy487CdY=,iv:loWfUcIw30kVXchmXwAts10FNUGxSsTY2UVRKs0RTJ8=,tag:WlTYugSv2ApR496Uc1KPEg==,type:str]
|
||||||
|
cyberghost-auth: ENC[AES256_GCM,data:v8PlO2qi06p2FZR1iFbHAVPr0k+X/A==,iv:oEzIIZ7KiVJ5EpMT2YMgvMZSJZwtIsnTWwkMXxl/R4w=,tag:+NOMggSKloW0SOYxopHrYA==,type:str]
|
||||||
|
cyberghost-ca: ENC[AES256_GCM,data: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,iv:mWBG6fP3do+VzwyBlCVXsWiywRNiyMiWNaxlYFTkms0=,tag:94YPWZtVy1viq0KbclF6LA==,type:str]
|
||||||
|
cyberghost-cert: ENC[AES256_GCM,data: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,iv:kavsBNAUcK7vHOJnj9nyX4D9dHzgP3aBwCLQb9umBJo=,tag:aeTpc2vpO72R45cjBR+cFw==,type:str]
|
||||||
|
cyberghost-key: ENC[AES256_GCM,data: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,iv:DmcNUOhsi9doTYta+s65BFpuIgiK7QAjAorfVq/VGLA=,tag:c/mZS7ZnasX5XX4HIx80AA==,type:str]
|
||||||
|
filebot-license: ENC[AES256_GCM,data:Ib+NXsb0dJfBqm5GYvXxGMOQk3zL28oeS3VXsk4pFIGV5XsakqZRZb307X1oJnafMVXva/eQppraI8CqjwBDzy3AZSnwpFgj96/zvPWZi12IWemnqhFUDFZGaMG4Scg6Ugcq8hwJqU39Mf5JehSbJ/PoULg/Ovekj8R0nG7WlmQsqxpZWXWeAdfYZHw1/4MQTX/IGZG82SdsdFc4J9jTedks0yvW5MLkS3azZSFDqY2WXAMVa5J9jXEcie58k6p0QqAUpWGltoXz1tdN/mTnVV7PA35wReS/Ejm11KS5AsjkTIZRLeJ14BL0vALUh3z6JSdr8kPG1S31MMYxarzGBqjI78LfFmFynGonAlqj/jPoxA7AXOew5xRfoEqnxf8lSUf2F2n7PF+B1SgY/hwFvwUeoaA4ep8uFugT7pCvg3N+FmfsV4TAP6tfU75WjwZ07UKQ0t63C/aDI1lZZjfKDzXDFqliD6omhFPUVxhGIo9gBkvOZj6ujdn7ErWohxAc7JSVsDJ8nBKqVGXMMgecWHyqU5LB9+3CKkE32vMy6fbGVlHhgRI4EofVCiuCn2Opg8txCz7Rh8eU297xotIjfCzhPjaY0CmD0dm/8SVLT9Tt3qQMDzlzolcWg+9o3e4bbjSOzliR+4c4fCGBPxZvBPd4kZ87eo22m6WqtS18tMTD1tq+8nVjpHFyrsyyMZtfv9/QXug0SFhJpUz6izCcl+iiNiQIb+DBi4Oifhr88Ji+Y4iQrj7mPUGzNr/T1x/uSBabv83HPsZUa5+OBrlbvp/Ya2ktIagiyv+JrHmcFMkVGYvFK4idLsREcvteduP7XeWaOXuJZJ5GOPZPDM+8DSxv3eOULH1B25J1/838vaVKZM3ukQkiybYTJWVV2co=,iv:G6bhfqx0go6vbJ2zwXkSbHLt5WdDRwu2o4BsCXw5Rlw=,tag:msCGdlefM3M8lbQWJPcOgA==,type:str]
|
||||||
|
sops:
|
||||||
|
age:
|
||||||
|
- recipient: age14grjcxaq4h55yfnjxvnqhtswxhj9sfdcvyas4lwvpa8py27pjy2sv3g6v7
|
||||||
|
enc: |
|
||||||
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvemExcjFLT2tjTlhtMlFl
|
||||||
|
UFlCQWhtUDc5YmFHU21SdDBoMXNCV2dlbXl3CnJzVmpMcXUwbWx1a1VLUVRhSnFu
|
||||||
|
MmtmYjVESVJpdFh1UmtwSWl5WE5WZFkKLS0tIE5wZzQ0MENna2EwMzVDUU9QcDlk
|
||||||
|
bitkdVVwM0l4ajJVYldWM2JqV05tUzgKsJem/g4ckwrmiTJgwtHc98zALWlwmVgH
|
||||||
|
+O0nH3kcU54SjDQYVRKUWdaCNbsXHEN9wqICS9q0Ill7pD2K0ElZLg==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
- recipient: age1exny8unxynaw03yu8ppahu5z28uermghr8ag34e7kdqnaduq9stsyettzz
|
||||||
|
enc: |
|
||||||
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBEb1dtSlJMKzFreWtmclBE
|
||||||
|
YUgvYXBQb1FtaFhZRjlqTWk1aUZkcjhCbjBFCi9rSUMzdWFkL3I3c3E0bUxERkVN
|
||||||
|
TDFaUCtHWE5xNEN4NzNXTlBWWnpYR2sKLS0tIDQ5dTM3S0JQdGJvaE0rTkZYWXNN
|
||||||
|
aTJQNUZuMW5kZVNJQ0lObkZRdzVyZ3MKwfD8PgUM1kHCa1aaDAp0Iv3zaSGsOWS8
|
||||||
|
f3W8gUMV2Qv1FC4hBccbYH2bHuq5ENVhkleIyE51GT+Ckwt5oR14vw==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
- recipient: age1v6p8dan2t3w9h94fz4flldl32082j3s9x6zqq7u5j66keth9aphsd6pvch
|
||||||
|
enc: |
|
||||||
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB0dE5zaEpBSEYvVE5QTjVF
|
||||||
|
dldFY2t3YThTalowbkMrckhUWjhGdXFVNG1BCldqV2lOS280Z0xjQXo4THlPenQ2
|
||||||
|
WVRiWDAxSFgzQUkvSjVZUEpBNzZkR0kKLS0tIC9rOHAvSUZadCs5OXhheFpERzlx
|
||||||
|
QmRCYldBUW9zeTF3cmtUOXVuV2pOMEEKDJC7lyekw9TQmuwfPRb9UsUgqdbAVaxy
|
||||||
|
tZYmhSYhUFBOUyJ7xwiIfMgOu5A4D2p/q+T2MPCmeOSLUDyycE8Zuw==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
- recipient: age1x3elhtccp4u8ha5ry32juj9fkpg0qg7qqx4gduuehgwwnnhcxp8s892hek
|
||||||
|
enc: |
|
||||||
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBzdkUramxKVFVZQVJVMU9E
|
||||||
|
a1pYck8yOEhpZWZGcktxYkR0U290dll6VmdnCnZWZU9uWXFWZ2ZrVnp0cDBteGdr
|
||||||
|
azVGVHg5Y0VuZi82UUtkWUtLeDA3UWcKLS0tIDNJZGJQNmpHdFpQUVdiMHh3djhP
|
||||||
|
WURRbGNNWWJvKzZabGxyd3NXN3lKZ2cKryVInc722ZsjoiYel0YYAQZUsgXDx0by
|
||||||
|
Ds65yQDcI0ttbmMyFN8oYqD7pnOaD1aZYg6cxqzUVPen9iqCkclMwg==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
lastmodified: "2025-12-08T12:02:01Z"
|
||||||
|
mac: ENC[AES256_GCM,data:K1kelYSO6R1kU3hLQVmlPI3vn9p4uEHDQnb7eVgn5PH/HFlqJrRj9HfagD/yKT0hBIehC3R8rxv73SeXacBcCaBx+A1Ty1fj/K18oQdEpFlOWxYhIvRX23NHaaqudFdVRiVg23spOoTgP48+mSzJdE4dk3jQcm94yxiUQy9kBSw=,iv:iSL9knAzk0SLXDJ1m6xy+Vkv6RqtUP2lzcluQTdKG5g=,tag:Z8I+UY/taf/uq4sQ7qIUEg==,type:str]
|
||||||
|
unencrypted_suffix: _unencrypted
|
||||||
|
version: 3.11.0
|
||||||
1
hosts/nas/utils
Symbolic link
1
hosts/nas/utils
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../utils
|
||||||
@@ -1 +1 @@
|
|||||||
https://channels.nixos.org/nixos-25.05
|
https://channels.nixos.org/nixos-25.11
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ in {
|
|||||||
./cachix.nix
|
./cachix.nix
|
||||||
./users
|
./users
|
||||||
|
|
||||||
|
./modules/epicenter.nix
|
||||||
|
|
||||||
# ./modules/steam.nix
|
# ./modules/steam.nix
|
||||||
./modules/fingerprint.nix
|
./modules/fingerprint.nix
|
||||||
./modules/set-nix-channel.nix
|
./modules/set-nix-channel.nix
|
||||||
@@ -189,6 +191,8 @@ in {
|
|||||||
networking.networkmanager.enable = true; # Easiest to use and most distros use this by default.
|
networking.networkmanager.enable = true; # Easiest to use and most distros use this by default.
|
||||||
networking.extraHosts = ''
|
networking.extraHosts = ''
|
||||||
77.119.230.30 vpn.cloonar.com
|
77.119.230.30 vpn.cloonar.com
|
||||||
|
23.88.38.1 api.ebs.amz.at
|
||||||
|
23.88.38.1 ebs.amz.at
|
||||||
'';
|
'';
|
||||||
|
|
||||||
# Set your time zone.
|
# Set your time zone.
|
||||||
@@ -287,8 +291,9 @@ in {
|
|||||||
settings = {
|
settings = {
|
||||||
auto-optimise-store = true;
|
auto-optimise-store = true;
|
||||||
experimental-features = [ "nix-command" "flakes" ];
|
experimental-features = [ "nix-command" "flakes" ];
|
||||||
max-jobs = 12;
|
max-jobs = 4;
|
||||||
cores = 2;
|
cores = 4;
|
||||||
|
max-substitution-jobs = 16;
|
||||||
};
|
};
|
||||||
gc = {
|
gc = {
|
||||||
automatic = true;
|
automatic = true;
|
||||||
@@ -302,7 +307,7 @@ in {
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
services.xserver.desktopManager.gnome.extraGSettingsOverrides = ''
|
services.desktopManager.gnome.extraGSettingsOverrides = ''
|
||||||
[org.gnome.desktop.interface]
|
[org.gnome.desktop.interface]
|
||||||
cursor-size=24
|
cursor-size=24
|
||||||
'';
|
'';
|
||||||
|
|||||||
@@ -115,7 +115,7 @@
|
|||||||
hardware.graphics = {
|
hardware.graphics = {
|
||||||
enable = true;
|
enable = true;
|
||||||
extraPackages = with pkgs; [
|
extraPackages = with pkgs; [
|
||||||
vaapiVdpau
|
libva-vdpau-driver
|
||||||
libvdpau-va-gl
|
libvdpau-va-gl
|
||||||
libva
|
libva
|
||||||
libva-utils
|
libva-utils
|
||||||
|
|||||||
@@ -26,14 +26,13 @@ in
|
|||||||
description = "Bitwarden Desktop";
|
description = "Bitwarden Desktop";
|
||||||
after = [ "graphical-session.target" "network-online.target" ];
|
after = [ "graphical-session.target" "network-online.target" ];
|
||||||
wantedBy = [ "graphical-session.target" ];
|
wantedBy = [ "graphical-session.target" ];
|
||||||
serviceConfig.ExecStart = "${pkgs.bitwarden}/bin/bitwarden";
|
serviceConfig.ExecStart = "${pkgs.bitwarden-desktop}/bin/bitwarden-desktop";
|
||||||
serviceConfig.Restart = "on-abort";
|
serviceConfig.Restart = "on-abort";
|
||||||
};
|
};
|
||||||
|
|
||||||
#### Handy tools #############################################################
|
#### Handy tools #############################################################
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
goldwarden
|
bitwarden-desktop
|
||||||
bitwarden
|
|
||||||
bitwarden-cli
|
bitwarden-cli
|
||||||
fprintd
|
fprintd
|
||||||
lxqt.lxqt-policykit
|
lxqt.lxqt-policykit
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ in {
|
|||||||
./thunderbird.nix
|
./thunderbird.nix
|
||||||
./bitwarden.nix
|
./bitwarden.nix
|
||||||
./rustdesk.nix
|
./rustdesk.nix
|
||||||
./rustdesk-epicenter.nix
|
|
||||||
./flatpak-packages.nix
|
./flatpak-packages.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -58,10 +57,10 @@ in {
|
|||||||
netflix
|
netflix
|
||||||
networkmanagerapplet
|
networkmanagerapplet
|
||||||
nextcloud-client
|
nextcloud-client
|
||||||
onlyoffice-bin
|
onlyoffice-desktopeditors
|
||||||
obs-studio
|
obs-studio
|
||||||
pavucontrol
|
pavucontrol
|
||||||
pinentry
|
pinentry-gnome3
|
||||||
rbw
|
rbw
|
||||||
rofi-rbw
|
rofi-rbw
|
||||||
swayimg
|
swayimg
|
||||||
@@ -104,7 +103,7 @@ in {
|
|||||||
fonts.packages = with pkgs; [
|
fonts.packages = with pkgs; [
|
||||||
noto-fonts
|
noto-fonts
|
||||||
noto-fonts-cjk-sans
|
noto-fonts-cjk-sans
|
||||||
noto-fonts-emoji
|
noto-fonts-color-emoji
|
||||||
nerd-fonts._0xproto
|
nerd-fonts._0xproto
|
||||||
nerd-fonts.droid-sans-mono
|
nerd-fonts.droid-sans-mono
|
||||||
open-sans
|
open-sans
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
{ config, pkgs, lib, ... }:
|
|
||||||
|
|
||||||
let
|
|
||||||
wrapperScript = pkgs.writeShellScriptBin "rustdesk-wrapper" ''
|
|
||||||
CONFIG_FILE="$HOME/.config/rustdesk/RustDesk2.toml"
|
|
||||||
CONFIG_DIR="$(dirname "$CONFIG_FILE")"
|
|
||||||
|
|
||||||
if [ ! -f "$CONFIG_FILE" ]; then
|
|
||||||
${pkgs.rustdesk-flutter}/bin/rustdesk &
|
|
||||||
RUSTDESK_PID=$!
|
|
||||||
sleep 3
|
|
||||||
kill $RUSTDESK_PID 2>/dev/null || true
|
|
||||||
sleep 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -f "$CONFIG_FILE" ]; then
|
|
||||||
sed -i "s|^rendezvous_server = .*|rendezvous_server = 'tools.epicenter.works:21116'|" "$CONFIG_FILE"
|
|
||||||
sed -i "s|^custom-rendezvous-server = .*|custom-rendezvous-server = 'tools.epicenter.works'|" "$CONFIG_FILE"
|
|
||||||
sed -i "/^key\s*=.*/d" "$CONFIG_FILE"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Launch RustDesk
|
|
||||||
exec ${pkgs.rustdesk-flutter}/bin/rustdesk "$@"
|
|
||||||
'';
|
|
||||||
|
|
||||||
rustdeskEpicenterDesktopItem = pkgs.makeDesktopItem {
|
|
||||||
name = "rustdesk-epicenter";
|
|
||||||
desktopName = "RustDesk Epicenter";
|
|
||||||
exec = "${wrapperScript}/bin/rustdesk-wrapper";
|
|
||||||
icon = "rustdesk"; # Using the standard rustdesk icon
|
|
||||||
categories = [ "Network" "RemoteAccess" ];
|
|
||||||
comment = "Remote desktop software configured for Epicenter";
|
|
||||||
};
|
|
||||||
in {
|
|
||||||
environment.systemPackages = [
|
|
||||||
rustdeskEpicenterDesktopItem
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -20,14 +20,24 @@ in {
|
|||||||
fi
|
fi
|
||||||
'';
|
'';
|
||||||
|
|
||||||
home.activation.addChromeDevtoolsMCP = lib.hm.dag.entryAfter [ "installClaudeCli" ] ''
|
# Disabled: chrome-devtools MCP spawns headless Chromium for every Claude session.
|
||||||
# Add via STDIO transport: Claude spawns `npx -y chrome-devtools-mcp ...`
|
# For frontend projects, enable per-project with:
|
||||||
# Browser must be running with remote debugging on 127.0.0.1:9222.
|
# claude mcp add --scope project chrome-devtools \
|
||||||
if ${config.home.homeDirectory}/.nix-profile/bin/claude mcp add --help >/dev/null 2>&1; then
|
# -- npx -y chrome-devtools-mcp \
|
||||||
${config.home.homeDirectory}/.nix-profile/bin/claude mcp add --scope user chrome-devtools \
|
# --executablePath=${pkgs.ungoogled-chromium}/bin/chromium \
|
||||||
-- npx -y chrome-devtools-mcp --executablePath=${pkgs.ungoogled-chromium}/bin/chromium --isolated=true --headless=true --chromeArg=--ozone-platform=wayland --chromeArg=--enable-features=UseOzonePlatform --chromeArg=--force-device-scale-factor=1 || true
|
# --isolated=true --headless=true \
|
||||||
fi
|
# --chromeArg=--ozone-platform=wayland \
|
||||||
'';
|
# --chromeArg=--enable-features=UseOzonePlatform \
|
||||||
|
# --chromeArg=--force-device-scale-factor=1
|
||||||
|
#
|
||||||
|
# home.activation.addChromeDevtoolsMCP = lib.hm.dag.entryAfter [ "installClaudeCli" ] ''
|
||||||
|
# # Add via STDIO transport: Claude spawns `npx -y chrome-devtools-mcp ...`
|
||||||
|
# # Browser must be running with remote debugging on 127.0.0.1:9222.
|
||||||
|
# if ${config.home.homeDirectory}/.nix-profile/bin/claude mcp add --help >/dev/null 2>&1; then
|
||||||
|
# ${config.home.homeDirectory}/.nix-profile/bin/claude mcp add --scope user chrome-devtools \
|
||||||
|
# -- npx -y chrome-devtools-mcp --executablePath=${pkgs.ungoogled-chromium}/bin/chromium --isolated=true --headless=true --chromeArg=--ozone-platform=wayland --chromeArg=--enable-features=UseOzonePlatform --chromeArg=--force-device-scale-factor=1 || true
|
||||||
|
# fi
|
||||||
|
# '';
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ let
|
|||||||
};
|
};
|
||||||
in {
|
in {
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
unstable.claude-code
|
claude-code # Using custom overlay with version 2.0.55
|
||||||
unstable.code-cursor
|
unstable.code-cursor
|
||||||
unstable.vscode
|
unstable.vscode
|
||||||
# android-studio-full
|
# android-studio-full
|
||||||
@@ -20,7 +20,7 @@ in {
|
|||||||
nixpkgs.config.android_sdk.accept_license = true;
|
nixpkgs.config.android_sdk.accept_license = true;
|
||||||
|
|
||||||
programs.adb.enable = true; # sets up udev + adb group
|
programs.adb.enable = true; # sets up udev + adb group
|
||||||
services.udev.packages = [ pkgs.android-udev-rules ];
|
# android-udev-rules removed in 25.11 - superseded by built-in systemd uaccess rules
|
||||||
|
|
||||||
users.users.dominik.extraGroups = [ "adbusers" ];
|
users.users.dominik.extraGroups = [ "adbusers" ];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ in {
|
|||||||
rbw
|
rbw
|
||||||
sops
|
sops
|
||||||
unzip
|
unzip
|
||||||
|
uv
|
||||||
vim
|
vim
|
||||||
wget
|
wget
|
||||||
wireguard-tools
|
wireguard-tools
|
||||||
@@ -52,11 +53,6 @@ in {
|
|||||||
# Socket activation - only start when needed to save battery
|
# Socket activation - only start when needed to save battery
|
||||||
onBoot = "ignore";
|
onBoot = "ignore";
|
||||||
onShutdown = "shutdown";
|
onShutdown = "shutdown";
|
||||||
qemu = {
|
# qemu.swtpm.enable = true; # enable if you need TPM emulation, etc.
|
||||||
ovmf = {
|
|
||||||
enable = true; # Enable OVMF firmware support
|
|
||||||
};
|
|
||||||
# swtpm.enable = true; # enable if you need TPM emulation, etc.
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
let
|
let
|
||||||
# Wrapper to launch Chromium on Wayland, scale=1, DevTools debugging on 127.0.0.1:9222
|
# Wrapper to launch Chromium on Wayland, scale=1, DevTools debugging on 127.0.0.1:9222
|
||||||
chromiumWaylandWrapper = pkgs.writeShellScriptBin "chromium-mcp" ''
|
chromiumWaylandWrapper = pkgs.writeShellScriptBin "chromium-mcp" ''
|
||||||
exec ${pkgs.chromium}/bin/chromium \
|
exec ${pkgs.ungoogled-chromium}/bin/chromium \
|
||||||
--ozone-platform=wayland \
|
--ozone-platform=wayland \
|
||||||
--enable-features=UseOzonePlatform \
|
--enable-features=UseOzonePlatform \
|
||||||
--force-device-scale-factor=1 \
|
--force-device-scale-factor=1 \
|
||||||
@@ -11,32 +11,13 @@ let
|
|||||||
--remote-debugging-port=9222 \
|
--remote-debugging-port=9222 \
|
||||||
"$@"
|
"$@"
|
||||||
'';
|
'';
|
||||||
|
|
||||||
# Desktop entry that uses our wrapper. The filename will be chromium.desktop
|
|
||||||
chromiumDesktopOverride = pkgs.makeDesktopItem {
|
|
||||||
name = "chromium"; # ← important: must match stock filename to override
|
|
||||||
desktopName = "Chromium";
|
|
||||||
genericName = "Web Browser";
|
|
||||||
comment = "Chromium on Wayland (scale=1) with DevTools remote debugging for MCP";
|
|
||||||
icon = "chromium";
|
|
||||||
exec = "${chromiumWaylandWrapper}/bin/chromium-mcp %U";
|
|
||||||
terminal = false;
|
|
||||||
categories = [ "Network" "WebBrowser" ];
|
|
||||||
mimeTypes = [
|
|
||||||
"text/html" "text/xml" "application/xhtml+xml"
|
|
||||||
"x-scheme-handler/http" "x-scheme-handler/https"
|
|
||||||
"x-scheme-handler/ftp" "x-scheme-handler/chrome"
|
|
||||||
];
|
|
||||||
# If you want extra desktop keys, you can add them as a raw block:
|
|
||||||
};
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
# Tools: Chromium, Node (for MCP server), our wrapper, and the desktop override
|
# Tools: Chromium, Node (for MCP server), our wrapper
|
||||||
environment.systemPackages = [
|
environment.systemPackages = [
|
||||||
pkgs.chromium
|
pkgs.ungoogled-chromium
|
||||||
pkgs.nodejs_22 # 25.05 ships Node 22 LTS; works great for MCP servers
|
pkgs.nodejs_22 # 25.05 ships Node 22 LTS; works great for MCP servers
|
||||||
chromiumWaylandWrapper
|
chromiumWaylandWrapper
|
||||||
chromiumDesktopOverride # ← keep AFTER pkgs.chromium so our .desktop wins
|
|
||||||
];
|
];
|
||||||
|
|
||||||
# Where Codex CLI reads config; we make it system-wide
|
# Where Codex CLI reads config; we make it system-wide
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
-- Set leader key before any other mappings
|
||||||
|
vim.g.mapleader = " "
|
||||||
|
|
||||||
-- vim.opt.expandtab = true
|
-- vim.opt.expandtab = true
|
||||||
-- vim.opt.hidden = true
|
-- vim.opt.hidden = true
|
||||||
-- vim.opt.incsearch = true
|
-- vim.opt.incsearch = true
|
||||||
|
|||||||
@@ -1,54 +1,31 @@
|
|||||||
local status, lspc = pcall(require, 'lspconfig')
|
-- LSP Capabilities (for nvim-cmp integration)
|
||||||
if (not status) then return end
|
|
||||||
|
|
||||||
lspc.clangd.setup{}
|
|
||||||
|
|
||||||
local buf_map = function(bufnr, mode, lhs, rhs, opts)
|
|
||||||
vim.api.nvim_buf_set_keymap(bufnr, mode, lhs, rhs, opts or {
|
|
||||||
silent = true,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
local protocol = require('vim.lsp.protocol')
|
|
||||||
|
|
||||||
local on_attach = function(client, buffnr)
|
|
||||||
if client.server.capabilities.documentFormattingProvider then
|
|
||||||
vim.api.nvim_create_autocmd("BufWritePre", {
|
|
||||||
group = vim.api.nvim_create_augroup("format", { clear = true }),
|
|
||||||
buffer = buffnr,
|
|
||||||
callback = function() vim.lsp.buf.formatting_seq_sync() end
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local capabilities = vim.lsp.protocol.make_client_capabilities()
|
local capabilities = vim.lsp.protocol.make_client_capabilities()
|
||||||
capabilities.textDocument.completion.completionItem.snippetSupport = true
|
capabilities.textDocument.completion.completionItem.snippetSupport = true
|
||||||
capabilities = require('cmp_nvim_lsp').default_capabilities(capabilities)
|
capabilities = require('cmp_nvim_lsp').default_capabilities(capabilities)
|
||||||
|
|
||||||
local servers = { 'ts_ls', 'lua_ls', 'cssls', 'yamlls', 'intelephense', 'gopls' }
|
-- Global LSP configuration
|
||||||
for _, lsp in pairs(servers) do
|
vim.lsp.config('*', {
|
||||||
require('lspconfig')[lsp].setup {
|
capabilities = capabilities,
|
||||||
-- on_attach = on_attach,
|
})
|
||||||
capabilities = capabilities,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
lspc.yamlls.setup({
|
-- Server-specific configurations
|
||||||
|
vim.lsp.config('clangd', {})
|
||||||
|
|
||||||
|
vim.lsp.config('yamlls', {
|
||||||
settings = {
|
settings = {
|
||||||
yaml = {
|
yaml = {
|
||||||
keyOrdering = false,
|
keyOrdering = false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
|
||||||
|
|
||||||
-- autoformat json files with jq
|
|
||||||
vim.api.nvim_create_autocmd("FileType", {
|
|
||||||
pattern = "json",
|
|
||||||
callback = function(ev)
|
|
||||||
vim.bo[ev.buf].formatprg = "jq"
|
|
||||||
print("It's a json file")
|
|
||||||
end,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
-- Enable all LSP servers
|
||||||
|
vim.lsp.enable({ 'clangd', 'ts_ls', 'lua_ls', 'cssls', 'yamlls', 'intelephense', 'gopls' })
|
||||||
|
|
||||||
-- lspc.intelephense.setup()
|
-- JSON file formatting with jq
|
||||||
|
vim.api.nvim_create_autocmd("FileType", {
|
||||||
|
pattern = "json",
|
||||||
|
callback = function(ev)
|
||||||
|
vim.bo[ev.buf].formatprg = "jq"
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|||||||
@@ -1,41 +1,13 @@
|
|||||||
config = {
|
local status_ok, project = pcall(require, "project")
|
||||||
---@usage set to false to disable project.nvim.
|
|
||||||
--- This is on by default since it's currently the expected behavior.
|
|
||||||
active = true,
|
|
||||||
|
|
||||||
on_config_done = nil,
|
|
||||||
|
|
||||||
---@usage set to true to disable setting the current-woriking directory
|
|
||||||
--- Manual mode doesn't automatically change your root directory, so you have
|
|
||||||
--- the option to manually do so using `:ProjectRoot` command.
|
|
||||||
manual_mode = false,
|
|
||||||
|
|
||||||
---@usage Methods of detecting the root directory
|
|
||||||
--- Allowed values: **"lsp"** uses the native neovim lsp
|
|
||||||
--- **"pattern"** uses vim-rooter like glob pattern matching. Here
|
|
||||||
--- order matters: if one is not detected, the other is used as fallback. You
|
|
||||||
--- can also delete or rearangne the detection methods.
|
|
||||||
-- detection_methods = { "lsp", "pattern" }, -- NOTE: lsp detection will get annoying with multiple langs in one project
|
|
||||||
detection_methods = { "pattern" },
|
|
||||||
|
|
||||||
---@usage patterns used to detect root dir, when **"pattern"** is in detection_methods
|
|
||||||
patterns = { ".git", "_darcs", ".hg", ".bzr", ".svn", "Makefile", "package.json", "pom.xml" },
|
|
||||||
|
|
||||||
---@ Show hidden files in telescope when searching for files in a project
|
|
||||||
show_hidden = false,
|
|
||||||
|
|
||||||
---@usage When set to false, you will get a message when project.nvim changes your directory.
|
|
||||||
-- When set to false, you will get a message when project.nvim changes your directory.
|
|
||||||
silent_chdir = true,
|
|
||||||
|
|
||||||
---@usage list of lsp client names to ignore when using **lsp** detection. eg: { "efm", ... }
|
|
||||||
ignore_lsp = {},
|
|
||||||
}
|
|
||||||
|
|
||||||
local status_ok, project = pcall(require, "project_nvim")
|
|
||||||
if not status_ok then
|
if not status_ok then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
project.setup(config)
|
project.setup({
|
||||||
|
use_lsp = false, -- Use pattern matching only (equivalent to old detection_methods = { "pattern" })
|
||||||
|
manual_mode = false,
|
||||||
|
patterns = { ".git", "_darcs", ".hg", ".bzr", ".svn", "Makefile", "package.json", "pom.xml" },
|
||||||
|
show_hidden = false,
|
||||||
|
silent_chdir = true,
|
||||||
|
ignore_lsp = {},
|
||||||
|
})
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ vim.api.nvim_create_autocmd("BufReadPre", {
|
|||||||
pattern = secrets_patterns,
|
pattern = secrets_patterns,
|
||||||
callback = function(args)
|
callback = function(args)
|
||||||
-- Set filetype to yaml before the file is read so syntax highlighting works
|
-- Set filetype to yaml before the file is read so syntax highlighting works
|
||||||
vim.bo.filetype = "yaml"
|
vim.bo[args.buf].filetype = "yaml"
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ vim.api.nvim_create_autocmd("BufReadPost", {
|
|||||||
group = sops_group,
|
group = sops_group,
|
||||||
pattern = secrets_patterns,
|
pattern = secrets_patterns,
|
||||||
callback = function(args)
|
callback = function(args)
|
||||||
local filepath = vim.fn.expand("%:p")
|
local filepath = vim.api.nvim_buf_get_name(args.buf)
|
||||||
|
|
||||||
-- Only decrypt if file exists and has content
|
-- Only decrypt if file exists and has content
|
||||||
if vim.fn.filereadable(filepath) == 1 and vim.fn.getfsize(filepath) > 0 then
|
if vim.fn.filereadable(filepath) == 1 and vim.fn.getfsize(filepath) > 0 then
|
||||||
@@ -98,26 +98,27 @@ vim.api.nvim_create_autocmd("BufReadPost", {
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Detach LSP clients BEFORE replacing buffer to prevent sync errors
|
||||||
|
detach_lsp_clients(args.buf)
|
||||||
|
|
||||||
-- Replace buffer content with decrypted content
|
-- Replace buffer content with decrypted content
|
||||||
vim.api.nvim_buf_set_lines(0, 0, -1, false, vim.split(result, "\n"))
|
-- Strip trailing newline to avoid adding extra empty line
|
||||||
|
vim.api.nvim_buf_set_lines(args.buf, 0, -1, false, vim.split(result:gsub("\n$", ""), "\n"))
|
||||||
|
|
||||||
-- Mark buffer as not modified (since we just loaded it)
|
-- Mark buffer as not modified (since we just loaded it)
|
||||||
vim.bo.modified = false
|
vim.bo[args.buf].modified = false
|
||||||
|
|
||||||
-- Restore cursor position
|
-- Restore cursor position
|
||||||
pcall(vim.api.nvim_win_set_cursor, 0, cursor_pos)
|
pcall(vim.api.nvim_win_set_cursor, 0, cursor_pos)
|
||||||
|
|
||||||
-- Disable swap, backup, and undo files for security
|
-- Disable swap, backup, and undo files for security
|
||||||
vim.bo.swapfile = false
|
vim.bo[args.buf].swapfile = false
|
||||||
vim.bo.backup = false
|
vim.bo[args.buf].backup = false
|
||||||
vim.bo.writebackup = false
|
vim.bo[args.buf].writebackup = false
|
||||||
vim.bo.undofile = false
|
vim.bo[args.buf].undofile = false
|
||||||
|
|
||||||
-- Ensure filetype is set to yaml for syntax highlighting
|
-- Ensure filetype is set to yaml for syntax highlighting
|
||||||
vim.bo.filetype = "yaml"
|
vim.bo[args.buf].filetype = "yaml"
|
||||||
|
|
||||||
-- Detach LSP clients to prevent sync errors when buffer content is replaced
|
|
||||||
detach_lsp_clients(0)
|
|
||||||
|
|
||||||
vim.notify("SOPS: File decrypted successfully", vim.log.levels.INFO)
|
vim.notify("SOPS: File decrypted successfully", vim.log.levels.INFO)
|
||||||
else
|
else
|
||||||
@@ -132,17 +133,22 @@ vim.api.nvim_create_autocmd("BufWriteCmd", {
|
|||||||
group = sops_group,
|
group = sops_group,
|
||||||
pattern = secrets_patterns,
|
pattern = secrets_patterns,
|
||||||
callback = function(args)
|
callback = function(args)
|
||||||
local filepath = vim.fn.expand("%:p")
|
local filepath = vim.api.nvim_buf_get_name(args.buf)
|
||||||
|
|
||||||
if is_secrets_file(filepath) then
|
if not is_secrets_file(filepath) then
|
||||||
-- Guard against double-execution
|
return
|
||||||
if currently_saving[filepath] then
|
end
|
||||||
return
|
|
||||||
end
|
|
||||||
currently_saving[filepath] = true
|
|
||||||
|
|
||||||
|
-- Guard against double-execution
|
||||||
|
if currently_saving[filepath] then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
currently_saving[filepath] = true
|
||||||
|
|
||||||
|
-- Use pcall to ensure guard is always cleared, even on unexpected errors
|
||||||
|
local ok, err = pcall(function()
|
||||||
-- Get current buffer content
|
-- Get current buffer content
|
||||||
local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false)
|
local lines = vim.api.nvim_buf_get_lines(args.buf, 0, -1, false)
|
||||||
local content = table.concat(lines, "\n")
|
local content = table.concat(lines, "\n")
|
||||||
|
|
||||||
-- Check buffer content size before encrypting
|
-- Check buffer content size before encrypting
|
||||||
@@ -153,8 +159,6 @@ vim.api.nvim_create_autocmd("BufWriteCmd", {
|
|||||||
string.format("SOPS: Buffer content too large (%sMB > %sMB limit). Cannot encrypt.", size_mb, limit_mb),
|
string.format("SOPS: Buffer content too large (%sMB > %sMB limit). Cannot encrypt.", size_mb, limit_mb),
|
||||||
vim.log.levels.ERROR
|
vim.log.levels.ERROR
|
||||||
)
|
)
|
||||||
-- Don't write anything, leave buffer marked as modified
|
|
||||||
currently_saving[filepath] = nil
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -162,22 +166,21 @@ vim.api.nvim_create_autocmd("BufWriteCmd", {
|
|||||||
-- This avoids /dev/stdin issues while keeping secrets secure (not in /tmp)
|
-- This avoids /dev/stdin issues while keeping secrets secure (not in /tmp)
|
||||||
local dir = vim.fn.fnamemodify(filepath, ":h")
|
local dir = vim.fn.fnamemodify(filepath, ":h")
|
||||||
local filename = vim.fn.fnamemodify(filepath, ":t")
|
local filename = vim.fn.fnamemodify(filepath, ":t")
|
||||||
local temp_file = string.format("%s/.%s.sops_tmp_%d", dir, filename, os.time())
|
local temp_file = string.format("%s/.%s.sops_tmp_%d_%d", dir, filename, os.time(), vim.loop.hrtime() % 1000000)
|
||||||
|
|
||||||
-- Write plaintext content to temp file
|
-- Write plaintext content to temp file
|
||||||
local temp_f, temp_err = io.open(temp_file, "w")
|
local temp_f, temp_err = io.open(temp_file, "w")
|
||||||
if not temp_f then
|
if not temp_f then
|
||||||
vim.notify("SOPS: Failed to create temp file: " .. (temp_err or "unknown error"), vim.log.levels.ERROR)
|
vim.notify("SOPS: Failed to create temp file: " .. (temp_err or "unknown error"), vim.log.levels.ERROR)
|
||||||
-- Don't write anything, leave buffer marked as modified
|
|
||||||
currently_saving[filepath] = nil
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
temp_f:write(content)
|
temp_f:write(content .. "\n")
|
||||||
temp_f:close()
|
temp_f:close()
|
||||||
|
|
||||||
-- Encrypt temp file with filename override so SOPS matches .sops.yaml rules
|
-- Encrypt temp file with filename override so SOPS matches .sops.yaml rules
|
||||||
-- Uses real filepath for rule matching, temp file for content
|
-- Uses real filepath for rule matching, temp file for content
|
||||||
local cmd = string.format("sops --encrypt --filename-override %s %s",
|
local cmd = string.format("timeout %d sops --encrypt --filename-override %s %s",
|
||||||
|
SOPS_TIMEOUT,
|
||||||
vim.fn.shellescape(filepath),
|
vim.fn.shellescape(filepath),
|
||||||
vim.fn.shellescape(temp_file))
|
vim.fn.shellescape(temp_file))
|
||||||
local encrypted = vim.fn.system(cmd)
|
local encrypted = vim.fn.system(cmd)
|
||||||
@@ -186,16 +189,25 @@ vim.api.nvim_create_autocmd("BufWriteCmd", {
|
|||||||
-- Always clean up temp file, even on error
|
-- Always clean up temp file, even on error
|
||||||
os.remove(temp_file)
|
os.remove(temp_file)
|
||||||
|
|
||||||
|
-- Check for timeout (exit code 124 from timeout command)
|
||||||
|
if sops_exit_code == 124 then
|
||||||
|
vim.notify(
|
||||||
|
string.format("SOPS: Encryption timed out after %d seconds.", SOPS_TIMEOUT),
|
||||||
|
vim.log.levels.ERROR
|
||||||
|
)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
if sops_exit_code == 0 then
|
if sops_exit_code == 0 then
|
||||||
-- Write encrypted content directly to file
|
-- Write encrypted content directly to file
|
||||||
local file, err = io.open(filepath, "w")
|
local file, file_err = io.open(filepath, "w")
|
||||||
if file then
|
if file then
|
||||||
local success, write_err = file:write(encrypted)
|
local success, write_err = file:write(encrypted)
|
||||||
file:close()
|
file:close()
|
||||||
|
|
||||||
if success then
|
if success then
|
||||||
-- Mark buffer as saved
|
-- Mark buffer as saved
|
||||||
vim.bo.modified = false
|
vim.bo[args.buf].modified = false
|
||||||
vim.notify("SOPS: File encrypted and saved successfully", vim.log.levels.INFO)
|
vim.notify("SOPS: File encrypted and saved successfully", vim.log.levels.INFO)
|
||||||
|
|
||||||
-- Re-decrypt to show plaintext in buffer
|
-- Re-decrypt to show plaintext in buffer
|
||||||
@@ -207,37 +219,38 @@ vim.api.nvim_create_autocmd("BufWriteCmd", {
|
|||||||
-- Save cursor position
|
-- Save cursor position
|
||||||
local cursor_pos = vim.api.nvim_win_get_cursor(0)
|
local cursor_pos = vim.api.nvim_win_get_cursor(0)
|
||||||
|
|
||||||
|
-- Detach LSP clients BEFORE replacing buffer to prevent sync errors
|
||||||
|
detach_lsp_clients(args.buf)
|
||||||
|
|
||||||
-- Replace buffer with decrypted content
|
-- Replace buffer with decrypted content
|
||||||
vim.api.nvim_buf_set_lines(0, 0, -1, false, vim.split(decrypted, "\n"))
|
-- Strip trailing newline to avoid adding extra empty line
|
||||||
|
vim.api.nvim_buf_set_lines(args.buf, 0, -1, false, vim.split(decrypted:gsub("\n$", ""), "\n"))
|
||||||
|
|
||||||
-- Mark as not modified since we just saved
|
-- Mark as not modified since we just saved
|
||||||
vim.bo.modified = false
|
vim.bo[args.buf].modified = false
|
||||||
|
|
||||||
-- Restore cursor position
|
-- Restore cursor position
|
||||||
pcall(vim.api.nvim_win_set_cursor, 0, cursor_pos)
|
pcall(vim.api.nvim_win_set_cursor, 0, cursor_pos)
|
||||||
|
|
||||||
-- Detach LSP clients to prevent sync errors when buffer content is replaced
|
|
||||||
detach_lsp_clients(0)
|
|
||||||
else
|
else
|
||||||
vim.notify("SOPS: Could not re-decrypt after save. Buffer may show encrypted content.", vim.log.levels.WARN)
|
vim.notify("SOPS: Could not re-decrypt after save. Buffer may show encrypted content.", vim.log.levels.WARN)
|
||||||
end
|
end
|
||||||
-- Clear guard after successful save
|
|
||||||
currently_saving[filepath] = nil
|
|
||||||
else
|
else
|
||||||
vim.notify("SOPS: Failed to write encrypted content: " .. (write_err or "unknown error"), vim.log.levels.ERROR)
|
vim.notify("SOPS: Failed to write encrypted content: " .. (write_err or "unknown error"), vim.log.levels.ERROR)
|
||||||
-- Don't mark as saved, keep buffer marked as modified
|
|
||||||
currently_saving[filepath] = nil
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
vim.notify("SOPS: Failed to open file for writing: " .. (err or "unknown error"), vim.log.levels.ERROR)
|
vim.notify("SOPS: Failed to open file for writing: " .. (file_err or "unknown error"), vim.log.levels.ERROR)
|
||||||
-- Don't mark as saved, keep buffer marked as modified
|
|
||||||
currently_saving[filepath] = nil
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
vim.notify("SOPS: Failed to encrypt file - NOT SAVED! Error: " .. encrypted, vim.log.levels.ERROR)
|
vim.notify("SOPS: Failed to encrypt file - NOT SAVED! Error: " .. encrypted, vim.log.levels.ERROR)
|
||||||
-- Don't write anything, leave buffer marked as modified
|
|
||||||
currently_saving[filepath] = nil
|
|
||||||
end
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Always clear guard, even if pcall caught an error
|
||||||
|
currently_saving[filepath] = nil
|
||||||
|
|
||||||
|
-- Re-throw unexpected errors so they're visible
|
||||||
|
if not ok then
|
||||||
|
vim.notify("SOPS: Unexpected error during save: " .. tostring(err), vim.log.levels.ERROR)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
@@ -247,7 +260,7 @@ vim.api.nvim_create_autocmd("BufLeave", {
|
|||||||
group = sops_group,
|
group = sops_group,
|
||||||
pattern = secrets_patterns,
|
pattern = secrets_patterns,
|
||||||
callback = function(args)
|
callback = function(args)
|
||||||
if vim.bo.modified then
|
if vim.bo[args.buf].modified then
|
||||||
vim.notify("Warning: Unsaved changes in secrets file!", vim.log.levels.WARN)
|
vim.notify("Warning: Unsaved changes in secrets file!", vim.log.levels.WARN)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
-- none-ls
|
-- none-ls (module is still named "null-ls" for backward compatibility)
|
||||||
local status_ok_nls, none_ls_module = pcall(require, "none-ls")
|
local status_ok_nls, none_ls_module = pcall(require, "null-ls")
|
||||||
if not status_ok_nls then
|
if not status_ok_nls then
|
||||||
vim.notify("none-ls plugin not found or failed to load. Check Nix config and plugin paths.", vim.log.levels.WARN)
|
vim.notify("null-ls plugin not found or failed to load. Check Nix config and plugin paths.", vim.log.levels.WARN)
|
||||||
else
|
else
|
||||||
local nb = none_ls_module.builtins
|
local nb = none_ls_module.builtins
|
||||||
none_ls_module.setup({
|
none_ls_module.setup({
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
vim.g.mapleader = " "
|
|
||||||
|
|
||||||
local function smart_quit()
|
local function smart_quit()
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
local bufnr = vim.api.nvim_get_current_buf()
|
||||||
local modified = vim.api.nvim_buf_get_option(bufnr, "modified")
|
local modified = vim.api.nvim_buf_get_option(bufnr, "modified")
|
||||||
@@ -27,122 +25,77 @@ end
|
|||||||
|
|
||||||
local wk = require("which-key")
|
local wk = require("which-key")
|
||||||
|
|
||||||
wk.setup({})
|
wk.setup({
|
||||||
|
preset = "classic",
|
||||||
wk.register({
|
delay = 0,
|
||||||
["<leader>"] = {
|
triggers = {
|
||||||
|
{ "<auto>", mode = "nxso" },
|
||||||
[";"] = { "<cmd>Alpha<CR>", "Dashboard" },
|
{ " ", mode = "n" }, -- literal space character
|
||||||
["w"] = { "<cmd>w!<CR>", "Save" },
|
},
|
||||||
["q"] = { "<cmd>smart_quit()<CR>", "Quit" },
|
|
||||||
["/"] = { "<Plug>(comment_toggle_linewise_current)", "Comment toggle current line" },
|
|
||||||
["c"] = { "<cmd>BufferKill<CR>", "Close Buffer" },
|
|
||||||
["f"] = { find_project_files, "Find File" },
|
|
||||||
["h"] = { "<cmd>nohlsearch<CR>", "No Highlight" },
|
|
||||||
["t"] = { "<cmd>TodoTelescope keywords=TODO,FIX<CR>", "Find TODO,FIX" },
|
|
||||||
b = {
|
|
||||||
name = "Buffers",
|
|
||||||
j = { "<cmd>BufferLinePick<cr>", "Jump" },
|
|
||||||
f = { "<cmd>Telescope buffers<cr>", "Find" },
|
|
||||||
b = { "<cmd>BufferLineCyclePrev<cr>", "Previous" },
|
|
||||||
n = { "<cmd>BufferLineCycleNext<cr>", "Next" },
|
|
||||||
-- w = { "<cmd>BufferWipeout<cr>", "Wipeout" }, -- TODO: implement this for bufferline
|
|
||||||
e = {
|
|
||||||
"<cmd>BufferLinePickClose<cr>",
|
|
||||||
"Pick which buffer to close",
|
|
||||||
},
|
|
||||||
h = { "<cmd>BufferLineCloseLeft<cr>", "Close all to the left" },
|
|
||||||
l = {
|
|
||||||
"<cmd>BufferLineCloseRight<cr>",
|
|
||||||
"Close all to the right",
|
|
||||||
},
|
|
||||||
D = {
|
|
||||||
"<cmd>BufferLineSortByDirectory<cr>",
|
|
||||||
"Sort by directory",
|
|
||||||
},
|
|
||||||
L = {
|
|
||||||
"<cmd>BufferLineSortByExtension<cr>",
|
|
||||||
"Sort by language",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
-- " Available Debug Adapters:
|
|
||||||
-- " https://microsoft.github.io/debug-adapter-protocol/implementors/adapters/
|
|
||||||
-- " Adapter configuration and installation instructions:
|
|
||||||
-- " https://github.com/mfussenegger/nvim-dap/wiki/Debug-Adapter-installation
|
|
||||||
-- " Debug Adapter protocol:
|
|
||||||
-- " https://microsoft.github.io/debug-adapter-protocol/
|
|
||||||
-- " Debugging
|
|
||||||
g = {
|
|
||||||
name = "Git",
|
|
||||||
g = { Lazygit_toggle, "Lazygit" },
|
|
||||||
j = { "<cmd>lua require 'gitsigns'.next_hunk({navigation_message = false})<cr>", "Next Hunk" },
|
|
||||||
k = { "<cmd>lua require 'gitsigns'.prev_hunk({navigation_message = false})<cr>", "Prev Hunk" },
|
|
||||||
l = { "<cmd>lua require 'gitsigns'.blame_line()<cr>", "Blame" },
|
|
||||||
p = { "<cmd>lua require 'gitsigns'.preview_hunk()<cr>", "Preview Hunk" },
|
|
||||||
r = { "<cmd>lua require 'gitsigns'.reset_hunk()<cr>", "Reset Hunk" },
|
|
||||||
R = { "<cmd>lua require 'gitsigns'.reset_buffer()<cr>", "Reset Buffer" },
|
|
||||||
s = { "<cmd>lua require 'gitsigns'.stage_hunk()<cr>", "Stage Hunk" },
|
|
||||||
u = {
|
|
||||||
"<cmd>lua require 'gitsigns'.undo_stage_hunk()<cr>",
|
|
||||||
"Undo Stage Hunk",
|
|
||||||
},
|
|
||||||
o = { "<cmd>Telescope git_status<cr>", "Open changed file" },
|
|
||||||
b = { "<cmd>Telescope git_branches<cr>", "Checkout branch" },
|
|
||||||
c = { "<cmd>Telescope git_commits<cr>", "Checkout commit" },
|
|
||||||
C = {
|
|
||||||
"<cmd>Telescope git_bcommits<cr>",
|
|
||||||
"Checkout commit(for current file)",
|
|
||||||
},
|
|
||||||
d = {
|
|
||||||
"<cmd>Gitsigns diffthis HEAD<cr>",
|
|
||||||
"Git Diff",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
l = {
|
|
||||||
name = "LSP",
|
|
||||||
a = { "<cmd>lua vim.lsp.buf.code_action()<cr>", "Code Action" },
|
|
||||||
d = { "<cmd>Telescope diagnostics bufnr=0 theme=get_ivy<cr>", "Buffer Diagnostics" },
|
|
||||||
w = { "<cmd>Telescope diagnostics<cr>", "Diagnostics" },
|
|
||||||
-- f = { require("lvim.lsp.utils").format, "Format" },
|
|
||||||
i = { "<cmd>LspInfo<cr>", "Info" },
|
|
||||||
I = { "<cmd>Mason<cr>", "Mason Info" },
|
|
||||||
j = {
|
|
||||||
vim.diagnostic.goto_next,
|
|
||||||
"Next Diagnostic",
|
|
||||||
},
|
|
||||||
k = {
|
|
||||||
vim.diagnostic.goto_prev,
|
|
||||||
"Prev Diagnostic",
|
|
||||||
},
|
|
||||||
l = { vim.lsp.codelens.run, "CodeLens Action" },
|
|
||||||
q = { vim.diagnostic.setloclist, "Quickfix" },
|
|
||||||
r = { vim.lsp.buf.rename, "Rename" },
|
|
||||||
s = { "<cmd>Telescope lsp_document_symbols<cr>", "Document Symbols" },
|
|
||||||
S = {
|
|
||||||
"<cmd>Telescope lsp_dynamic_workspace_symbols<cr>",
|
|
||||||
"Workspace Symbols",
|
|
||||||
},
|
|
||||||
e = { "<cmd>Telescope quickfix<cr>", "Telescope Quickfix" },
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
a = { "<cmd>lua require('telescope.builtin').lsp_code_actions()<cr>", "Code Actions" },
|
|
||||||
d = { "<cmd>lua require('telescope.builtin').lsp_document_diagnostics()<cr>", "LSP Diagnostics" },
|
|
||||||
k = { "<cmd>lua vim.lsp.buf.signature_help()<cr>", "Signature Help" },
|
|
||||||
P = { "<cmd>lua require'telescope'.extensions.projects.projects{}<cr>", "Signature Help" },
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
wk.register(
|
wk.add({
|
||||||
{
|
-- Single key mappings
|
||||||
["/"] = { "<Plug>(comment_toggle_linewise_visual)", "Comment toggle linewise (visual)" },
|
{ "<leader>;", "<cmd>Alpha<CR>", desc = "Dashboard" },
|
||||||
},
|
{ "<leader>w", "<cmd>w!<CR>", desc = "Save" },
|
||||||
{
|
{ "<leader>q", smart_quit, desc = "Quit" },
|
||||||
mode = "v", -- VISUAL mode
|
{ "<leader>/", "<Plug>(comment_toggle_linewise_current)", desc = "Comment toggle current line" },
|
||||||
prefix = "<leader>",
|
{ "<leader>c", "<cmd>BufferKill<CR>", desc = "Close Buffer" },
|
||||||
buffer = nil, -- Global mappings. Specify a buffer number for buffer local mappings
|
{ "<leader>f", find_project_files, desc = "Find File" },
|
||||||
silent = true, -- use `silent` when creating keymaps
|
{ "<leader>h", "<cmd>nohlsearch<CR>", desc = "No Highlight" },
|
||||||
noremap = true, -- use `noremap` when creating keymaps
|
{ "<leader>t", "<cmd>TodoTelescope keywords=TODO,FIX<CR>", desc = "Find TODO,FIX" },
|
||||||
nowait = true, -- use `nowait` when creating keymaps
|
|
||||||
}
|
-- Buffers group
|
||||||
)
|
{ "<leader>b", group = "Buffers" },
|
||||||
|
{ "<leader>bj", "<cmd>BufferLinePick<cr>", desc = "Jump" },
|
||||||
|
{ "<leader>bf", "<cmd>Telescope buffers<cr>", desc = "Find" },
|
||||||
|
{ "<leader>bb", "<cmd>BufferLineCyclePrev<cr>", desc = "Previous" },
|
||||||
|
{ "<leader>bn", "<cmd>BufferLineCycleNext<cr>", desc = "Next" },
|
||||||
|
{ "<leader>be", "<cmd>BufferLinePickClose<cr>", desc = "Pick which buffer to close" },
|
||||||
|
{ "<leader>bh", "<cmd>BufferLineCloseLeft<cr>", desc = "Close all to the left" },
|
||||||
|
{ "<leader>bl", "<cmd>BufferLineCloseRight<cr>", desc = "Close all to the right" },
|
||||||
|
{ "<leader>bD", "<cmd>BufferLineSortByDirectory<cr>", desc = "Sort by directory" },
|
||||||
|
{ "<leader>bL", "<cmd>BufferLineSortByExtension<cr>", desc = "Sort by language" },
|
||||||
|
|
||||||
|
-- Git group
|
||||||
|
{ "<leader>g", group = "Git" },
|
||||||
|
{ "<leader>gg", Lazygit_toggle, desc = "Lazygit" },
|
||||||
|
{ "<leader>gj", "<cmd>lua require 'gitsigns'.next_hunk({navigation_message = false})<cr>", desc = "Next Hunk" },
|
||||||
|
{ "<leader>gk", "<cmd>lua require 'gitsigns'.prev_hunk({navigation_message = false})<cr>", desc = "Prev Hunk" },
|
||||||
|
{ "<leader>gl", "<cmd>lua require 'gitsigns'.blame_line()<cr>", desc = "Blame" },
|
||||||
|
{ "<leader>gp", "<cmd>lua require 'gitsigns'.preview_hunk()<cr>", desc = "Preview Hunk" },
|
||||||
|
{ "<leader>gr", "<cmd>lua require 'gitsigns'.reset_hunk()<cr>", desc = "Reset Hunk" },
|
||||||
|
{ "<leader>gR", "<cmd>lua require 'gitsigns'.reset_buffer()<cr>", desc = "Reset Buffer" },
|
||||||
|
{ "<leader>gs", "<cmd>lua require 'gitsigns'.stage_hunk()<cr>", desc = "Stage Hunk" },
|
||||||
|
{ "<leader>gu", "<cmd>lua require 'gitsigns'.undo_stage_hunk()<cr>", desc = "Undo Stage Hunk" },
|
||||||
|
{ "<leader>go", "<cmd>Telescope git_status<cr>", desc = "Open changed file" },
|
||||||
|
{ "<leader>gb", "<cmd>Telescope git_branches<cr>", desc = "Checkout branch" },
|
||||||
|
{ "<leader>gc", "<cmd>Telescope git_commits<cr>", desc = "Checkout commit" },
|
||||||
|
{ "<leader>gC", "<cmd>Telescope git_bcommits<cr>", desc = "Checkout commit(for current file)" },
|
||||||
|
{ "<leader>gd", "<cmd>Gitsigns diffthis HEAD<cr>", desc = "Git Diff" },
|
||||||
|
|
||||||
|
-- LSP group
|
||||||
|
{ "<leader>l", group = "LSP" },
|
||||||
|
{ "<leader>la", "<cmd>lua vim.lsp.buf.code_action()<cr>", desc = "Code Action" },
|
||||||
|
{ "<leader>ld", "<cmd>Telescope diagnostics bufnr=0 theme=get_ivy<cr>", desc = "Buffer Diagnostics" },
|
||||||
|
{ "<leader>lw", "<cmd>Telescope diagnostics<cr>", desc = "Diagnostics" },
|
||||||
|
{ "<leader>li", "<cmd>LspInfo<cr>", desc = "Info" },
|
||||||
|
{ "<leader>lI", "<cmd>Mason<cr>", desc = "Mason Info" },
|
||||||
|
{ "<leader>lj", vim.diagnostic.goto_next, desc = "Next Diagnostic" },
|
||||||
|
{ "<leader>lk", vim.diagnostic.goto_prev, desc = "Prev Diagnostic" },
|
||||||
|
{ "<leader>ll", vim.lsp.codelens.run, desc = "CodeLens Action" },
|
||||||
|
{ "<leader>lq", vim.diagnostic.setloclist, desc = "Quickfix" },
|
||||||
|
{ "<leader>lr", vim.lsp.buf.rename, desc = "Rename" },
|
||||||
|
{ "<leader>ls", "<cmd>Telescope lsp_document_symbols<cr>", desc = "Document Symbols" },
|
||||||
|
{ "<leader>lS", "<cmd>Telescope lsp_dynamic_workspace_symbols<cr>", desc = "Workspace Symbols" },
|
||||||
|
{ "<leader>le", "<cmd>Telescope quickfix<cr>", desc = "Telescope Quickfix" },
|
||||||
|
|
||||||
|
-- Direct LSP shortcuts
|
||||||
|
{ "<leader>a", "<cmd>lua require('telescope.builtin').lsp_code_actions()<cr>", desc = "Code Actions" },
|
||||||
|
{ "<leader>d", "<cmd>lua require('telescope.builtin').lsp_document_diagnostics()<cr>", desc = "LSP Diagnostics" },
|
||||||
|
{ "<leader>k", "<cmd>lua vim.lsp.buf.signature_help()<cr>", desc = "Signature Help" },
|
||||||
|
{ "<leader>P", "<cmd>lua require'telescope'.extensions.projects.projects{}<cr>", desc = "Projects" },
|
||||||
|
|
||||||
|
-- Visual mode mappings
|
||||||
|
{ "<leader>/", "<Plug>(comment_toggle_linewise_visual)", desc = "Comment toggle linewise (visual)", mode = "v" },
|
||||||
|
})
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ in
|
|||||||
|
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
nodePackages.typescript-language-server
|
nodePackages.typescript-language-server
|
||||||
sumneko-lua-language-server
|
lua-language-server
|
||||||
nest
|
nest
|
||||||
nodePackages.intelephense
|
nodePackages.intelephense
|
||||||
nodePackages.vscode-langservers-extracted
|
nodePackages.vscode-langservers-extracted
|
||||||
@@ -105,9 +105,9 @@ in
|
|||||||
"sops"
|
"sops"
|
||||||
]);
|
]);
|
||||||
in ''
|
in ''
|
||||||
lua << EOF
|
lua << EOF
|
||||||
${luaConfig}
|
${luaConfig}
|
||||||
EOF
|
EOF
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
extraLuaPackages = luaPackages: [ luaPackages.lyaml ];
|
extraLuaPackages = luaPackages: [ luaPackages.lyaml ];
|
||||||
|
|||||||
78
hosts/nb/modules/epicenter.nix
Normal file
78
hosts/nb/modules/epicenter.nix
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
{ lib, pkgs, ... }:
|
||||||
|
let
|
||||||
|
wrapperScript = pkgs.writeShellScriptBin "rustdesk-epicenter-wrapper" ''
|
||||||
|
# Grant epicenter user access to the Wayland socket
|
||||||
|
${pkgs.acl}/bin/setfacl -m u:epicenter:x "$XDG_RUNTIME_DIR"
|
||||||
|
${pkgs.acl}/bin/setfacl -m u:epicenter:rwx "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY"
|
||||||
|
|
||||||
|
# Run rustdesk as epicenter user with absolute path to Wayland socket
|
||||||
|
exec /run/wrappers/bin/sudo -u epicenter \
|
||||||
|
WAYLAND_DISPLAY="$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" \
|
||||||
|
XDG_RUNTIME_DIR=/run/user/1001 \
|
||||||
|
${pkgs.rustdesk-flutter}/bin/rustdesk "$@"
|
||||||
|
'';
|
||||||
|
|
||||||
|
rustdeskEpicenterDesktopItem = pkgs.makeDesktopItem {
|
||||||
|
name = "rustdesk-epicenter";
|
||||||
|
desktopName = "RustDesk Epicenter";
|
||||||
|
exec = "${wrapperScript}/bin/rustdesk-epicenter-wrapper";
|
||||||
|
icon = "rustdesk";
|
||||||
|
categories = [ "Network" "RemoteAccess" ];
|
||||||
|
comment = "Remote desktop software for office user (Epicenter)";
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
environment.systemPackages = [
|
||||||
|
rustdeskEpicenterDesktopItem
|
||||||
|
];
|
||||||
|
|
||||||
|
users.users.epicenter = {
|
||||||
|
isNormalUser = true;
|
||||||
|
extraGroups = [ ]; # Minimal groups
|
||||||
|
};
|
||||||
|
|
||||||
|
users.groups.epicenter = {};
|
||||||
|
|
||||||
|
# Allow dominik to run rustdesk as epicenter user without password
|
||||||
|
security.sudo.extraRules = [
|
||||||
|
{
|
||||||
|
users = [ "dominik" ];
|
||||||
|
runAs = "epicenter";
|
||||||
|
commands = [
|
||||||
|
{
|
||||||
|
command = "${pkgs.rustdesk-flutter}/bin/rustdesk";
|
||||||
|
options = [ "NOPASSWD" "SETENV" ];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
home-manager.users.epicenter = {
|
||||||
|
home.stateVersion = "24.05";
|
||||||
|
home.username = "epicenter";
|
||||||
|
home.homeDirectory = "/home/epicenter";
|
||||||
|
|
||||||
|
# Add rustdesk to the epicenter user's packages
|
||||||
|
home.packages = with pkgs; [
|
||||||
|
rustdesk-flutter
|
||||||
|
];
|
||||||
|
|
||||||
|
# Declaratively configure RustDesk for Epicenter server
|
||||||
|
home.file.".config/rustdesk/RustDesk2.toml" = {
|
||||||
|
force = true;
|
||||||
|
text = ''
|
||||||
|
rendezvous_server = 'rustdesk.helsinki.tools:21116'
|
||||||
|
nat_type = 1
|
||||||
|
serial = 0
|
||||||
|
unlock_pin = '''
|
||||||
|
trusted_devices = '''
|
||||||
|
|
||||||
|
[options]
|
||||||
|
av1-test = 'N'
|
||||||
|
key = '8jkD3HoWK+flkWcAMIqRnyn0jr4r9VPb+JYIbBtb+7k='
|
||||||
|
api-server = 'https://rustdesk.helsinki.tools'
|
||||||
|
custom-rendezvous-server = 'rustdesk.helsinki.tools'
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{ config, pkgs, ... }: {
|
{ config, pkgs, ... }: {
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
chromium
|
ungoogled-chromium
|
||||||
nodejs
|
nodejs
|
||||||
# Graphics and font dependencies
|
# Graphics and font dependencies
|
||||||
freetype
|
freetype
|
||||||
@@ -30,6 +30,6 @@
|
|||||||
];
|
];
|
||||||
|
|
||||||
environment.variables = {
|
environment.variables = {
|
||||||
PUPPETEER_EXECUTABLE_PATH = "${pkgs.chromium}/bin/chromium";
|
PUPPETEER_EXECUTABLE_PATH = "${pkgs.ungoogled-chromium}/bin/chromium";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,46 +1,47 @@
|
|||||||
epicenter_vpn_ca: ENC[AES256_GCM,data:DUvpuL92zpQkK0auXGdHDw+f5gzjMARMroBknmgR+eq1LV5aISdA0XOCw7d3VFpMtHY+tPM4pDEWlnGJDoHDJBSFAUdmbLkDq2DvoDR1RBbsidmlXpvu7UnL4OyCgrN9G3I65HQmCh64/453T0Y5MZKUiFZXn9SZJgOU3h0qOnAiIKTQADXmUo6imhLuUdPxjwiLkNp8zHNystbfUuZkF15J+TXV9yndy/E8E4sFs4uysK9E+VM84v0q75zTf48cheE+cBGI9xOP5QMvSND6MloyGYUTPZOiQyz8M9AmJObvQFryysKf5Q1W1GBiTz9FVuSsr1IM7meljdBYwfxQaA5MurdsXVFYfdRgL6NyL5x7WOd367pgtBBwuVyT+cygg5ITIBc6YTuT8thp/q0BsJkq1OdVQrLa4PK12Tg2IOUg2Za/tLJxxiNWqs1gAmTWEIGAeJWmNgCZAjJIISwTGcdbpdWqhjAEgaaLf9ZD0hUXQ5MmSO+KXzP7lIGgqCXoMEc7W7rn3R2VkIrvaVgCBK3psTg6+CxoPQwnYbUKgPLG7ys54eECJyRfc8YPH1957Q67pYkVD166ZP/sDJfplOGH19QyFnaaSLRLoXCAfWuO72NwO/fSljN4+pmB6Ev1cRCe4mXicz7TTqGG740VOam/JW4OJCrnHVs67cs9/MsVSNZOsI+x44TKJjaFph4onaodDh5P7e52IkfRnHnjK6FjEvYPasUr9YDqUR3ucHxhD9UqvINDwhp3L/zyFb2HRuO1KzEQLzmG96xiJpJxBVl8GOTeNrK2owOf6cCuh6o3iPaAFjFok26gI1ujX/mPbBigOxB6S0cLOLoA7oA+E6L22nsoYdIwjU4b6Y/DQvndgsZFnycLsSA4TRYHUH1Q51fGU3S/zAlB66rYchsw3JqODD51axxEdo5uu/2a6K9c8BSDo+stHBPmGvty6IorhM+17IGwSVrnxBFbSICja/Mi9eHmkUuUQWaXe5iWiNGYOIe0Xsbu4PQANhDE0f4U1LboVdI46uVhBV36zLSRJ5hUYARdmaz+aUSfNSE20xwCQiqd4U1cb2W6ZRER5WOfNFa8LCjk1YyhDY1yKCbo5tZrYZtmo4T47EuH/2uyW9vPtDlAhpZWmJJ0LEbZMhl9hIEAgGYmhnxPIVItJWHgq4O+YavYWvu1qgbdBC/FZJ8xx0uSy48oKCbuTUbIBUHQ37/6wp+IC+FAoYc2CDgCKzYvYjGrjMr+l/bhWE6KqI2DE/8yG4sOZIyrKNOYRq/aqDRkaeu96bLSYZECoLpohEKRNLTFQ+J8btjGX+xak0HRNEX9bxx8Zs3ml8mDKfh11uy05zPMVU4jaLrc5VtvmNdCg1EffbtEIRhi88aP5K2flRLxvSsYODd8iisqJ3CEqa8/C/FoHhWqgs7vk9UeRs46CJjGQ2Nx7UeQhAK8ey8FwqqSPQ6hp6jFnAv5ha583GZm3G8CsapajioHOpNcyYRhUW/ekdQ1E7DafOLRRO0hdWws8fsP/96uuWJ1Ir1ec2pepmh8s9zCZl/CKSU6+PUjX03Y9buZDnAYao5nDFsF5hgi2nLCTRbHnCh/S5C4NL/Lss2gi/9HdQUWr3KNONgoGbdRNS4MHtK/t9MtxQT8FOS54fM76XLygYZhQEQuDHUr3vaihOPKXncPNx+M4IGd+tsOoGADfpZk7W4OLd5jl8OiCulKvmRXzGCrmyofifh6XBE/EDa97j4eXt/fZPhUh+kv7i39mLKiccPUqpq9WYA/pqlMc84PAsewRerk3Z7jygFb2oX8LwYX2vDer565q+74n/y+oqz/CQ7jypoGBC8f9a16h2e2ZuvjQZ2sUdBB0xKwmLHC5mXLRkJYZ8Myt0Bzp0iVnC8P,iv:0GfL3sG36nsg/4BPw32kKMB78TmbN+mLq/mqEFp0yas=,tag:x+kxJsS+Fn7VO3MlOmqgwQ==,type:str]
|
clicknload-proxy-config: ENC[AES256_GCM,data:lGoX0iItw21nFT6n5qZRddiz9O0KGjaktXzHamSBmpE7nZv0INhIic1etwrluOnv4n4+0pn6qGeqLcEYsN3S9zDDshdYgrxmPl1xwpVUkvCIA8MGMSg1rdLByYjMOA==,iv:F0sBov54Rk8te34n70s4pPitQXjCfrKQT1+SgQO4ZZk=,tag:x50kpNEr5hYipdIknRQ99A==,type:str]
|
||||||
epicenter_vpn_cert: ENC[AES256_GCM,data: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,iv:MVId1jgmyhY/iUxnjca5IpYwlzUAsa6Nwchg52AKgRc=,tag:1RASj3dFAYVNphJ4zjXxtA==,type:str]
|
epicenter_vpn_ca: ENC[AES256_GCM,data:qWq8Y2JRQIdEzVOKki8YmSMhpxFmFougQSJtGTaSgak2uP4MRzgrwpKGqDqhkPfqXeD1GSHRJNPe4plIv6//en0IEjV4vvupUrJuSp5cwmjhvbJLWWpUSQOhhDM/sN/ODDPLy+JucUoQRIt3bgrOoWRWro3cPuorGcFJfOedOy2tc7oTrpQBYNyx32kDfh3IHzuaasSFtCAnE2y5TVEXsLcMzkzvBIpTGzHUOG+9tn/nR5Ro7EodQKXfs11pL3mvr/iMlRX4QTSGYfl3pJE4O+Jssn1+/zDd0bNrSU1kKunDsuKDlv9Nkp0hIjpCzc55v74Jrda+UATB3f0muLLgULbsFA3JfaqqufLP/rgYjkFZ14qidjby55Z2+IkoH6U6et2jL1O6BcUaxYo7dbsE8kbNPdk4XPoV+5WYSOKOQrx0xXiW33fQnWq0R1x3k6YJ1oVO1CoxMLadbaAkjhoZeAP+VFviKHoX/GqMGlkox5KgdO4dUz4UcJon6F69wG/LzXf+gb1VBacqx1MltrdN+nUFNTowFor0AofUofWMM6xpn4Xy+gLy3AFQi2+3hkBmPbtgcWEWnTuekhixuPUrO8h7CwXMRgpiBBqeJTQVVoQ1puY/1Rw+K1KXY4QdRwd5laynkU4owvJtgHk1jogUIBpfAOtIq18gOznS1HhgH8s0Af0eU7lSAAPDfRoe5awydb9Wejpjed2LypMwtYE0qEC+tR0cq+NTYnINg9bcAwDz5iRSpKtGPmy8Xj320LAlH5eDyhcxQjF4ZHCtJ9/3QcpbBJoIwWlS2+R0fk4qOLq1/q4RqnP2aDc0CLd0MKx+tJs9yBZyMU8dLJtqTnd7z28odhrZ9E7PmJ/Ky2t7WsLNZF9lOWWbJ2og2Mxsu85HCmntQxjX8Jk17Eyl6jOl5NFnFEWMTPfSsaf3J5HcwffGV3RjdM/XAleHCoNP+ydM/hPra+HsFqoyMYqT/HuZPv6ZMLY1KogBy/17OdgO6Pava7kB717v6t9/eO2FH0LGKYrPUJBha8Yq7vUJFhpABO29vTJSdG6gfrRpPafgKjZig5z8BXNU+R03DiGrL9LdraFUFnu+HSF4YVXd0EGrVfQF7mw7KrH/TsAOc1KOtlHaXFkP/SgZ+etwyC48xCy1sgQi/7t2kozPtM+RcAQJMhF0DHGFQUWwj1HesHsKaOl/HdM+eU7DcMwN01is2lD1HUp63HC5OBItET374DcdqfVUBBjiOt3srd1OWuE+FkrqKpCaxLihu2dHP8SR3DYMlSIrGwy8U09Q3iIe1hD863HVOCcn3uQZHj+g1GOXpUytouUhE9EEC0THEaPNwygNQvSu9AmG+YnFaXn6ZWVokbvf4xfttSc9KUD50qK54gd7Aa32fvzsPH9O6gMtQ18wPmeCzTzVEMBFUO33BI38UKpmKB4/r5k6F53L5YOOvVsD6R5H/0ABoIjXPF8gFW2f8RkGuAH+8dOS3VdOWFvk7ZUQxa8QjMrg09n+khjZ4kPPz42W9uaRVQeXAWLhcNOBeT7U/pT23Z0M3Qfs+NK6JRXhIOj1Os77uSkLYVKuf8yfhbI+1J1/Y7WVe7/W3BkHi7SN7Unyt30QntY+FrAfbFb59aqNptkSlmDX0TbVTWxAeD7NGi/nBnZSJyE40iJ86YzqDjv2E/oS3BWRV023tfp8iljRKKD3lLinheKwaFoZE5s+h2MY1tGkO/LeZlRhxsBznouCinQoqxdxinNxCiSpP8tTxcZ1VW5W9/JxWwoRhTR/WyDVa38vVHaVMVmhCruMDCHlckHMU5qo1+5xNkDNM2kirADqIJ5CygRI+qc5CLVWqmmve/ICG5cGff3JvMTENnptNhkWIzHDemMukJpaPCF5pmH9mP6b,iv:HiTBKc+JD2NETPL4JYOfYCHjNmagZXscpWV0o1QXyfE=,tag:SM+tAq6NfBG5xDnrfqN96A==,type:str]
|
||||||
epicenter_vpn_key: ENC[AES256_GCM,data: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,iv:pB/cNgmHi14ugi6kd+J6poWXX79LMHiiakNa03ibZ0Q=,tag:nLfjOesXDm5/QtwHznJROw==,type:str]
|
epicenter_vpn_cert: ENC[AES256_GCM,data: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,iv:jUlERMQxELOGGZBu9ZZ5Z3pIaNdpiV38BnSCfvkS5wg=,tag:k7I+eceH9Kuk45yHjV9EHA==,type:str]
|
||||||
wg_private_key: ENC[AES256_GCM,data:A80vGf9aMxowC2xME4FIVTmKpSRLNB2tWiUQeP1v8vCRk6Gt8BKYOuXYt04=,iv:vr7qvfr78syrI5pIytjLouPwZcw4xvBTvEUzzv7ibnQ=,tag:qjALlFkd8JocLJqMKFERaw==,type:str]
|
epicenter_vpn_key: ENC[AES256_GCM,data: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,iv:IAxV9RV1hqq1Fn6e78a1Iv353mVZ1Qb/yFpzkeFtvjQ=,tag:deb0M9hJPqzsL0shaDJ1Pg==,type:str]
|
||||||
wg_preshared_key: ENC[AES256_GCM,data:bhXoD95ahDRawoHd5Z35FY0G6Xv0PHwWJf300fHQ5jNsGN1TQKHsIswx8YI=,iv:fBsIWkVZUt8pahuO9daaRBIEEIWsSnFW5Velj9uP2ZY=,tag:RvbCYhnRv0OrjTxjsNFW6g==,type:str]
|
wg_private_key: ENC[AES256_GCM,data:XJ2dZINMd+gKlqdQtI9XZw7nQ5UIx0IWbYdj+HWIa4x/zERGPZy35EWcK4c=,iv:6YWnWwuHHw69S1gq4xrS3p0DXpF6lJt1lmZZ7NJ4uGU=,tag:gpgMkD/e2p7XOy7uacv1WA==,type:str]
|
||||||
wg-cloonar-key: ENC[AES256_GCM,data:ZMEeIZApOD0ij3nPMZeQRwJ4MwVx0sHu08F+m/u6IMHBGid5YwMgxZ7qbLk=,iv:OfIZ9TqBLjToIQi7zRUBATrynBtu0bzXeGVI/EAUPhQ=,tag:mJICT/ak5U76JE/IxJsCKw==,type:str]
|
wg_preshared_key: ENC[AES256_GCM,data:k7NdIMYTes35HKKCpDcPCCoGo3SdO5KlwB8C2EvSAJmRM7VFOfum6TYNae8=,iv:gbYR7OTOsIaFQtchYxblHcoj7dc9D5qz+N8F4+Ru9os=,tag:FfxMVal5j1593s/HHprlYg==,type:str]
|
||||||
cyberghost_user_pass: ENC[AES256_GCM,data:Eaz8iEV9vNZh/bJePmacQ06zU8FfIA==,iv:GcnU10VLVJsoeIU6t6eVjopLsBamvk12DpMbM9BsBv0=,tag:rLYoghkD8O9JepnopJfiuw==,type:str]
|
wg-cloonar-key: ENC[AES256_GCM,data:lHJdTGPA+XexVKA3JCckUkGXQm+ev2p5VhPnHpsYfRiao+rr5+N3byoBw3I=,iv:q2djx79niGelafKNl03OBPfMAcIAyPhEqsiN0UUum3k=,tag:96CU7iCd+Dd1iAKmHWcEkw==,type:str]
|
||||||
cyberghost_ca: ENC[AES256_GCM,data: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,iv:8uQbG4ObsDSS0DeKx24lt1vpfeSms2v7KGRQrKoWwds=,tag:2RoiW5VWLXfMgXA4cbnKBg==,type:str]
|
cyberghost_user_pass: ENC[AES256_GCM,data:ogaoPZDUosjpoCVmiIrvSnVEcCYSUg==,iv:QS8HQbcv4t/Utv3RwVi2xS2mSvnr44z0YwaqKGHGXPk=,tag:bwh91qZDkE4N0So2dbd8iQ==,type:str]
|
||||||
cyberghost_cert: ENC[AES256_GCM,data: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,iv:vYJer+NYyRo/jcpGb66askFA42T+TmSfWTm3DKOIIt4=,tag:RlvqzLfvtJN0HloJZTJb1Q==,type:str]
|
cyberghost_ca: ENC[AES256_GCM,data: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,iv:n1d++hy58nH4kZHEyXrELMCdQ3botkc3tHUA/Czm6iw=,tag:Fcd9qGgOwqQ5uZGbbObhLg==,type:str]
|
||||||
cyberghost_key: ENC[AES256_GCM,data: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,iv:EgSXZvyWmcBxBkAe6asJ2B12FKaLQPy4tRAtCvkys3M=,tag:B0lyX7IRNHX1CqlvBZaSpg==,type:str]
|
cyberghost_cert: ENC[AES256_GCM,data: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,iv:YokQRmSP1F43BhS51wOry5iiQa3f78Rvs50STxeyphY=,tag:k+ZxxVSmurIr2PiJJIoFjQ==,type:str]
|
||||||
openai_api_key: ENC[AES256_GCM,data:lH5Jf/xvtRRwuGYPM3g39J5DTQJowSKVqLtObgXRo+GOfpY/BKb46/R25rkWrOUv5pYK8PXmSm7obEkVBfoaYG0WufR6SzGH+R7hjrNgCzBA6g5pxmlE7CWPlPy6C6XqVgblL8aP2vD9qxJmIw8FYCLRgFHt5hK4d+Gd23BeBnkmPWIH23xyRFhCcb891CoiEEdXL5p1LNAOAW5ePmAyavc/Dlk=,iv:lWXy8/LlWeRVSect87/qsG2nOkmL5W7jpw/DVSnAbEs=,tag:n3pWUJ5uEMXtgt/kHG8J8w==,type:str]
|
cyberghost_key: ENC[AES256_GCM,data:756vFyRjKUgh2mZYadTUaxGA5KwAe7NriRYGS59e5a0uWxPLe5VNeYV01V4z6nHgWVolsEmTDglsLwRZnMwkHhfBbRM4D3JU5py6t0ANznwclOdTy/5vpFML5uujSv3MsloqUXmpR0Bmt0QZRSfa0GNKzXI+q3HZoiQrlDW11oInBVH5wg3Pi5eemAb5f/+wJ7m1i6Je7AaHAwOwPDIumUQc8OzgxJB/R1CFEXg4UgQ7Kkd0fqiVyzxNm/G40+07LV8hqenA/qE6KgdGjR4hE3Jj5BCeSnYJsQ4dIjpDE0s5etxWAg/apZMQUvnvp626ZXhGhzbzOyOYVUaLML3hrCnIYYz6ipdpQCXEtK+Elx/fXz4R+fkmqTuASqRVenEwDwQBl8zGBeyURYP29DqFJtIhDrmLRJLlU6hI3v4PpZaInQbVy10LQmvMX2M1zeW7hL84z9UBW4Gr8i4SzDc6B2xypLCGdUdmThW9jDeQEEkNIbAF3W0LNDZFghxsDeTnIh7fiyrUPWvCJtY92kr+L2v/4bkuJbyC6Ag4dEVnpfdg58qJTW/z7EgqaG/gaB1tWPG8bwXNJeD7IJWLSrTDPG0XJkbQbFH5Ihar8kw+bIUa+0SH/IYVOGzcYkVsvmXEHp5x4akH0bekd3bA+MsRvQc858KAaUcaStQQLYYeWH8azQrGtWdfqlYhT8uqYQk35G2Uti38hTXeiUmm/KkEGUTmoDrdvYRF0C3E6pkxBwcZa0CTvF1oeXptUz8Uk+xrLC+svp8C9fLNb3eWJQ0fgwKiM5t37mJwDw4uH6qhA9ujshrZhATdkSHGT3ZfDYrgBnM1yEAtf591OzKzMsMXTKZmEx4HaVhpTFBk1gi7WF5lFm5NYYFcTN5btDt7CUZDvYZKGYyxWJukg797sR5SLdcjNTNFNdKFJHWfRgTfojOE5KIXZ5GN5TekAL1fE8890zGRsb2lVJF6Nr8aCuluMhZvghPu5Qn554mxaeAx8TVLjSikjgFDAMjca1bhv+5d2CemHx1K4zBrEPBtdttCl4wnJjwLBePIHj5qxzQXsXdSvCxeP7y3KwV3pweGcuNzXZrHgynIAx7NVw9abrtyftIEaUzQvBl9UFQYJDHEsYO5VHISlLtpbN+1TJpOGP3fJwtMfCHAtfvFxzg4O6B8esIl2VSYeA/bNbaYBL2RsA3XPmCwPf3VBfyI/At2DXbHUFIzcgmsKTs7q5fkZUQgCGHvf+eobiaWGRuFDrUulpeQfAE/KpCkkO/dBgUDfWaeoJE46KK9Yo901eIWycPwpTCNtuA+7esjnZpjmV/hyOaF2bi9XTbHKeM+mQRZ96LepyCr1cUhAblDP+F6tI+TdUyRv7iV7sPcikV7p9EOHS9nw6miPTDGNBUKCVXHnWH/SHkMnUMikDbu/FvzTJwnULucaEbHHpFt31xOqWM3T4KdyZiKIQnHUvNQEQCTQwr1u1uj6GPqW/xTKKjIKsd4Gxzws4GsEvr6hE23fy6eHwqab/RvGLBMZkUpvIAqwN1otkL1H3WbkiaYTeaIucuB/QxQu1xigmSrmfW0sv5qFmhRy1Ry+mIvjXs6IG3TYWb31kcLy5xmJRssdhgHSPCnPqHLzetNxMyZ7pd+X/4otlypzyLjuY8BZWp3wst2HXHzq9eqmeclecsCXlVvKOEmIW8Vdd31bJbGeczBgU433ZPmlAmcGO0okFzHsMnBBopHr5cU/18xitGwKGcC6rM0fpnGGQ8+4QUYjlUOl1kms0B7hqlrTOkv7gKEMazChuuB9Mpgw6xmD2cFS7ZW68QbtxUvsXGAOkRMIOpzKviV1aERabCIIccuFyNAdNwgsHopblkdvg5EIE2MH3tRTS2uaJsixrZ1xTDjLql7J2n0FqM1D1iTRK8moi8NFrFWt/ahiCZgj9sQ7HNFU1EdmA11G1F4AFkFHiOQHzHu2k5SiOmN2uY4TO5dlooJefud9aiuGGqk/uX2YiexrYi2XukLAFg9cToG48WOS8HJMKziyxBG5c+bn3uWrgFOC0bqJ7+be7rZGINdxMQXz9xJj3cPayRZFkhS6EP4kBLnJ6OhTR3xBeDNUC3F4QZIqnkEEAT0wHQ6TbgkZpkKgGP2Scs37zb1Q8/ubqniwdg9RdQbCvNOQiyDsX14yTXSAUpJUhF1GGPlTXVuO0gXO0tLxu/SBj0SdZciNyKRP8tDTZYB7/mU35t0v1ZgU47j6HKKrNXR10Ioewmf+ggrKTXzyKbgsBgZpYX8VZVb63rMsOFBQWIsaypiEGmNd9o5wF36/R5IBb8yfHmSdsTbQ9PbY9Bf6BHa2oc8oUW9QzQoUdpCLmnfmX0NIzykhyxFSnL+t1vR6cezPdqEqu3FfIqGJlCkhROKnNTXhQEz0j7BjHoDJjNIBk5kUohR5EsTB1CPUVriHdVtXbpfrLDDgbl/NNOCyEcu4cUHDt1BX2gtZSWd//EzrrYzNgD7jHUlM7VpfhiX1TWnexEGe6VMdGzxVNYyNOHaK7mpL0E5y0tBcJPym1MuYHM3nx7D2RmF6wd4P11qaVHp7qhQvYzcAikjQmk1QeHdxV2Bzva8ZM59N+DTuMQG+G76LH1XkYwHTl7S6ipZeKWaSzCKwkzCUvwHFUBGQqe8xT66ILoqhBO6ECooxbSg2DHP8lPO+yCmuFaVT2yS3j6BVGKiKKKhhKXJqmBAq3SJpKNCag+jBEyB0xjpNNi0NKTN6n1TNhYnsyGIMwK141/8T+D95lKt82smF6KgizrYB/HjQmc8gDJolf1+robTOFV0oqg0vNuLA8iVw1I5b1wkbGwkh5P9//Mxdpi4dVT5zXJwmAzgHNylsNU9IGD7AbQdc50PptILxbdQIRxcVK5+B14iysYMOsnOIOEysp+f24eGx806HTWUBtHjNZFo5iOKMmNgbwy0u3HGL2019bdmwc4gOtRMXLNMKSWHuNeHPbjd82xGoBkgFwnYQPdd12JKOrZTF4sgFfcSRkabd1L0cK+Qac+L0m9pGSCQiIP/uqZPOZtYS49zXiRdIj/TL29dMSm05tsPi+q8qAj9eqg6Tpodnpp4v9lY6RscY7Oj+qYvzxo9ZX0a0YhljE2G2hduip+Fmy5WWqYI8c4l08eecQgBgl7qr/BfCQdkWuM+XQEn+cUuOqGMiCPUnGGO3t/Cs5BjH2jvsIA7p7U+wI/1WkIEH0c3pEfNp32kvL9gIrK7uriERh29Qt+pYFX8m8lsMn8F5VOl4KKhvG3JSzsa/HvyE9PzCQ9Hui5vVqPIQnc66Fw+ra66KQ7GhiRYHfl2cqSR7PSacG1SDdUToFsBpY7LxFBkmJtV5Y6BzTKMKKGMwJ88QKv+2OQe+1vgMhKejUztbLCYUG5lhDmGVPadpQTfwjwImSh/3HAV8zWxI+nqlTCo23ojjGJcqrvK6+7IxMHL/xNYkYwmODg5+q4Fq00/qKomq4U0xUJyoThN3E+pgKoEyWJE0WeJod3EVHw6gl6/XaA/JaDCtzJ+DTG9RityVuEk8DP2u0I4zEg5KpFEOz1h485Gk5xcqAARR3oWs3KpG6mQ3j2zZEckFqpkXvZNFlzjV9tCVyOd8VDx6kypJnm70U1bSEjjBnHw8FREtusT4R1FCRKmuNMyYFHwz08tY8E9kQl3loUuXXxnLS7N8cpeibcRLAPGMIO6MBzYZb6ra3Ttkgqzk1bSY5Er/1rrKq+0HRSBYUkiCsbRKyYPOJ2mBTunu1ah8T5Pc34+x9IKhKPfl2wuaUDHEyY05JXkIs+9eacr+iCZIp2VObkM62KRPA7UZq+57mhJ8KzNu0mXTIMGD8qS1ozBk9JC4nP+AGFoYAVJ10cOfWXbt9lIiAB9oTVQ9IKiw3rehedOirLitmBYKaMIYfG01LDu+4mL6IdTa4SsVkxfSxuHFUylfHixbAQT/m3qZtb/qqX+GvIH+awhGvWG5Y63U9lZvvCl4405vRuxmnQ++fMpv0DHgGj8sQnzIJJ6u0qscxG7MkvKND4F0Pdk+aeiJQaE8Ak7BPdUStTXoqhE3znFbTw+CR1d79rzkAk0OPMbnN3KDd0ZuercIemCkpstE7apIaayUYPkgDgPp2nKEqlnKCp1tz8NRDvf6oU1V43LLE1atMkXmHsG7lzH6oLVdKvlGtl2Hf4kJaxVuJtqdFHiAhZF0roKW+S/W2yXjRgr3zQmEiowNT4VCljwm30OC8GpwsvsCX0RoSrTFKeVq8eOIs6mIMul66zLb4CRJtR9/XHaKMTqf6aMhnsVqhuIkriHdsOC0Bn6+pYVTwy9ck1DidN46VwZ+avybsxXjxZrxCpkwiirso1yjicLoMjiLe0J5gjElOw=,iv:LPskqYo7V7SMKD8HdEXDNwxyiLFLaLr5AdrU5x53R4Q=,tag:zhEYACY4xhl0ba+dBUBT1A==,type:str]
|
||||||
mcp-brave-env: ENC[AES256_GCM,data:FND006AIjGjSkJqx12RVd8NnyDIhqdUBgZxucaAOFlnt7ZRYAHj4xQqgSJuFxg==,iv:Fn/663cGELKYQWg+Ok1uCtNCeIP7zZBJgxhhcsR/Q8M=,tag:v8K6JQ15CYwcLj7vSjyN6g==,type:str]
|
openai_api_key: ENC[AES256_GCM,data:Z5AiYIRQoFSmjErwgJ7mxkxmp5BVOgVy72xwag+PJeRiQAddgNMeeqOh1ka0nP/HLPnbbWgSBgzdXxD+Kx4m6dX3W2AtzO0QE68XDyuM+PKts2pUSDYDmFSWZ7FFlQSQrpuHgnit1xjorpQqRjgVkHXgNObYGvs1wUZwhfUg4zPd9UdTU7daXcUdJ1c9t5G6FuJLMNF3gmqr6j7hMYXPcCGrx18=,iv:SMy/19Jw1CX9OYEQ3VABisZ61fYyVVbGzgeIwlqdtRs=,tag:oakwiviNCiu5jWIwuAQP6Q==,type:str]
|
||||||
rustdesk-cloonar-key: ENC[AES256_GCM,data:nn1PKpOvhs3E1Lqtbx+W3oWon8+j74CrB4KuB+BqjfaHAaKdj+KMstYAv+0=,iv:UGjTZ9LauAALRMvJnqKkHYqGH6rjZMTZPQs0O7/aDd0=,tag:g6IZMCsE1dqZh3QYRuitzw==,type:str]
|
mcp-brave-env: ENC[AES256_GCM,data:JAMYqb9rOYDLM8y8HABGQIvEGZUUKWt/iRJXwLZ/UftHd5iMPLDAHDcRva1MpQ==,iv:wFWJr74H6nX2nOaCUWHdeQkjBDYTEltxE84BTQfqy7I=,tag:cAj9Cjjo9WBVot2rklRWZQ==,type:str]
|
||||||
|
rustdesk-cloonar-key: ENC[AES256_GCM,data:genBO0paccI1pcH8//X8q+88yBeBFfgEd8jKmglEHbvnf2f9DDxfWVrHlX8=,iv:FEHkJ2wPBAKZFhoRRBbLzjkv28DRDJ4npBkTGVEK6l4=,tag:hx9FPzlyDf96BuMhrEXLEQ==,type:str]
|
||||||
sops:
|
sops:
|
||||||
age:
|
age:
|
||||||
- recipient: age14grjcxaq4h55yfnjxvnqhtswxhj9sfdcvyas4lwvpa8py27pjy2sv3g6v7
|
- recipient: age14grjcxaq4h55yfnjxvnqhtswxhj9sfdcvyas4lwvpa8py27pjy2sv3g6v7
|
||||||
enc: |
|
enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBYYWRBbk1wckFYbkZxQllS
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSByeDBlZ00wSDkxaVc1K3Qr
|
||||||
T09LUEljM0VKbUw2cW5hLys1OUZZdFBBSXlnClRvc3YyRFpaQW9GOTVjR1dpdU1Q
|
aGt2YXlHdXJ5UXNhM3R4dnRQT2ZiNGdvcmtNCmxmdDZYQWt6NktVdGNwQjlUOEZG
|
||||||
TWVsYmwyZ3Nxb29HZVdTSXhmV3F2MWMKLS0tIGNZM0cyWmRiMUNFUkhVUkJhUjRR
|
My9mUHFBT3lWOFc0N0JNcWh2dm00a0kKLS0tIFhud215VVA1SWVFdVZDZHZjY3ZK
|
||||||
SHpCTVR6VW9pdzFTMDBtWkxxY2VIWmMKrReAwG9+6W/R1AoUr8JFw2QQ9WZ+e+Pn
|
ektUZVQ0aTVKRUFnQWUyUUtzYUs5QkUKkLJ2e2+a3VYcVepB2QIrD4Dsv/Vw0WiV
|
||||||
wuTlcKayDrNHuM1ldW6BEYQAV+8Z4Nhbj1ygo+2tqOsXm2YL6uzlBA==
|
ZXqudx8UqY4xbsYaWehEVrIBIaQ/cLqVP+0YKaS6CFvIWHhP+lu/2Q==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
- recipient: age1exny8unxynaw03yu8ppahu5z28uermghr8ag34e7kdqnaduq9stsyettzz
|
- recipient: age1exny8unxynaw03yu8ppahu5z28uermghr8ag34e7kdqnaduq9stsyettzz
|
||||||
enc: |
|
enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBCYk9zVTZUUUl3YWJobFNu
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4R1I0RjBxUWdrelAwY1Nt
|
||||||
bndaSGJyTWdoOWxUZHZYVDNMTjdkQVdyaFY4ClFEUjZuaTJRdVdlcEU5UFIzbmlC
|
Qjk2UXRqVS91MHhDY0xFaVR4THFLVXM2MGhJCmNaeHhwQ0I4bGNsMi9ySi9YMkg1
|
||||||
OE44MFhLOGh4ZkJmeFl1QlNCM05kYVkKLS0tIFNCWnJhMFJ3MG1hKzZVRXlpODl5
|
NWVrb0pKN0FaZ3QxUkJSSDd0VWQ4M2MKLS0tIHIwYnEvYVhONDhGazZ2ekJ3TnVx
|
||||||
R1dpWnZKNHR4RERSdm5OclI1SncvYkUKBDZKeh6xlTn3tRnZOCD6oe2uFP1NeQe9
|
WE44dU43QmxPTncycHVGNHNlTG83MU0KQPJWz3YF8RXwxp8SUFRuo39NN/2tSzw2
|
||||||
b7JuigPRPnhah0rWZ6jPjnk38Jp6z/I1Oqh5UJ94H4KNi/h3HKqMSw==
|
bMuv5JWSI8i3TNiWmOELF/Lt34Z4p5yZZ7OA8mfx1bbTsZOJ35I0LA==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
- recipient: age1v6p8dan2t3w9h94fz4flldl32082j3s9x6zqq7u5j66keth9aphsd6pvch
|
- recipient: age1v6p8dan2t3w9h94fz4flldl32082j3s9x6zqq7u5j66keth9aphsd6pvch
|
||||||
enc: |
|
enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBPTkdSM3hoTWdlcE9McDFt
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJcUFXVi8yOTBWWVhLbjZ4
|
||||||
engvOU5hZUtKaVZjUEFnT0huNWpMWnBjUFZnClZxZ1JrMThrT2kyejFkUnlUVUhU
|
VU9YazRzWW8zTmNzalArcVNtQXpLdVJ0U2kwCnY0ci9nSE5qZTZpSk93MlNpR2pp
|
||||||
bVhTNUovM0ZRZVRRemRsNml2RXcrNk0KLS0tIDZWYWJUTHlXWUFMcE82YlZBby9E
|
WGVENTdWbHUrdTN0K1ZHaTZEUkdpemcKLS0tIFhseFdlL3BpZHFQbE0rNlo3c1dO
|
||||||
clM4RXI2V1pIRVZNeWdaM24xZlFJSjQKY1+Nw3X0FynI6BhhLE5caUpDENqa6S6d
|
SmtzUnk3V0pCakpXcmhvZWVTWURKK3MKuB/3RDvEZh1o7WHmJHFh5njBdjICO34G
|
||||||
HMRhiL7SIZQrmkdIeCSikjRCkvqBFIgn7sff3S+7neYxgGkFp1nzGg==
|
o8KdfkLcAwTnSbOy0mEVLhQjLayhRq/ifMWwJfN4/MUZo1cSBwO+iQ==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
lastmodified: "2025-06-05T16:28:03Z"
|
lastmodified: "2025-12-07T23:35:10Z"
|
||||||
mac: ENC[AES256_GCM,data:NNYwveO78Q4cWOPPt3Pyqh6AtbfRj/ax6D4t2KlVXWSLzKTUZKKaULXGY5PBp/jI2pyhPp5yEMhEyjRPWC8Xhvxjv+NLb6KltgaMfzIBS/jfSNk3dcYx6i8Y2oSG1efLJrRMc2Q/uACeztyivtjV9A7JCrEtb84Wb9HzkI4nZVs=,iv:Q8cTw+/RMJ3WHrkB9lyaAyI2K3O1ZhDnAMUYMJ4JMRk=,tag:JvrLiaKKYXiOmud4oZZZ1w==,type:str]
|
mac: ENC[AES256_GCM,data:5tRkzTUeiKf3WQBFJZBBCa3vnPGVLspbCF/bf+rQEGuQ0ehA5uZXLlc3nBlC3fBqQh3JQUyVw1xcqYJc8Ziu1a5pcG28Mdls6IWr8JOzh+h2fAgqWbn9UGqEU9fNJE0aSzHSptpKA5fqgJYqmb3voz8tFn3jMNrPsGenHFn20C0=,iv:s6lOchFabWnyW2gkoYaXBhOkJyhhXyh2ZbnFTwj83bs=,tag:HosULSsgllcEzoBifQLYSA==,type:str]
|
||||||
unencrypted_suffix: _unencrypted
|
unencrypted_suffix: _unencrypted
|
||||||
version: 3.10.2
|
version: 3.11.0
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{ config, pkgs, ... }:
|
{ config, pkgs, ... }:
|
||||||
let
|
let
|
||||||
home-manager = builtins.fetchTarball "https://github.com/nix-community/home-manager/archive/release-24.11.tar.gz";
|
home-manager = builtins.fetchTarball "https://github.com/nix-community/home-manager/archive/release-25.11.tar.gz";
|
||||||
|
|
||||||
|
|
||||||
in
|
in
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ let
|
|||||||
config = { allowUnfree = true; };
|
config = { allowUnfree = true; };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
clicknload-proxy = pkgs.callPackage ../utils/pkgs/clicknload-proxy {};
|
||||||
|
|
||||||
thunderbirdSettings = {
|
thunderbirdSettings = {
|
||||||
"extensions.activeThemeID" = "thunderbird-compact-dark@mozilla.org";
|
"extensions.activeThemeID" = "thunderbird-compact-dark@mozilla.org";
|
||||||
"browser.theme.content-theme" = 0;
|
"browser.theme.content-theme" = 0;
|
||||||
@@ -135,11 +137,11 @@ let
|
|||||||
{ name = "q"; value = "{searchTerms}"; }
|
{ name = "q"; value = "{searchTerms}"; }
|
||||||
];
|
];
|
||||||
}];
|
}];
|
||||||
iconUpdateURL = "https://perplexity.ai/favicon.ico";
|
icon = "https://perplexity.ai/favicon.ico";
|
||||||
definedAliases = [ "@perplexity" ];
|
definedAliases = [ "@perplexity" ];
|
||||||
};
|
};
|
||||||
"Google".metaData.hidden = true;
|
"google".metaData.hidden = true;
|
||||||
"Bing".metaData.hidden = true;
|
"bing".metaData.hidden = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -160,6 +162,9 @@ in
|
|||||||
sops.secrets.openai_api_key = {
|
sops.secrets.openai_api_key = {
|
||||||
owner = "dominik";
|
owner = "dominik";
|
||||||
};
|
};
|
||||||
|
sops.secrets.clicknload-proxy-config = {
|
||||||
|
owner = "dominik";
|
||||||
|
};
|
||||||
programs.fuse.userAllowOther = true;
|
programs.fuse.userAllowOther = true;
|
||||||
|
|
||||||
programs.zsh = {
|
programs.zsh = {
|
||||||
@@ -172,7 +177,7 @@ in
|
|||||||
home-manager.users.dominik = { lib, pkgs, ... }: {
|
home-manager.users.dominik = { lib, pkgs, ... }: {
|
||||||
# imports = [ "${impermanence}/home-manager.nix" ];
|
# imports = [ "${impermanence}/home-manager.nix" ];
|
||||||
/* The home.stateVersion option does not have a default and must be set */
|
/* The home.stateVersion option does not have a default and must be set */
|
||||||
home.stateVersion = "24.05";
|
home.stateVersion = "25.05";
|
||||||
home.enableNixpkgsReleaseCheck = false;
|
home.enableNixpkgsReleaseCheck = false;
|
||||||
home.sessionVariables = {
|
home.sessionVariables = {
|
||||||
MOZ_ENABLE_WAYLAND = "1";
|
MOZ_ENABLE_WAYLAND = "1";
|
||||||
@@ -228,6 +233,21 @@ in
|
|||||||
Restart = "always";
|
Restart = "always";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
clicknload-proxy = {
|
||||||
|
Unit = {
|
||||||
|
Description = "Click'n'Load proxy for pyLoad";
|
||||||
|
After = [ "graphical-session-pre.target" ];
|
||||||
|
PartOf = [ "graphical-session.target" ];
|
||||||
|
};
|
||||||
|
Install = {
|
||||||
|
WantedBy = [ "graphical-session.target" ];
|
||||||
|
};
|
||||||
|
Service = {
|
||||||
|
ExecStart = "${clicknload-proxy}/bin/clicknload-proxy --config ${config.sops.secrets.clicknload-proxy-config.path}";
|
||||||
|
Restart = "always";
|
||||||
|
RestartSec = "10s";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
programs.chromium = {
|
programs.chromium = {
|
||||||
@@ -286,26 +306,23 @@ in
|
|||||||
programs.git = {
|
programs.git = {
|
||||||
enable = true;
|
enable = true;
|
||||||
lfs.enable = true;
|
lfs.enable = true;
|
||||||
package = pkgs.gitAndTools.gitFull;
|
package = pkgs.gitFull;
|
||||||
userName = "Dominik Polakovics";
|
|
||||||
userEmail = "dominik.polakovics@cloonar.com";
|
|
||||||
# signing = {
|
# signing = {
|
||||||
# key = "dominik.polakovics@cloonar.com";
|
# key = "dominik.polakovics@cloonar.com";
|
||||||
# signByDefault = false;
|
# signByDefault = false;
|
||||||
# };
|
# };
|
||||||
iniContent = {
|
settings = {
|
||||||
|
user.name = "Dominik Polakovics";
|
||||||
|
user.email = "dominik.polakovics@cloonar.com";
|
||||||
# Branch with most recent change comes first
|
# Branch with most recent change comes first
|
||||||
branch.sort = "-committerdate";
|
branch.sort = "-committerdate";
|
||||||
# Remember and auto-resolve merge conflicts
|
# Remember and auto-resolve merge conflicts
|
||||||
# https://git-scm.com/book/en/v2/Git-Tools-Rerere
|
# https://git-scm.com/book/en/v2/Git-Tools-Rerere
|
||||||
rerere.enabled = true;
|
rerere.enabled = true;
|
||||||
};
|
"url \"gitea@git.cloonar.com:\"" = {
|
||||||
extraConfig = {
|
|
||||||
"url.gitea@git.cloonar.com:" = {
|
|
||||||
insteadOf = "https://git.cloonar.com/";
|
insteadOf = "https://git.cloonar.com/";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
programs.thunderbird = {
|
programs.thunderbird = {
|
||||||
@@ -510,7 +527,7 @@ in
|
|||||||
settings = firefoxSettings;
|
settings = firefoxSettings;
|
||||||
# userChrome = firefoxUserChrome;
|
# userChrome = firefoxUserChrome;
|
||||||
search = firefoxSearchSettings;
|
search = firefoxSearchSettings;
|
||||||
extensions = firefoxExtensions;
|
extensions.packages = firefoxExtensions;
|
||||||
};
|
};
|
||||||
social = {
|
social = {
|
||||||
id = 1;
|
id = 1;
|
||||||
@@ -545,7 +562,7 @@ in
|
|||||||
id = 3;
|
id = 3;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
extensions = firefoxExtensions;
|
extensions.packages = firefoxExtensions;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
https://channels.nixos.org/nixos-25.05
|
https://channels.nixos.org/nixos-25.11
|
||||||
|
|||||||
@@ -63,7 +63,7 @@
|
|||||||
|
|
||||||
time.timeZone = "Europe/Vienna";
|
time.timeZone = "Europe/Vienna";
|
||||||
|
|
||||||
services.logind.extraConfig = "RuntimeDirectorySize=2G";
|
services.logind.settings.Login.RuntimeDirectorySize = "2G";
|
||||||
|
|
||||||
sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
|
sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
|
||||||
sops.defaultSopsFile = ./secrets.yaml;
|
sops.defaultSopsFile = ./secrets.yaml;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
datasourceUid = "vm-datasource-uid";
|
datasourceUid = "vm-datasource-uid";
|
||||||
model = {
|
model = {
|
||||||
editorMode = "code";
|
editorMode = "code";
|
||||||
expr = "node_systemd_unit_state{state=\"active\", name=\"dovecot2.service\"} OR on() vector(0)";
|
expr = "node_systemd_unit_state{state=\"active\", name=\"dovecot.service\"} OR on() vector(0)";
|
||||||
hide = false;
|
hide = false;
|
||||||
intervalMs = 1000;
|
intervalMs = 1000;
|
||||||
legendFormat = "__auto";
|
legendFormat = "__auto";
|
||||||
|
|||||||
17
hosts/web-arm/modules/grafana/alerting/storage/default.nix
Normal file
17
hosts/web-arm/modules/grafana/alerting/storage/default.nix
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{ lib, pkgs, config, ... }:
|
||||||
|
let
|
||||||
|
smartAlertRules = (import ./smart_alerts.nix { inherit lib pkgs config; }).grafanaAlertRuleDefinitions;
|
||||||
|
raidAlertRules = (import ./raid_alerts.nix { inherit lib pkgs config; }).grafanaAlertRuleDefinitions;
|
||||||
|
|
||||||
|
allStorageRules = smartAlertRules ++ raidAlertRules;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
services.grafana.provision.alerting.rules.settings.groups = [
|
||||||
|
{
|
||||||
|
name = "Storage Alerts";
|
||||||
|
folder = "Storage Alerts";
|
||||||
|
interval = "5m"; # Check every 5 minutes (metrics collected every 20 min)
|
||||||
|
rules = allStorageRules;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
102
hosts/web-arm/modules/grafana/alerting/storage/raid_alerts.nix
Normal file
102
hosts/web-arm/modules/grafana/alerting/storage/raid_alerts.nix
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
{ lib, pkgs, config, ... }:
|
||||||
|
{
|
||||||
|
grafanaAlertRuleDefinitions = [
|
||||||
|
# RAID array degraded - critical
|
||||||
|
{
|
||||||
|
uid = "raid-array-degraded-uid";
|
||||||
|
title = "RaidArrayDegraded";
|
||||||
|
condition = "D";
|
||||||
|
data = [
|
||||||
|
{
|
||||||
|
refId = "A";
|
||||||
|
datasourceUid = "vm-datasource-uid";
|
||||||
|
relativeTimeRange = { from = 300; to = 0; };
|
||||||
|
model = {
|
||||||
|
expr = ''mdadm_array_state == 0'';
|
||||||
|
instant = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
refId = "C";
|
||||||
|
datasourceUid = "__expr__";
|
||||||
|
model = {
|
||||||
|
type = "reduce";
|
||||||
|
expression = "A";
|
||||||
|
reducer = "last";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
refId = "D";
|
||||||
|
datasourceUid = "__expr__";
|
||||||
|
model = {
|
||||||
|
type = "math";
|
||||||
|
expression = "$C == 0";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
for = "0s";
|
||||||
|
noDataState = "NoData";
|
||||||
|
execErrState = "Error";
|
||||||
|
annotations = {
|
||||||
|
summary = "RAID array {{ $labels.array }} is degraded";
|
||||||
|
description = ''
|
||||||
|
RAID array {{ $labels.array }} on {{ $labels.instance }} is in state "{{ $labels.state }}".
|
||||||
|
The array is not in a healthy state. Check for failed disks immediately!
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
labels = {
|
||||||
|
severity = "critical";
|
||||||
|
category = "storage";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
# RAID missing devices - critical
|
||||||
|
{
|
||||||
|
uid = "raid-missing-devices-uid";
|
||||||
|
title = "RaidMissingDevices";
|
||||||
|
condition = "D";
|
||||||
|
data = [
|
||||||
|
{
|
||||||
|
refId = "A";
|
||||||
|
datasourceUid = "vm-datasource-uid";
|
||||||
|
relativeTimeRange = { from = 300; to = 0; };
|
||||||
|
model = {
|
||||||
|
expr = ''mdadm_array_devices_active < mdadm_array_devices_total'';
|
||||||
|
instant = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
refId = "C";
|
||||||
|
datasourceUid = "__expr__";
|
||||||
|
model = {
|
||||||
|
type = "reduce";
|
||||||
|
expression = "A";
|
||||||
|
reducer = "last";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
refId = "D";
|
||||||
|
datasourceUid = "__expr__";
|
||||||
|
model = {
|
||||||
|
type = "math";
|
||||||
|
expression = "$C > 0";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
for = "0s";
|
||||||
|
noDataState = "NoData";
|
||||||
|
execErrState = "Error";
|
||||||
|
annotations = {
|
||||||
|
summary = "RAID array {{ $labels.array }} has missing devices";
|
||||||
|
description = ''
|
||||||
|
RAID array {{ $labels.array }} on {{ $labels.instance }} has fewer active devices than expected.
|
||||||
|
A disk may have failed or been removed. Check array status immediately!
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
labels = {
|
||||||
|
severity = "critical";
|
||||||
|
category = "storage";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
298
hosts/web-arm/modules/grafana/alerting/storage/smart_alerts.nix
Normal file
298
hosts/web-arm/modules/grafana/alerting/storage/smart_alerts.nix
Normal file
@@ -0,0 +1,298 @@
|
|||||||
|
{ lib, pkgs, config, ... }:
|
||||||
|
{
|
||||||
|
grafanaAlertRuleDefinitions = [
|
||||||
|
# S.M.A.R.T. overall health failed - critical
|
||||||
|
{
|
||||||
|
uid = "smart-health-failed-uid";
|
||||||
|
title = "DiskSmartHealthFailed";
|
||||||
|
condition = "D";
|
||||||
|
data = [
|
||||||
|
{
|
||||||
|
refId = "A";
|
||||||
|
datasourceUid = "vm-datasource-uid";
|
||||||
|
relativeTimeRange = { from = 300; to = 0; };
|
||||||
|
model = {
|
||||||
|
expr = ''smart_health_passed == 0'';
|
||||||
|
instant = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
refId = "C";
|
||||||
|
datasourceUid = "__expr__";
|
||||||
|
model = {
|
||||||
|
type = "reduce";
|
||||||
|
expression = "A";
|
||||||
|
reducer = "last";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
refId = "D";
|
||||||
|
datasourceUid = "__expr__";
|
||||||
|
model = {
|
||||||
|
type = "math";
|
||||||
|
expression = "$C == 0";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
for = "0s";
|
||||||
|
noDataState = "NoData";
|
||||||
|
execErrState = "Error";
|
||||||
|
annotations = {
|
||||||
|
summary = "S.M.A.R.T. health check FAILED on {{ $labels.device }}";
|
||||||
|
description = ''
|
||||||
|
Disk {{ $labels.device }} ({{ $labels.serial }}) on {{ $labels.instance }} has failed its S.M.A.R.T. health check.
|
||||||
|
This indicates imminent disk failure. Replace the disk immediately!
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
labels = {
|
||||||
|
severity = "critical";
|
||||||
|
category = "storage";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
# Reallocated sectors - warning (any count > 0 is concerning)
|
||||||
|
{
|
||||||
|
uid = "smart-reallocated-sectors-uid";
|
||||||
|
title = "DiskReallocatedSectors";
|
||||||
|
condition = "D";
|
||||||
|
data = [
|
||||||
|
{
|
||||||
|
refId = "A";
|
||||||
|
datasourceUid = "vm-datasource-uid";
|
||||||
|
relativeTimeRange = { from = 300; to = 0; };
|
||||||
|
model = {
|
||||||
|
expr = ''smart_reallocated_sector_ct > 0'';
|
||||||
|
instant = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
refId = "C";
|
||||||
|
datasourceUid = "__expr__";
|
||||||
|
model = {
|
||||||
|
type = "reduce";
|
||||||
|
expression = "A";
|
||||||
|
reducer = "last";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
refId = "D";
|
||||||
|
datasourceUid = "__expr__";
|
||||||
|
model = {
|
||||||
|
type = "math";
|
||||||
|
expression = "$C > 0";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
for = "0s";
|
||||||
|
noDataState = "NoData";
|
||||||
|
execErrState = "Error";
|
||||||
|
annotations = {
|
||||||
|
summary = "Reallocated sectors detected on {{ $labels.device }}";
|
||||||
|
description = ''
|
||||||
|
Disk {{ $labels.device }} ({{ $labels.serial }}) on {{ $labels.instance }} has reallocated sectors.
|
||||||
|
This indicates disk surface damage. Monitor closely and plan replacement.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
labels = {
|
||||||
|
severity = "warning";
|
||||||
|
category = "storage";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
# Current pending sectors
|
||||||
|
{
|
||||||
|
uid = "smart-pending-sectors-uid";
|
||||||
|
title = "DiskPendingSectors";
|
||||||
|
condition = "D";
|
||||||
|
data = [
|
||||||
|
{
|
||||||
|
refId = "A";
|
||||||
|
datasourceUid = "vm-datasource-uid";
|
||||||
|
relativeTimeRange = { from = 300; to = 0; };
|
||||||
|
model = {
|
||||||
|
expr = ''smart_current_pending_sector > 0'';
|
||||||
|
instant = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
refId = "C";
|
||||||
|
datasourceUid = "__expr__";
|
||||||
|
model = {
|
||||||
|
type = "reduce";
|
||||||
|
expression = "A";
|
||||||
|
reducer = "last";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
refId = "D";
|
||||||
|
datasourceUid = "__expr__";
|
||||||
|
model = {
|
||||||
|
type = "math";
|
||||||
|
expression = "$C > 0";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
for = "0s";
|
||||||
|
noDataState = "NoData";
|
||||||
|
execErrState = "Error";
|
||||||
|
annotations = {
|
||||||
|
summary = "Pending sectors detected on {{ $labels.device }}";
|
||||||
|
description = ''
|
||||||
|
Disk {{ $labels.device }} ({{ $labels.serial }}) on {{ $labels.instance }} has pending sectors.
|
||||||
|
These sectors could not be read and may be reallocated. Monitor for increase.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
labels = {
|
||||||
|
severity = "warning";
|
||||||
|
category = "storage";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
# Offline uncorrectable errors
|
||||||
|
{
|
||||||
|
uid = "smart-offline-uncorrectable-uid";
|
||||||
|
title = "DiskOfflineUncorrectable";
|
||||||
|
condition = "D";
|
||||||
|
data = [
|
||||||
|
{
|
||||||
|
refId = "A";
|
||||||
|
datasourceUid = "vm-datasource-uid";
|
||||||
|
relativeTimeRange = { from = 300; to = 0; };
|
||||||
|
model = {
|
||||||
|
expr = ''smart_offline_uncorrectable > 0'';
|
||||||
|
instant = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
refId = "C";
|
||||||
|
datasourceUid = "__expr__";
|
||||||
|
model = {
|
||||||
|
type = "reduce";
|
||||||
|
expression = "A";
|
||||||
|
reducer = "last";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
refId = "D";
|
||||||
|
datasourceUid = "__expr__";
|
||||||
|
model = {
|
||||||
|
type = "math";
|
||||||
|
expression = "$C > 0";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
for = "0s";
|
||||||
|
noDataState = "NoData";
|
||||||
|
execErrState = "Error";
|
||||||
|
annotations = {
|
||||||
|
summary = "Offline uncorrectable errors on {{ $labels.device }}";
|
||||||
|
description = ''
|
||||||
|
Disk {{ $labels.device }} ({{ $labels.serial }}) on {{ $labels.instance }} has offline uncorrectable errors.
|
||||||
|
This indicates data integrity issues. Consider replacement.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
labels = {
|
||||||
|
severity = "warning";
|
||||||
|
category = "storage";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
# High temperature (Seagate enterprise: warning at 50C)
|
||||||
|
{
|
||||||
|
uid = "smart-high-temperature-uid";
|
||||||
|
title = "DiskHighTemperature";
|
||||||
|
condition = "D";
|
||||||
|
data = [
|
||||||
|
{
|
||||||
|
refId = "A";
|
||||||
|
datasourceUid = "vm-datasource-uid";
|
||||||
|
relativeTimeRange = { from = 600; to = 0; };
|
||||||
|
model = {
|
||||||
|
expr = ''smart_temperature_celsius > 50'';
|
||||||
|
instant = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
refId = "C";
|
||||||
|
datasourceUid = "__expr__";
|
||||||
|
model = {
|
||||||
|
type = "reduce";
|
||||||
|
expression = "A";
|
||||||
|
reducer = "last";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
refId = "D";
|
||||||
|
datasourceUid = "__expr__";
|
||||||
|
model = {
|
||||||
|
type = "math";
|
||||||
|
expression = "$C > 0";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
for = "10m";
|
||||||
|
noDataState = "NoData";
|
||||||
|
execErrState = "Error";
|
||||||
|
annotations = {
|
||||||
|
summary = "High temperature on {{ $labels.device }}";
|
||||||
|
description = ''
|
||||||
|
Disk {{ $labels.device }} ({{ $labels.serial }}) on {{ $labels.instance }} temperature exceeds 50°C.
|
||||||
|
Check cooling and ventilation.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
labels = {
|
||||||
|
severity = "warning";
|
||||||
|
category = "storage";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
# UDMA CRC errors (cable/connection issues)
|
||||||
|
{
|
||||||
|
uid = "smart-udma-crc-errors-uid";
|
||||||
|
title = "DiskUDMACRCErrors";
|
||||||
|
condition = "D";
|
||||||
|
data = [
|
||||||
|
{
|
||||||
|
refId = "A";
|
||||||
|
datasourceUid = "vm-datasource-uid";
|
||||||
|
relativeTimeRange = { from = 86400; to = 0; };
|
||||||
|
model = {
|
||||||
|
expr = ''increase(smart_udma_crc_error_count[24h]) > 0'';
|
||||||
|
instant = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
refId = "C";
|
||||||
|
datasourceUid = "__expr__";
|
||||||
|
model = {
|
||||||
|
type = "reduce";
|
||||||
|
expression = "A";
|
||||||
|
reducer = "last";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
refId = "D";
|
||||||
|
datasourceUid = "__expr__";
|
||||||
|
model = {
|
||||||
|
type = "math";
|
||||||
|
expression = "$C > 0";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
for = "0s";
|
||||||
|
noDataState = "NoData";
|
||||||
|
execErrState = "Error";
|
||||||
|
annotations = {
|
||||||
|
summary = "UDMA CRC errors on {{ $labels.device }}";
|
||||||
|
description = ''
|
||||||
|
Disk {{ $labels.device }} ({{ $labels.serial }}) on {{ $labels.instance }} has new CRC errors.
|
||||||
|
This typically indicates SATA cable or connection issues. Check cables.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
labels = {
|
||||||
|
severity = "warning";
|
||||||
|
category = "storage";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
17
hosts/web-arm/modules/grafana/dashboards/default.nix
Normal file
17
hosts/web-arm/modules/grafana/dashboards/default.nix
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{ lib, pkgs, ... }:
|
||||||
|
let
|
||||||
|
smartDashboard = import ./smart-dashboard.nix { inherit lib pkgs; };
|
||||||
|
dashboardDir = pkgs.linkFarm "grafana-dashboards" [
|
||||||
|
{ name = "smart.json"; path = smartDashboard; }
|
||||||
|
];
|
||||||
|
in
|
||||||
|
{
|
||||||
|
services.grafana.provision.dashboards.settings = {
|
||||||
|
apiVersion = 1;
|
||||||
|
providers = [{
|
||||||
|
name = "nix-dashboards";
|
||||||
|
type = "file";
|
||||||
|
options.path = dashboardDir;
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
}
|
||||||
463
hosts/web-arm/modules/grafana/dashboards/smart-dashboard.nix
Normal file
463
hosts/web-arm/modules/grafana/dashboards/smart-dashboard.nix
Normal file
@@ -0,0 +1,463 @@
|
|||||||
|
{ lib, pkgs }:
|
||||||
|
let
|
||||||
|
datasourceUid = "vm-datasource-uid";
|
||||||
|
|
||||||
|
# Helper to create a panel with common defaults
|
||||||
|
mkPanel = { id, title, type, gridPos, targets, options ? { }, fieldConfig ? { }, ... }@args:
|
||||||
|
{
|
||||||
|
inherit id title type gridPos targets;
|
||||||
|
datasource = { uid = datasourceUid; type = "prometheus"; };
|
||||||
|
options = options;
|
||||||
|
fieldConfig = {
|
||||||
|
defaults = fieldConfig.defaults or { };
|
||||||
|
overrides = fieldConfig.overrides or [ ];
|
||||||
|
};
|
||||||
|
} // (builtins.removeAttrs args [ "id" "title" "type" "gridPos" "targets" "options" "fieldConfig" ]);
|
||||||
|
|
||||||
|
# Dashboard definition
|
||||||
|
dashboard = {
|
||||||
|
uid = "smart-disk-health";
|
||||||
|
title = "S.M.A.R.T Disk Health";
|
||||||
|
description = "S.M.A.R.T metrics and RAID array status";
|
||||||
|
tags = [ "disk" "smart" "storage" "nas" ];
|
||||||
|
timezone = "browser";
|
||||||
|
editable = false;
|
||||||
|
refresh = "5m";
|
||||||
|
schemaVersion = 39;
|
||||||
|
version = 1;
|
||||||
|
|
||||||
|
# Variables
|
||||||
|
templating.list = [
|
||||||
|
{
|
||||||
|
name = "host";
|
||||||
|
label = "Host";
|
||||||
|
type = "query";
|
||||||
|
datasource = { uid = datasourceUid; type = "prometheus"; };
|
||||||
|
query = "label_values(smart_health_passed, instance)";
|
||||||
|
regex = "";
|
||||||
|
sort = 1;
|
||||||
|
refresh = 1;
|
||||||
|
includeAll = true;
|
||||||
|
multi = false;
|
||||||
|
current = { selected = true; text = "All"; value = "$__all"; };
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
# Panels
|
||||||
|
panels = [
|
||||||
|
# === OVERVIEW ROW ===
|
||||||
|
{
|
||||||
|
id = 1;
|
||||||
|
type = "row";
|
||||||
|
title = "Overview";
|
||||||
|
collapsed = false;
|
||||||
|
gridPos = { x = 0; y = 0; w = 24; h = 1; };
|
||||||
|
panels = [ ];
|
||||||
|
}
|
||||||
|
|
||||||
|
# Alert Status - Shows firing disk alerts
|
||||||
|
{
|
||||||
|
id = 5;
|
||||||
|
title = "Alert Status";
|
||||||
|
type = "alertlist";
|
||||||
|
gridPos = { x = 0; y = 1; w = 6; h = 5; };
|
||||||
|
options = {
|
||||||
|
alertInstanceLabelFilter = "";
|
||||||
|
alertName = "Disk";
|
||||||
|
dashboardAlerts = false;
|
||||||
|
groupBy = [ ];
|
||||||
|
groupMode = "default";
|
||||||
|
maxItems = 20;
|
||||||
|
sortOrder = 1;
|
||||||
|
stateFilter = {
|
||||||
|
"error" = true;
|
||||||
|
firing = true;
|
||||||
|
noData = false;
|
||||||
|
normal = false;
|
||||||
|
pending = false;
|
||||||
|
};
|
||||||
|
viewMode = "list";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
# Health Status - Stat panel
|
||||||
|
(mkPanel {
|
||||||
|
id = 2;
|
||||||
|
title = "Disk Health Status";
|
||||||
|
type = "stat";
|
||||||
|
gridPos = { x = 6; y = 1; w = 6; h = 5; };
|
||||||
|
targets = [{
|
||||||
|
expr = ''smart_health_passed{instance=~"$host"}'';
|
||||||
|
legendFormat = "{{device}}";
|
||||||
|
refId = "A";
|
||||||
|
}];
|
||||||
|
options = {
|
||||||
|
reduceOptions = { values = false; calcs = [ "lastNotNull" ]; fields = ""; };
|
||||||
|
orientation = "horizontal";
|
||||||
|
textMode = "auto";
|
||||||
|
colorMode = "background";
|
||||||
|
graphMode = "none";
|
||||||
|
};
|
||||||
|
fieldConfig = {
|
||||||
|
defaults = {
|
||||||
|
mappings = [
|
||||||
|
{ type = "value"; options."1" = { text = "PASSED"; color = "green"; index = 0; }; }
|
||||||
|
{ type = "value"; options."0" = { text = "FAILED"; color = "red"; index = 1; }; }
|
||||||
|
];
|
||||||
|
thresholds = {
|
||||||
|
mode = "absolute";
|
||||||
|
steps = [
|
||||||
|
{ color = "red"; value = null; }
|
||||||
|
{ color = "green"; value = 1; }
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
||||||
|
# Temperature Gauge
|
||||||
|
(mkPanel {
|
||||||
|
id = 3;
|
||||||
|
title = "Disk Temperatures";
|
||||||
|
type = "gauge";
|
||||||
|
gridPos = { x = 12; y = 1; w = 6; h = 8; };
|
||||||
|
targets = [{
|
||||||
|
expr = ''smart_temperature_celsius{instance=~"$host"}'';
|
||||||
|
legendFormat = "{{device}}";
|
||||||
|
refId = "A";
|
||||||
|
}];
|
||||||
|
options = {
|
||||||
|
reduceOptions = { values = false; calcs = [ "lastNotNull" ]; fields = ""; };
|
||||||
|
orientation = "auto";
|
||||||
|
showThresholdLabels = false;
|
||||||
|
showThresholdMarkers = true;
|
||||||
|
};
|
||||||
|
fieldConfig = {
|
||||||
|
defaults = {
|
||||||
|
unit = "celsius";
|
||||||
|
min = 0;
|
||||||
|
max = 70;
|
||||||
|
thresholds = {
|
||||||
|
mode = "absolute";
|
||||||
|
steps = [
|
||||||
|
{ color = "green"; value = null; }
|
||||||
|
{ color = "yellow"; value = 45; }
|
||||||
|
{ color = "red"; value = 55; }
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
||||||
|
# RAID Status - Stat panel
|
||||||
|
(mkPanel {
|
||||||
|
id = 4;
|
||||||
|
title = "RAID Array Status";
|
||||||
|
type = "stat";
|
||||||
|
gridPos = { x = 18; y = 1; w = 6; h = 8; };
|
||||||
|
targets = [{
|
||||||
|
expr = ''mdadm_array_state{instance=~"$host"}'';
|
||||||
|
legendFormat = "{{array}}";
|
||||||
|
refId = "A";
|
||||||
|
}];
|
||||||
|
options = {
|
||||||
|
reduceOptions = { values = false; calcs = [ "lastNotNull" ]; fields = ""; };
|
||||||
|
orientation = "horizontal";
|
||||||
|
textMode = "auto";
|
||||||
|
colorMode = "background";
|
||||||
|
graphMode = "none";
|
||||||
|
};
|
||||||
|
fieldConfig = {
|
||||||
|
defaults = {
|
||||||
|
mappings = [
|
||||||
|
{ type = "value"; options."1" = { text = "Healthy"; color = "green"; index = 0; }; }
|
||||||
|
{ type = "value"; options."0" = { text = "Degraded"; color = "red"; index = 1; }; }
|
||||||
|
];
|
||||||
|
thresholds = {
|
||||||
|
mode = "absolute";
|
||||||
|
steps = [
|
||||||
|
{ color = "red"; value = null; }
|
||||||
|
{ color = "green"; value = 1; }
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
||||||
|
# Sector Health Table - Promoted to overview for visibility
|
||||||
|
(mkPanel {
|
||||||
|
id = 13;
|
||||||
|
title = "Sector Health";
|
||||||
|
type = "table";
|
||||||
|
gridPos = { x = 0; y = 6; w = 12; h = 4; };
|
||||||
|
targets = [
|
||||||
|
{
|
||||||
|
expr = ''smart_reallocated_sector_ct{instance=~"$host"}'';
|
||||||
|
legendFormat = "{{device}}";
|
||||||
|
refId = "A";
|
||||||
|
format = "table";
|
||||||
|
instant = true;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
expr = ''smart_current_pending_sector{instance=~"$host"}'';
|
||||||
|
legendFormat = "{{device}}";
|
||||||
|
refId = "B";
|
||||||
|
format = "table";
|
||||||
|
instant = true;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
expr = ''smart_offline_uncorrectable{instance=~"$host"}'';
|
||||||
|
legendFormat = "{{device}}";
|
||||||
|
refId = "C";
|
||||||
|
format = "table";
|
||||||
|
instant = true;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
options = {
|
||||||
|
showHeader = true;
|
||||||
|
cellHeight = "sm";
|
||||||
|
};
|
||||||
|
transformations = [
|
||||||
|
{ id = "merge"; options = { }; }
|
||||||
|
{
|
||||||
|
id = "organize";
|
||||||
|
options = {
|
||||||
|
excludeByName = { Time = true; __name__ = true; instance = true; job = true; serial = true; };
|
||||||
|
renameByName = {
|
||||||
|
device = "Device";
|
||||||
|
"Value #A" = "Reallocated Sectors";
|
||||||
|
"Value #B" = "Pending Sectors";
|
||||||
|
"Value #C" = "Offline Uncorrectable";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
fieldConfig = {
|
||||||
|
defaults = {
|
||||||
|
thresholds = {
|
||||||
|
mode = "absolute";
|
||||||
|
steps = [
|
||||||
|
{ color = "green"; value = null; }
|
||||||
|
{ color = "yellow"; value = 1; }
|
||||||
|
{ color = "red"; value = 10; }
|
||||||
|
];
|
||||||
|
};
|
||||||
|
custom = { displayMode = "color-background-solid"; };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
||||||
|
# === DETAILED METRICS ROW ===
|
||||||
|
{
|
||||||
|
id = 10;
|
||||||
|
type = "row";
|
||||||
|
title = "Detailed Metrics";
|
||||||
|
collapsed = false;
|
||||||
|
gridPos = { x = 0; y = 10; w = 24; h = 1; };
|
||||||
|
panels = [ ];
|
||||||
|
}
|
||||||
|
|
||||||
|
# Temperature Time Series
|
||||||
|
(mkPanel {
|
||||||
|
id = 11;
|
||||||
|
title = "Temperature Over Time";
|
||||||
|
type = "timeseries";
|
||||||
|
gridPos = { x = 0; y = 11; w = 12; h = 8; };
|
||||||
|
targets = [{
|
||||||
|
expr = ''smart_temperature_celsius{instance=~"$host"}'';
|
||||||
|
legendFormat = "{{device}}";
|
||||||
|
refId = "A";
|
||||||
|
}];
|
||||||
|
options = {
|
||||||
|
legend = { displayMode = "list"; placement = "bottom"; showLegend = true; };
|
||||||
|
tooltip = { mode = "multi"; sort = "desc"; };
|
||||||
|
};
|
||||||
|
fieldConfig = {
|
||||||
|
defaults = {
|
||||||
|
unit = "celsius";
|
||||||
|
custom = {
|
||||||
|
drawStyle = "line";
|
||||||
|
lineInterpolation = "smooth";
|
||||||
|
fillOpacity = 10;
|
||||||
|
pointSize = 5;
|
||||||
|
showPoints = "auto";
|
||||||
|
};
|
||||||
|
thresholds = {
|
||||||
|
mode = "absolute";
|
||||||
|
steps = [
|
||||||
|
{ color = "green"; value = null; }
|
||||||
|
{ color = "yellow"; value = 45; }
|
||||||
|
{ color = "red"; value = 55; }
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
||||||
|
# Power On Hours
|
||||||
|
(mkPanel {
|
||||||
|
id = 12;
|
||||||
|
title = "Power On Hours";
|
||||||
|
type = "stat";
|
||||||
|
gridPos = { x = 12; y = 11; w = 12; h = 8; };
|
||||||
|
targets = [{
|
||||||
|
expr = ''smart_power_on_hours{instance=~"$host"}'';
|
||||||
|
legendFormat = "{{device}}";
|
||||||
|
refId = "A";
|
||||||
|
}];
|
||||||
|
options = {
|
||||||
|
reduceOptions = { values = false; calcs = [ "lastNotNull" ]; fields = ""; };
|
||||||
|
orientation = "horizontal";
|
||||||
|
textMode = "value_and_name";
|
||||||
|
colorMode = "none";
|
||||||
|
graphMode = "none";
|
||||||
|
};
|
||||||
|
fieldConfig = {
|
||||||
|
defaults = {
|
||||||
|
unit = "h";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
||||||
|
# === RAID DETAILS ROW ===
|
||||||
|
{
|
||||||
|
id = 20;
|
||||||
|
type = "row";
|
||||||
|
title = "RAID Details";
|
||||||
|
collapsed = false;
|
||||||
|
gridPos = { x = 0; y = 19; w = 24; h = 1; };
|
||||||
|
panels = [ ];
|
||||||
|
}
|
||||||
|
|
||||||
|
# RAID Devices
|
||||||
|
(mkPanel {
|
||||||
|
id = 21;
|
||||||
|
title = "RAID Array Devices";
|
||||||
|
type = "stat";
|
||||||
|
gridPos = { x = 0; y = 20; w = 12; h = 4; };
|
||||||
|
targets = [
|
||||||
|
{
|
||||||
|
expr = ''mdadm_array_devices_active{instance=~"$host"}'';
|
||||||
|
legendFormat = "{{array}} Active";
|
||||||
|
refId = "A";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
expr = ''mdadm_array_devices_total{instance=~"$host"}'';
|
||||||
|
legendFormat = "{{array}} Total";
|
||||||
|
refId = "B";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
options = {
|
||||||
|
reduceOptions = { values = false; calcs = [ "lastNotNull" ]; fields = ""; };
|
||||||
|
orientation = "horizontal";
|
||||||
|
textMode = "value_and_name";
|
||||||
|
colorMode = "value";
|
||||||
|
graphMode = "none";
|
||||||
|
};
|
||||||
|
fieldConfig = {
|
||||||
|
defaults = {
|
||||||
|
unit = "short";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
||||||
|
# UDMA CRC Errors
|
||||||
|
(mkPanel {
|
||||||
|
id = 22;
|
||||||
|
title = "UDMA CRC Errors";
|
||||||
|
type = "timeseries";
|
||||||
|
gridPos = { x = 12; y = 20; w = 12; h = 4; };
|
||||||
|
targets = [{
|
||||||
|
expr = ''smart_udma_crc_error_count{instance=~"$host"}'';
|
||||||
|
legendFormat = "{{device}}";
|
||||||
|
refId = "A";
|
||||||
|
}];
|
||||||
|
options = {
|
||||||
|
legend = { displayMode = "list"; placement = "bottom"; showLegend = true; };
|
||||||
|
tooltip = { mode = "multi"; sort = "desc"; };
|
||||||
|
};
|
||||||
|
fieldConfig = {
|
||||||
|
defaults = {
|
||||||
|
unit = "short";
|
||||||
|
custom = {
|
||||||
|
drawStyle = "line";
|
||||||
|
lineInterpolation = "stepAfter";
|
||||||
|
fillOpacity = 0;
|
||||||
|
pointSize = 5;
|
||||||
|
showPoints = "auto";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
||||||
|
# Last Update Timestamp
|
||||||
|
(mkPanel {
|
||||||
|
id = 30;
|
||||||
|
title = "Last Metrics Update";
|
||||||
|
type = "stat";
|
||||||
|
gridPos = { x = 0; y = 24; w = 6; h = 5; };
|
||||||
|
targets = [{
|
||||||
|
expr = ''time() - disk_metrics_last_update{instance=~"$host"}'';
|
||||||
|
legendFormat = "Age";
|
||||||
|
refId = "A";
|
||||||
|
}];
|
||||||
|
options = {
|
||||||
|
reduceOptions = { values = false; calcs = [ "lastNotNull" ]; fields = ""; };
|
||||||
|
orientation = "horizontal";
|
||||||
|
textMode = "value";
|
||||||
|
colorMode = "value";
|
||||||
|
graphMode = "none";
|
||||||
|
};
|
||||||
|
fieldConfig = {
|
||||||
|
defaults = {
|
||||||
|
unit = "s";
|
||||||
|
thresholds = {
|
||||||
|
mode = "absolute";
|
||||||
|
steps = [
|
||||||
|
{ color = "green"; value = null; }
|
||||||
|
{ color = "yellow"; value = 1800; }
|
||||||
|
{ color = "red"; value = 3600; }
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
||||||
|
# Device Activity Status
|
||||||
|
(mkPanel {
|
||||||
|
id = 31;
|
||||||
|
title = "Device Activity";
|
||||||
|
type = "stat";
|
||||||
|
gridPos = { x = 6; y = 24; w = 18; h = 5; };
|
||||||
|
targets = [{
|
||||||
|
expr = ''smart_device_active{instance=~"$host"}'';
|
||||||
|
legendFormat = "{{device}}";
|
||||||
|
refId = "A";
|
||||||
|
}];
|
||||||
|
options = {
|
||||||
|
reduceOptions = { values = false; calcs = [ "lastNotNull" ]; fields = ""; };
|
||||||
|
orientation = "horizontal";
|
||||||
|
textMode = "auto";
|
||||||
|
colorMode = "background";
|
||||||
|
graphMode = "none";
|
||||||
|
};
|
||||||
|
fieldConfig = {
|
||||||
|
defaults = {
|
||||||
|
mappings = [
|
||||||
|
{ type = "value"; options."1" = { text = "Active"; color = "green"; index = 0; }; }
|
||||||
|
{ type = "value"; options."0" = { text = "Standby"; color = "blue"; index = 1; }; }
|
||||||
|
];
|
||||||
|
thresholds = {
|
||||||
|
mode = "absolute";
|
||||||
|
steps = [
|
||||||
|
{ color = "blue"; value = null; }
|
||||||
|
{ color = "green"; value = 1; }
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})
|
||||||
|
];
|
||||||
|
};
|
||||||
|
in
|
||||||
|
pkgs.writeText "smart-dashboard.json" (builtins.toJSON dashboard)
|
||||||
@@ -31,10 +31,13 @@ in
|
|||||||
./alerting/system/default.nix
|
./alerting/system/default.nix
|
||||||
./alerting/service/default.nix
|
./alerting/service/default.nix
|
||||||
./alerting/websites/default.nix
|
./alerting/websites/default.nix
|
||||||
|
./alerting/storage/default.nix
|
||||||
|
|
||||||
./datasources/victoriametrics.nix
|
./datasources/victoriametrics.nix
|
||||||
./datasources/loki.nix
|
./datasources/loki.nix
|
||||||
|
|
||||||
|
./dashboards/default.nix
|
||||||
|
|
||||||
./alert-cleanup.nix
|
./alert-cleanup.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -102,27 +105,44 @@ in
|
|||||||
contactPoints = {
|
contactPoints = {
|
||||||
settings = {
|
settings = {
|
||||||
apiVersion = 1;
|
apiVersion = 1;
|
||||||
contactPoints = [{
|
contactPoints = [
|
||||||
orgId = 1;
|
{
|
||||||
name = "cp_dominik";
|
orgId = 1;
|
||||||
receivers = [{
|
name = "cp_dominik_emergency";
|
||||||
uid = "dominik_pushover_cp_receiver";
|
receivers = [{
|
||||||
type = "pushover";
|
uid = "dominik_pushover_emergency";
|
||||||
settings = {
|
type = "pushover";
|
||||||
apiToken = "\${PUSHOVER_API_TOKEN}";
|
settings = {
|
||||||
userKey = "\${PUSHOVER_USER_KEY}";
|
apiToken = "\${PUSHOVER_API_TOKEN}";
|
||||||
device = "iphone";
|
userKey = "\${PUSHOVER_USER_KEY}";
|
||||||
priority = 2;
|
device = "iphone";
|
||||||
retry = "30s";
|
priority = 2;
|
||||||
expire = "2m";
|
retry = "30s";
|
||||||
sound = "siren";
|
expire = "2m";
|
||||||
okSound = "magic";
|
sound = "siren";
|
||||||
message = ''
|
okSound = "magic";
|
||||||
{{ template "default.message" . }}
|
message = ''{{ template "default.message" . }}'';
|
||||||
'';
|
};
|
||||||
};
|
}];
|
||||||
}];
|
}
|
||||||
}];
|
{
|
||||||
|
orgId = 1;
|
||||||
|
name = "cp_dominik_normal";
|
||||||
|
receivers = [{
|
||||||
|
uid = "dominik_pushover_normal";
|
||||||
|
type = "pushover";
|
||||||
|
settings = {
|
||||||
|
apiToken = "\${PUSHOVER_API_TOKEN}";
|
||||||
|
userKey = "\${PUSHOVER_USER_KEY}";
|
||||||
|
device = "iphone";
|
||||||
|
priority = 1;
|
||||||
|
sound = "siren";
|
||||||
|
okSound = "magic";
|
||||||
|
message = ''{{ template "default.message" . }}'';
|
||||||
|
};
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -130,7 +150,15 @@ in
|
|||||||
settings = {
|
settings = {
|
||||||
apiVersion = 1;
|
apiVersion = 1;
|
||||||
policies = [{
|
policies = [{
|
||||||
receiver = "cp_dominik";
|
receiver = "cp_dominik_normal";
|
||||||
|
repeat_interval = "999d";
|
||||||
|
routes = [
|
||||||
|
{
|
||||||
|
receiver = "cp_dominik_emergency";
|
||||||
|
matchers = [ "alertname = HostDown" ];
|
||||||
|
repeat_interval = "999d";
|
||||||
|
}
|
||||||
|
];
|
||||||
}];
|
}];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ buildGoModule rec {
|
|||||||
subPackages = [ "." ];
|
subPackages = [ "." ];
|
||||||
|
|
||||||
# Optional tuning
|
# Optional tuning
|
||||||
CGO_ENABLED = 0;
|
env.CGO_ENABLED = "0";
|
||||||
ldflags = [ "-s" "-w" ];
|
ldflags = [ "-s" "-w" ];
|
||||||
doCheck = false;
|
doCheck = false;
|
||||||
|
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ fi
|
|||||||
|
|
||||||
HOSTNAME="$1"
|
HOSTNAME="$1"
|
||||||
|
|
||||||
# Check if 'nixos-rebuild' command is available
|
# Check if 'nix-instantiate' command is available
|
||||||
if ! command -v nixos-rebuild > /dev/null; then
|
if ! command -v nix-instantiate > /dev/null; then
|
||||||
echo "ERROR: 'nixos-rebuild' command not found. Please ensure it is installed and in your PATH." >&2
|
echo "ERROR: 'nix-instantiate' command not found. Please ensure Nix is installed and in your PATH." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -38,27 +38,42 @@ if [ ! -f "$CONFIG_PATH" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Check for host-specific channel file
|
||||||
|
CHANNEL_PATH="$SCRIPT_DIR/../hosts/$HOSTNAME/channel"
|
||||||
|
CHANNEL_OPT=""
|
||||||
|
|
||||||
|
if [ -f "$CHANNEL_PATH" ]; then
|
||||||
|
CHANNEL_URL=$(cat "$CHANNEL_PATH")
|
||||||
|
# Append /nixexprs.tar.xz to get the actual tarball URL
|
||||||
|
TARBALL_URL="${CHANNEL_URL}/nixexprs.tar.xz"
|
||||||
|
echo "INFO: Using channel '$TARBALL_URL' from '$CHANNEL_PATH'."
|
||||||
|
CHANNEL_OPT="-I nixpkgs=$TARBALL_URL"
|
||||||
|
else
|
||||||
|
echo "WARNING: No channel file found at '$CHANNEL_PATH'. Using system default." >&2
|
||||||
|
fi
|
||||||
|
|
||||||
echo "INFO: Attempting dry-build for host '$HOSTNAME' using configuration '$CONFIG_PATH'..."
|
echo "INFO: Attempting dry-build for host '$HOSTNAME' using configuration '$CONFIG_PATH'..."
|
||||||
if [ "$VERBOSE" = true ]; then
|
if [ "$VERBOSE" = true ]; then
|
||||||
echo "INFO: Verbose mode enabled, --show-trace will be used."
|
echo "INFO: Verbose mode enabled, --show-trace will be used."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Execute nixos-rebuild dry-build
|
# Execute nix-instantiate to evaluate the configuration
|
||||||
# Store the output and error streams, and the exit code
|
# nix-instantiate fetches fresh tarballs and catches all evaluation errors
|
||||||
NIX_OUTPUT_ERR=$(nixos-rebuild dry-build $SHOW_TRACE_OPT -I nixos-config="$CONFIG_PATH" --show-trace 2>&1)
|
# unlike nixos-rebuild which may use cached results
|
||||||
|
NIX_OUTPUT_ERR=$(nix-instantiate $SHOW_TRACE_OPT $CHANNEL_OPT -I nixos-config="$CONFIG_PATH" '<nixpkgs/nixos>' -A system 2>&1)
|
||||||
NIX_EXIT_STATUS=$?
|
NIX_EXIT_STATUS=$?
|
||||||
|
|
||||||
# Check the exit status
|
# Check the exit status
|
||||||
if [ "$NIX_EXIT_STATUS" -eq 0 ]; then
|
if [ "$NIX_EXIT_STATUS" -eq 0 ]; then
|
||||||
echo "INFO: Dry-build for host '$HOSTNAME' completed successfully."
|
echo "INFO: Dry-build for host '$HOSTNAME' completed successfully."
|
||||||
if [ "$VERBOSE" = true ]; then
|
if [ "$VERBOSE" = true ]; then
|
||||||
echo "Output from nixos-rebuild:"
|
echo "Output from nix-instantiate:"
|
||||||
echo "$NIX_OUTPUT_ERR"
|
echo "$NIX_OUTPUT_ERR"
|
||||||
fi
|
fi
|
||||||
exit 0
|
exit 0
|
||||||
else
|
else
|
||||||
echo "ERROR: Dry-build for host '$HOSTNAME' failed. 'nixos-rebuild' exited with status $NIX_EXIT_STATUS." >&2
|
echo "ERROR: Dry-build for host '$HOSTNAME' failed. 'nix-instantiate' exited with status $NIX_EXIT_STATUS." >&2
|
||||||
echo "Output from nixos-rebuild:" >&2
|
echo "Output from nix-instantiate:" >&2
|
||||||
echo "$NIX_OUTPUT_ERR" >&2
|
echo "$NIX_OUTPUT_ERR" >&2
|
||||||
exit "$NIX_EXIT_STATUS"
|
exit "$NIX_EXIT_STATUS"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -1,88 +1,97 @@
|
|||||||
promtail-password: ENC[AES256_GCM,data:DykxIRTXttQgJ6vv3oBOhX1h2PrPimLz+dEHZwjFvg34UEGWfQu5nODw7h6qAJrKIGR5217LgTGZzg1HedbM4Dsb2OJW9c39bXIga730eVvGCm6RcMbpv8GDHPuVCfO1NwQox9Fba8veDWDNqNisHQuYDRQrNZrg1QEiKsujZdY=,iv:kM5Ec376USXMoXCVF/4g7F1NbJNbWfTMVd7LKsTnTuE=,tag:y8aEF+Q6/Cm16W2LYF+orA==,type:str]
|
promtail-password: ENC[AES256_GCM,data:jooCw16EEw9JC+W19bXvoOjnCo/KP0H1Bpc0UqfGN+mCqFLK98TDU80hNu54pYQowcAtgjB5ZM64gWt+stqFKWVWihF0d4A3KuTTfpxmXGdGi6ThRcAXhMmXLH5SYR4N96d4WsvHNsFTRGItnUlp2juQMKHnZ2At9RQWgBQqK6Q=,iv:HFttRHz2fIU9qZzP5r24/AKMHTWwDhhIrgQpxw6Ol/Q=,tag:umq2lzodL2nijayGms7ciw==,type:str]
|
||||||
sops:
|
sops:
|
||||||
age:
|
age:
|
||||||
- recipient: age14grjcxaq4h55yfnjxvnqhtswxhj9sfdcvyas4lwvpa8py27pjy2sv3g6v7
|
- recipient: age14grjcxaq4h55yfnjxvnqhtswxhj9sfdcvyas4lwvpa8py27pjy2sv3g6v7
|
||||||
enc: |
|
enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxQTkrMDlpM3RnZ0pNZVlM
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBFT21QaTZ5ditZekVmaitY
|
||||||
dkNya243OFlycmRRS1o3c3Z1Vm1UNWxBbkVBCmw4dDUrQkg0NExaTHJaSk1JYnpY
|
b2locXR6M1NNSXhIL0pjK3Y4dzlYcjRXMlFNCnVtelpDdWovc0FiUGQwWm5TRlk1
|
||||||
UDNHa09Rd081N1FVbXgyRHVWbUtna1EKLS0tICszQ2Z4aWpNV1U5RVNibllGdGlY
|
b1pka2paQ1NBd2c3WG12U0N3N24vbHMKLS0tIHgrMHdxS3J1WWZNdGdiaVRaWjBm
|
||||||
alFRNFZVNDlOUTJRbVQ0T3dRTTlJZUEKx+ftKJc+RMmxXoRxLd6gsvN6Jfnn5Xre
|
cmh0VEx3UDRocFVlckFUN21YblpEb0kKKF7CPzXn6e9o1+BctLSHcLZTWYdYiXQs
|
||||||
48TolLwPoBSr6uSmfWfcXIL+2uzo5cTGhMReCEQrlHOWGxhk+XDmfw==
|
dwX8ohGJc/Q5Ewrrdmm77gu3ttg7Ml/70ToG/yTBExH1lwGb1z7Qag==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
- recipient: age1exny8unxynaw03yu8ppahu5z28uermghr8ag34e7kdqnaduq9stsyettzz
|
- recipient: age1exny8unxynaw03yu8ppahu5z28uermghr8ag34e7kdqnaduq9stsyettzz
|
||||||
enc: |
|
enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBVSjAvWC9haUh3blQ2bHJj
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3R2hwWmtZVjlTSFZTMUd5
|
||||||
cnh5YkkvUWhEYXFFV3RNbURwVlROZk1yUmc0CjdMdXExWE52WWNyRGdyVFRzT1o1
|
cXpqbXkwTmQrYS9TT1NYaXZ4azFjRTlOZ0Y0ClBIUGk5R0JGenBrSXhNbExLaWkr
|
||||||
Umo4OWhMYTZjTkJvbW9UaHJVaE1YNG8KLS0tIHBSOEdmQjFCZ25jNGlHMmZoalpW
|
eUJLUVBYYzBFbHg3Z2l4N2JINTBSVjAKLS0tIHFTa3JKUTVPNFM2TktUbm9mSkVo
|
||||||
c0FZUzBVYXRTMHFZSGYxVDdzS2d5a0kK1a/FQ841bIKuXHjVAjV2YPTpkmI0R7fX
|
Vno4TER6SFR4bDM3L3FYSkw1UHJoeXcK0mR/ysz38ZhEAqhEZZXmuH3rykMUeFk4
|
||||||
ohkPSQneoOnwZPXby69PJLSYwX0IcQCckkGXa1z6KLr6iueSpyM6JA==
|
tPvIV3LpRXpU+yiT3zpLJXVi3GDy9vaq/h/uG7rDhE/nPoaIIVBBhg==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
- recipient: age1v6p8dan2t3w9h94fz4flldl32082j3s9x6zqq7u5j66keth9aphsd6pvch
|
- recipient: age1v6p8dan2t3w9h94fz4flldl32082j3s9x6zqq7u5j66keth9aphsd6pvch
|
||||||
enc: |
|
enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBtYUNabzVBcFpIdFhUTW85
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBtV2tlLzhqQWlDd2FaZ0lJ
|
||||||
ZlVjamVZUERSMGNMUkhlUUVVd2ozUkFLWHlzCnh0c2gweU1ud2cxS2p1eXUxNy9j
|
dEt5Q2YvTWR6Q3pBUHl1elJEN3lJcE01YTBrCm1IWlVVd1poVk1Gazd3NnBCa21L
|
||||||
d2tCTVR0YjY5bktQa09tUmFvM0F3aEkKLS0tIFVWMVFUU1RMV1FoaklnS3Z0VzBJ
|
UXByN05KNGdUTW9uckhvNUE4bFVMME0KLS0tIDVSRCtJNnRSdmFzcWVNNHEzV092
|
||||||
bitzcStWdzM3TXBMbGJKNGVZQTNVZ0EK0qjI7PKk9lUDG+0ZeCL/9ILI9KRIEU+z
|
NHFYYTFRdUVXNFh5Tlc0U2twYWpuWDQKTqRXFxn/OuYrjVSlGNyHWtCwmaV+4PMr
|
||||||
6o4AcdGcd44QkUjYboLTwGvdf4QdKZvyfBk6xliUIzn0tbX0CrEHOA==
|
+wpjkS+3pEWYaMtRhoBKJmPXhbE9e0SSzFV/HEYILswUfIWuQNpNUQ==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
- recipient: age1ylrpaytkm0k5kcecsxvyv5xd9ts4md0uap48g6wsmj9pwm4lf5esffu0gw
|
- recipient: age1ylrpaytkm0k5kcecsxvyv5xd9ts4md0uap48g6wsmj9pwm4lf5esffu0gw
|
||||||
enc: |
|
enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQdmhqdXpIblh0QVRuSGdl
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4RFl5UHpOcUIrdEVoREFn
|
||||||
WmpaV20za2d5MFdYbTNTRTEzR3BUVFJLU2tRCkdHT1JFNmVEZGNkRGJ1S1cwU1Ru
|
ZmloRG5nb2d3eHpScmRWVjVLUXpveW9pZzJrClRVa3h1Y2dIL0RCZFJpM3NvTndu
|
||||||
MEp3ck1MN0tYRXBPY2xQR3JIMURpWkEKLS0tIEF2UERsV2J6UzZYUm5sTFdPWGlo
|
VzVMT1dwbWJEOXFKZDNVdEgwV3RHZE0KLS0tIElkTUk1SHRGcFp2d3BpUDgxZWVG
|
||||||
NjVGSDdndDRsQkx4V3U3N3FjNldUTTgKY8ohcy0H+fxkmBksfWzVLZsbfqDfWUzA
|
RE5UQktjTEtzd2I1SmVZWE8xWm5SSDAKOfrr3seS8+UqGZXiJfraGh9wTqx7zFnH
|
||||||
5FUdmqCHdg47Mct3K8qXHSEbvegn/8Hp4vSgkVQcEA2YFcf4J5GRpw==
|
GMBBlCj2SLAHP56efITiPJ6kFISFoc6QgBj024oUXop2HT3CQh5hJw==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
- recipient: age1jyeppc8yl2twnv8fwcewutd5gjewnxl59lmhev6ygds9qel8zf8syt7zz4
|
- recipient: age1jyeppc8yl2twnv8fwcewutd5gjewnxl59lmhev6ygds9qel8zf8syt7zz4
|
||||||
enc: |
|
enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBnbFMrNzVMYmlHWE1yb1hj
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBoMWhoa1lKWU8yRHZnSUNm
|
||||||
eGw2SS9qNTdKTDdSQUlTNjhnS1dZaTd4NFFzCll6SEhzVnY5UnJUbUtlUzJzZS9N
|
cVhycko2MFczWE56UlNDUGlNdndXNnY0blhvCmlIeGRHUjRuODhCNk5lVlVqZmR1
|
||||||
cUZMYnV0bU5DRjU0MW8vSFpIN2pNT00KLS0tIFdTTlBPT3J0cmF6Y0lnaGRpQW4r
|
ZEpMTUl6dnpoUkhGbTBsOUNzdDliM0UKLS0tIDRHWjllMEZyaDN1OTY0RXpzWUVZ
|
||||||
TjRsa2dlR3hrZkVQTFFWQm1xR1pLQUkK2Kio6ShvcsbJ2n1UG97gxt5AcdqKolMq
|
WklqZW5DT09DclBBOUZ4VmpIMVdCRU0K5c8JtZ5dfzxmtMlnL+3637/6YBWN9qdP
|
||||||
3sdoF7b87Crd3QSzDKx2Rm97EjeQskOBOgpasF2W8GoRYCol05Y0bQ==
|
+/l78vhb0KVt1SOI2d6ZnfkKEXSO/PyBpOkz+AOubxdpQMNOyQsgcA==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
- recipient: age14uarclad0ty5supc8ep09793xrnwkv8a4h9j0fq8d8lc92n2dadqkf64vw
|
- recipient: age14uarclad0ty5supc8ep09793xrnwkv8a4h9j0fq8d8lc92n2dadqkf64vw
|
||||||
enc: |
|
enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBOZGZobW1aaThidmkxSEk1
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3N1BEMjFsZG03K3gxUVli
|
||||||
VXVzdEdIMENnRC9sTGpXQUNwSElPWVlLTVFNClRuQ2pYanFibEJoSllXYVhmSk90
|
b1hEUEY0NDRRc0xDUkErOXExa05uMkdDK1J3Cm1WUFNRNzZGMWdOOU5kb2kyOFNs
|
||||||
QVlGUVBjMkN4RG9BempCRDlFZHJPancKLS0tIEc3Q29tUzhzYzViMkpzS1RNczBE
|
Y2I1aC90SEZqSER3TXU0RTlVd3VOTHMKLS0tIGFMdDM0YWpJVTFFVEFYcUl3b1Nl
|
||||||
djdYNVdvZHRkOHBWMGk1N3dlb3JLUFEKiruFC9YV3gloPaP9+wY0Sir2xA9NUcPN
|
dlEwNWRmVllHSmtsRWVvb2h3ZGJaZUkKrEzfrlYGgB05NWxc3h6olIzGmdRCYDWj
|
||||||
matBs8oPjlB5dlrCoiHi8kl1i5ROnlu4tlNpLB0PcO9fCUMP1ypAQQ==
|
mr5PEAWo0KGcvPK61lxwpHdThp3NGV0pqAHUU5+7Td/PbguHvaEPhA==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
- recipient: age1wq82xjyj80htz33x7agxddjfumr3wkwh3r24tasagepxw7ka893sau68df
|
- recipient: age1wq82xjyj80htz33x7agxddjfumr3wkwh3r24tasagepxw7ka893sau68df
|
||||||
enc: |
|
enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJcFNWb2EvQWF3czQrVVg3
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBBandodFZnbU05SVN5cVlO
|
||||||
Wm1hVm16bEtYS2pnM0R4elhGMUlqRUZXcUdvCnlJN3dxU2VKUk9JTk10SjdubmVO
|
V1BVaWtLNWdJMmRtUWxaMGdITmNKUDdXdHlRCk5HbGdSWlFnKytTVWxOcWtPZ3VJ
|
||||||
NlgwU3hqMEp2cmF6R0pmdU9EZllJVTAKLS0tIFNwMC9jdjh0MXJpYzU5cE5mc0Jr
|
bi9EZ2p3VURkeEZuN2xsbEhkcWovUUEKLS0tIGtzTnJFOXFsTittUlZ1eCtjWTVX
|
||||||
KzJoVGlKTUNZYXhpV1NMcVVuVXI3SHcK7PIY6HznGsckYauyFGVxmU344FqkPYhm
|
SXdWczV0ZnI2a24zeElIZUsvU0ZSeUEKNX9qLko/2aFcrwW5LaMjvg9IJlNszSKi
|
||||||
1x74NydHuGLAkMd3H7AchnxP9tVzSX3sOD9AqYqgg3nRS7yaIet+sw==
|
7nl1d1fTLGCeMUvgwZU1uBIyCm/p0HTikBaDob5L5fJAVlSQNZxiBQ==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
- recipient: age12msc2c6drsaw0yk2hjlaw0q0lyq0emjx5e8rq7qc7ql689k593kqfmhss2
|
- recipient: age12msc2c6drsaw0yk2hjlaw0q0lyq0emjx5e8rq7qc7ql689k593kqfmhss2
|
||||||
enc: |
|
enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBROTV4Y1dQMkZtM1RUQ1lD
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBVdDdBTEpMZEI4NFc2aU1F
|
||||||
QVdyUXY1TVRxWURBZlRsZWVYTEhnQ0lMYUU4ClFLcVdUZ01YcDc1OUYwMWpJRUpy
|
aGlwcDk3ajd6NjE0UUhXbW5WT1RCSmR0M1hzCk1zVVV5V3UvUmx5REZuRk1RT3pW
|
||||||
NHVMM1FrK1B4TEU1QUhsbjdCL3M0dmMKLS0tIFRFSndnZ0V0a2VKV2VXY0N2Qjgz
|
WUVocW9iOXppWFBqRkVlZE1TZzFUbmMKLS0tIDlzLzdnWEhkWWFFQm1seVJlVHBw
|
||||||
dFZQbm13d3JOWlZiSXZTcUpkSSsyVTgKI1GJ1uRRcTH/13lkAiUxNhBNmDgf4MFA
|
THlweWtPcFNyT2RCNk9UQVc3Y0lnNFEK/d2fvmsIrRTHc3kBH2sAUBg0MCp4nXNT
|
||||||
5nk6z1/nJglnvajYyGXlAlZF7XofbUtUWZeBbtWwbeWImjIa/+KaSw==
|
imm7SINgt6aH390yL7BWHMBKzdgNHO6hn3plLV8EW8upsETwJCbrfA==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
- recipient: age1x3elhtccp4u8ha5ry32juj9fkpg0qg7qqx4gduuehgwwnnhcxp8s892hek
|
||||||
|
enc: |
|
||||||
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAvc3ZteDRVQ0xsRUpxY24x
|
||||||
|
Wmt4MS9ZL1cybUY4cHdPbC9rQU16WGtESkU4ClhoMnhuY2NrT0xyNzl6Z2hJR3dt
|
||||||
|
VHhLa2hCeUhCcjFPZTkxeUVaazFWa1kKLS0tIEVndkhYaS9GbDJKSzFIb0xQUzQ0
|
||||||
|
V2EzOXNWNnYwaXc5dkg5b0RDdjBpa1EKfIC1OigtPBRWIgXUyb4SSjpbO2Koqaiw
|
||||||
|
TQT+hnR+VkThbcfyWPZ+Zpe4lZzfcdMGfr3m7tdv/xY8epwrThInjA==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
- recipient: age1xcgc6u7fmc2trgxtdtf5nhrd7axzweuxlg0ya9jre3sdrg6c6easecue9w
|
- recipient: age1xcgc6u7fmc2trgxtdtf5nhrd7axzweuxlg0ya9jre3sdrg6c6easecue9w
|
||||||
enc: |
|
enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBjTUhiU0RnMTAyV0xycnNL
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB0WHdpbGJNcmZhK3ExRi9t
|
||||||
V2xSWEttejB0SWhJR0FoNTZKL2x6MEJkcUZzCnFjVUVWNGV2SW1NZEpkN24rUVpX
|
K3ZHTld5QmxMYnZXTFZmNFVIbEZtS0pFRDM4CmJPK1BURU83UmFVdVNoam1reG0x
|
||||||
dUJ3Wkx5aUlsWDByOTlpaERpNEpIa0UKLS0tIDExTVVJeDFEUStzamw3RGU5cHdE
|
NlpLbDVQY1FNR1RyWm1TWkQrUHcrNUUKLS0tIFpVVGVscFFPclVTMG5IblkvWXlr
|
||||||
WlNqQm1jRnpLWXBzRVRZUjc3Z0c4dncKonlHRgH7P4da+RJkGdWHRPiN76oPbH5U
|
ek5lNTFkMVUvSU5McTFDS2tWWmZ1UmsKyhUXdaSGxKFFZnATRlTh7GzDu7eZ/mkq
|
||||||
DzNuS7mPsRAuajnCAGeqodzqllsGJatZUOVKFem8Of56Wm3pw3yLhg==
|
V+9pqaob2fshwQ3tNVZtXmWTHv1geyIBxmQCFVSOaHIPVLpiC4Bhow==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
lastmodified: "2025-11-14T11:28:03Z"
|
lastmodified: "2025-11-29T19:44:44Z"
|
||||||
mac: ENC[AES256_GCM,data:PBPNSGj6UaGoxH1Jq25bD4q/d42HrnBNhe5KFo1MoQCp/bzsphN8v6+tbHIdGh/VAoU7auRZVWXYALOl/3cGnpL52zvJGDaMlPlDVdzz6wHkl24z1ousWM7FKPwBtvGuAWAknYQW7KpQTtpobbBr8QHy/O4dB/NqxXTj/MSsbxY=,iv:1QOFK1LiKPnAuXeXNBJbeL0d73nsMq+DJCpeVruDumE=,tag:hJH3S3ZurYd0hcoWyWOocw==,type:str]
|
mac: ENC[AES256_GCM,data:pRol7WdkK+Vr3fEc7UaEhoHlLvwwm0KdGOCReS6Rz12gD0Fw2UuNYsPnaj1XTdSLSfJITpEorFTmt455BpC6wMCszICSkqRn+EBgu4WWFZrv5v1m6BjSOTsU8bj1iAggiqsx57WS9opMThCzCOSIJD/EEzQmk5/qva/aBIJni/c=,iv:jorZE1XG0xJDGsXgw8EvuX7AL7yCuSynrqaeveCF4SE=,tag:btZHuaiPdZhdRj/+JU9dSA==,type:str]
|
||||||
unencrypted_suffix: _unencrypted
|
unencrypted_suffix: _unencrypted
|
||||||
version: 3.11.0
|
version: 3.11.0
|
||||||
|
|||||||
@@ -1,88 +1,97 @@
|
|||||||
victoria-agent-env: ENC[AES256_GCM,data:m+o9GgDSm2qYVk90199H6J+RqE0fZH92G7uFjP0Al1JlvclOFjHnNlFJ7y8YfgBcPUFutIU45HN4+I2X2k0+GFyKlrKxevH3wUKNIWG/l/6VlOmZr7SMRQAlVv88aT0EeBIEh+AOilCcWsD4egQPS/faP5yolsqrm/sXltsdbdI6i7EVXBKUUryBjogE6tv5nroN0ter0hXtyspkV4oBxcZIqTK4/t6staEq1OcY/augdpqz6z1aBFVun3c0q70vS5VBP1KIKd+nKVABWWwt,iv:7mnSPuP3jh06uIFQbWUI2VRFF5wbGFLoTS4rf7PUF7M=,tag:BtVruJm2+9pGNYOzQOV32A==,type:str]
|
victoria-agent-env: ENC[AES256_GCM,data:kkbtEi4nVqv7jvL0Y3XCsil0InrVsL1zasBcOSKpA32ix73q0bRIwT8JVXX5hI6G8REYvp1oGNRlMBmSPy7H5YxxUd3RsK/5SbLkPfifTAxl8qz1STKPOmq6dZn016809AIkBiDWuu4FOHvzoSVkaO5EXluzb9wVwGH1HM/5pYBDFzqtkgWbCGbeGRXComgD4bqlFjDg/HRX2CaZf0sBByFQxH+NFvXCwdBeEZ82x5gBmXsaPnsAmh8fJotK7Eyu4+Urnahcp8V/4cTadhvg,iv:eizMQCL7vuTn3F0lY23fQfsvxiE8P3CdHqImth9X4JA=,tag:BMltfLO93YMEKv3sycDF2A==,type:str]
|
||||||
sops:
|
sops:
|
||||||
age:
|
age:
|
||||||
- recipient: age14grjcxaq4h55yfnjxvnqhtswxhj9sfdcvyas4lwvpa8py27pjy2sv3g6v7
|
- recipient: age14grjcxaq4h55yfnjxvnqhtswxhj9sfdcvyas4lwvpa8py27pjy2sv3g6v7
|
||||||
enc: |
|
enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhaFZ6dXVqbHppZ21NeWFi
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhSjNMUlh2TFB4L2JBSWU3
|
||||||
NU1kV2FOUkVDWlc4RVdUdGNZU21jODdUUXpVCnU4aDdTbXlWNlZ2OUVaOGEwcDkr
|
U2xyQTVoUmlQYUV5R1Y5MWtOa3VSVkkyTURzClN6QVFpZjZDdXdrU2ZUWDUyZDN0
|
||||||
aGNrSzVuZTVyU1BMNXArZTBSWnBacG8KLS0tIFVCMUNDeWdTL0VxLzdYNjgyMHY3
|
S2RIY1pjdk9vSjg2cUZyTUpQbE51a28KLS0tIDFxSUNiZTQ3UWMySnNCWVZ0MzNZ
|
||||||
QmNCNGdaeFRHOTY4S2Z4RE9LZVB4TG8KQAe6ensRM4QVJKnDgbnFk9ZYoLk4L7iQ
|
NnI1RFhXN3pqdTYyQ0NFZG95UHdvWkUKD23hIRYhZ0BRamLcjfKOpcHIzeatwEFb
|
||||||
4V8jODObt9m2WDCqASJdt/8m/l84E7FTw4g9aimr4fBOU4zOqpGe2w==
|
0dvIm8RWcZEYK4w96GY5EQPkJsvcoBNWIyMFrJkgtcRfie3+W6kdag==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
- recipient: age1exny8unxynaw03yu8ppahu5z28uermghr8ag34e7kdqnaduq9stsyettzz
|
- recipient: age1exny8unxynaw03yu8ppahu5z28uermghr8ag34e7kdqnaduq9stsyettzz
|
||||||
enc: |
|
enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBCYnhvamdLYjN2SG9DRVl1
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA5dlY4c1FPU21VbkI2bzMx
|
||||||
aEdGSWxQTUwrSTBtQURLS3RiQjl3by8xN1hzCjVLc2hQV1dCbTd1UkRaUC9ZN25V
|
WS9BNDJSU0JyTEVvY1VFQkU4L3J4U0hrd0NZCmlkQWw1N3RxU1MyZVVuNVZpVStk
|
||||||
aDJMczFSNGhCZTZFWnpLSW9sSENoWUEKLS0tIG5wRnBvRytKTXo5TXZ3bEdlVkVX
|
UXlyLzlUaFlWTEdDdUZBV2Q5RGpvV28KLS0tIFRHTkwxWTRuMlFJWTV1SVQwU0Ji
|
||||||
bDF4UUR5bE1JK0tGejJWanFkVDJCMUUKuIbnEMXPsDkJ2eDriJ6gKmwC4gj4ijfU
|
c3JnQmlPd1JMQU9pcnVoaG9teVlBWWcKMOIDN36cLqOemzP62uRgKe/myhRs4F4g
|
||||||
vmuAEsdpjo2UzP2Sjvm6tFSK85LvZi9lDu5NXdVTdwcq13+OYX1TLw==
|
MiNVYUL+d3WR3HQEvhovUU5RowlCD84U6Za8z+ss2sApJ6jdUcSP5A==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
- recipient: age1v6p8dan2t3w9h94fz4flldl32082j3s9x6zqq7u5j66keth9aphsd6pvch
|
- recipient: age1v6p8dan2t3w9h94fz4flldl32082j3s9x6zqq7u5j66keth9aphsd6pvch
|
||||||
enc: |
|
enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA1aHJlbDFBZlAvbjhRNHkr
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBUMS9CbzFrZENWam5BOW5t
|
||||||
angxd01kWkpVckZHcXA3VzJCa0xRSEpuZWlRCjNrUVV5NnM5VWV0YkRmQnE4aU1y
|
K3ExNGplNWZLNTVqMndnQ2ZSOFVIL2YvdHlvClkyS1RvZ3FaQWVkNjlWWExkbDZQ
|
||||||
SDc1eXBDM1hsZHh4UkFYREZmTTFLMW8KLS0tIEJZSmhWa1l2SFpqdXVQWHZuWkc2
|
NFVPWmkyNkdoTXdjY0xvS3QrMmFLeXMKLS0tIDhHV2hmMmIzY3dKeTVsUnB5ZGhR
|
||||||
ZFNPcDhyc3dIaWx6TlhTMlIrK1NLWmsKhMpNkZEvlaIKWauZsVoLzwYWx0k1sbmk
|
cXpPcklDUDRkcytIcytDY1NYYXNQcTAKvjGjCCrj//KRijPCLOEDAxiexY0CnnqY
|
||||||
KO+pNAUz2RMX+N4ykCRgFfeV6SDMxbaOACFm1/6yyDXHgvaI7zrQTQ==
|
80l7bSnQPOOs27HO/9YlJerQlhPBRZo/KJ+cY3T2fAIfad/f7XN04g==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
- recipient: age1ylrpaytkm0k5kcecsxvyv5xd9ts4md0uap48g6wsmj9pwm4lf5esffu0gw
|
- recipient: age1ylrpaytkm0k5kcecsxvyv5xd9ts4md0uap48g6wsmj9pwm4lf5esffu0gw
|
||||||
enc: |
|
enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA1WkQxb3h1aUVDVGJTMUd1
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBaMHUrQWZMNmxtSWtxVllM
|
||||||
RWdXMUQvWUk2TDYwbzd3ZW5ZSEVGZFhUOVg0CmxEd0lOV3EvN014NUFQUEdaWjNq
|
U1VsMGxVOGt4UmtJSTE5TUdQaEdXUVRIc1FBCk1vTlU2c0lHUDh4VmYwMlFwZU44
|
||||||
ZVE5MGZ4SVpXNTRwRnplMHhrQlBHMm8KLS0tIFpUeWZQZmQvWlBrcG9oNERIVEZt
|
Q1p6N1l5ZWlveGY3VFZJNDNpTFpqNEEKLS0tIHJxdlJtTDkrWHFTcmZXbnVCQ0ti
|
||||||
UlBxcFgxVlc2WFRxVkhjWmZCdUdjSEUKPbadYyvWy0Kfbs5EovcpL3Aukj7wiZPJ
|
K2ovOUo4UTN0Y0dubllQM013SW05QmMKQUN03J+ju/JVs6geEMWLnnnlzjPfZOqS
|
||||||
VlDkELrTtbvFYp1aAASFZOQb+0NYUyOCtM/OI5qNYigR7TgJBAf/Ig==
|
rn3ldZv1aC7ckAp2JiVoznkpsBHFOV8T0pxE9vnmDipAU263k/4ktg==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
- recipient: age1jyeppc8yl2twnv8fwcewutd5gjewnxl59lmhev6ygds9qel8zf8syt7zz4
|
- recipient: age1jyeppc8yl2twnv8fwcewutd5gjewnxl59lmhev6ygds9qel8zf8syt7zz4
|
||||||
enc: |
|
enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwcnZrUklWYUZBbm11S2RX
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBwa1hoalhrQVdsc0NrNzN6
|
||||||
b0F6UjNGQVlXQm83NDBCTFoybHhhTWd6NWs4ClM2dXNXT3pPZkpOYnpRR1BwV1k1
|
ZU5JVTdDRjhBWFUzMXhJM2FiaU5HcmlvZzE4CnJ5NDlUYmZmbzZsdC9jYXJBc3Rx
|
||||||
NWlXNXpBeFJHalhxaExybTJ5NG5nMlkKLS0tIDdXa09CKzdVeVp6TFdpei9PempS
|
c1RDZDFOdnVHYjZSc2VNc2tOTGF4aTgKLS0tIFhEK1JaRWk3VzhtUE1GYUxoOVJD
|
||||||
dVNPL051aXpYN09EVVErZkV6czI2dkEK2UZPimVhLwjgjXjj1m7Qc/w36xYDe7sQ
|
ODRHRUxheVRuaEVoZ01sQkNOZGkzMUEKUBpUd30EjSkRFK2cbCARdycf9hHamoVG
|
||||||
D/5gub0EFuycIQj3lw0y59Jds4GoxBImExC0AKIaX9XBGW1BfBFtPQ==
|
XjCfIf1BLGe76+c88zDKaPvp/iAWfGypQkA71tRUoe6pEtrAT8sRgQ==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
- recipient: age14uarclad0ty5supc8ep09793xrnwkv8a4h9j0fq8d8lc92n2dadqkf64vw
|
- recipient: age14uarclad0ty5supc8ep09793xrnwkv8a4h9j0fq8d8lc92n2dadqkf64vw
|
||||||
enc: |
|
enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBKM29sYklYMDUxTGJRZGlw
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4a1d1UVVmNDVkaWtiOWlN
|
||||||
V2tVQTFaZXhoa0xSb2VNZldpUER5a0hWQVVVCktiVDRORDBPM25xSjVucTFsOUha
|
enA5RS9WSGgyT0ZqNWNvZ0lVbGpkcWxESzNJClFMMlJJRDJDSnRyVVZmRUZIU0JU
|
||||||
dHdVMjMxWDcvbVVoTDR5czFrMnVXd00KLS0tIEc2V3RtUVJBbHpKWkhneFRzcCti
|
RFlTUm1nSzBFWVFEOGtROFVWMit1R1UKLS0tIEtRRXZvZFBKa0kxaDRXazJEcW1P
|
||||||
NkN5SkZNWlp2dFBmclNjcW1iWXZZY2cK6fqN6xbVFLSTRPfDhvRALVt1yxizvyzI
|
ajdFVHVpSWtjVU1ZSmlmUmxIY2NxMmcKVMc+JpeCxXs9RGjg7RN6c1TC9ndIOvWw
|
||||||
C32AxiKQo9XrXGBmD5Zi6dtTy+Kdm4PqZjk7M1vW0LOJCErlVI0AjQ==
|
uFCTBRbTZJHyDC5fYrQQdhMQprm8UT3Fr51i1RWVWjsHz8GZEBwQVg==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
- recipient: age1wq82xjyj80htz33x7agxddjfumr3wkwh3r24tasagepxw7ka893sau68df
|
- recipient: age1wq82xjyj80htz33x7agxddjfumr3wkwh3r24tasagepxw7ka893sau68df
|
||||||
enc: |
|
enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBNT1dBY3kycWVINUV1Wm93
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBtTm8xeUlpbC8xRndpSGQw
|
||||||
VEMvNTRSQzV5U2N4ODhiSERHcnVVK3c3Q0hFCmxwWXE3Q1BCRFpIMUlFemRQOTRB
|
S2RDSE93ZG8wcnlHZFBXbjNzMXlpeWRIVjBBCnlVRDMzL2paRmFWL2toZVFBOFRk
|
||||||
TERiVzdOZGZDbmxXT2ZYZHdhY0FpOEEKLS0tIHRVVkYvVW9nYnJ3WkFhNEQ4UkVY
|
bi9LRjhWM2ZBcTBFMk9ydTFhaTdrblEKLS0tIFQzUjRpSGR5NzNYcFNVNUNVUks0
|
||||||
US96T0JUUlJjVFcxc1ZSUjY1aVg4NmMKc4jD2CtrKc43wArBm566h117ko1vrsHG
|
R3AreG1Cd3d1NDNIWnlkcm94L3h6NGcKurqFEsNJklMgu3G7mDNq449o2vMX/2t1
|
||||||
ONq9zOp4z90WPvY9octFOEn9cZ2tJvcGYDhWyBGH3rVRYN5hzTRLdg==
|
aUOLRLzi5GTJoQzmuJbveQA+HIgv/B9aNC762YH6df4N0Yk1GRt7pw==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
- recipient: age12msc2c6drsaw0yk2hjlaw0q0lyq0emjx5e8rq7qc7ql689k593kqfmhss2
|
- recipient: age12msc2c6drsaw0yk2hjlaw0q0lyq0emjx5e8rq7qc7ql689k593kqfmhss2
|
||||||
enc: |
|
enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBUd241b3c1MFUvUTRRc2R3
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwUHZ0Yk14c2NBQm9SelNR
|
||||||
dFNLREZ0Qi9kcWlVait0bVhJZ05PZ21ScEFjCitab1NaWDlEWkEwZlpzTmFFQ3RV
|
cjY0K0xYUWREUkhWQVNRUVFUczlvZjZ4V1VnClV2b3hhMnlhTUt2U1FLTERlVThv
|
||||||
YTVWUmx4VEJ2Q1J1TllZZ2VTaVF0OFUKLS0tIGZxdGNaM1Q0Z1JEbkFpQm0xaW5T
|
K2Nqd0dPVWZLTXI2Y2VhaG9Yano1d1kKLS0tIExRY3NlZ0x2VWtDZWhJc0dyM0ow
|
||||||
bHkyeHVtbCtzb2pKOTUvcm91MnBockkKCrwgT0auKuuRYNwajiqBVzpgG13li8KO
|
dlp5SzFqZmNncGQzYzR0THkyeVR6WXcKUb9DJ1u3/VnYKXIgzpTdImSgE2lWdCOD
|
||||||
W52Fy9lIc9poyxEnvyIqpPz29gQdL4aqGHX735f2+n2fq/SIgiKHfw==
|
yVOJXwY5IrU6NJgBYbZlbIysIS+ihO9pLodjaYhbZduNCasdrhwu7A==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
- recipient: age1x3elhtccp4u8ha5ry32juj9fkpg0qg7qqx4gduuehgwwnnhcxp8s892hek
|
||||||
|
enc: |
|
||||||
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA5cFJka2xuSDhyaXowSzVw
|
||||||
|
cXJSR1ZUWnhDdWRwbFpmenJDZURmbXhDNVhRCm1GMTZ3VHA1S0o3ekVsR2loS3Bi
|
||||||
|
SmJrOEFBUzJZVUlxazh1TVhkZ0kzVm8KLS0tIDZwajVOUWlIS1FHWHNpZy9jSXhK
|
||||||
|
RHBjNjRBRzVaUlMrL0d5ZGZTdUcvYkUK0hf09zou0OqZSV81G8HMeNRx+uz9Nade
|
||||||
|
dhyQ1CRVWT5Q5HW/4t/abB0hSxFId8+X7ufJIjjVSbywLZ7WHLKCSw==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
- recipient: age1xcgc6u7fmc2trgxtdtf5nhrd7axzweuxlg0ya9jre3sdrg6c6easecue9w
|
- recipient: age1xcgc6u7fmc2trgxtdtf5nhrd7axzweuxlg0ya9jre3sdrg6c6easecue9w
|
||||||
enc: |
|
enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBrNzB5bHVMU2MySmtaOHA4
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBndjYrRnFRd2RDeTRDQ2Uz
|
||||||
K0N3RnZINWpTbkxiTWJnVTZ4Z3d1L1N3T0hjCmxieHdhc1VFWXM1WHExdWNPdW16
|
UXk2WE1ORERTOWtOcXI3eXVHZzJJQ2RMRTMwClBPR2J6cmMzSzR1cXJWaHJJUlJa
|
||||||
ZVQwTzdGcFVWamMva1RpZGV6U2hpSUEKLS0tIER1T3ZJYUQ0RE5TVTk2dVdmYXhU
|
ZVd1S3hpWEdFYXpvUXd3ZUs3SVJrZHcKLS0tIGN5MUFET0M3WWZkcHRlV1QyTEQx
|
||||||
eUNKWkczb3dwMnB3SS8rVjRHU1VBQkkKOdh1+qrx9WX0NGSVrGdptFNU8C1ZZFNi
|
eTFJUnZpM2haMUZBekh3SjBQRGFsQUUKUI4laym7zGzu1iIlMYnnKjyDHUqblcn6
|
||||||
3lJmzNvJRyVMxXNjWqpXUfCDWQONvCKEGuAvt7Zra+z7/2GtcFTNZw==
|
K49AdBT+U8WUnVNwAOc5Irf86GfNvJ0S0qk6+v4CpPDgKFksc3flFg==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
lastmodified: "2025-11-14T12:05:25Z"
|
lastmodified: "2025-11-28T22:51:04Z"
|
||||||
mac: ENC[AES256_GCM,data:DuD/sqYIwfW2XW+QojfuwLOKI+Y3lXCYOmI5ayCqQlWU9F0W3H1WSnVyfp4YanwY9zjNJukZy9vQNB7wk7DoPGrYTgPihEFmt1GWV10kCFhOxQCMaB9Pt8a7JucF1k8+4jbT4SW282KLAoITDqIAe4qXBPEKKx1+SH34VhKZh3Y=,iv:j5U41i0LHVntwyhTjHExkS65K/gWpBdNy68Cb+bIXmg=,tag:Wu46HI+T+ZzeTb4Rf35vdg==,type:str]
|
mac: ENC[AES256_GCM,data:pXXNkrHSC4mp+K2NfiJ7RucJ299BkVv7wQiWlV2SboLwS7le753CKf2yBGsOYEgxelhLAnIFW3biDR/v+P+7kvUinRUPIYyxeYzMT864Rz8Fb+FBjf4Bd/j/oBdSWk1QM6aOPQRcAiNAol+AcdajhSmy68prUp/lR0gvDPGuczA=,iv:t18AtziHtA58uMoVSsDXsn4PPQUSJCNCG6jg9VBvM0w=,tag:RKQRWjI3NE3Rzb+JdMrOng==,type:str]
|
||||||
unencrypted_suffix: _unencrypted
|
unencrypted_suffix: _unencrypted
|
||||||
version: 3.11.0
|
version: 3.11.0
|
||||||
|
|||||||
@@ -5,6 +5,18 @@ self: super: {
|
|||||||
openaudible = (super.callPackage ../pkgs/openaudible.nix { });
|
openaudible = (super.callPackage ../pkgs/openaudible.nix { });
|
||||||
openmanus = (super.callPackage ../pkgs/openmanus.nix { });
|
openmanus = (super.callPackage ../pkgs/openmanus.nix { });
|
||||||
ai-mailer = self.callPackage ../pkgs/ai-mailer.nix { };
|
ai-mailer = self.callPackage ../pkgs/ai-mailer.nix { };
|
||||||
|
claude-code = self.callPackage ../pkgs/claude-code { claude-code = super.claude-code; };
|
||||||
|
|
||||||
|
# Python packages
|
||||||
|
python3 = super.python3.override {
|
||||||
|
packageOverrides = pself: psuper: {
|
||||||
|
aia-chaser = pself.callPackage ../pkgs/aia-chaser { };
|
||||||
|
mini-racer = pself.callPackage ../pkgs/mini-racer.nix { };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
python3Packages = self.python3.pkgs;
|
||||||
|
|
||||||
|
pyload-ng = self.callPackage ../pkgs/pyload-ng { pyload-ng = super.pyload-ng; };
|
||||||
|
|
||||||
# vscode-insiders = (super.callPackage ../pkgs/vscode-insiders.nix { });
|
# vscode-insiders = (super.callPackage ../pkgs/vscode-insiders.nix { });
|
||||||
}
|
}
|
||||||
|
|||||||
32
utils/pkgs/aia-chaser/default.nix
Normal file
32
utils/pkgs/aia-chaser/default.nix
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{ lib
|
||||||
|
, buildPythonPackage
|
||||||
|
, fetchPypi
|
||||||
|
, cryptography
|
||||||
|
}:
|
||||||
|
|
||||||
|
buildPythonPackage rec {
|
||||||
|
pname = "aia-chaser";
|
||||||
|
version = "3.3.0";
|
||||||
|
format = "wheel";
|
||||||
|
|
||||||
|
src = fetchPypi {
|
||||||
|
pname = "aia_chaser";
|
||||||
|
inherit version format;
|
||||||
|
dist = "py3";
|
||||||
|
python = "py3";
|
||||||
|
hash = "sha256-L0aBV3kfAVI1aJH7VgiiEXzGBSP/HU2zAlahkHeT8hk=";
|
||||||
|
};
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
cryptography
|
||||||
|
];
|
||||||
|
|
||||||
|
pythonImportsCheck = [ "aia_chaser" ];
|
||||||
|
|
||||||
|
meta = with lib; {
|
||||||
|
description = "Retrieve missing certificates to complete SSL certificate chains";
|
||||||
|
homepage = "https://github.com/dirkjanm/aia-chaser";
|
||||||
|
license = licenses.mit;
|
||||||
|
maintainers = [ ];
|
||||||
|
};
|
||||||
|
}
|
||||||
30
utils/pkgs/claude-code/default.nix
Normal file
30
utils/pkgs/claude-code/default.nix
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{ lib, pkgs, runCommand, claude-code }:
|
||||||
|
|
||||||
|
let
|
||||||
|
version = "2.0.55";
|
||||||
|
|
||||||
|
src = pkgs.fetchzip {
|
||||||
|
url = "https://registry.npmjs.org/@anthropic-ai/claude-code/-/claude-code-${version}.tgz";
|
||||||
|
hash = "sha256-wsjOkNxuBLMYprjaZQyUZHiqWl8UG7cZ1njkyKZpRYg=";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Create a modified source with our package-lock.json
|
||||||
|
srcWithLock = runCommand "claude-code-src-${version}" {} ''
|
||||||
|
cp -r ${src} $out
|
||||||
|
chmod -R +w $out
|
||||||
|
cp ${./package-lock.json} $out/package-lock.json
|
||||||
|
'';
|
||||||
|
|
||||||
|
in
|
||||||
|
(claude-code.override {}).overrideAttrs (oldAttrs: {
|
||||||
|
inherit version;
|
||||||
|
src = srcWithLock;
|
||||||
|
|
||||||
|
npmDeps = pkgs.fetchNpmDeps {
|
||||||
|
src = srcWithLock;
|
||||||
|
hash = "sha256-cFvPoCmh3XpJe/5LPZizfBz6F6xAPYnBNimrK4+VbPw=";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Remove the old postPatch since srcWithLock already includes package-lock.json
|
||||||
|
postPatch = "";
|
||||||
|
})
|
||||||
319
utils/pkgs/claude-code/package-lock.json
generated
Normal file
319
utils/pkgs/claude-code/package-lock.json
generated
Normal file
@@ -0,0 +1,319 @@
|
|||||||
|
{
|
||||||
|
"name": "claude-code",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"dependencies": {
|
||||||
|
"@anthropic-ai/claude-code": "^2.0.55"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@anthropic-ai/claude-code": {
|
||||||
|
"version": "2.0.55",
|
||||||
|
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code/-/claude-code-2.0.55.tgz",
|
||||||
|
"integrity": "sha512-IVY6J2KgTP5BiCbLmuP3kAl8jbXfd6yGoXtvc0L0eiZwxJUMa+cubUU0U8qHRnVkNmDAis+O4P00KmeuGzSLWg==",
|
||||||
|
"license": "SEE LICENSE IN README.md",
|
||||||
|
"bin": {
|
||||||
|
"claude": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-darwin-arm64": "^0.33.5",
|
||||||
|
"@img/sharp-darwin-x64": "^0.33.5",
|
||||||
|
"@img/sharp-linux-arm": "^0.33.5",
|
||||||
|
"@img/sharp-linux-arm64": "^0.33.5",
|
||||||
|
"@img/sharp-linux-x64": "^0.33.5",
|
||||||
|
"@img/sharp-linuxmusl-arm64": "^0.33.5",
|
||||||
|
"@img/sharp-linuxmusl-x64": "^0.33.5",
|
||||||
|
"@img/sharp-win32-x64": "^0.33.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-darwin-arm64": {
|
||||||
|
"version": "0.33.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
|
||||||
|
"integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-libvips-darwin-arm64": "1.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-darwin-x64": {
|
||||||
|
"version": "0.33.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz",
|
||||||
|
"integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-libvips-darwin-x64": "1.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-libvips-darwin-arm64": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-libvips-darwin-x64": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-libvips-linux-arm": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-libvips-linux-arm64": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-libvips-linux-x64": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-linux-arm": {
|
||||||
|
"version": "0.33.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz",
|
||||||
|
"integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-libvips-linux-arm": "1.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-linux-arm64": {
|
||||||
|
"version": "0.33.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz",
|
||||||
|
"integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-libvips-linux-arm64": "1.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-linux-x64": {
|
||||||
|
"version": "0.33.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
|
||||||
|
"integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-libvips-linux-x64": "1.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-linuxmusl-arm64": {
|
||||||
|
"version": "0.33.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz",
|
||||||
|
"integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-libvips-linuxmusl-arm64": "1.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-linuxmusl-x64": {
|
||||||
|
"version": "0.33.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz",
|
||||||
|
"integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-libvips-linuxmusl-x64": "1.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-win32-x64": {
|
||||||
|
"version": "0.33.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz",
|
||||||
|
"integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
73
utils/pkgs/claude-code/update.sh
Executable file
73
utils/pkgs/claude-code/update.sh
Executable file
@@ -0,0 +1,73 @@
|
|||||||
|
#!/usr/bin/env nix-shell
|
||||||
|
#!nix-shell -i bash -p nodejs nix-prefetch cacert
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||||
|
|
||||||
|
# Get latest version from npm
|
||||||
|
version=$(npm view @anthropic-ai/claude-code version)
|
||||||
|
echo "Latest version: $version"
|
||||||
|
|
||||||
|
# Update version in default.nix
|
||||||
|
sed -i "s/version = \".*\";/version = \"$version\";/" default.nix
|
||||||
|
|
||||||
|
# Fetch and update source hash
|
||||||
|
echo "Fetching source tarball..."
|
||||||
|
url="https://registry.npmjs.org/@anthropic-ai/claude-code/-/claude-code-${version}.tgz"
|
||||||
|
hash=$(nix-prefetch-url --unpack --type sha256 "$url" 2>/dev/null)
|
||||||
|
sri_hash=$(nix-hash --type sha256 --to-sri "$hash")
|
||||||
|
# Update only the source hash (in fetchzip block)
|
||||||
|
sed -i "/src = pkgs.fetchzip/,/};/ s|hash = \"sha256-.*\";|hash = \"$sri_hash\";|" default.nix
|
||||||
|
|
||||||
|
# Generate new package-lock.json
|
||||||
|
echo "Generating package-lock.json..."
|
||||||
|
npm i --package-lock-only @anthropic-ai/claude-code@"$version"
|
||||||
|
rm -f package.json
|
||||||
|
|
||||||
|
# Set placeholder for npmDepsHash only (in fetchNpmDeps block)
|
||||||
|
sed -i "/npmDeps = pkgs.fetchNpmDeps/,/};/ s|hash = \"sha256-.*\";|hash = \"sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\";|" default.nix
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Updated to version $version"
|
||||||
|
echo "Source hash: $sri_hash"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Go to repo root (3 levels up from utils/pkgs/claude-code)
|
||||||
|
repo_root="$(cd ../../.. && pwd)"
|
||||||
|
cd "$repo_root"
|
||||||
|
|
||||||
|
# Build to get the correct npmDepsHash
|
||||||
|
echo "Building package to determine npmDepsHash..."
|
||||||
|
build_output=$(NIXPKGS_ALLOW_UNFREE=1 nix build --impure --no-link --expr 'with import <nixpkgs> {}; callPackage ./utils/pkgs/claude-code { inherit lib pkgs runCommand claude-code; }' 2>&1 || true)
|
||||||
|
|
||||||
|
# Extract the hash from build output
|
||||||
|
npm_deps_hash=$(echo "$build_output" | grep -oP "got:\s+sha256-[A-Za-z0-9+/=]+" | tail -1 | awk '{print $2}')
|
||||||
|
|
||||||
|
if [ -z "$npm_deps_hash" ]; then
|
||||||
|
echo "Error: Could not determine npmDepsHash from build output"
|
||||||
|
echo "Build output:"
|
||||||
|
echo "$build_output"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "npmDepsHash: $npm_deps_hash"
|
||||||
|
|
||||||
|
# Update npmDepsHash in default.nix
|
||||||
|
cd "$repo_root/utils/pkgs/claude-code"
|
||||||
|
sed -i "/npmDeps = pkgs.fetchNpmDeps/,/};/ s|hash = \"sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\";|hash = \"$npm_deps_hash\";|" default.nix
|
||||||
|
|
||||||
|
# Verify the build works
|
||||||
|
echo ""
|
||||||
|
echo "Verifying build..."
|
||||||
|
cd "$repo_root"
|
||||||
|
if NIXPKGS_ALLOW_UNFREE=1 nix build --impure --no-link --expr 'with import <nixpkgs> {}; callPackage ./utils/pkgs/claude-code { inherit lib pkgs runCommand claude-code; }'; then
|
||||||
|
echo ""
|
||||||
|
echo "✓ Successfully updated claude-code to version $version"
|
||||||
|
echo " Source hash: $sri_hash"
|
||||||
|
echo " npmDepsHash: $npm_deps_hash"
|
||||||
|
else
|
||||||
|
echo ""
|
||||||
|
echo "✗ Build failed after updating hashes"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
205
utils/pkgs/clicknload-proxy/clicknload-proxy.py
Normal file
205
utils/pkgs/clicknload-proxy/clicknload-proxy.py
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Click'n'Load proxy - receives CNL requests and forwards to pyLoad API.
|
||||||
|
|
||||||
|
Implements the Click'n'Load protocol:
|
||||||
|
1. GET /jdcheck.js -> responds with jdownloader=true
|
||||||
|
2. POST /flash/addcrypted2 -> decrypts links and sends to pyLoad
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
import urllib.request
|
||||||
|
import urllib.parse
|
||||||
|
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||||
|
from Crypto.Cipher import AES
|
||||||
|
|
||||||
|
|
||||||
|
def log(msg):
|
||||||
|
"""Log with immediate flush for systemd journal visibility."""
|
||||||
|
print(f"[CNL] {msg}", flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
class ClickNLoadHandler(BaseHTTPRequestHandler):
|
||||||
|
pyload_url = None
|
||||||
|
pyload_user = None
|
||||||
|
pyload_pass = None
|
||||||
|
|
||||||
|
def log_message(self, format, *args):
|
||||||
|
log(f"{self.command} {self.path}")
|
||||||
|
|
||||||
|
def send_cors_headers(self):
|
||||||
|
"""Add CORS headers to allow cross-origin requests."""
|
||||||
|
self.send_header("Access-Control-Allow-Origin", "*")
|
||||||
|
self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
|
||||||
|
self.send_header("Access-Control-Allow-Headers", "Content-Type")
|
||||||
|
|
||||||
|
def do_OPTIONS(self):
|
||||||
|
"""Handle CORS preflight requests."""
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_cors_headers()
|
||||||
|
self.end_headers()
|
||||||
|
|
||||||
|
def do_GET(self):
|
||||||
|
if self.path == "/jdcheck.js":
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header("Content-Type", "text/javascript")
|
||||||
|
self.send_cors_headers()
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(b"jdownloader=true;")
|
||||||
|
else:
|
||||||
|
self.send_response(404)
|
||||||
|
self.send_cors_headers()
|
||||||
|
self.end_headers()
|
||||||
|
|
||||||
|
def do_POST(self):
|
||||||
|
if self.path == "/flash/addcrypted2":
|
||||||
|
content_length = int(self.headers.get("Content-Length", 0))
|
||||||
|
post_data = self.rfile.read(content_length).decode("utf-8")
|
||||||
|
params = urllib.parse.parse_qs(post_data)
|
||||||
|
|
||||||
|
jk = params.get("jk", [""])[0]
|
||||||
|
crypted = params.get("crypted", [""])[0]
|
||||||
|
source = params.get("source", ["Click'n'Load"])[0]
|
||||||
|
|
||||||
|
log(f"Received addcrypted2: source={source}, jk_len={len(jk)}, crypted_len={len(crypted)}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
links = self.decrypt_links(jk, crypted)
|
||||||
|
if links:
|
||||||
|
self.add_to_pyload(links, source)
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_cors_headers()
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(b"success")
|
||||||
|
else:
|
||||||
|
log("Error: No links found after decryption")
|
||||||
|
self.send_response(500)
|
||||||
|
self.send_cors_headers()
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(b"No links found")
|
||||||
|
except Exception as e:
|
||||||
|
log(f"Error: {e}")
|
||||||
|
log(traceback.format_exc())
|
||||||
|
self.send_response(500)
|
||||||
|
self.send_cors_headers()
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(str(e).encode())
|
||||||
|
else:
|
||||||
|
self.send_response(404)
|
||||||
|
self.send_cors_headers()
|
||||||
|
self.end_headers()
|
||||||
|
|
||||||
|
def decrypt_links(self, jk, crypted):
|
||||||
|
"""Decrypt Click'n'Load encrypted links."""
|
||||||
|
# Extract hex key from JavaScript function
|
||||||
|
# Format: function f(){ return '...hex...'; }
|
||||||
|
match = re.search(r"return\s*['\"]([0-9a-fA-F]+)['\"]", jk)
|
||||||
|
if not match:
|
||||||
|
log(f"Could not extract key from jk: {jk[:100]}...")
|
||||||
|
raise ValueError("Could not extract key from jk")
|
||||||
|
|
||||||
|
key_hex = match.group(1)
|
||||||
|
key = bytes.fromhex(key_hex)
|
||||||
|
log(f"Extracted key: {len(key)} bytes")
|
||||||
|
|
||||||
|
# Decrypt (AES-128-CBC, key is also IV)
|
||||||
|
cipher = AES.new(key, AES.MODE_CBC, iv=key)
|
||||||
|
encrypted = base64.b64decode(crypted)
|
||||||
|
decrypted = cipher.decrypt(encrypted)
|
||||||
|
|
||||||
|
# Remove PKCS7 padding
|
||||||
|
pad_len = decrypted[-1]
|
||||||
|
decrypted = decrypted[:-pad_len]
|
||||||
|
|
||||||
|
# Split into links
|
||||||
|
links = decrypted.decode("utf-8").strip().split("\n")
|
||||||
|
links = [l.strip() for l in links if l.strip()]
|
||||||
|
|
||||||
|
log(f"Decrypted {len(links)} links")
|
||||||
|
return links
|
||||||
|
|
||||||
|
def add_to_pyload(self, links, package_name):
|
||||||
|
"""Add links to pyLoad via API using HTTP Basic Auth."""
|
||||||
|
if not self.pyload_url:
|
||||||
|
log("No pyLoad URL configured, printing links:")
|
||||||
|
for link in links:
|
||||||
|
log(f" {link}")
|
||||||
|
return
|
||||||
|
|
||||||
|
log(f"Adding package to pyLoad at {self.pyload_url}")
|
||||||
|
|
||||||
|
# Build Basic Auth header
|
||||||
|
credentials = f"{self.pyload_user}:{self.pyload_pass}"
|
||||||
|
auth_header = base64.b64encode(credentials.encode()).decode()
|
||||||
|
|
||||||
|
# Add package using new API endpoint with HTTP Basic Auth
|
||||||
|
add_url = f"{self.pyload_url}/api/add_package"
|
||||||
|
add_data = json.dumps({
|
||||||
|
"name": package_name or "Click'n'Load",
|
||||||
|
"links": links
|
||||||
|
}).encode()
|
||||||
|
|
||||||
|
req = urllib.request.Request(
|
||||||
|
add_url,
|
||||||
|
data=add_data,
|
||||||
|
headers={
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": f"Basic {auth_header}"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req, timeout=10) as resp:
|
||||||
|
result = resp.read().decode()
|
||||||
|
log(f"Added package to pyLoad: {result}")
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
body = e.read().decode() if e.fp else ""
|
||||||
|
log(f"Failed to add package: {e.code} {e.reason} - {body}")
|
||||||
|
raise
|
||||||
|
except urllib.error.URLError as e:
|
||||||
|
log(f"Failed to add package: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="Click'n'Load proxy for pyLoad")
|
||||||
|
parser.add_argument("--port", type=int, default=9666, help="Port to listen on")
|
||||||
|
parser.add_argument("--host", default="127.0.0.1", help="Host to bind to")
|
||||||
|
parser.add_argument("--pyload-url", help="pyLoad URL (e.g., https://pyload.example.com)")
|
||||||
|
parser.add_argument("--pyload-user", help="pyLoad username")
|
||||||
|
parser.add_argument("--pyload-pass", help="pyLoad password")
|
||||||
|
parser.add_argument("--config", help="Config file with pyLoad credentials (JSON)")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Load config from file if provided
|
||||||
|
if args.config:
|
||||||
|
with open(args.config) as f:
|
||||||
|
config = json.load(f)
|
||||||
|
args.pyload_url = config.get("pyloadUrl", args.pyload_url)
|
||||||
|
args.pyload_user = config.get("pyloadUser", args.pyload_user)
|
||||||
|
args.pyload_pass = config.get("pyloadPW", args.pyload_pass)
|
||||||
|
|
||||||
|
ClickNLoadHandler.pyload_url = args.pyload_url
|
||||||
|
ClickNLoadHandler.pyload_user = args.pyload_user
|
||||||
|
ClickNLoadHandler.pyload_pass = args.pyload_pass
|
||||||
|
|
||||||
|
server = HTTPServer((args.host, args.port), ClickNLoadHandler)
|
||||||
|
log(f"Click'n'Load proxy listening on {args.host}:{args.port}")
|
||||||
|
if args.pyload_url:
|
||||||
|
log(f"Forwarding to pyLoad at {args.pyload_url}")
|
||||||
|
else:
|
||||||
|
log("No pyLoad URL configured, will print links to stdout")
|
||||||
|
|
||||||
|
try:
|
||||||
|
server.serve_forever()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
log("Shutting down")
|
||||||
|
server.shutdown()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
34
utils/pkgs/clicknload-proxy/default.nix
Normal file
34
utils/pkgs/clicknload-proxy/default.nix
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{ lib
|
||||||
|
, python3
|
||||||
|
, makeWrapper
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
python = python3.withPackages (ps: [ ps.pycryptodome ]);
|
||||||
|
in
|
||||||
|
python3.pkgs.buildPythonApplication {
|
||||||
|
pname = "clicknload-proxy";
|
||||||
|
version = "1.0.0";
|
||||||
|
format = "other";
|
||||||
|
|
||||||
|
src = ./.;
|
||||||
|
|
||||||
|
nativeBuildInputs = [ makeWrapper ];
|
||||||
|
|
||||||
|
propagatedBuildInputs = [ python3.pkgs.pycryptodome ];
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
mkdir -p $out/bin
|
||||||
|
cp clicknload-proxy.py $out/bin/clicknload-proxy
|
||||||
|
chmod +x $out/bin/clicknload-proxy
|
||||||
|
|
||||||
|
wrapProgram $out/bin/clicknload-proxy \
|
||||||
|
--prefix PYTHONPATH : "${python}/${python.sitePackages}"
|
||||||
|
'';
|
||||||
|
|
||||||
|
meta = with lib; {
|
||||||
|
description = "Click'n'Load proxy that forwards links to pyLoad";
|
||||||
|
license = licenses.mit;
|
||||||
|
mainProgram = "clicknload-proxy";
|
||||||
|
};
|
||||||
|
}
|
||||||
41
utils/pkgs/mini-racer.nix
Normal file
41
utils/pkgs/mini-racer.nix
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{ lib
|
||||||
|
, buildPythonPackage
|
||||||
|
, fetchPypi
|
||||||
|
, stdenv
|
||||||
|
, autoPatchelfHook
|
||||||
|
}:
|
||||||
|
|
||||||
|
buildPythonPackage rec {
|
||||||
|
pname = "mini-racer";
|
||||||
|
version = "0.12.4";
|
||||||
|
format = "wheel";
|
||||||
|
|
||||||
|
src = fetchPypi {
|
||||||
|
pname = "mini_racer";
|
||||||
|
inherit version format;
|
||||||
|
dist = "py3";
|
||||||
|
python = "py3";
|
||||||
|
abi = "none";
|
||||||
|
platform = "manylinux_2_31_x86_64";
|
||||||
|
hash = "sha256-aaHETQKpBpuIFoTO8VotdH/gdD3ynq3Igf2nACquX9I=";
|
||||||
|
};
|
||||||
|
|
||||||
|
nativeBuildInputs = [
|
||||||
|
autoPatchelfHook
|
||||||
|
];
|
||||||
|
|
||||||
|
buildInputs = [
|
||||||
|
stdenv.cc.cc.lib
|
||||||
|
];
|
||||||
|
|
||||||
|
# Don't strip binaries, it breaks V8
|
||||||
|
dontStrip = true;
|
||||||
|
|
||||||
|
meta = with lib; {
|
||||||
|
description = "Minimal Python wrapper for V8 JavaScript engine";
|
||||||
|
homepage = "https://github.com/bpcreech/PyMiniRacer";
|
||||||
|
license = licenses.isc;
|
||||||
|
maintainers = [ ];
|
||||||
|
platforms = [ "x86_64-linux" ];
|
||||||
|
};
|
||||||
|
}
|
||||||
26
utils/pkgs/pyload-ng/default.nix
Normal file
26
utils/pkgs/pyload-ng/default.nix
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{ lib, pyload-ng, fetchFromGitHub, python3Packages }:
|
||||||
|
|
||||||
|
pyload-ng.overridePythonAttrs (oldAttrs: rec {
|
||||||
|
version = "0.5.0b3.dev93+git";
|
||||||
|
|
||||||
|
src = fetchFromGitHub {
|
||||||
|
owner = "pyload";
|
||||||
|
repo = "pyload";
|
||||||
|
rev = "71f2700184ee9344dc313d9833ca7a6bb36007db"; # [DdownloadCom] fix #4537
|
||||||
|
hash = "sha256-XAa+XbC3kko+zvEMZkPXRoaHAmEFGsNBDxysX+X06Jc=";
|
||||||
|
};
|
||||||
|
|
||||||
|
patches = [
|
||||||
|
./patches/declarative-env-config.patch
|
||||||
|
];
|
||||||
|
|
||||||
|
# Add new dependencies required in newer versions
|
||||||
|
propagatedBuildInputs = (oldAttrs.propagatedBuildInputs or []) ++ (with python3Packages; [
|
||||||
|
aia-chaser
|
||||||
|
mini-racer
|
||||||
|
packaging
|
||||||
|
pydantic
|
||||||
|
flask-wtf
|
||||||
|
defusedxml
|
||||||
|
]);
|
||||||
|
})
|
||||||
38
utils/pkgs/pyload-ng/patches/declarative-env-config.patch
Normal file
38
utils/pkgs/pyload-ng/patches/declarative-env-config.patch
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
--- a/src/pyload/core/__init__.py
|
||||||
|
+++ b/src/pyload/core/__init__.py
|
||||||
|
@@ -130,6 +130,14 @@ class Core:
|
||||||
|
else:
|
||||||
|
self._debug = max(0, int(debug))
|
||||||
|
|
||||||
|
+ # Process core config environment variables (NixOS declarative config)
|
||||||
|
+ for env, value in os.environ.items():
|
||||||
|
+ if not env.startswith("PYLOAD__"):
|
||||||
|
+ continue
|
||||||
|
+ parts = env.removeprefix("PYLOAD__").lower().split("__", 1)
|
||||||
|
+ if len(parts) == 2 and parts[0] in self.config.config:
|
||||||
|
+ self.config.set(parts[0], parts[1], value)
|
||||||
|
+
|
||||||
|
# If no argument set, read storage dir from config file,
|
||||||
|
# otherwise save setting to config dir
|
||||||
|
if storagedir is None:
|
||||||
|
@@ -226,6 +234,20 @@ class Core:
|
||||||
|
self.acm = self.account_manager = AccountManager(self)
|
||||||
|
self.thm = self.thread_manager = ThreadManager(self)
|
||||||
|
self.cpm = self.captcha_manager = CaptchaManager(self)
|
||||||
|
+
|
||||||
|
+ # Process plugin config environment variables BEFORE AddonManager (NixOS declarative config)
|
||||||
|
+ # This must happen before AddonManager reads the enabled flag to decide which addons to start
|
||||||
|
+ # Build case-insensitive lookup map for plugin names
|
||||||
|
+ plugin_name_map = {name.lower(): name for name in self.config.plugin.keys()}
|
||||||
|
+
|
||||||
|
+ for env, value in os.environ.items():
|
||||||
|
+ if not env.startswith("PYLOAD__"):
|
||||||
|
+ continue
|
||||||
|
+ parts = env.removeprefix("PYLOAD__").lower().split("__", 1)
|
||||||
|
+ if len(parts) == 2 and parts[0] in plugin_name_map:
|
||||||
|
+ actual_plugin_name = plugin_name_map[parts[0]]
|
||||||
|
+ self.config.set_plugin(actual_plugin_name, parts[1], value)
|
||||||
|
+
|
||||||
|
self.adm = self.addon_manager = AddonManager(self)
|
||||||
|
|
||||||
|
def _setup_permissions(self):
|
||||||
107
utils/pkgs/pyload-ng/update.sh
Executable file
107
utils/pkgs/pyload-ng/update.sh
Executable file
@@ -0,0 +1,107 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Script and repo directories
|
||||||
|
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||||
|
PKG_DIR="$(pwd)"
|
||||||
|
REPO_ROOT="$(cd ../../.. && pwd)"
|
||||||
|
|
||||||
|
# Check if commit SHA is provided
|
||||||
|
if [ $# -ne 1 ]; then
|
||||||
|
echo -e "${RED}Error: Git commit SHA required${NC}"
|
||||||
|
echo "Usage: $0 <commit-sha>"
|
||||||
|
echo "Example: $0 e5fe7038f3e116d878c58059323b682e426f9c84"
|
||||||
|
echo ""
|
||||||
|
echo "To find the latest commit:"
|
||||||
|
echo " Visit: https://github.com/pyload/pyload/commits/main"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
COMMIT_SHA="$1"
|
||||||
|
|
||||||
|
# Validate commit SHA format (40 character hex string)
|
||||||
|
if ! [[ "$COMMIT_SHA" =~ ^[0-9a-f]{40}$ ]]; then
|
||||||
|
echo -e "${RED}Error: Invalid commit SHA format${NC}"
|
||||||
|
echo "Commit SHA must be a 40-character hexadecimal string"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}==> Updating pyload-ng to commit: ${COMMIT_SHA}${NC}"
|
||||||
|
|
||||||
|
# File to update
|
||||||
|
PKG_FILE="$PKG_DIR/default.nix"
|
||||||
|
|
||||||
|
if [ ! -f "$PKG_FILE" ]; then
|
||||||
|
echo -e "${RED}Error: Package file not found: $PKG_FILE${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step 1: Update commit SHA in package file
|
||||||
|
echo -e "${YELLOW}Step 1: Updating commit SHA in package file...${NC}"
|
||||||
|
sed -i "s/rev = \"[0-9a-f]*\";/rev = \"$COMMIT_SHA\";/" "$PKG_FILE"
|
||||||
|
echo " ✓ Updated commit SHA in $PKG_FILE"
|
||||||
|
|
||||||
|
# Step 2: Set hash to a fake value to trigger hash discovery
|
||||||
|
echo -e "${YELLOW}Step 2: Setting hash to fake value...${NC}"
|
||||||
|
sed -i 's/hash = "sha256-[^"]*";/hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";/' "$PKG_FILE"
|
||||||
|
echo " ✓ Updated hash in $PKG_FILE"
|
||||||
|
|
||||||
|
# Step 3: Build package to discover the correct hash
|
||||||
|
echo -e "${YELLOW}Step 3: Building package to discover hash...${NC}"
|
||||||
|
cd "$REPO_ROOT"
|
||||||
|
BUILD_OUTPUT=$(nix-build --impure -E "with import <nixpkgs> { overlays = [ (import ./utils/overlays/packages.nix) ]; }; callPackage ./utils/pkgs/pyload-ng { }" 2>&1 || true)
|
||||||
|
|
||||||
|
# Extract hash from error message
|
||||||
|
HASH=$(echo "$BUILD_OUTPUT" | grep -oP '\s+got:\s+\Ksha256-[A-Za-z0-9+/=]+' | head -1)
|
||||||
|
|
||||||
|
if [ -z "$HASH" ]; then
|
||||||
|
echo -e "${RED}Error: Failed to extract hash from build output${NC}"
|
||||||
|
echo "Build output:"
|
||||||
|
echo "$BUILD_OUTPUT"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo " ✓ Discovered hash: $HASH"
|
||||||
|
|
||||||
|
# Step 4: Update package file with the correct hash
|
||||||
|
echo -e "${YELLOW}Step 4: Updating hash in package file...${NC}"
|
||||||
|
sed -i "s|hash = \"sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\";|hash = \"$HASH\";|" "$PKG_FILE"
|
||||||
|
echo " ✓ Updated hash in $PKG_FILE"
|
||||||
|
|
||||||
|
# Step 5: Verify the build succeeds
|
||||||
|
echo -e "${YELLOW}Step 5: Verifying build with correct hash...${NC}"
|
||||||
|
if nix-build --impure -E "with import <nixpkgs> { overlays = [ (import ./utils/overlays/packages.nix) ]; }; callPackage ./utils/pkgs/pyload-ng { }" > /dev/null 2>&1; then
|
||||||
|
echo " ✓ Build verification successful"
|
||||||
|
else
|
||||||
|
echo -e "${RED}Error: Build verification failed${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step 6: Test configuration for fw host (which uses pyload)
|
||||||
|
echo -e "${YELLOW}Step 6: Testing fw configuration...${NC}"
|
||||||
|
if ./scripts/test-configuration fw > /dev/null 2>&1; then
|
||||||
|
echo " ✓ Configuration test passed"
|
||||||
|
else
|
||||||
|
echo -e "${RED}Warning: Configuration test failed${NC}"
|
||||||
|
echo "This may be due to missing secrets or other issues unrelated to the hash update."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Success summary
|
||||||
|
echo -e "${GREEN}"
|
||||||
|
echo "======================================"
|
||||||
|
echo "✓ pyload-ng updated successfully!"
|
||||||
|
echo "======================================"
|
||||||
|
echo "Commit: $COMMIT_SHA"
|
||||||
|
echo "Hash: $HASH"
|
||||||
|
echo -e "${NC}"
|
||||||
|
echo "Next steps:"
|
||||||
|
echo " 1. Review changes: git diff $PKG_FILE"
|
||||||
|
echo " 2. Test locally if needed"
|
||||||
|
echo " 3. Commit changes: git add $PKG_FILE && git commit -m 'update: pyload-ng to commit ${COMMIT_SHA:0:8}'"
|
||||||
|
echo " 4. Push to trigger automatic deployment"
|
||||||
Reference in New Issue
Block a user