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/laravel-storage.nix
|
||||
./modules/blackbox-exporter.nix
|
||||
./modules/postfix.nix
|
||||
./modules/rspamd.nix
|
||||
|
||||
./utils/modules/autoupgrade.nix
|
||||
./utils/modules/promtail
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
systemd.tmpfiles.rules = [
|
||||
# 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/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/views 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
|
||||
"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/views 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
|
||||
"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/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 -"
|
||||
|
||||
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-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]
|
||||
mysql-readonly-password: ENC[AES256_GCM,data:KQiL0ZJGkJEqX7wADmY2YucT79Grt+tCQA/aER7llHgqUIvjJHO8C2yw+VI=,iv:M3QchAeKXp7BjP2FfaWgUNiGPs0qQHe9P5lttxO5+Fg=,tag:TPGUWXYQsf40hlVu7PGEEg==,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: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: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:
|
||||
age:
|
||||
- recipient: age14grjcxaq4h55yfnjxvnqhtswxhj9sfdcvyas4lwvpa8py27pjy2sv3g6v7
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAvWjd4K3BueGVxNGs2OC9t
|
||||
YURtcTIzNytCMDdYd29KenZOdm4vMk9mWEZFCkJrZlVZdkVJeW4rNzc0N3NBY3hM
|
||||
dE1ORzRHRHlONEQ2dW83R051aE45QVEKLS0tIE0zOVpVbWphNitPaUg0cmxUbW5m
|
||||
ZHViVHJrOWREb0pYR3hOTm0zSHZ3N0EKeNcZOM+H0XZN3Ji1ubBoHMgycuJFX3+C
|
||||
YvJ795wSwtXMU+mCDB04tcYPSAI0RC82wGT9r3XLNZgbF/xP0Er3nw==
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhQUpWNUgxVnhuTXd2TkF0
|
||||
SVVHemFKRWlYczZ0TnBESVNRczhuRUNnUG1BCmJKQ2JZbHhFcXJidHJzci9OaFBm
|
||||
ZTd0MGhsaVBic3dMb3psUHRCRnR3ODQKLS0tIERrSG1GVTRHdkJpVWpqdTZ4Yytq
|
||||
OHhlZjV6MjRVbXFsWjlQSU03ZDNwYm8KAswHRSdV0BW/oJyZx63iZRHsF7SZ6PO+
|
||||
hajQqmEyfcVfEu39zZzxQ2mtWlOr69I++irOhE3NeiFeJ1yIRQDJEQ==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1exny8unxynaw03yu8ppahu5z28uermghr8ag34e7kdqnaduq9stsyettzz
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWTnpIdHBmMGdBQzk2N0xW
|
||||
SGlNMUxjYU5SZHJLK0wvWnZyYkEzK04vdWd3CmVGWW50RUQ2K0lLRC90WW9KY1hj
|
||||
NGVpMEdzaHUyTUVBaDcxb2w5MC9BUjAKLS0tIFdSSjlFTHl1Z3NYNVhxaSs0WkJE
|
||||
eGVWZHdnMkhaNzlDNlhCa24wZzlvNmsK7pLzsxtlMevP2o9nJOjVgDAjrYdEgRUu
|
||||
NlJHfO0m9U7fJfeu6XSWQgGYRJm7tSmTZKvsJgTS+pKcynHz8B9rkQ==
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBUaUNnY0hpdDAzMTNIUS9D
|
||||
RmdKbmplUk9DRXlLRXEvSnVjT05sQjcvTnpJCkd6bGRINm5yYUZOUTVzWEdjRmtG
|
||||
Mmx0ci93N2wvTWV5MzlRVnlYdUxoUWsKLS0tIEVHUlNWYStWTG01RzRrVnNXc3BW
|
||||
VkRkUXROU3plNmwvTUVhYmhCS2syQkEKKgC0EmUu1u2vZ/SZTnam+h846gZSyY4V
|
||||
JyMzkws8O5TY9juWdDzXJIU67mIgc4qrWWN3uh8k28JBZGc078b5bg==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1v6p8dan2t3w9h94fz4flldl32082j3s9x6zqq7u5j66keth9aphsd6pvch
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBNMk9VL2s0ZmoxSzY3NmFQ
|
||||
MTZEaml5Q0kxRDFwSEN5dG90SzZmcXZLUjJBClpVNUJEZEdaa09hM1BQUU1jVVQz
|
||||
M2w4QXdmZnJya2pCTEV1QW9keXgyTWMKLS0tIFA1VWl3RzF5V2FMUE1mZ2NYRnBU
|
||||
OURWSFZnM0lEMXJEcjVPL3hnZ0pIQ1kKVvoCVQuayH/XRfddMKq2d8TssXOS5e1o
|
||||
bIL6F+tRBle2UgVuXSMkyggCnvLePA8OxfAdMMg5npSFkPgTZrAYYQ==
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBoK2JUNVdYTzkvM1BBWXRm
|
||||
citCNlE4Z1NLdEZ2R0tNZTVSMlFSeGxGOURnClJnYURYa0JZaVprQWdBcmVnOWVj
|
||||
TGVCK1JWMVlueHJUaTZZYmROM0E5aDAKLS0tIEJxYkdadGtZM250d2d6Ujl2UU9C
|
||||
YUpkVll2S2RpT0I1UVZiZFRKS1prMEEKp/bGImanJ/58vTQG/gUun/Y2QdmOEi3h
|
||||
hVS0V2QcfuGgi0/YofLOM3+M6k6ViXw07XfXmR+puvLIHKr2y11x1Q==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1xcgc6u7fmc2trgxtdtf5nhrd7axzweuxlg0ya9jre3sdrg6c6easecue9w
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3QmR3T3FYN2RpR1JrV0w0
|
||||
aVMycUJUN1NKME1UKzVsZmpNNTAzTmxUUUZNClhmdUF5N0Q4K09IeVhNOWhNNEc2
|
||||
UTNzeGJ4NlpxMUtEaHZDWDZOeHdvSU0KLS0tIFNTaThWbklXeE85c3hSMWZwNTNN
|
||||
RWFUVXVXWjdsSHM5ZGljd3YyQW5ja2MKvAhwHL5PcLFxuU7MfV/cWtNfzTb9yoqR
|
||||
3iD4UJsDDagCIkpvjKods4ydlzh3agOyLHswDSX/WmUur9J5pd4PAg==
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBDSGdEZnZEaDRpWUJVcnds
|
||||
VGFSQklvczBZdEdEbXhodW8vME9wMUpVRENjClFZcnVqYkJxdlBiZFhma0tmZjgz
|
||||
YXlIdlRDTDU4MHg1dzhGVDRJb2FGYVUKLS0tIDBXSWZ2NkxzdEk0ZlFRM00ybFNy
|
||||
M0doaWl5R2cwU2RxQm5DbWxXeTZ5S2MKwrB3SysmgzCThQOhEVx18dxIfko0+oZY
|
||||
9BSZOoFbfuwiLbtpL4J8bzxDvxn6sXxB8EBJH1hbpID53AquWDsxSw==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
lastmodified: "2025-11-14T11:33:59Z"
|
||||
mac: ENC[AES256_GCM,data:AnEs3yzpOJ5/wyCL/sHV6U5V7FBhZZlBQeA+mCGfZ25JZAL3Yb6yD6xJhmGC8AqIFS6PIFSWa2r0suDRQAoVO2AwFVwd9Y/TEwjPGnXvWfwB82+mnyLIakyzM/pcLjiMePUqr5nnJ8tWoKzuqs/jQHuMOGkItqwjkVDr9/hx3lc=,iv:kc08z63phDfs7gzruHjnQA9bXAvWMGkE14/0Kyfhuds=,tag:9wedcRCTAv0HMtSap55JOw==,type:str]
|
||||
lastmodified: "2025-11-19T11:16:25Z"
|
||||
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
|
||||
version: 3.11.0
|
||||
|
||||
@@ -18,7 +18,7 @@ in {
|
||||
# React client-side routing support
|
||||
locations."/".extraConfig = ''
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
try_files $uri $uri/ /index.html$is_args$args;
|
||||
'';
|
||||
|
||||
# Cache static assets
|
||||
|
||||
@@ -18,7 +18,7 @@ in {
|
||||
# React client-side routing support
|
||||
locations."/".extraConfig = ''
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
try_files $uri $uri/ /index.html$is_args$args;
|
||||
'';
|
||||
|
||||
# Cache static assets
|
||||
|
||||
@@ -18,7 +18,7 @@ in {
|
||||
# React client-side routing support
|
||||
locations."/".extraConfig = ''
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
try_files $uri $uri/ /index.html$is_args$args;
|
||||
'';
|
||||
|
||||
# Cache static assets
|
||||
|
||||
@@ -18,7 +18,7 @@ in {
|
||||
# React client-side routing support
|
||||
locations."/".extraConfig = ''
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
try_files $uri $uri/ /index.html$is_args$args;
|
||||
'';
|
||||
|
||||
# Cache static assets
|
||||
|
||||
@@ -49,7 +49,8 @@
|
||||
|
||||
./modules/firefox-sync.nix
|
||||
./modules/fivefilters.nix
|
||||
|
||||
./modules/pyload.nix
|
||||
|
||||
# home assistant
|
||||
./modules/home-assistant
|
||||
./modules/deconz.nix
|
||||
@@ -87,6 +88,18 @@
|
||||
"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";
|
||||
|
||||
services.logind.extraConfig = "RuntimeDirectorySize=2G";
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
|
||||
context:
|
||||
urls:
|
||||
- "https://paraclub.cloonar.dev/de/tandemfallschirmspringen/faq/"
|
||||
- "https://paraclub.at/de/"
|
||||
- "https://paraclub.at/de/tandemfallschirmspringen/alle-infos/"
|
||||
- "https://paraclub.at/de/tandemfallschirmspringen/kosten-tandemsprung/"
|
||||
|
||||
@@ -2,21 +2,33 @@
|
||||
virtualisation = {
|
||||
oci-containers.containers = {
|
||||
deconz = {
|
||||
autoStart = false;
|
||||
autoStart = true;
|
||||
image = "marthoc/deconz";
|
||||
volumes = [
|
||||
"/etc/localtime:/etc/localtime:ro"
|
||||
"/var/lib/deconz:/root/.local/share/dresden-elektronik/deCONZ"
|
||||
"/dev/bus/usb:/dev/bus/usb:ro"
|
||||
"/run/udev:/run/udev:ro"
|
||||
];
|
||||
environment = {
|
||||
DECONZ_DEVICE = "/dev/ttyACM0";
|
||||
TZ = "Europe/Vienna";
|
||||
DECONZ_UID = "0";
|
||||
DECONZ_GID = "0";
|
||||
DECONZ_START_VERBOSE = "1";
|
||||
};
|
||||
extraOptions = [
|
||||
"--network=server"
|
||||
"--ip=${config.networkPrefix}.97.22"
|
||||
"--device=/dev/ttyACM0"
|
||||
"--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"
|
||||
"/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"
|
||||
|
||||
"/ddl-warez.to/172.67.184.30"
|
||||
|
||||
@@ -396,12 +396,7 @@
|
||||
all = true;
|
||||
entities = [
|
||||
"light.livingroom_switch"
|
||||
"light.livingroom_bulb_1_rgbcw_bulb"
|
||||
"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"
|
||||
"light.living_room"
|
||||
];
|
||||
}
|
||||
{
|
||||
|
||||
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;
|
||||
podman = 10004;
|
||||
foundry-vtt = 10005;
|
||||
pyload = 10006;
|
||||
jellyfin = 10007;
|
||||
};
|
||||
gids = {
|
||||
unbound = 10001;
|
||||
@@ -12,5 +14,7 @@
|
||||
gitea-runner = 10003;
|
||||
podman = 10004;
|
||||
foundry-vtt = 10005;
|
||||
pyload = 10006;
|
||||
jellyfin = 10007;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -33,4 +33,51 @@
|
||||
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.extraHosts = ''
|
||||
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.
|
||||
@@ -287,8 +289,9 @@ in {
|
||||
settings = {
|
||||
auto-optimise-store = true;
|
||||
experimental-features = [ "nix-command" "flakes" ];
|
||||
max-jobs = 12;
|
||||
cores = 2;
|
||||
max-jobs = 4;
|
||||
cores = 4;
|
||||
max-substitution-jobs = 16;
|
||||
};
|
||||
gc = {
|
||||
automatic = true;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
let
|
||||
# Wrapper to launch Chromium on Wayland, scale=1, DevTools debugging on 127.0.0.1:9222
|
||||
chromiumWaylandWrapper = pkgs.writeShellScriptBin "chromium-mcp" ''
|
||||
exec ${pkgs.chromium}/bin/chromium \
|
||||
exec ${pkgs.ungoogled-chromium}/bin/chromium \
|
||||
--ozone-platform=wayland \
|
||||
--enable-features=UseOzonePlatform \
|
||||
--force-device-scale-factor=1 \
|
||||
@@ -11,32 +11,13 @@ let
|
||||
--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
|
||||
{
|
||||
# Tools: Chromium, Node (for MCP server), our wrapper, and the desktop override
|
||||
# Tools: Chromium, Node (for MCP server), our wrapper
|
||||
environment.systemPackages = [
|
||||
pkgs.chromium
|
||||
pkgs.ungoogled-chromium
|
||||
pkgs.nodejs_22 # 25.05 ships Node 22 LTS; works great for MCP servers
|
||||
chromiumWaylandWrapper
|
||||
chromiumDesktopOverride # ← keep AFTER pkgs.chromium so our .desktop wins
|
||||
];
|
||||
|
||||
# Where Codex CLI reads config; we make it system-wide
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{ config, pkgs, ... }: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
chromium
|
||||
ungoogled-chromium
|
||||
nodejs
|
||||
# Graphics and font dependencies
|
||||
freetype
|
||||
@@ -30,6 +30,6 @@
|
||||
];
|
||||
|
||||
environment.variables = {
|
||||
PUPPETEER_EXECUTABLE_PATH = "${pkgs.chromium}/bin/chromium";
|
||||
PUPPETEER_EXECUTABLE_PATH = "${pkgs.ungoogled-chromium}/bin/chromium";
|
||||
};
|
||||
}
|
||||
|
||||
@@ -228,6 +228,21 @@ in
|
||||
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 = {
|
||||
|
||||
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 { });
|
||||
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 { });
|
||||
}
|
||||
|
||||
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