Compare commits
11 Commits
a912c4dc55
...
59a37c9b46
| Author | SHA1 | Date | |
|---|---|---|---|
| 59a37c9b46 | |||
| d7d3722ce7 | |||
| 6475524d23 | |||
| 1a70ca9564 | |||
| d6f206f0bb | |||
| b3c5366f31 | |||
| fab06ca4d5 | |||
| bd1d04943d | |||
| 8305d1b0c5 | |||
| 2d812c03eb | |||
| 156e63fd6c |
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.
|
||||||
@@ -8,6 +8,8 @@
|
|||||||
./modules/web/stack.nix
|
./modules/web/stack.nix
|
||||||
./modules/laravel-storage.nix
|
./modules/laravel-storage.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
|
||||||
|
|||||||
@@ -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 -"
|
||||||
|
|||||||
55
hosts/amzebs-01/modules/postfix.nix
Normal file
55
hosts/amzebs-01/modules/postfix.nix
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
{ pkgs
|
||||||
|
, lib
|
||||||
|
, config
|
||||||
|
, ...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
# Header checks file for validating email headers
|
||||||
|
environment.etc."postfix/header_checks".text = ''
|
||||||
|
# Warn about missing critical headers (but don't reject from localhost)
|
||||||
|
# These help identify misconfigured applications
|
||||||
|
/^$/ WARN Missing headers detected
|
||||||
|
'';
|
||||||
|
|
||||||
|
services.postfix = {
|
||||||
|
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:/etc/postfix/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
|
||||||
|
|||||||
@@ -49,7 +49,8 @@
|
|||||||
|
|
||||||
./modules/firefox-sync.nix
|
./modules/firefox-sync.nix
|
||||||
./modules/fivefilters.nix
|
./modules/fivefilters.nix
|
||||||
|
./modules/pyload.nix
|
||||||
|
|
||||||
# home assistant
|
# home assistant
|
||||||
./modules/home-assistant
|
./modules/home-assistant
|
||||||
./modules/deconz.nix
|
./modules/deconz.nix
|
||||||
@@ -87,6 +88,18 @@
|
|||||||
"ai-mailer"
|
"ai-mailer"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
# 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.extraConfig = "RuntimeDirectorySize=2G";
|
||||||
|
|||||||
@@ -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/"
|
||||||
|
|||||||
@@ -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"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -133,6 +133,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,7 @@
|
|||||||
all = true;
|
all = true;
|
||||||
entities = [
|
entities = [
|
||||||
"light.livingroom_switch"
|
"light.livingroom_switch"
|
||||||
"light.livingroom_bulb_1_rgbcw_bulb"
|
"light.living_room"
|
||||||
"light.livingroom_bulb_2_rgbcw_bulb"
|
|
||||||
"light.livingroom_bulb_3_rgbcw_bulb"
|
|
||||||
"light.livingroom_bulb_4_rgbcw_bulb"
|
|
||||||
"light.livingroom_bulb_5_rgbcw_bulb"
|
|
||||||
"light.livingroom_bulb_6_rgbcw_bulb"
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
|||||||
191
hosts/fw/modules/pyload.nix
Normal file
191
hosts/fw/modules/pyload.nix
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
{ config, pkgs, ... }:
|
||||||
|
let
|
||||||
|
cids = import ./staticids.nix;
|
||||||
|
networkPrefix = config.networkPrefix;
|
||||||
|
|
||||||
|
pyloadUser = {
|
||||||
|
isSystemUser = true;
|
||||||
|
uid = cids.uids.pyload;
|
||||||
|
group = "pyload";
|
||||||
|
home = "/var/lib/pyload";
|
||||||
|
createHome = true;
|
||||||
|
};
|
||||||
|
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 multimedia directory structure on the host
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
"d /var/lib/multimedia 0755 root root - -"
|
||||||
|
"d /var/lib/multimedia/downloads 0755 pyload pyload - -"
|
||||||
|
"d /var/lib/multimedia/movies 0755 jellyfin jellyfin - -"
|
||||||
|
"d /var/lib/multimedia/tv-shows 0755 jellyfin jellyfin - -"
|
||||||
|
"d /var/lib/multimedia/music 0755 jellyfin jellyfin - -"
|
||||||
|
"d /var/lib/jellyfin 0755 jellyfin jellyfin - -"
|
||||||
|
];
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
"/multimedia" = {
|
||||||
|
hostPath = "/var/lib/multimedia";
|
||||||
|
isReadOnly = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = { lib, config, pkgs, ... }: {
|
||||||
|
nixpkgs.overlays = [
|
||||||
|
(import ../utils/overlays/packages.nix)
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [
|
||||||
|
"unrar"
|
||||||
|
];
|
||||||
|
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
unrar # Required for RAR archive extraction
|
||||||
|
];
|
||||||
|
|
||||||
|
# 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";
|
||||||
|
};
|
||||||
|
|
||||||
|
networking = {
|
||||||
|
hostName = "pyload";
|
||||||
|
useHostResolvConf = false;
|
||||||
|
defaultGateway = {
|
||||||
|
address = "${networkPrefix}.97.1";
|
||||||
|
interface = "eth0";
|
||||||
|
};
|
||||||
|
nameservers = [ "${networkPrefix}.97.1" ];
|
||||||
|
firewall.enable = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
services.pyload = {
|
||||||
|
enable = true;
|
||||||
|
downloadDirectory = "/multimedia/downloads";
|
||||||
|
listenAddress = "0.0.0.0";
|
||||||
|
port = 8000;
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Disable SSL certificate verification
|
||||||
|
systemd.services.pyload = {
|
||||||
|
environment = {
|
||||||
|
PYLOAD__GENERAL__SSL_VERIFY = "0";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Bind-mount DNS configuration files and system tools into the chroot
|
||||||
|
serviceConfig = {
|
||||||
|
BindReadOnlyPaths = [
|
||||||
|
"/etc/resolv.conf"
|
||||||
|
"/etc/nsswitch.conf"
|
||||||
|
"/etc/hosts"
|
||||||
|
"/etc/ssl"
|
||||||
|
"/etc/static/ssl"
|
||||||
|
# Make all system packages (including unrar) accessible
|
||||||
|
"/run/current-system/sw/bin"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# 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";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -5,6 +5,8 @@
|
|||||||
gitea-runner = 10003;
|
gitea-runner = 10003;
|
||||||
podman = 10004;
|
podman = 10004;
|
||||||
foundry-vtt = 10005;
|
foundry-vtt = 10005;
|
||||||
|
pyload = 10006;
|
||||||
|
jellyfin = 10007;
|
||||||
};
|
};
|
||||||
gids = {
|
gids = {
|
||||||
unbound = 10001;
|
unbound = 10001;
|
||||||
@@ -12,5 +14,7 @@
|
|||||||
gitea-runner = 10003;
|
gitea-runner = 10003;
|
||||||
podman = 10004;
|
podman = 10004;
|
||||||
foundry-vtt = 10005;
|
foundry-vtt = 10005;
|
||||||
|
pyload = 10006;
|
||||||
|
jellyfin = 10007;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,4 +33,51 @@
|
|||||||
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}.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}.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;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -189,6 +189,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 +289,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;
|
||||||
|
|||||||
@@ -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,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";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -228,6 +228,21 @@ in
|
|||||||
Restart = "always";
|
Restart = "always";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
pyload-tunnel = {
|
||||||
|
Unit = {
|
||||||
|
Description = "SSH tunnel for pyLoad Click'n'Load";
|
||||||
|
After = [ "graphical-session-pre.target" ];
|
||||||
|
PartOf = [ "graphical-session.target" ];
|
||||||
|
};
|
||||||
|
Install = {
|
||||||
|
WantedBy = [ "graphical-session.target" ];
|
||||||
|
};
|
||||||
|
Service = {
|
||||||
|
ExecStart = "${pkgs.openssh}/bin/ssh -N -L 9666:10.42.97.11:9666 -o ServerAliveInterval=60 -o ServerAliveCountMax=3 root@fw.cloonar.com";
|
||||||
|
Restart = "always";
|
||||||
|
RestartSec = "10s";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
programs.chromium = {
|
programs.chromium = {
|
||||||
|
|||||||
106
scripts/update-pyload-hash
Executable file
106
scripts/update-pyload-hash
Executable file
@@ -0,0 +1,106 @@
|
|||||||
|
#!/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 directory
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && 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="$REPO_ROOT/utils/pkgs/pyload-ng-updated.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}"
|
||||||
|
BUILD_OUTPUT=$(nix-build --impure -E "with import <nixpkgs> { overlays = [ (import $REPO_ROOT/utils/overlays/packages.nix) ]; }; callPackage $PKG_FILE { }" 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 $REPO_ROOT/utils/overlays/packages.nix) ]; }; callPackage $PKG_FILE { }" > /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}"
|
||||||
|
cd "$REPO_ROOT"
|
||||||
|
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"
|
||||||
@@ -6,5 +6,15 @@ self: super: {
|
|||||||
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 { };
|
||||||
|
|
||||||
|
# Python packages
|
||||||
|
python3 = super.python3.override {
|
||||||
|
packageOverrides = pself: psuper: {
|
||||||
|
mini-racer = pself.callPackage ../pkgs/mini-racer.nix { };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
python3Packages = self.python3.pkgs;
|
||||||
|
|
||||||
|
pyload-ng = self.callPackage ../pkgs/pyload-ng-updated.nix { pyload-ng = super.pyload-ng; };
|
||||||
|
|
||||||
# vscode-insiders = (super.callPackage ../pkgs/vscode-insiders.nix { });
|
# vscode-insiders = (super.callPackage ../pkgs/vscode-insiders.nix { });
|
||||||
}
|
}
|
||||||
|
|||||||
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" ];
|
||||||
|
};
|
||||||
|
}
|
||||||
21
utils/pkgs/pyload-ng-updated.nix
Normal file
21
utils/pkgs/pyload-ng-updated.nix
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{ lib, pyload-ng, fetchFromGitHub, python3Packages }:
|
||||||
|
|
||||||
|
pyload-ng.overridePythonAttrs (oldAttrs: rec {
|
||||||
|
version = "0.5.0b3.dev93+git";
|
||||||
|
|
||||||
|
src = fetchFromGitHub {
|
||||||
|
owner = "pyload";
|
||||||
|
repo = "pyload";
|
||||||
|
rev = "3115740a2210fd57b5d050cd0850a0e61ec493ed"; # [DdownloadCom] fix #4537
|
||||||
|
hash = "sha256-g1eEeNnr3Axtr+0BJzMcNQomTEX4EsUG1Jxt+huPyoc=";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Add new dependencies required in newer versions
|
||||||
|
propagatedBuildInputs = (oldAttrs.propagatedBuildInputs or []) ++ (with python3Packages; [
|
||||||
|
mini-racer
|
||||||
|
packaging
|
||||||
|
pydantic
|
||||||
|
flask-wtf
|
||||||
|
defusedxml
|
||||||
|
]);
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user