Compare commits
No commits in common. "dev" and "2386e1e9429ca9686f42d4f84cbe1417c5e1cd81" have entirely different histories.
dev
...
2386e1e942
62 changed files with 341 additions and 4643 deletions
42
.forgejo/workflows/build-golden-image.yml
Normal file
42
.forgejo/workflows/build-golden-image.yml
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
name: Build Golden Image
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'hosts/Template/**'
|
||||
- 'flake.nix'
|
||||
- 'flake.lock'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: self-hosted
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v27
|
||||
with:
|
||||
extra_nix_config: |
|
||||
experimental-features = nix-command flakes
|
||||
|
||||
- name: Build Proxmox Image
|
||||
run: |
|
||||
nix run github:nix-community/nixos-generators -- --format proxmox -c hosts/Template/default.nix -o result
|
||||
|
||||
- name: Upload to Proxmox
|
||||
env:
|
||||
PROXMOX_URL: "https://proxmox.local:8006/api2/json"
|
||||
PROXMOX_TOKEN_ID: "terraform@pve!tf-automation"
|
||||
PROXMOX_TOKEN_SECRET: ${{ secrets.PROXMOX_TOKEN_SECRET }}
|
||||
NODE_NAME: "pve"
|
||||
STORAGE_NAME: "local-zfs"
|
||||
VMID: 9000
|
||||
run: |
|
||||
IMAGE_PATH=$(find result -name "*.qcow2" | head -n 1)
|
||||
echo "Uploading $IMAGE_PATH to Proxmox as Template $VMID"
|
||||
# In a real scenario, this would use a script or API client to upload the image
|
||||
# e.g., using qm importdisk via ssh or the Proxmox API directly.
|
||||
# For example, using a custom script: ./scripts/upload-to-proxmox.sh $IMAGE_PATH $VMID
|
||||
echo "TODO: Implement Proxmox upload API call using PROXMOX_TOKEN_SECRET"
|
||||
25
.forgejo/workflows/renovate.yml
Normal file
25
.forgejo/workflows/renovate.yml
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
name: RenovateBot
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Run at 2:00 AM every day
|
||||
- cron: '0 2 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
renovate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Self-hosted Renovate
|
||||
uses: renovatebot/github-action@v40.1.11
|
||||
env:
|
||||
# Token needs permission to read/write repository contents and pull requests
|
||||
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
|
||||
# The platform needs to be explicitly defined for Forgejo/Gitea
|
||||
RENOVATE_PLATFORM: "gitea"
|
||||
RENOVATE_ENDPOINT: "https://your-forgejo-instance.local/api/v1"
|
||||
# The repository format: owner/repo
|
||||
RENOVATE_REPOSITORIES: "your-username/nix-config"
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1 +0,0 @@
|
|||
.idea
|
||||
31
.sops.yaml
31
.sops.yaml
|
|
@ -1,10 +1,29 @@
|
|||
# SOPS configuration for NixOS GitOps Migration (v2)
|
||||
# See README.md for key management instructions.
|
||||
|
||||
keys:
|
||||
- &tdpeuter_Tibo-NixFatDesk age1fva6s64s884z0q2w7de024sp69ucvqu0pg9shrhhqsn3ewlpjfpsh6md7y
|
||||
- &tdpeuter_Tibo-NixTop age1qzutny0mqpcccqw6myyfntu6wcskruu9ghzvt6r4te7afkqwnguq05ex37
|
||||
# Master Keys (Used for generic/global secrets if needed, but usually we encrypt for specific hosts)
|
||||
- &prod_master age1... # TODO: Replace with the public key of the prod-master offline USB key
|
||||
- &staging_master age1... # TODO: Replace with the public key of the staging-master key
|
||||
|
||||
# Host Keys (Used by the specific VM to decrypt its own secrets at boot)
|
||||
# - &host_control_center age1...
|
||||
|
||||
creation_rules:
|
||||
- path_regex: secrets/[^/]+\.(yaml|json|env|ini)$
|
||||
# Staging Environment
|
||||
# All files under secrets/staging/ are encrypted purely with the staging_master key.
|
||||
# Ephemeral VMs are injected with this private key at boot via Cloud-Init.
|
||||
- path_regex: secrets/staging/.*\.ya?ml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *tdpeuter_Tibo-NixFatDesk
|
||||
- *tdpeuter_Tibo-NixTop
|
||||
- age:
|
||||
- *staging_master
|
||||
|
||||
# Production Environment
|
||||
# All files under secrets/prod/ are encrypted with the prod_master key AND the specific host's key.
|
||||
# Ephemeral VMs CANNOT decrypt these files.
|
||||
- path_regex: secrets/prod/.*\.ya?ml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *prod_master
|
||||
# Add host keys here as they are provisioned
|
||||
# - *host_control_center
|
||||
|
|
|
|||
52
BOOTSTRAP.md
Normal file
52
BOOTSTRAP.md
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# Bootstrap Guide
|
||||
|
||||
This document outlines the manual, real-world steps required to initialize the NixOS GitOps environment for the first time. You must perform these steps before the CI/CD pipeline or any automated staging environments can function.
|
||||
|
||||
## 1. Secret Management Initialization (SOPS-Nix)
|
||||
|
||||
We use `sops-nix` to manage secrets, adhering to a strict separation between Production and Staging. You must generate these keys locally on a secure workstation.
|
||||
|
||||
**Prerequisites:** Install `age` ([age documentation](https://github.com/FiloSottile/age)).
|
||||
|
||||
1. **Generate the Production Master Key:**
|
||||
```bash
|
||||
age-keygen -o prod-master.txt
|
||||
```
|
||||
> [!CAUTION]
|
||||
> Move `prod-master.txt` to a secure offline USB drive and/or print it on paper. **Do not** store this private key on any server.
|
||||
|
||||
2. **Generate the Staging Master Key:**
|
||||
```bash
|
||||
age-keygen -o staging-master.txt
|
||||
```
|
||||
|
||||
3. **Update Configuration:**
|
||||
Open both `.txt` files and copy their **Public Keys** (the strings starting with `age1...`). Open `.sops.yaml` in the root of this repository and replace the `# TODO` placeholders with your newly generated public keys. Commit and push this change.
|
||||
|
||||
## 2. Proxmox Hypervisor Authentication
|
||||
|
||||
The CI/CD pipeline needs restricted API access to Proxmox to provision Virtual Machines.
|
||||
|
||||
**Prerequisites:** Install `terraform` ([Terraform installation](https://developer.hashicorp.com/terraform/downloads)).
|
||||
|
||||
1. Ensure you have network access to your Proxmox host (e.g., via Tailscale).
|
||||
2. Execute the bootstrap script from the root of the repository:
|
||||
```bash
|
||||
./scripts/control-center-bootstrap.sh
|
||||
```
|
||||
3. Terraform will prompt you. You will need to provide your Proxmox `root@pam` credentials via environment variables or prompt.
|
||||
4. Upon successful completion, the script will output a secure **API Token**. Copy this token securely.
|
||||
|
||||
## 3. Forgejo Secrets Configuration
|
||||
|
||||
The CI/CD actions require access to the Proxmox token and the staging secret key.
|
||||
|
||||
1. Navigate to your Forgejo Web UI.
|
||||
2. Go to **Settings > Actions > Secrets** for this repository.
|
||||
3. Add the following repository secrets:
|
||||
* `PROXMOX_TOKEN_SECRET`: Paste the token generated from Step 2.
|
||||
* `STAGING_AGE_KEY`: Paste the *entire contents* of your `staging-master.txt` file (the private key).
|
||||
* `RENOVATE_TOKEN`: Create a Personal Access Token (PAT) for your user in Forgejo with read/write access to code and pull requests, and paste it here.
|
||||
|
||||
## Next Steps
|
||||
Once these bootstrap steps are complete, the foundational authentication is in place. The Forgejo CI actions will now have the necessary permissions to build images, provision VMs, and test staging environments autonomously.
|
||||
32
DISASTER_RECOVERY.md
Normal file
32
DISASTER_RECOVERY.md
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# Disaster Recovery Plan (Zero-Login Strategy)
|
||||
|
||||
In the event of a catastrophic failure, this document outlines how to restore services without causing configuration drift (i.e., without SSHing directly into target nodes and making manual undocumented changes).
|
||||
|
||||
## 1. Full Proxmox Host Freeze (Hardware/GPU Bug)
|
||||
If the entire Proxmox hypervisor freezes (often caused by the AMD GPU reset bug when a VM reboots):
|
||||
* **DO NOT** attempt to SSH into the host or the VMs.
|
||||
* **Recovery Action**: Use your Out-of-Band Management (OOBM) solution. Access your PiKVM, IPMI interface, or remotely controlled Smart Plug to perform a **hard power cycle** of the host.
|
||||
* Upon reboot, Proxmox will automatically start the VMs, and `comin` will pull the latest working configuration.
|
||||
|
||||
## 2. Production Service Failure (Code Bug)
|
||||
If a bad PR was merged and a stateless service is failing to start:
|
||||
* **DO NOT** SSH into the VM to fix the config manually.
|
||||
* **Recovery Action**: Open the Forgejo Web UI and click **Revert** on the merged PR.
|
||||
* Within 60 seconds, the `comin` agent running on the target VM will detect the new commit on the `v2` branch, pull the reverted configuration, and restart the service automatically.
|
||||
|
||||
## 3. Production Data Corruption (Database/Stateful Bug)
|
||||
If a bad deployment corrupted persistent data on TrueNAS (e.g., a failed one-way database schema migration):
|
||||
* **Recovery Action**:
|
||||
1. Revert the code PR in Forgejo as described in section 2.
|
||||
2. Execute the `scripts/dr-rollback-dataset.sh` script from the secure **Control Center VM**. This script uses the TrueNAS REST API to rollback the specific ZFS dataset to the automated snapshot taken immediately prior to the deployment.
|
||||
|
||||
## 4. Total Git Repository Loss
|
||||
If the `v2` branch is completely destroyed or the Gitea server is unrecoverable:
|
||||
* Your `prod-master` private age key is stored safely offline on your USB drive.
|
||||
* A recent backup of the repository should be available via your external backup mechanism.
|
||||
* Clone the backup to your local workstation, run the `proxmox-bootstrap` Terraform state locally to ensure the hypervisor is reachable, and use Terraform to redeploy the Control Center VM and Forgejo instances.
|
||||
|
||||
## 5. Manually Obtaining the Golden Image
|
||||
If the CI pipeline is down and you urgently need to provision a new VM:
|
||||
1. Run `nix run github:nix-community/nixos-generators -- --format proxmox -c hosts/Template/default.nix -o result` on your local machine.
|
||||
2. The output `.qcow2` image can be uploaded to the Proxmox Web UI manually under `local` -> `ISO Images` (or directly via `qm importdisk` if you must use SSH as a last resort).
|
||||
34
README.md
Normal file
34
README.md
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# NixOS GitOps Migration (v2)
|
||||
|
||||
This branch contains the automated, pull-based GitOps architecture using `comin`, Terraform, and Forgejo.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Getting Started:** If you are setting up this repository from scratch, you **must** follow the steps in [BOOTSTRAP.md](file:///c:/Users/tibod/Documents/projects/Bos55/nix-config/BOOTSTRAP.md) before the automated pipelines can function.
|
||||
|
||||
## Secret Management (SOPS-Nix)
|
||||
|
||||
This repository uses `sops-nix` for secret management, adhering to a strict separation between Production and Staging environments to prevent credential leakage during CI runs.
|
||||
|
||||
### Production Keys
|
||||
* **`prod-master`**: This is the ultimate master key for production secrets.
|
||||
* **Best Practice**: The private key is **NEVER** stored on any server (including the Control Center VM). It is kept locally on a securely backed-up USB key and a paper backup.
|
||||
* You only import the private key to your local machine temporarily when you need to encrypt a new secret or during a disaster recovery scenario.
|
||||
* **Host Keys**: Production VMs use their individual SSH host keys (generated during provisioning) to decrypt their specific secrets at boot time.
|
||||
|
||||
### Staging Keys
|
||||
* **`staging-master`**: This key is used exclusively for encrypting secrets inside the `secrets/staging/` directory.
|
||||
* The public key is in `.sops.yaml`.
|
||||
* The private key is stored securely in Forgejo Secrets.
|
||||
* During CI runs, the Forgejo Runner injects this private key into the ephemeral Staging VMs via Cloud-Init.
|
||||
* Even if this key is compromised, it cannot decrypt production data.
|
||||
|
||||
### Generating the Master Keys
|
||||
To generate your master keys (run this on your secure local workstation):
|
||||
```bash
|
||||
# Generate the prod-master key (save the private key to your USB drive)
|
||||
age-keygen -o prod-master.txt
|
||||
|
||||
# Generate the staging-master key (upload the private key to Forgejo Secrets)
|
||||
age-keygen -o staging-master.txt
|
||||
```
|
||||
Update `.sops.yaml` with the public keys outputted by these commands.
|
||||
102
flake.lock
generated
102
flake.lock
generated
|
|
@ -1,102 +0,0 @@
|
|||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1760524057,
|
||||
"narHash": "sha256-EVAqOteLBFmd7pKkb0+FIUyzTF61VKi7YmvP1tw4nEw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "544961dfcce86422ba200ed9a0b00dd4b1486ec5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"ref": "nixos-unstable",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"sops-nix": "sops-nix",
|
||||
"utils": "utils"
|
||||
}
|
||||
},
|
||||
"sops-nix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1760393368,
|
||||
"narHash": "sha256-8mN3kqyqa2PKY0wwZ2UmMEYMcxvNTwLaOrrDsw6Qi4E=",
|
||||
"owner": "Mic92",
|
||||
"repo": "sops-nix",
|
||||
"rev": "ab8d56e85b8be14cff9d93735951e30c3e86a437",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "Mic92",
|
||||
"repo": "sops-nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"utils": {
|
||||
"inputs": {
|
||||
"flake-utils": [
|
||||
"flake-utils"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1738591040,
|
||||
"narHash": "sha256-4WNeriUToshQ/L5J+dTSWC5OJIwT39SEP7V7oylndi8=",
|
||||
"owner": "gytis-ivaskevicius",
|
||||
"repo": "flake-utils-plus",
|
||||
"rev": "afcb15b845e74ac5e998358709b2b5fe42a948d1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "gytis-ivaskevicius",
|
||||
"repo": "flake-utils-plus",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
47
flake.nix
47
flake.nix
|
|
@ -1,23 +1,30 @@
|
|||
{
|
||||
description = "Homelab configuration using flakes";
|
||||
description = "Homelab configuration using flakes (v2 GitOps)";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "nixpkgs/nixos-unstable";
|
||||
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
|
||||
sops-nix = {
|
||||
url = "github:Mic92/sops-nix";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
utils = {
|
||||
url = "github:gytis-ivaskevicius/flake-utils-plus";
|
||||
inputs.flake-utils.follows = "flake-utils";
|
||||
};
|
||||
|
||||
comin = {
|
||||
url = "github:nlewo/comin";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = inputs@{
|
||||
self, nixpkgs,
|
||||
flake-utils, sops-nix, utils,
|
||||
flake-utils, sops-nix, utils, comin,
|
||||
...
|
||||
}:
|
||||
let
|
||||
|
|
@ -34,31 +41,25 @@
|
|||
./users
|
||||
|
||||
sops-nix.nixosModules.sops
|
||||
comin.nixosModules.comin
|
||||
|
||||
# Base comin configuration for all nodes
|
||||
({ config, pkgs, ... }: {
|
||||
services.comin = {
|
||||
enable = true;
|
||||
remotes = [{
|
||||
name = "origin";
|
||||
# Replace with actual internal forgejo URL once available
|
||||
url = "https://github.com/example/nix-config.git";
|
||||
branches.main.name = "v2";
|
||||
}];
|
||||
};
|
||||
})
|
||||
];
|
||||
};
|
||||
|
||||
hosts = {
|
||||
# Physical hosts
|
||||
Niko.modules = [ ./hosts/Niko ];
|
||||
|
||||
# Virtual machines
|
||||
|
||||
# Single-service
|
||||
Ingress.modules = [ ./hosts/Ingress ];
|
||||
Gitea.modules = [ ./hosts/Gitea ];
|
||||
Vaultwarden.modules = [ ./hosts/Vaultwarden ];
|
||||
|
||||
# Production multi-service
|
||||
Binnenpost.modules = [ ./hosts/Binnenpost ];
|
||||
Production.modules = [ ./hosts/Production ];
|
||||
ProductionGPU.modules = [ ./hosts/ProductionGPU ];
|
||||
ProductionArr.modules = [ ./hosts/ProductionArr ];
|
||||
ACE.modules = [ ./hosts/ACE ];
|
||||
|
||||
# Others
|
||||
Template.modules = [ ./hosts/Template ];
|
||||
Development.modules = [ ./hosts/Development ];
|
||||
Testing.modules = [ ./hosts/Testing ];
|
||||
# Hosts will be populated here as they are migrated to the v2 branch.
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,41 +0,0 @@
|
|||
{ pkgs, ... }:
|
||||
|
||||
{
|
||||
config = {
|
||||
homelab = {
|
||||
services.actions.enable = true;
|
||||
virtualisation.guest.enable = true;
|
||||
};
|
||||
|
||||
networking = {
|
||||
hostName = "ACE";
|
||||
hostId = "aaaa4100";
|
||||
domain = "depeuter.dev";
|
||||
|
||||
useDHCP = false;
|
||||
|
||||
enableIPv6 = true;
|
||||
|
||||
defaultGateway = {
|
||||
address = "192.168.0.1";
|
||||
interface = "ens18";
|
||||
};
|
||||
|
||||
interfaces.ens18 = {
|
||||
ipv4.addresses = [
|
||||
{
|
||||
address = "192.168.0.41";
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
nameservers = [
|
||||
"1.1.1.1" # Cloudflare
|
||||
"1.0.0.1" # Cloudflare
|
||||
];
|
||||
};
|
||||
|
||||
system.stateVersion = "24.05";
|
||||
};
|
||||
}
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
{ pkgs, ... }:
|
||||
|
||||
{
|
||||
config = {
|
||||
environment = {
|
||||
etc."nixos/tailscale-authkey".text = ''
|
||||
tskey-auth-k1tfJLTnGB11CNTRL-HhnegtfNzQ3G8h71SC2DR38PFXwseQiu
|
||||
'';
|
||||
|
||||
systemPackages = with pkgs; [
|
||||
ethtool
|
||||
];
|
||||
};
|
||||
|
||||
homelab = {
|
||||
apps = {
|
||||
speedtest.enable = true;
|
||||
technitiumDNS.enable = true;
|
||||
traefik.enable = true;
|
||||
};
|
||||
virtualisation.guest.enable = true;
|
||||
};
|
||||
|
||||
networking = {
|
||||
hostName = "Binnenpost";
|
||||
hostId = "aaaa1001";
|
||||
domain = "depeuter.dev";
|
||||
|
||||
useDHCP = false;
|
||||
|
||||
enableIPv6 = true;
|
||||
|
||||
defaultGateway = {
|
||||
address = "192.168.0.1";
|
||||
interface = "ens18";
|
||||
};
|
||||
|
||||
# Open ports in the firewall.
|
||||
firewall = {
|
||||
enable = true;
|
||||
};
|
||||
|
||||
interfaces.ens18 = {
|
||||
ipv4.addresses = [
|
||||
{
|
||||
address = "192.168.0.89";
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
nameservers = [
|
||||
"1.1.1.1" # Cloudflare
|
||||
"1.0.0.1" # Cloudflare
|
||||
];
|
||||
};
|
||||
|
||||
services = {
|
||||
networkd-dispatcher = {
|
||||
enable = true;
|
||||
rules."50-tailscale" = {
|
||||
onState = ["routable"];
|
||||
script = ''
|
||||
${pkgs.ethtool}/bin/ethtool -K ens18 rx-udp-gro-forwarding on rx-gro-list off
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
tailscale = {
|
||||
enable = true;
|
||||
useRoutingFeatures = "server";
|
||||
authKeyFile = "/etc/nixos/tailscale-authkey";
|
||||
extraUpFlags = [
|
||||
"--advertise-routes=192.168.0.0/24"
|
||||
"--exit-node"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
virtualisation.oci-containers.containers.traefik.labels = {
|
||||
"traefik.http.routers.roxanne.rule" = "Host(`roxanne.depeuter.dev`)";
|
||||
"traefik.http.services.roxanne.loadbalancer.server.url" = "https://192.168.0.13:8006";
|
||||
|
||||
"traefik.http.routers.hugo.rule" = "Host(`hugo.depeuter.dev`)";
|
||||
"traefik.http.services.hugo.loadbalancer.server.url" = "https://192.168.0.11:444";
|
||||
};
|
||||
|
||||
system.stateVersion = "24.05";
|
||||
};
|
||||
}
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
{ config, pkgs, lib, system, ... }:
|
||||
|
||||
{
|
||||
config = {
|
||||
homelab = {
|
||||
apps = {
|
||||
bind9.enable = true;
|
||||
homepage = {
|
||||
enable = true;
|
||||
exposePort = true;
|
||||
};
|
||||
traefik.enable = true;
|
||||
plex.enable = true;
|
||||
solidtime.enable = true;
|
||||
};
|
||||
virtualisation.guest.enable = true;
|
||||
};
|
||||
|
||||
networking = {
|
||||
hostId = "aaaa9100";
|
||||
domain = "roxanne.depeuter.dev";
|
||||
|
||||
useDHCP = false;
|
||||
|
||||
enableIPv6 = true;
|
||||
|
||||
defaultGateway = {
|
||||
address = "192.168.0.1";
|
||||
interface = "ens18";
|
||||
};
|
||||
|
||||
# Open ports in the firewall.
|
||||
firewall = {
|
||||
enable = true;
|
||||
};
|
||||
|
||||
interfaces.ens18 = {
|
||||
ipv4.addresses = [
|
||||
{
|
||||
address = "192.168.0.91";
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
nameservers = [
|
||||
"1.1.1.1" # Cloudflare
|
||||
"1.0.0.1" # Cloudflare
|
||||
];
|
||||
};
|
||||
|
||||
system.stateVersion = "24.05";
|
||||
|
||||
virtualisation.oci-containers.containers = {
|
||||
pgadmin = {
|
||||
image = "dpage/pgadmin4:8.11.0";
|
||||
ports = [
|
||||
"30056:80/tcp"
|
||||
];
|
||||
environment = {
|
||||
# NOTE Required
|
||||
# The email address used when setting up the initial administrator account to login to pgAdmin.
|
||||
PGADMIN_DEFAULT_EMAIL = "kmtl.hugo+pgadmin@gmail.com";
|
||||
# NOTE Required
|
||||
# The password used when setting up the initial administrator account to login to pgAdmin.
|
||||
PGADMIN_DEFAULT_PASSWORD = "ChangeMe";
|
||||
};
|
||||
autoStart = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
{ config, pkgs, lib, system, ... }:
|
||||
|
||||
{
|
||||
config = {
|
||||
homelab = {
|
||||
apps.gitea.enable = true;
|
||||
virtualisation.guest.enable = true;
|
||||
|
||||
users.admin = {
|
||||
enable = true;
|
||||
authorizedKeys = [
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFrp6aM62Bf7bj1YM5AlAWuNrANU3N5e8+LtbbpmZPKS"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
networking = {
|
||||
hostId = "aaaa1500";
|
||||
domain = "depeuter.dev";
|
||||
|
||||
enableIPv6 = true;
|
||||
|
||||
useDHCP = false;
|
||||
defaultGateway = {
|
||||
address = "192.168.0.1";
|
||||
interface = "ens18";
|
||||
};
|
||||
interfaces.ens18 = {
|
||||
ipv4.addresses = [
|
||||
{
|
||||
address = "192.168.0.24";
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
nameservers = [
|
||||
"1.1.1.1" # Cloudflare
|
||||
"1.0.0.1" # Cloudflare
|
||||
];
|
||||
};
|
||||
|
||||
system.stateVersion = "24.05";
|
||||
};
|
||||
}
|
||||
|
|
@ -1,263 +0,0 @@
|
|||
{ config, pkgs, modulesPath, lib, system, ... }:
|
||||
|
||||
{
|
||||
config = {
|
||||
homelab.virtualisation.guest.enable = true;
|
||||
|
||||
networking = {
|
||||
hostName = "Ingress";
|
||||
hostId = "aaaa1000";
|
||||
domain = "depeuter.dev";
|
||||
|
||||
enableIPv6 = true;
|
||||
|
||||
useDHCP = false;
|
||||
defaultGateway = {
|
||||
address = "192.168.0.1";
|
||||
interface = "ens18";
|
||||
};
|
||||
interfaces.ens18 = {
|
||||
ipv4.addresses = [
|
||||
{
|
||||
address = "192.168.0.10";
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
nameservers = [
|
||||
"1.1.1.1" # Cloudflare
|
||||
"1.0.0.1" # Cloudflare
|
||||
];
|
||||
|
||||
firewall = {
|
||||
enable = true;
|
||||
allowedTCPPorts = [
|
||||
80 # HTTP
|
||||
443 # HTTPS
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
security.acme = {
|
||||
acceptTerms = true;
|
||||
defaults = {
|
||||
inherit (config.services.nginx) group;
|
||||
dnsPropagationCheck = true;
|
||||
dnsProvider = "cloudflare";
|
||||
dnsResolver = "1.1.1.1:53";
|
||||
email = "tibo.depeuter@telenet.be";
|
||||
credentialFiles = {
|
||||
CLOUDFLARE_DNS_API_TOKEN_FILE = "/var/lib/secrets/depeuter-dev-cloudflare-api-token";
|
||||
};
|
||||
reloadServices = [ "nginx" ];
|
||||
};
|
||||
certs = {
|
||||
"depeuter.dev" = {
|
||||
domain = "depeuter.dev";
|
||||
extraDomainNames = [ "*.depeuter.dev" ];
|
||||
};
|
||||
"cloud.depeuter.dev" = { };
|
||||
"git.depeuter.dev" = { };
|
||||
"home.depeuter.dev" = { };
|
||||
"jelly.depeuter.dev" = { };
|
||||
"vault.depeuter.dev" = { };
|
||||
};
|
||||
};
|
||||
|
||||
# List services that you want to enable.
|
||||
services = {
|
||||
# Enable Nginx as a reverse proxy
|
||||
nginx = let
|
||||
nextcloud = {
|
||||
host = "192.168.0.23";
|
||||
officePort = 8080;
|
||||
};
|
||||
in {
|
||||
enable = true;
|
||||
|
||||
# Use recommended settings
|
||||
# recommendedGzipSettings = true;
|
||||
# recommendedOptimisation = true;
|
||||
# recommendedProxySettings = true;
|
||||
# recommendedTlsSettings = true;
|
||||
|
||||
# Only allow PFS-enabled ciphers with AES256
|
||||
sslCiphers = "AES256+EECDH:AES256+EDH:!aNULL";
|
||||
|
||||
upstreams.docservice.servers."${nextcloud.host}:${toString nextcloud.officePort}" = {};
|
||||
|
||||
appendHttpConfig = ''
|
||||
map $http_x_forwarded_proto $the_scheme {
|
||||
default $http_x_forwarded_proto;
|
||||
"" $scheme;
|
||||
}
|
||||
|
||||
map $http_x_forwarded_host $the_host {
|
||||
default $http_x_forwarded_host;
|
||||
"" $host;
|
||||
}
|
||||
|
||||
map $http_upgrade $proxy_connection {
|
||||
default upgrade;
|
||||
"" close;
|
||||
}
|
||||
'';
|
||||
|
||||
# Define hosts
|
||||
virtualHosts = {
|
||||
# Disable automatic routing.
|
||||
"default" = {
|
||||
locations."/".return = "301 https://youtu.be/dQw4w9WgXcQ";
|
||||
default = true;
|
||||
};
|
||||
|
||||
"cloud.depeuter.dev" = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
locations = {
|
||||
"/" = {
|
||||
proxyPass = "http://${nextcloud.host}";
|
||||
extraConfig = ''
|
||||
add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;
|
||||
fastcgi_request_buffering off;
|
||||
'';
|
||||
};
|
||||
"/office/" = {
|
||||
proxyPass = "http://${nextcloud.host}:${toString nextcloud.officePort}/";
|
||||
priority = 500;
|
||||
recommendedProxySettings = false;
|
||||
extraConfig = ''
|
||||
proxy_http_version 1.1;
|
||||
'';
|
||||
};
|
||||
};
|
||||
extraConfig = ''
|
||||
client_max_body_size 10G;
|
||||
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $proxy_connection;
|
||||
proxy_set_header X-Forwarded-Host $the_host/office;
|
||||
proxy_set_header X-Forwarded-Proto $the_scheme;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
'';
|
||||
};
|
||||
|
||||
"home.depeuter.dev" = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
locations."/" = {
|
||||
proxyPass = "http://192.168.0.21:8123";
|
||||
extraConfig = ''
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
proxy_set_header Host $host;
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
"jelly.depeuter.dev" = let
|
||||
jellyfin = {
|
||||
host = "192.168.0.94";
|
||||
port = 8096;
|
||||
};
|
||||
in {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
locations = {
|
||||
"/" = {
|
||||
proxyPass = "http://${jellyfin.host}:${toString jellyfin.port}";
|
||||
extraConfig = ''
|
||||
# Proxy main Jellyfin traffic
|
||||
proxy_set_header Host $host;
|
||||
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-Protocol $scheme;
|
||||
proxy_set_header X-Forwarded-Host $http_host;
|
||||
|
||||
# Disable buffering when the nginx proxy gets very resource heavy upon streaming
|
||||
proxy_buffering off;
|
||||
'';
|
||||
};
|
||||
"/socket" = {
|
||||
proxyPass = "http://${jellyfin.host}:${toString jellyfin.port}";
|
||||
extraConfig = ''
|
||||
# Proxy Jellyfin Websockets traffic
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
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-Protocol $scheme;
|
||||
proxy_set_header X-Forwarded-Host $http_host;
|
||||
'';
|
||||
};
|
||||
};
|
||||
extraConfig = ''
|
||||
client_max_body_size 512M;
|
||||
|
||||
# Security / XSS Mitigation Headers
|
||||
# NOTE: X-Frame-Options may cause issues with the webOS app
|
||||
add_header X-Frame-Options "SAMEORIGIN";
|
||||
add_header X-Content-Type-Options "nosniff";
|
||||
|
||||
# Permissions policy. May cause issues with some clients
|
||||
add_header Permissions-Policy "accelerometer=(), ambient-light-sensor=(), battery=(), bluetooth=(), camera=(), clipboard-read=(), display-capture=(), document-domain=(), encrypted-media=(), gamepad=(), geolocation=(), gyroscope=(), hid=(), idle-detection=(), interest-cohort=(), keyboard-map=(), local-fonts=(), magnetometer=(), microphone=(), payment=(), publickey-credentials-get=(), serial=(), sync-xhr=(), usb=(), xr-spatial-tracking=()" always;
|
||||
|
||||
# Content Security Policy
|
||||
# See: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
|
||||
# Enforces https content and restricts JS/CSS to origin
|
||||
# External Javascript (such as cast_sender.js for Chromecast) must be whitelisted.
|
||||
# NOTE: The default CSP headers may cause issues with the webOS app
|
||||
add_header Content-Security-Policy "default-src https: data: blob: ; img-src 'self' https://* ; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' https://www.gstatic.com https://www.youtube.com blob:; worker-src 'self' blob:; connect-src 'self'; object-src 'none'; frame-ancestors 'self'";
|
||||
'';
|
||||
};
|
||||
"git.depeuter.dev" = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
locations."/".proxyPass = "http://192.168.0.24:3000";
|
||||
extraConfig = ''
|
||||
proxy_set_header Connection $http_connection;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Host $host;
|
||||
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;
|
||||
|
||||
client_max_body_size 10G;
|
||||
keepalive_timeout 600s;
|
||||
proxy_buffers 4 256k; # Number and size of buffers for reading response
|
||||
proxy_buffer_size 256k; # Buffer for the first part of the response
|
||||
proxy_busy_buffers_size 256k; # Max size of busy buffers
|
||||
proxy_http_version 1.1;
|
||||
proxy_read_timeout 600s;
|
||||
proxy_temp_file_write_size 256k; # Size of temp file for large responses
|
||||
'';
|
||||
};
|
||||
"vault.depeuter.dev" = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
locations = {
|
||||
"/" = {
|
||||
proxyPass = "http://192.168.0.22:10102";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
"~ ^/admin".return = 403;
|
||||
};
|
||||
};
|
||||
"rss.depeuter.dev" = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
locations."/".proxyPass = "http://192.168.92:${toString config.homelab.apps.freshrss.port}";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
system.stateVersion = "24.05";
|
||||
};
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
- Office:
|
||||
- Zoho Mail:
|
||||
- icon: zohomail
|
||||
href: https://mail.zoho.eu
|
||||
- Network:
|
||||
- Cloudlfare:
|
||||
- icon: cloudflare
|
||||
href: https://dash.cloudflare.com
|
||||
- Pulsetic:
|
||||
- href: https://status.depeuter.dev
|
||||
icon: https://pulsetic.com/favicon-196x196.png
|
||||
- Telenet Internet usage:
|
||||
- icon: https://static.telenet.be/assets/favicon/favicon.ico
|
||||
href: https://www2.telenet.be/nl/klantenservice/raadpleeg-je-internetverbruik/
|
||||
- Telenet Modem:
|
||||
- icon: https://static.telenet.be/assets/favicon/favicon.ico
|
||||
# href: https://mijn.telenet.be/mijntelenet/rgw/settings.do?identifier=u381160&action=showAdvancedSettings
|
||||
href: https://www2.telenet.be/residential/nl/mijn-telenet/je-thuisnetwerk#/mainnavitem=hgw/mainnavitemid=item-1/subnavitem=modem_general
|
||||
- TransIP:
|
||||
- icon: https://www.transip.eu/cache-60c9b25f/img/transip-new/favicons/favicon.png
|
||||
href: https://www.transip.eu/cp/
|
||||
- Homemade:
|
||||
- AI-Transparency:
|
||||
- href: https://ai-transparency.depeuter.dev
|
||||
icon: https://ai-transparency.depeuter.dev/img/transparency.png
|
||||
- Down-message:
|
||||
- href: https://down.depeuter.dev
|
||||
icon: https://down.depeuter.dev/assets/icon.jpg
|
||||
- Portfolio:
|
||||
- href: https://tibo.depeuter.dev
|
||||
icon: https://tibo.depeuter.dev/assets/owl_circuit.png
|
||||
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
- Networking:
|
||||
- AXE5400 Tri-Band Wi-Fi 6E Router:
|
||||
description: Router
|
||||
href: https://tplinkwifi.net
|
||||
ping: http://192.168.0.1
|
||||
icon: tp-link
|
||||
- Traefik Isabel:
|
||||
description: Reverse proxy manager
|
||||
href: https://traefik.isabel.depeuter.dev/dashboard/#
|
||||
ping: https://traefik.isabel.depeuter.dev/dashboard/#
|
||||
icon: traefik
|
||||
widget:
|
||||
type: traefik
|
||||
url: https://traefik.isabel.depeuter.dev
|
||||
- Traefik Niko:
|
||||
description: Reverse proxy manager
|
||||
href: https://traefik.niko.depeuter.dev/dashboard/#
|
||||
ping: https://traefik.niko.depeuter.dev/dashboard/#
|
||||
- Technitium DNS Isabel:
|
||||
description: DNS server
|
||||
href: https://dns.Isabel.depeuter.dev
|
||||
ping: http://192.168.0.13:53
|
||||
icon: technitium
|
||||
- Technitium DNS Niko:
|
||||
description: DNS server
|
||||
href: https://dns.niko.depeuter.dev
|
||||
ping: http://192.168.0.30:53
|
||||
icon: technitium
|
||||
|
||||
|
||||
|
|
@ -1,255 +0,0 @@
|
|||
{ config, pkgs, ... }:
|
||||
|
||||
{
|
||||
imports = [
|
||||
# Include the results of the hardware scan.
|
||||
./hardware-configuration.nix
|
||||
];
|
||||
|
||||
# Use the systemd-boot EFI boot loader.
|
||||
boot.loader = {
|
||||
systemd-boot.enable = true;
|
||||
efi = {
|
||||
canTouchEfiVariables = true;
|
||||
efiSysMountPoint = "/boot/efi";
|
||||
};
|
||||
};
|
||||
|
||||
console = {
|
||||
font = "Lat2-Terminus16";
|
||||
keyMap = "us";
|
||||
};
|
||||
|
||||
# List packages installed in the system profile. To search, run:
|
||||
# $ nix search wget
|
||||
environment.systemPackages = with pkgs; [
|
||||
];
|
||||
|
||||
environment.etc = {
|
||||
"homepage/bookmarks.yaml".text = ''
|
||||
- Office:
|
||||
- Zoho Mail:
|
||||
- icon: zohomail
|
||||
href: https://mail.zoho.eu
|
||||
- Network:
|
||||
- Cloudlfare:
|
||||
- icon: cloudflare
|
||||
href: https://dash.cloudflare.com
|
||||
- TransIP:
|
||||
- icon: https://www.transip.eu/cache-60c9b25f/img/transip-new/favicons/favicon.png
|
||||
href: https://www.transip.eu/cp/
|
||||
- Telenet Internet usage:
|
||||
- icon: https://static.telenet.be/assets/favicon/favicon.ico
|
||||
href: https://www2.telenet.be/nl/klantenservice/raadpleeg-je-internetverbruik/
|
||||
- Telenet Modem:
|
||||
- icon: https://static.telenet.be/assets/favicon/favicon.ico
|
||||
# href: https://mijn.telenet.be/mijntelenet/rgw/settings.do?identifier=u381160&action=showAdvancedSettings
|
||||
href: https://www2.telenet.be/residential/nl/mijn-telenet/je-thuisnetwerk#/mainnavitem=hgw/mainnavitemid=item-1/subnavitem=modem_general
|
||||
- Pulsetic:
|
||||
- href: https://status.depeuter.dev
|
||||
icon: https://pulsetic.com/favicon-196x196.png
|
||||
- Homemade:
|
||||
- AI-Transparency:
|
||||
- href: https://ai-transparency.depeuter.dev
|
||||
icon: https://ai-transparency.depeuter.dev/img/transparency.png
|
||||
- Down-message:
|
||||
- href: https://down.depeuter.dev
|
||||
icon: https://down.depeuter.dev/assets/icon.jpg
|
||||
- Portfolio:
|
||||
- href: https://tibo.depeuter.dev
|
||||
icon: https://tibo.depeuter.dev/assets/owl_circuit.png
|
||||
'';
|
||||
|
||||
"homepage/services.yaml".text = ''
|
||||
- Networking:
|
||||
- Traefik Isabel:
|
||||
description: Reverse proxy manager
|
||||
href: https://traefik.isabel.depeuter.dev/dashboard/#
|
||||
ping: https://traefik.isabel.depeuter.dev/dashboard/#
|
||||
icon: traefik
|
||||
widget:
|
||||
type: traefik
|
||||
url: https://traefik.isabel.depeuter.dev
|
||||
- Traefik Niko:
|
||||
description: Reverse proxy manager
|
||||
href: https://traefik.niko.depeuter.dev/dashboard/#
|
||||
ping: https://traefik.niko.depeuter.dev/dashboard/#
|
||||
icon: traefik
|
||||
widget:
|
||||
type: traefik
|
||||
url: https://traefik.niko.depeuter.dev
|
||||
'';
|
||||
|
||||
"homepage/settings.yaml".text = ''
|
||||
---
|
||||
# For configuration options and examples, please see:
|
||||
# https://gethomepage.dev/en/configs/settings
|
||||
|
||||
providers:
|
||||
openweathermap: openweathermapapikey
|
||||
weatherapi: weatherapiapikey
|
||||
'';
|
||||
};
|
||||
|
||||
homelab.apps.technitiumDNS.enable = true;
|
||||
|
||||
# Select internationalisation properties.
|
||||
i18n.defaultLocale = "en_GB.utf8";
|
||||
|
||||
networking = {
|
||||
hostName = "Hugo-Isabel";
|
||||
domain = "depeuter.dev";
|
||||
|
||||
enableIPv6 = true;
|
||||
|
||||
# Open ports in the firewall.
|
||||
firewall = {
|
||||
enable = true;
|
||||
};
|
||||
|
||||
networkmanager.enable = true;
|
||||
};
|
||||
|
||||
# List services that you want to enable:
|
||||
services = {
|
||||
tailscale = {
|
||||
enable = true;
|
||||
useRoutingFeatures = "server";
|
||||
authKeyFile = "/etc/nixos/tailscale-authkey";
|
||||
extraUpFlags = [
|
||||
"--advertise-routes=192.168.0.0/24"
|
||||
"--exit-node"
|
||||
];
|
||||
};
|
||||
|
||||
# Fix DNS issues. See:
|
||||
# https://github.com/tailscale/tailscale/issues/4254
|
||||
# resolved.enable = true;
|
||||
};
|
||||
|
||||
system.stateVersion = "24.05";
|
||||
|
||||
security.sudo = {
|
||||
enable = true;
|
||||
};
|
||||
|
||||
virtualisation = {
|
||||
docker = {
|
||||
enable = true;
|
||||
autoPrune.enable = true;
|
||||
};
|
||||
|
||||
oci-containers = {
|
||||
backend = "docker";
|
||||
containers = {
|
||||
reverse-proxy = {
|
||||
hostname = "traefik";
|
||||
image = "traefik:v3.0";
|
||||
cmd = [
|
||||
"--api.insecure=true"
|
||||
# Add Docker provider
|
||||
"--providers.docker=true"
|
||||
"--providers.docker.exposedByDefault=false"
|
||||
# Add web entrypoint
|
||||
"--entrypoints.web.address=:80/tcp"
|
||||
"--entrypoints.web.http.redirections.entrypoint.to=websecure"
|
||||
"--entrypoints.web.http.redirections.entrypoint.scheme=https"
|
||||
# Add websecure entrypoint
|
||||
"--entrypoints.websecure.address=:443/tcp"
|
||||
"--entrypoints.websecure.http.tls=true"
|
||||
"--entrypoints.websecure.http.tls.certResolver=letsencrypt"
|
||||
"--entrypoints.websecure.http.tls.domains[0].main=depeuter.dev"
|
||||
"--entrypoints.websecure.http.tls.domains[0].sans=*.depeuter.dev"
|
||||
"--entrypoints.websecure.http.tls.domains[1].sans=*.isabel.depeuter.dev"
|
||||
"--entrypoints.websecure.http.tls.domains[2].sans=*.jelly.depeuter.dev"
|
||||
# Certificates
|
||||
"--certificatesresolvers.letsencrypt.acme.dnschallenge=true"
|
||||
"--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare"
|
||||
"--certificatesresolvers.letsencrypt.acme.email=tibo.depeuter@telenet.be"
|
||||
"--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
|
||||
|
||||
# Additional routes
|
||||
];
|
||||
ports = [
|
||||
"80:80/tcp"
|
||||
"443:443/tcp"
|
||||
# "8080:8080/tcp" # The Web UI (enabled by --api.insecure=true)
|
||||
];
|
||||
environment = {
|
||||
# TODO Hide this!
|
||||
"CLOUDFLARE_DNS_API_TOKEN" = "6Vz64Op_a6Ls1ljGeBxFoOVfQ-yB-svRbf6OyPv2";
|
||||
};
|
||||
environmentFiles = [
|
||||
];
|
||||
volumes = [
|
||||
"/var/run/docker.sock:/var/run/docker.sock:ro" # So that Traefik can listen to the Docker events
|
||||
"letsencrypt:/letsencrypt"
|
||||
];
|
||||
labels = {
|
||||
"traefik.enable" = "true";
|
||||
"traefik.http.routers.traefik.rule" = "Host(`traefik.isabel.depeuter.dev`)";
|
||||
"traefik.http.services.traefik.loadbalancer.server.port" = "8080";
|
||||
};
|
||||
autoStart = true;
|
||||
};
|
||||
feishin = {
|
||||
hostname = "feishin";
|
||||
image = "ghcr.io/jeffvli/feishin:0.7.1";
|
||||
ports = [
|
||||
# "9180:9180/tcp" # Web player (HTTP)
|
||||
];
|
||||
environment = {
|
||||
# pre defined server name
|
||||
SERVER_NAME = "Hugo";
|
||||
# When true AND name/type/url are set, only username/password can be toggled
|
||||
SERVER_LOCK = "true";
|
||||
# navidrome also works
|
||||
SERVER_TYPE = "jellyfin";
|
||||
# http://address:port
|
||||
SERVER_URL= "https://jelly.depeuter.dev";
|
||||
TZ = config.time.timeZone;
|
||||
};
|
||||
labels = {
|
||||
"traefik.enable" = "true";
|
||||
"traefik.http.routers.feishin.rule" = "Host(`music.depeuter.dev`)";
|
||||
"traefik.http.services.feishin.loadbalancer.server.port" = "9180";
|
||||
"traefik.tls.options.default.minVersion" = "VersionTLS13";
|
||||
};
|
||||
autoStart = true;
|
||||
};
|
||||
dashboard = {
|
||||
hostname = "dashboard";
|
||||
image = "ghcr.io/gethomepage/homepage:v0.9.3";
|
||||
ports = [
|
||||
# "3000:3000/tcp"
|
||||
];
|
||||
volumes = [
|
||||
"/etc/homepage:/app/config" # Make sure your local config directory exists
|
||||
"/var/run/docker.sock:/var/run/docker.sock:ro" # optional, for docker integrations
|
||||
];
|
||||
labels = {
|
||||
"traefik.enable" = "true";
|
||||
"traefik.http.routers.dashboard.rule" = "Host(`dash.depeuter.dev`)";
|
||||
"traefik.http.services.dashboard.loadbalancer.server.port" = "3000";
|
||||
"traefik.tls.options.default.minVersion" = "VersionTLS13";
|
||||
};
|
||||
autoStart = true;
|
||||
};
|
||||
prometheus = {
|
||||
hostname = "prometheus";
|
||||
image = "prom/prometheus:v2.45.6";
|
||||
ports = [
|
||||
# "127.0.0.1:9090:9090/tcp"
|
||||
];
|
||||
labels = {
|
||||
"traefik.enable" = "true";
|
||||
"traefik.http.routers.prometheus.rule" = "Host(`prometheus.isabel.depeuter.dev`)";
|
||||
"traefik.http.services.prometheus.loadbalancer.server.port" = "9090";
|
||||
"traefik.tls.options.default.minVersion" = "VersionTLS13";
|
||||
};
|
||||
autoStart = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
# Do not modify this file! It was generated by ‘nixos-generate-config’
|
||||
# and may be overwritten by future invocations. Please make changes
|
||||
# to /etc/nixos/configuration.nix instead.
|
||||
{ config, lib, pkgs, modulesPath, ... }:
|
||||
|
||||
{
|
||||
imports =
|
||||
[ (modulesPath + "/profiles/qemu-guest.nix")
|
||||
];
|
||||
|
||||
boot.initrd.availableKernelModules = [ "ata_piix" "xhci_pci" "ahci" "sd_mod" "sr_mod" ];
|
||||
boot.initrd.kernelModules = [ ];
|
||||
boot.kernelModules = [ ];
|
||||
boot.extraModulePackages = [ ];
|
||||
|
||||
fileSystems."/" =
|
||||
{ device = "/dev/disk/by-label/NIX-ROOT";
|
||||
fsType = "ext4";
|
||||
};
|
||||
|
||||
fileSystems."/boot/efi" =
|
||||
{ device = "/dev/disk/by-label/NIX-BOOT";
|
||||
fsType = "vfat";
|
||||
};
|
||||
|
||||
swapDevices =
|
||||
[ { device = "/dev/disk/by-label/SWAP"; }
|
||||
];
|
||||
|
||||
# Enables DHCP on each ethernet and wireless interface. In case of scripted networking
|
||||
# (the default) this is the recommended approach. When using systemd-networkd it's
|
||||
# still possible to use this option, but it's recommended to use it in conjunction
|
||||
# with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`.
|
||||
networking.useDHCP = lib.mkDefault true;
|
||||
# networking.interfaces.ens3.useDHCP = lib.mkDefault true;
|
||||
|
||||
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
||||
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
|
||||
}
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
{ config, pkgs, ... }:
|
||||
|
||||
{
|
||||
imports = [
|
||||
# Include the results of the hardware scan.
|
||||
./hardware-configuration.nix
|
||||
];
|
||||
|
||||
homelab = {
|
||||
apps = {
|
||||
technitiumDNS.enable = true;
|
||||
traefik.enable = true;
|
||||
};
|
||||
users.deploy.enable = true;
|
||||
};
|
||||
|
||||
# Use the systemd-boot EFI boot loader.
|
||||
boot.loader = {
|
||||
systemd-boot.enable = true;
|
||||
efi = {
|
||||
canTouchEfiVariables = true;
|
||||
efiSysMountPoint = "/boot/efi";
|
||||
};
|
||||
};
|
||||
|
||||
console = {
|
||||
font = "Lat2-Terminus16";
|
||||
keyMap = "us";
|
||||
};
|
||||
|
||||
# List packages installed in the system profile. To search, run:
|
||||
# $ nix search wget
|
||||
environment.systemPackages = with pkgs; [
|
||||
cifs-utils
|
||||
];
|
||||
|
||||
hardware = {
|
||||
enableRedistributableFirmware = true;
|
||||
enableAllFirmware = true;
|
||||
graphics.enable = true;
|
||||
};
|
||||
|
||||
# Select internationalisation properties.
|
||||
i18n.defaultLocale = "en_GB.UTF-8";
|
||||
|
||||
networking = {
|
||||
hostName = "Niko";
|
||||
domain = "depeuter.dev";
|
||||
|
||||
enableIPv6 = true;
|
||||
|
||||
# Open ports in the firewall.
|
||||
firewall = {
|
||||
enable = true;
|
||||
};
|
||||
|
||||
networkmanager.enable = true;
|
||||
|
||||
extraHosts = ''
|
||||
192.168.0.11 jelly.depeuter.dev
|
||||
'';
|
||||
};
|
||||
|
||||
nixpkgs.config.allowUnfree = true;
|
||||
|
||||
# List services that you want to enable:
|
||||
services = {
|
||||
# Cage, a wayland kiosk service
|
||||
cage = {
|
||||
enable = true;
|
||||
environment = {
|
||||
# Do not fail when there are no input devices.
|
||||
# WLR_LIBINPUT_NO_DEVICES = "1";
|
||||
};
|
||||
extraArguments = [
|
||||
"-d" # Don't draw client side decorations, when possible
|
||||
# "-m" "last" # Use only the last connected output
|
||||
"-s" # Allow VT switching
|
||||
];
|
||||
program = "/home/jellyfin-mpv-shim/start.sh";
|
||||
user = config.users.users.jellyfin-mpv-shim.name;
|
||||
};
|
||||
|
||||
pulseaudio.enable = true;
|
||||
|
||||
tailscale = {
|
||||
enable = true;
|
||||
useRoutingFeatures = "server";
|
||||
authKeyFile = "/etc/nixos/tailscale-authkey";
|
||||
extraUpFlags = [
|
||||
"--advertise-routes=192.168.0.0/24"
|
||||
"--exit-node"
|
||||
];
|
||||
};
|
||||
|
||||
# Fix DNS issues. See:
|
||||
# https://github.com/tailscale/tailscale/issues/4254
|
||||
# resolved.enable = true;
|
||||
};
|
||||
|
||||
# Define a user account. Don't forget to set a password with 'passwd'.
|
||||
users.users.jellyfin-mpv-shim = {
|
||||
description = "Jellyfin MPV Shim User";
|
||||
isNormalUser = true;
|
||||
extraGroups = [
|
||||
config.users.groups.audio.name
|
||||
config.users.groups.video.name
|
||||
];
|
||||
packages = with pkgs; [
|
||||
jellyfin-mpv-shim
|
||||
mpv
|
||||
socat
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services."cage-tty1".serviceConfig.Restart = "always";
|
||||
|
||||
system.stateVersion = "24.05";
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
{ config, lib, pkgs, modulesPath, ... }:
|
||||
|
||||
{
|
||||
imports = [
|
||||
(modulesPath + "/installer/scan/not-detected.nix")
|
||||
];
|
||||
|
||||
boot = {
|
||||
initrd = {
|
||||
availableKernelModules = [
|
||||
"xhci_pci"
|
||||
"ahci"
|
||||
"usb_storage"
|
||||
"sd_mod"
|
||||
];
|
||||
};
|
||||
kernelModules = [ ];
|
||||
extraModulePackages = [ ];
|
||||
};
|
||||
|
||||
fileSystems = {
|
||||
"/" = {
|
||||
device = "/dev/disk/by-uuid/20b7eff3-fca5-4b60-a5a9-13219f70ce23";
|
||||
fsType = "ext4";
|
||||
};
|
||||
|
||||
"/boot/efi" = {
|
||||
device = "/dev/disk/by-uuid/0B6D-0DCD";
|
||||
fsType = "vfat";
|
||||
};
|
||||
|
||||
"/media/photos" = {
|
||||
device = "//192.168.0.11/CANVAS";
|
||||
fsType = "cifs";
|
||||
options = let
|
||||
# This line prevents hanging on network split
|
||||
automount_opts = "x-systemd.automount,noauto,x-systemd.idle-timeout=60,x-systemd.device-timeout=5s,x-systemd.mount-timeout=5s,user,users";
|
||||
in ["${automount_opts},credentials=/etc/nixos/smb-secrets,uid=1002,gid=100"];
|
||||
};
|
||||
};
|
||||
|
||||
swapDevices = [
|
||||
{ device = "/dev/disk/by-uuid/f3679da0-45b3-45c0-a1d0-af8d771a7dbf"; }
|
||||
];
|
||||
|
||||
networking = {
|
||||
hostId = "7a139e16";
|
||||
useDHCP = lib.mkDefault true;
|
||||
};
|
||||
|
||||
powerManagement.cpuFreqGovernor = lib.mkDefault "powersave";
|
||||
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
|
||||
}
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
{ config, pkgs, lib, system, ... }:
|
||||
|
||||
{
|
||||
config = {
|
||||
homelab = {
|
||||
apps = {
|
||||
calibre.enable = true;
|
||||
traefik.enable = true;
|
||||
};
|
||||
virtualisation.guest.enable = true;
|
||||
};
|
||||
|
||||
networking = {
|
||||
hostId = "aaaa2100";
|
||||
domain = "roxanne.depeuter.dev";
|
||||
|
||||
useDHCP = false;
|
||||
|
||||
enableIPv6 = true;
|
||||
|
||||
defaultGateway = {
|
||||
address = "192.168.0.1";
|
||||
interface = "ens18";
|
||||
};
|
||||
|
||||
# Open ports in the firewall.
|
||||
firewall = {
|
||||
enable = true;
|
||||
};
|
||||
|
||||
interfaces.ens18 = {
|
||||
ipv4.addresses = [
|
||||
{
|
||||
address = "192.168.0.31";
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
nameservers = [
|
||||
"1.1.1.1" # Cloudflare
|
||||
"1.0.0.1" # Cloudflare
|
||||
];
|
||||
};
|
||||
|
||||
system.stateVersion = "24.05";
|
||||
};
|
||||
}
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
{ config, pkgs, lib, system, ... }:
|
||||
|
||||
{
|
||||
config = {
|
||||
homelab = {
|
||||
apps = {
|
||||
arr.enable = true;
|
||||
traefik.enable = true;
|
||||
};
|
||||
virtualisation.guest.enable = true;
|
||||
};
|
||||
|
||||
networking = {
|
||||
hostId = "aaaa2300";
|
||||
domain = "roxanne.depeuter.dev";
|
||||
|
||||
useDHCP = false;
|
||||
|
||||
enableIPv6 = true;
|
||||
|
||||
defaultGateway = {
|
||||
address = "192.168.0.1";
|
||||
interface = "ens18";
|
||||
};
|
||||
|
||||
# Open ports in the firewall.
|
||||
firewall = {
|
||||
enable = true;
|
||||
};
|
||||
|
||||
interfaces.ens18 = {
|
||||
ipv4.addresses = [
|
||||
{
|
||||
address = "192.168.0.33";
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
nameservers = [
|
||||
"1.1.1.1" # Cloudflare
|
||||
"1.0.0.1" # Cloudflare
|
||||
];
|
||||
};
|
||||
|
||||
system.stateVersion = "24.05";
|
||||
};
|
||||
}
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
{ config, pkgs, lib, system, ... }:
|
||||
|
||||
{
|
||||
config = {
|
||||
homelab = {
|
||||
apps.jellyfin.enable = true;
|
||||
virtualisation.guest.enable = true;
|
||||
};
|
||||
|
||||
networking = {
|
||||
hostId = "aaaa2200";
|
||||
domain = "roxanne.depeuter.dev";
|
||||
|
||||
useDHCP = false;
|
||||
|
||||
enableIPv6 = true;
|
||||
|
||||
defaultGateway = {
|
||||
address = "192.168.0.1";
|
||||
interface = "ens18";
|
||||
};
|
||||
|
||||
# Open ports in the firewall.
|
||||
firewall = {
|
||||
enable = true;
|
||||
};
|
||||
|
||||
interfaces.ens18 = {
|
||||
ipv4.addresses = [
|
||||
{
|
||||
address = "192.168.0.94";
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
nameservers = [
|
||||
"1.1.1.1" # Cloudflare
|
||||
"1.0.0.1" # Cloudflare
|
||||
];
|
||||
};
|
||||
|
||||
system.stateVersion = "24.11";
|
||||
|
||||
### Nvidia GPU support ###
|
||||
|
||||
services.xserver.videoDrivers = [ "nvidia" ];
|
||||
|
||||
# virtualisation.docker.package = pkgs.nvidia-docker;
|
||||
|
||||
nixpkgs.config = {
|
||||
allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [
|
||||
"nvidia-x11"
|
||||
"nvidia-settings"
|
||||
"nvidia-persistenced"
|
||||
];
|
||||
|
||||
# enable vaapi on OS-level
|
||||
# packageOverrides = pkgs: {
|
||||
# vaapiIntel = pkgs.vaapiIntel.override {
|
||||
# enableHybridCodec = true;
|
||||
# };
|
||||
# };
|
||||
};
|
||||
|
||||
hardware = {
|
||||
graphics = {
|
||||
enable = true;
|
||||
# driSupport = true;
|
||||
# driSupport32Bit = true;
|
||||
extraPackages = with pkgs; [
|
||||
# intel-media-driver
|
||||
# intel-vaapi-driver # previously vaapiIntel
|
||||
# vaapiVdpau
|
||||
# intel-compute-runtime # OpenCL filter support (hardware tonemapping and subtitle burn-in)
|
||||
# unstable.vpl-gpu-rt # QSV on 11th gen or newer
|
||||
# intel-media-sdk # QSV up to 11th gen
|
||||
];
|
||||
};
|
||||
|
||||
nvidia = {
|
||||
package = config.boot.kernelPackages.nvidiaPackages.stable;
|
||||
# Whether to enable kernel modesetting when using the NVIDIA proprietary driver.
|
||||
modesetting.enable = true;
|
||||
# powerManagement = {
|
||||
# enable = false;
|
||||
# finegrained = false;
|
||||
# };
|
||||
open = false;
|
||||
nvidiaSettings = false;
|
||||
|
||||
# Whether to enable nvidia-persistenced a update for NVIDIA GPU headless mode, i.e. It ensures all GPUs stay awake even during headless mode .
|
||||
# nvidiaPersistenced = true;
|
||||
};
|
||||
nvidia-container-toolkit.enable = true;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
{ config, pkgs, lib, system, ... }:
|
||||
|
||||
{
|
||||
config = {
|
||||
homelab.virtualisation.guest.enable = true;
|
||||
|
||||
networking = {
|
||||
# TODO hostName = "nixos";
|
||||
# TODO hostId = "aaaa9000";
|
||||
domain = "depeuter.dev";
|
||||
|
||||
enableIPv6 = true;
|
||||
|
||||
useDHCP = false;
|
||||
defaultGateway = {
|
||||
address = "192.168.0.1";
|
||||
interface = "ens18";
|
||||
};
|
||||
interfaces.ens18 = {
|
||||
ipv4.addresses = [
|
||||
{
|
||||
address = "192.168.0.90";
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
nameservers = [
|
||||
"1.1.1.1" # Cloudflare
|
||||
"1.0.0.1" # Cloudflare
|
||||
];
|
||||
};
|
||||
|
||||
system.stateVersion = "24.05";
|
||||
};
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
{ config, pkgs, lib, system, ... }:
|
||||
|
||||
{
|
||||
config = {
|
||||
homelab = {
|
||||
apps = {
|
||||
freshrss.enable = true;
|
||||
traefik.enable = true;
|
||||
};
|
||||
virtualisation.guest.enable = true;
|
||||
};
|
||||
|
||||
networking = {
|
||||
hostName = "Testing";
|
||||
hostId = "aaaa9200";
|
||||
domain = "roxanne.depeuter.dev";
|
||||
|
||||
useDHCP = false;
|
||||
|
||||
enableIPv6 = true;
|
||||
|
||||
defaultGateway = {
|
||||
address = "192.168.0.1";
|
||||
interface = "ens18";
|
||||
};
|
||||
|
||||
# Open ports in the firewall.
|
||||
firewall = {
|
||||
enable = true;
|
||||
};
|
||||
|
||||
interfaces.ens18 = {
|
||||
ipv4.addresses = [
|
||||
{
|
||||
address = "192.168.0.92";
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
nameservers = [
|
||||
"1.1.1.1" # Cloudflare
|
||||
"1.0.0.1" # Cloudflare
|
||||
];
|
||||
};
|
||||
|
||||
system.stateVersion = "24.05";
|
||||
};
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
{ config, pkgs, lib, system, ... }:
|
||||
|
||||
{
|
||||
config = {
|
||||
homelab = {
|
||||
apps.vaultwarden = {
|
||||
enable = true;
|
||||
domain = "https://vault.depeuter.dev";
|
||||
name = "Hugo's Vault";
|
||||
};
|
||||
virtualisation.guest.enable = true;
|
||||
|
||||
users.admin = {
|
||||
enable = true;
|
||||
authorizedKeys = [
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJnihoyozOCnm6T9OzL2xoMeMZckBYR2w43us68ABA93"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
networking = {
|
||||
hostId = "aaaa1300";
|
||||
domain = "depeuter.dev";
|
||||
|
||||
enableIPv6 = true;
|
||||
|
||||
useDHCP = false;
|
||||
defaultGateway = {
|
||||
address = "192.168.0.1";
|
||||
interface = "ens18";
|
||||
};
|
||||
interfaces.ens18 = {
|
||||
ipv4.addresses = [
|
||||
{
|
||||
address = "192.168.0.22";
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
nameservers = [
|
||||
"1.1.1.1" # Cloudflare
|
||||
"1.0.0.1" # Cloudflare
|
||||
];
|
||||
};
|
||||
|
||||
system.stateVersion = "24.05";
|
||||
};
|
||||
}
|
||||
|
|
@ -1,303 +0,0 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.homelab.apps.arr;
|
||||
|
||||
networkName = "arrStack";
|
||||
proxyNet = config.homelab.apps.traefik.sharedNetworkName;
|
||||
|
||||
appNames = [ "bazarr" "prowlarr" "qbittorrent" "radarr" "sonarr" ];
|
||||
inUse = builtins.any (app: cfg.${app}.enable) appNames;
|
||||
|
||||
PGID = toString config.users.groups.media.gid;
|
||||
UMASK = "002";
|
||||
in {
|
||||
options.homelab.apps.arr = let
|
||||
mkAppOption = appName: {
|
||||
enable = lib.mkEnableOption "${appName} using Docker";
|
||||
exposePorts = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
description = "Expose ${appName} port";
|
||||
default = cfg.exposePorts;
|
||||
};
|
||||
};
|
||||
in {
|
||||
enable = lib.mkEnableOption "Arr Stack using Docker";
|
||||
exposePorts = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
description = "Expose all app ports";
|
||||
# Only expose ports by default if Traefik is not in use.
|
||||
default = ! config.homelab.apps.traefik.enable;
|
||||
};
|
||||
|
||||
bazarr = mkAppOption "Bazarr";
|
||||
prowlarr = mkAppOption "Prowlarr";
|
||||
qbittorrent = mkAppOption "qBittorrent";
|
||||
radarr = mkAppOption "Radarr";
|
||||
sonarr = mkAppOption "Sonarr";
|
||||
};
|
||||
|
||||
config = {
|
||||
homelab = {
|
||||
users = lib.mkIf inUse {
|
||||
apps.enable = true;
|
||||
media.enable = true;
|
||||
};
|
||||
|
||||
# "Master switch": Enable all apps.
|
||||
apps.arr = lib.mkIf cfg.enable {
|
||||
bazarr.enable = true;
|
||||
prowlarr.enable = true;
|
||||
qbittorrent.enable = true;
|
||||
radarr.enable = true;
|
||||
sonarr.enable = true;
|
||||
};
|
||||
|
||||
fileSystems.media.video = {
|
||||
enable = true;
|
||||
permissions = [ "read" "write" ];
|
||||
};
|
||||
|
||||
virtualisation.containers.enable = lib.mkIf inUse true;
|
||||
};
|
||||
|
||||
fileSystems = let
|
||||
mkFileSystem = device: {
|
||||
inherit device;
|
||||
fsType = "nfs";
|
||||
options = [
|
||||
"rw"
|
||||
"auto"
|
||||
"nfsvers=4.2"
|
||||
"rsize=1048576" "wsize=1048576"
|
||||
"hard"
|
||||
"timeo=600" "retrans=2"
|
||||
"_netdev" "nosuid" "tcp"
|
||||
];
|
||||
};
|
||||
|
||||
hugoBackup = "192.168.0.11:/mnt/BIG/BACKUP";
|
||||
in lib.mkIf inUse {
|
||||
"/srv/bazarr-backup" = lib.mkIf cfg.bazarr.enable (mkFileSystem "${hugoBackup}/BAZARR");
|
||||
"/srv/prowlarr-backup" = lib.mkIf cfg.bazarr.enable (mkFileSystem "${hugoBackup}/PROWLARR");
|
||||
"/srv/qbittorrent" = lib.mkIf cfg.qbittorrent.enable (mkFileSystem "192.168.0.11:/mnt/SMALL/CONFIG/QBITTORRENT");
|
||||
"/srv/radarr-backup" = lib.mkIf cfg.radarr.enable (mkFileSystem "${hugoBackup}/RADARR");
|
||||
"/srv/sonarr-backup" = lib.mkIf cfg.sonarr.enable (mkFileSystem "${hugoBackup}/SONARR");
|
||||
"/srv/torrent" = mkFileSystem "192.168.0.11:/mnt/SMALL/MEDIA/TORRENT";
|
||||
};
|
||||
|
||||
# Make sure the Docker network exists.
|
||||
systemd.services."docker-${networkName}-create-network" = lib.mkIf inUse {
|
||||
description = "Create Docker network for ${networkName}";
|
||||
requiredBy = [
|
||||
"docker-bazarr.service"
|
||||
"docker-prowlarr.service"
|
||||
"docker-qbittorrent.service"
|
||||
"docker-radarr.service"
|
||||
"docker-sonarr.service"
|
||||
];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
script = ''
|
||||
if ! ${pkgs.docker}/bin/docker network ls | grep -q ${networkName}; then
|
||||
${pkgs.docker}/bin/docker network create ${networkName}
|
||||
fi
|
||||
'';
|
||||
};
|
||||
|
||||
# Create a user for each app.
|
||||
users.users = let
|
||||
mkUser = uid: {
|
||||
uid = lib.mkForce uid;
|
||||
isSystemUser = true;
|
||||
group = config.users.groups.media.name;
|
||||
home = "/var/empty";
|
||||
shell = null;
|
||||
};
|
||||
in {
|
||||
bazarr = lib.mkIf cfg.bazarr.enable (mkUser 3003);
|
||||
prowlarr = lib.mkIf cfg.prowlarr.enable (mkUser 3004);
|
||||
qbittorrent = lib.mkIf cfg.qbittorrent.enable (mkUser 3005) // {
|
||||
extraGroups = [
|
||||
config.users.groups.apps.name
|
||||
];
|
||||
};
|
||||
radarr = lib.mkIf cfg.radarr.enable (mkUser 3006);
|
||||
sonarr = lib.mkIf cfg.sonarr.enable (mkUser 3007);
|
||||
};
|
||||
|
||||
virtualisation.oci-containers.containers = let
|
||||
videoHostPath = config.homelab.fileSystems.media.video.hostPath;
|
||||
in {
|
||||
bazarr = let
|
||||
port = 6767;
|
||||
in lib.mkIf cfg.bazarr.enable {
|
||||
hostname = "bazarr";
|
||||
image = "ghcr.io/hotio/bazarr:release-1.5.2";
|
||||
autoStart = true;
|
||||
ports = lib.mkIf cfg.bazarr.exposePorts [
|
||||
"${toString port}:${toString port}/tcp"
|
||||
"${toString port}:${toString port}/udp"
|
||||
];
|
||||
extraOptions = [
|
||||
"--network=${networkName}"
|
||||
"--network=${proxyNet}"
|
||||
];
|
||||
environment = {
|
||||
PUID = toString config.users.users.bazarr.uid;
|
||||
inherit PGID UMASK;
|
||||
TZ = config.time.timeZone;
|
||||
WEBUI_PORTS = "${toString port}/tcp,${toString port}/udp";
|
||||
};
|
||||
volumes = [
|
||||
"bazarr-config:/config"
|
||||
|
||||
"/srv/bazarr-backup:/config/backup"
|
||||
|
||||
"${videoHostPath}/Films:/media/movies"
|
||||
"${videoHostPath}/Series:/media/series"
|
||||
];
|
||||
labels = {
|
||||
"traefik.enable" = "true";
|
||||
"traefik.docker.network" = proxyNet;
|
||||
"traefik.http.routers.bazarr.rule" = "Host(`bazarr.depeuter.dev`)";
|
||||
"traefik.http.services.bazarr.loadbalancer.server.port" = toString port;
|
||||
};
|
||||
};
|
||||
|
||||
prowlarr = let
|
||||
port = 9696;
|
||||
in lib.mkIf cfg.prowlarr.enable {
|
||||
hostname = "prowlarr";
|
||||
image = "ghcr.io/hotio/prowlarr:release-2.0.5.5160";
|
||||
autoStart = true;
|
||||
ports = lib.mkIf cfg.prowlarr.exposePorts [
|
||||
"${toString port}:${toString port}/tcp"
|
||||
];
|
||||
extraOptions = [
|
||||
"--network=${networkName}"
|
||||
"--network=${proxyNet}"
|
||||
];
|
||||
environment = {
|
||||
PUID = toString config.users.users.prowlarr.uid;
|
||||
inherit PGID UMASK;
|
||||
TZ = config.time.timeZone;
|
||||
};
|
||||
volumes = [
|
||||
"prowlarr-config:/config"
|
||||
|
||||
"/srv/prowlarr-backup:/config/Backups"
|
||||
];
|
||||
labels = {
|
||||
"traefik.enable" = "true";
|
||||
"traefik.docker.network" = proxyNet;
|
||||
"traefik.http.routers.prowlarr.rule" = "Host(`prowlarr.depeuter.dev`)";
|
||||
"traefik.http.services.prowlarr.loadbalancer.server.port" = toString port;
|
||||
};
|
||||
};
|
||||
|
||||
qbittorrent = let
|
||||
port = 10095;
|
||||
in lib.mkIf cfg.qbittorrent.enable {
|
||||
hostname = "qbittorrent";
|
||||
image = "ghcr.io/hotio/qbittorrent:release-5.1.2";
|
||||
autoStart = true;
|
||||
ports = lib.mkIf cfg.qbittorrent.exposePorts [
|
||||
"${toString port}:${toString port}/tcp"
|
||||
"${toString port}:${toString port}/udp"
|
||||
];
|
||||
extraOptions = [
|
||||
"--network=${networkName}"
|
||||
"--network=${proxyNet}"
|
||||
];
|
||||
environment = {
|
||||
PUID = toString config.users.users.qbittorrent.uid;
|
||||
inherit PGID UMASK;
|
||||
TZ = config.time.timeZone;
|
||||
WEBUI_PORTS = "${toString port}/tcp,${toString port}/udp";
|
||||
};
|
||||
volumes = [
|
||||
"/srv/qbittorrent:/config"
|
||||
|
||||
"/srv/torrent:/media/cache"
|
||||
];
|
||||
labels = {
|
||||
"traefik.enable" = "true";
|
||||
"traefik.docker.network" = proxyNet;
|
||||
"traefik.http.routers.qbittorrent.rule" = "Host(`qb.depeuter.dev`)";
|
||||
"traefik.http.services.qbittorrent.loadbalancer.server.port" = toString port;
|
||||
};
|
||||
};
|
||||
|
||||
radarr = let
|
||||
port = 7878;
|
||||
in lib.mkIf cfg.radarr.enable {
|
||||
hostname = "radarr";
|
||||
image = "ghcr.io/hotio/radarr:testing-5.28.0.10205";
|
||||
autoStart = true;
|
||||
ports = lib.mkIf cfg.radarr.exposePorts [
|
||||
"${toString port}:${toString port}/tcp"
|
||||
];
|
||||
extraOptions = [
|
||||
"--network=${networkName}"
|
||||
"--network=${proxyNet}"
|
||||
];
|
||||
environment = {
|
||||
PUID = toString config.users.users.radarr.uid;
|
||||
inherit PGID UMASK;
|
||||
TZ = config.time.timeZone;
|
||||
};
|
||||
volumes = [
|
||||
"radarr-config:/config"
|
||||
|
||||
"/srv/radarr-backup:/config/Backups"
|
||||
|
||||
"/srv/torrent:/media/cache"
|
||||
"${videoHostPath}/Films:/media/movies"
|
||||
];
|
||||
labels = {
|
||||
"traefik.enable" = "true";
|
||||
"traefik.docker.network" = proxyNet;
|
||||
"traefik.http.routers.radarr.rule" = "Host(`radarr.depeuter.dev`)";
|
||||
"traefik.http.services.radarr.loadbalancer.server.port" = toString port;
|
||||
};
|
||||
};
|
||||
|
||||
sonarr = let
|
||||
port = 8989;
|
||||
in lib.mkIf cfg.sonarr.enable {
|
||||
hostname = "sonarr";
|
||||
image = "ghcr.io/hotio/sonarr:release-4.0.15.2941";
|
||||
autoStart = true;
|
||||
ports = lib.mkIf cfg.sonarr.exposePorts [
|
||||
"${toString port}:${toString port}/tcp"
|
||||
];
|
||||
extraOptions = [
|
||||
"--network=${networkName}"
|
||||
"--network=${proxyNet}"
|
||||
];
|
||||
environment = {
|
||||
PUID = toString config.users.users.sonarr.uid;
|
||||
inherit PGID UMASK;
|
||||
TZ = config.time.timeZone;
|
||||
};
|
||||
volumes = [
|
||||
"sonarr-config:/config"
|
||||
|
||||
"/srv/sonarr-backup:/config/Backups"
|
||||
|
||||
"/srv/torrent:/media/cache"
|
||||
"${videoHostPath}/Series:/media/series"
|
||||
];
|
||||
labels = {
|
||||
"traefik.enable" = "true";
|
||||
"traefik.docker.network" = proxyNet;
|
||||
"traefik.http.routers.sonarr.rule" = "Host(`sonarr.depeuter.dev`)";
|
||||
"traefik.http.services.sonarr.loadbalancer.server.port" = toString port;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
$TTL 604800
|
||||
@ IN SOA ns1 admin (
|
||||
15 ; Serial
|
||||
604800 ; Refresh
|
||||
86400 ; Retry
|
||||
2419200 ; Expire
|
||||
604800 ) ; Negative Cache TTL
|
||||
|
||||
; Name servers - NS records
|
||||
IN NS ns1
|
||||
; IN NS ns2
|
||||
|
||||
ns1 IN A 192.168.0.91
|
||||
;ns2 IN A 192.158.0.X
|
||||
|
||||
; Hostnames
|
||||
hugo.kmtl IN A 192.168.0.11
|
||||
|
||||
ingress.kmtl IN A 192.168.0.10
|
||||
ingress.kmtl IN AAAA fe80::be24:11ff:fed6:842a
|
||||
|
||||
; Core services
|
||||
cloud IN A 192.168.0.10
|
||||
git IN A 78.23.37.117
|
||||
home IN A 192.168.0.10
|
||||
jelly IN CNAME ingress.kmtl
|
||||
vault IN A 192.168.0.10
|
||||
|
||||
; Production VM
|
||||
books IN A 192.168.0.31
|
||||
calibre IN A 192.168.0.31
|
||||
|
||||
; Production VM - Arr
|
||||
bazarr IN A 192.168.0.33
|
||||
prowlarr IN A 192.168.0.33
|
||||
qb IN A 192.168.0.33
|
||||
radarr IN A 192.168.0.33
|
||||
sonarr IN A 192.168.0.33
|
||||
|
||||
; Development VM
|
||||
plex IN A 192.168.0.91
|
||||
|
||||
; Catchalls
|
||||
*.production IN A 192.168.0.31
|
||||
*.development IN A 192.168.0.91
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
cfg = config.homelab.apps.bind9;
|
||||
in {
|
||||
options.homelab.apps.bind9.enable = lib.mkEnableOption "ISC BIND 9 (Docker)";
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
homelab.virtualisation.containers.enable = true;
|
||||
|
||||
environment.etc = {
|
||||
"bind/named.conf" = {
|
||||
source = ./named.conf;
|
||||
mode = "0555";
|
||||
};
|
||||
"bind/named.conf.options" = {
|
||||
source = ./named.conf.options;
|
||||
mode = "0555";
|
||||
};
|
||||
"bind/named.conf.local" = {
|
||||
source = ./named.conf.local;
|
||||
mode = "0555";
|
||||
};
|
||||
"bind/zones/db.depeuter.dev" = {
|
||||
source = ./db.depeuter.dev;
|
||||
mode = "0555";
|
||||
};
|
||||
};
|
||||
|
||||
virtualisation.oci-containers.containers.bind9 = {
|
||||
hostname = "bind9";
|
||||
#image = "internetsystemsconsortium/bind9:9.20"; # Current stable
|
||||
image = "ubuntu/bind9"; # Current stable
|
||||
autoStart = true;
|
||||
ports = [
|
||||
"53:53/udp"
|
||||
"53:53/tcp"
|
||||
"953:953/tcp"
|
||||
];
|
||||
extraOptions = [
|
||||
];
|
||||
environment = {
|
||||
};
|
||||
volumes = [
|
||||
"/etc/bind:/etc/bind" # For configuration, your `named.conf` lives here
|
||||
"bind9-cache:/var/cache/bind"
|
||||
#"...:/var/lib/bind" # Secondary zones
|
||||
"bind9-logs:/var/log" # Logfiles
|
||||
];
|
||||
labels = {
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
include "/etc/bind/named.conf.options";
|
||||
include "/etc/bind/named.conf.local";
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
zone "depeuter.dev" {
|
||||
type primary;
|
||||
file "/etc/bind/zones/db.depeuter.dev";
|
||||
};
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
http local {
|
||||
endpoints { "/dns-query"; };
|
||||
};
|
||||
|
||||
acl bogusnets {
|
||||
};
|
||||
|
||||
acl trusted {
|
||||
192.168.0.0/16;
|
||||
};
|
||||
|
||||
options {
|
||||
directory "/var/cache/bind";
|
||||
|
||||
version "not currently available";
|
||||
|
||||
listen-on { any; };
|
||||
listen-on-v6 { any; };
|
||||
listen-on tls ephemeral { any; };
|
||||
listen-on-v6 tls ephemeral { any; };
|
||||
listen-on tls ephemeral http local { any; };
|
||||
listen-on-v6 tls ephemeral http local { any; };
|
||||
|
||||
recursion yes;
|
||||
forwarders {
|
||||
9.9.9.9;
|
||||
149.112.112.112;
|
||||
};
|
||||
forward only;
|
||||
|
||||
allow-query { any; };
|
||||
allow-recursion { any; };
|
||||
allow-transfer { none; };
|
||||
blackhole { bogusnets; };
|
||||
};
|
||||
|
|
@ -1,189 +0,0 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.homelab.apps.calibre;
|
||||
|
||||
PUID = toString config.users.users.calibre.uid;
|
||||
PGID = toString config.users.groups.media.gid;
|
||||
|
||||
books = "/srv/books";
|
||||
calibre-config = "/srv/calibre-config";
|
||||
calibre-web-config = "/srv/calibre-web-config";
|
||||
|
||||
networkName = "calibre";
|
||||
proxyNet = config.homelab.apps.traefik.sharedNetworkName;
|
||||
in {
|
||||
options.homelab.apps.calibre = {
|
||||
enable = lib.mkEnableOption "Calibre (Desktop + Web)";
|
||||
desktop.enable = lib.mkEnableOption "Calibre Desktop (KasmVNC)";
|
||||
web.enable = lib.mkEnableOption "Calibre Web";
|
||||
};
|
||||
|
||||
config = lib.mkMerge [
|
||||
{
|
||||
homelab.apps.calibre = lib.mkIf cfg.enable {
|
||||
desktop.enable = true;
|
||||
web.enable = true;
|
||||
};
|
||||
}
|
||||
|
||||
# Common
|
||||
(lib.mkIf (cfg.desktop.enable || cfg.web.enable) {
|
||||
homelab = {
|
||||
users.media.enable = true;
|
||||
virtualisation.containers.enable = true;
|
||||
};
|
||||
|
||||
users.users.calibre = {
|
||||
uid = lib.mkForce 3010;
|
||||
isSystemUser = true;
|
||||
group = config.users.groups.media.name;
|
||||
home = "/var/empty";
|
||||
shell = null;
|
||||
};
|
||||
|
||||
fileSystems."${books}" = {
|
||||
device = "192.168.0.11:/mnt/SMALL/MEDIA/BOOKS";
|
||||
fsType = "nfs";
|
||||
options = [
|
||||
"rw"
|
||||
"auto"
|
||||
"nfsvers=4.2"
|
||||
"rsize=1048576" "wsize=1048576"
|
||||
"soft"
|
||||
"timeo=600" "retrans=2"
|
||||
"_netdev" "nosuid" "tcp"
|
||||
];
|
||||
};
|
||||
|
||||
# Make sure the Docker network exists.
|
||||
systemd.services."docker-${networkName}-create-network" = {
|
||||
requiredBy = [
|
||||
"docker-calibre.service"
|
||||
];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
script = ''
|
||||
if ! ${pkgs.docker}/bin/docker network ls | grep -q ${networkName}; then
|
||||
${pkgs.docker}/bin/docker network create ${networkName}
|
||||
fi
|
||||
'';
|
||||
};
|
||||
})
|
||||
|
||||
# Calibre desktop
|
||||
(lib.mkIf cfg.desktop.enable {
|
||||
fileSystems."${calibre-config}" = {
|
||||
device = "192.168.0.11:/mnt/SMALL/CONFIG/CALIBRE";
|
||||
fsType = "nfs";
|
||||
options = [
|
||||
"rw"
|
||||
"auto"
|
||||
"nfsvers=4.2"
|
||||
"rsize=1048576" "wsize=1048576"
|
||||
"soft"
|
||||
"timeo=600" "retrans=2"
|
||||
"_netdev" "nosuid" "tcp"
|
||||
];
|
||||
};
|
||||
|
||||
virtualisation.oci-containers.containers.calibre = let
|
||||
innerPort = 8080;
|
||||
in {
|
||||
hostname = "calibre";
|
||||
image = "lscr.io/linuxserver/calibre:v8.10.0-ls354";
|
||||
autoStart = true;
|
||||
ports = [
|
||||
# Open ports if you don't use Traefik
|
||||
"9480:${toString innerPort}" # Calibre desktop GUI
|
||||
#"9481:8181" # Calibre desktop GUI HTTPS
|
||||
#"9581:8081" # Calibre webserver gui
|
||||
];
|
||||
extraOptions = [
|
||||
"--network=${networkName}"
|
||||
"--network=${proxyNet}"
|
||||
|
||||
# syscalls are unkown to Docker
|
||||
#"--security-opt" "seccomp=unconfined"
|
||||
];
|
||||
environment = {
|
||||
inherit PUID PGID;
|
||||
#UMASK = "022";
|
||||
|
||||
TZ = config.time.timeZone;
|
||||
|
||||
#PASSWORD = "";
|
||||
#CLI_ARGS = "";
|
||||
};
|
||||
volumes = [
|
||||
"${calibre-config}:/config"
|
||||
|
||||
"${books}:/media/books"
|
||||
];
|
||||
labels = {
|
||||
"traefik.enable" = "true";
|
||||
"traefik.docker.network" = proxyNet;
|
||||
"traefik.http.routers.calibre.rule" = "Host(`calibre.depeuter.dev`)";
|
||||
"traefik.http.services.calibre.loadbalancer.server.port" = toString innerPort;
|
||||
};
|
||||
};
|
||||
})
|
||||
|
||||
# Calibre Web
|
||||
(lib.mkIf cfg.web.enable {
|
||||
fileSystems."${calibre-web-config}" = {
|
||||
device = "192.168.0.11:/mnt/SMALL/CONFIG/CALIBRE-WEB";
|
||||
fsType = "nfs";
|
||||
options = [
|
||||
"rw"
|
||||
"auto"
|
||||
"nfsvers=4.2"
|
||||
"rsize=1048576" "wsize=1048576"
|
||||
"soft"
|
||||
"timeo=600" "retrans=2"
|
||||
"_netdev" "nosuid" "tcp"
|
||||
];
|
||||
};
|
||||
|
||||
virtualisation.oci-containers.containers.calibre-web = let
|
||||
innerPort = 8083;
|
||||
in {
|
||||
hostname = "calibre-web";
|
||||
image = "lscr.io/linuxserver/calibre-web:0.6.25-ls346";
|
||||
autoStart = true;
|
||||
ports = [
|
||||
# Open ports if you don't use Traefik
|
||||
"8083:${toString innerPort}" # Web UI
|
||||
];
|
||||
extraOptions = [
|
||||
"--network=${networkName}"
|
||||
"--network=${proxyNet}"
|
||||
];
|
||||
environment = {
|
||||
inherit PUID PGID;
|
||||
#UMASK = "022";
|
||||
|
||||
TZ = config.time.timeZone;
|
||||
|
||||
# (x86-64 only) Adds the ability to perform ebook conversion
|
||||
DOCKER_MODS = "linuxserver/mods:universal-calibre";
|
||||
# Allow Google Oauth
|
||||
#OAUTHLIB_RELAX_TOKEN_SCOPE = "1";
|
||||
};
|
||||
volumes = [
|
||||
"${calibre-web-config}:/config"
|
||||
|
||||
"${books}:/media/books"
|
||||
];
|
||||
labels = {
|
||||
"traefik.enable" = "true";
|
||||
"traefik.docker.network" = proxyNet;
|
||||
"traefik.http.routers.calibre-web.rule" = "Host(`books.depeuter.dev`)";
|
||||
"traefik.http.services.calibre-web.loadbalancer.server.port" = toString innerPort;
|
||||
};
|
||||
};
|
||||
})
|
||||
];
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.homelab.apps.changedetection;
|
||||
in {
|
||||
options.homelab.apps.changedetection.enable = lib.mkEnableOption "Changedetection.io";
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
homelab.virtualisation.containers.enable = true;
|
||||
|
||||
virtualisation.oci-containers.containers.changedetection = {
|
||||
hostname = "changedetection";
|
||||
image = "ghcr.io/dgtlmoon/changedetection.io";
|
||||
autoStart = true;
|
||||
ports = [
|
||||
"5000:5000/tcp"
|
||||
];
|
||||
extraOptions = [
|
||||
];
|
||||
volumes = [
|
||||
"changedetection:/datastore"
|
||||
];
|
||||
environment = {
|
||||
LOGGER_LEVEL = "WARNING";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
imports = [
|
||||
./arr
|
||||
./bind9
|
||||
./calibre
|
||||
./changedetection
|
||||
./freshrss
|
||||
./gitea
|
||||
./homepage
|
||||
./jellyfin
|
||||
./plex
|
||||
./solidtime
|
||||
./speedtest
|
||||
./technitium-dns
|
||||
./traefik
|
||||
./vaultwarden
|
||||
];
|
||||
}
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.homelab.apps.freshrss;
|
||||
|
||||
networkName = "freshrss";
|
||||
in {
|
||||
options.homelab.apps.freshrss = {
|
||||
enable = lib.mkEnableOption "FreshRSS";
|
||||
port = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
default = 9080;
|
||||
description = "FreshRSS WebUI port";
|
||||
};
|
||||
};
|
||||
|
||||
config = let
|
||||
inherit (config.homelab.apps.freshrss) port;
|
||||
in
|
||||
lib.mkIf cfg.enable {
|
||||
homelab.virtualisation.containers.enable = true;
|
||||
|
||||
fileSystems."/srv/freshrss" = {
|
||||
device = "192.168.0.11:/mnt/SMALL/CONFIG/FRESHRSS";
|
||||
fsType = "nfs";
|
||||
options = [
|
||||
"rw"
|
||||
"auto"
|
||||
"nfsvers=4.2"
|
||||
"async" "soft" "timeo=600"
|
||||
"retrans=2"
|
||||
"_netdev"
|
||||
"nosuid"
|
||||
"tcp"
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services."docker-${networkName}-create-network" = {
|
||||
description = "Create Docker network for ${networkName}";
|
||||
requiredBy = [
|
||||
"docker-freshrss.service"
|
||||
];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
script = ''
|
||||
if ! ${pkgs.docker}/bin/docker network ls | grep -q ${networkName}; then
|
||||
${pkgs.docker}/bin/docker network create ${networkName}
|
||||
fi
|
||||
'';
|
||||
};
|
||||
|
||||
virtualisation.oci-containers.containers.freshrss = {
|
||||
hostname = "freshrss";
|
||||
image = "freshrss/freshrss:1.25.0";
|
||||
autoStart = true;
|
||||
user = "0:33";
|
||||
ports = [
|
||||
"${toString port}:80/tcp"
|
||||
];
|
||||
extraOptions = [
|
||||
"--network=${networkName}"
|
||||
];
|
||||
environment = {
|
||||
TZ = config.time.timeZone;
|
||||
CRON_MIN = "3,18,33,48"; # Alternatively, configure cron inside container.
|
||||
SERVER_DNS = "rss.depeuter.dev";
|
||||
TRUSTED_PROXY = "172.16.0.1/12 192.168.0.1/16";
|
||||
};
|
||||
volumes = [
|
||||
"/srv/freshrss/www/freshrss/data:/var/www/FreshRSS/data"
|
||||
"/srv/freshrss/www/freshrss/extensions:/var/www/FreshRSS/extensions"
|
||||
];
|
||||
labels = {
|
||||
"traefik.enable" = "true";
|
||||
|
||||
"traefik.http.middlewares.freshrssM1.compress" = "true";
|
||||
"traefik.http.middlewares.freshrssM2.headers.browserXssFilter" = "true";
|
||||
"traefik.http.middlewares.freshrssM2.headers.forceSTSHeader" = "true";
|
||||
"traefik.http.middlewares.freshrssM2.headers.frameDeny" = "true";
|
||||
"traefik.http.middlewares.freshrssM2.headers.referrerPolicy" = "no-referrer-when-downgrade";
|
||||
"traefik.http.middlewares.freshrssM2.headers.stsSeconds" = "31536000";
|
||||
"traefik.http.routers.freshrss.entryPoints" = "websecure";
|
||||
"traefik.http.routers.freshrss.tls" = "true";
|
||||
|
||||
"traefik.http.services.freshrss.loadbalancer.server.port" = "80";
|
||||
"traefik.http.routers.freshrss.middlewares" = "freshrssM1,freshrssM2";
|
||||
"traefik.http.routers.freshrss.rule" = "Host(`rss.depeuter.dev`)";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,673 +0,0 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.homelab.apps.gitea;
|
||||
|
||||
networkName = "gitea";
|
||||
|
||||
UID = 3015;
|
||||
GID = config.users.groups.apps.gid;
|
||||
postgresPassword = "ChangeMe";
|
||||
repoDir = "/srv/git";
|
||||
webPort = 3000;
|
||||
sshPort = 2222;
|
||||
dbPort = 5432;
|
||||
redisPort = 6379;
|
||||
|
||||
title = "Hugo's Forge";
|
||||
slogan = "Forging ideas into reality.";
|
||||
description = "Personal git server for projects that don't need collaboration.";
|
||||
in {
|
||||
options.homelab.apps.gitea.enable = lib.mkEnableOption "Gitea";
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
homelab = {
|
||||
users = {
|
||||
apps.enable = true;
|
||||
backup.enable = true;
|
||||
};
|
||||
|
||||
virtualisation.containers.enable = true;
|
||||
};
|
||||
|
||||
users.users.gitea = {
|
||||
uid = lib.mkForce UID;
|
||||
isSystemUser = true;
|
||||
group = config.users.groups.apps.name;
|
||||
home = "/var/empty";
|
||||
shell = null;
|
||||
};
|
||||
|
||||
# Use filesystem mounts because rootless containers otherwise don't have access to the mount path (nested in docker directories).
|
||||
# You could probably fix this by modifying the access rights on the path, but what would the point of that be?
|
||||
fileSystems = {
|
||||
# Mount options:
|
||||
# - hard: retry requests indefinitely if the server becomes unresponsive.
|
||||
# - nosuid: prevent set-user-id and set-group-id bits
|
||||
"/srv/gitea-config" = {
|
||||
device = "192.168.0.11:/mnt/SMALL/CONFIG/GITEA";
|
||||
fsType = "nfs";
|
||||
options = [
|
||||
"rw"
|
||||
"nfsvers=4.2"
|
||||
"async" "soft" "timeo=100" "retry=50" "actimeo=1800" "lookupcache=all"
|
||||
"nosuid"
|
||||
"tcp"
|
||||
];
|
||||
};
|
||||
|
||||
"/srv/gitea-git" = {
|
||||
device = "192.168.0.11:/mnt/SMALL/DATA/GIT";
|
||||
fsType = "nfs";
|
||||
options = [
|
||||
"rw"
|
||||
"nfsvers=4.2"
|
||||
"async" "soft" "timeo=100" "retry=50" "actimeo=1800" "lookupcache=all"
|
||||
"nosuid"
|
||||
"tcp"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
# Make sure the Docker network exists.
|
||||
systemd.services."docker-${networkName}-create-network" = {
|
||||
description = "Create Docker network for ${networkName}";
|
||||
requiredBy = [
|
||||
"docker-gitea-db.service"
|
||||
"docker-gitea.service"
|
||||
];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
script = ''
|
||||
if ! ${pkgs.docker}/bin/docker network ls | grep -q ${networkName}; then
|
||||
${pkgs.docker}/bin/docker network create ${networkName}
|
||||
fi
|
||||
'';
|
||||
};
|
||||
|
||||
virtualisation.oci-containers.containers = {
|
||||
gitea-db = {
|
||||
hostname = "gitea-db";
|
||||
image = "postgres:15.8-alpine";
|
||||
autoStart = true;
|
||||
ports = [
|
||||
"5432:${toString dbPort}/tcp"
|
||||
];
|
||||
extraOptions = [
|
||||
"--network=${networkName}"
|
||||
];
|
||||
environment = {
|
||||
POSTGRES_PASSWORD = "ChangeMe";
|
||||
PGDATA = "/var/lib/postgresql/data/pgdata";
|
||||
};
|
||||
volumes = [
|
||||
"gitea-db:/var/lib/postgresql/data/pgdata"
|
||||
];
|
||||
};
|
||||
|
||||
gitea-redis = {
|
||||
hostname = "gitea-redis";
|
||||
image = "redis:7.4.0-alpine3.20";
|
||||
autoStart = true;
|
||||
ports = [
|
||||
"6379:${toString redisPort}/tcp"
|
||||
];
|
||||
extraOptions = [
|
||||
"--network=${networkName}"
|
||||
];
|
||||
volumes = [
|
||||
"gitea-redis:/data"
|
||||
];
|
||||
};
|
||||
|
||||
gitea = {
|
||||
hostname = "gitea";
|
||||
image = "codeberg.org/forgejo/forgejo:11.0.1-rootless";
|
||||
autoStart = true;
|
||||
user = "${toString UID}:${toString GID}";
|
||||
ports = [
|
||||
"3000:${toString webPort}/tcp"
|
||||
"2222:${toString sshPort}/tcp"
|
||||
];
|
||||
extraOptions = [
|
||||
"--network=${networkName}"
|
||||
];
|
||||
dependsOn = [
|
||||
"gitea-db"
|
||||
"gitea-redis"
|
||||
];
|
||||
volumes = [
|
||||
"/srv/gitea-config:/var/lib/gitea"
|
||||
"/srv/gitea-git:/srv/git"
|
||||
"/etc/timezone:/etc/timezone:ro"
|
||||
"/etc/localtime:/etc/localtime:ro"
|
||||
];
|
||||
environmentFiles = [
|
||||
# NOTE Don't forget to create this file.
|
||||
# TODO Put in place using age(nix)?
|
||||
"/var/lib/gitea.env"
|
||||
];
|
||||
environment = {
|
||||
# App name that shows in every page title.
|
||||
FORGEJO__APP_NAME = title;
|
||||
# Shows a slogan near the App name in every page title.
|
||||
FORGEJO__APP_SLOGAN = slogan;
|
||||
# Defines how the AppDisplayName should be presented.
|
||||
#FORGEJO__APP_DISPLAY_NAME_FORMAT = "";
|
||||
# Will automaticaly detect the current user - but you can set it here.
|
||||
FORGEJO__RUN_USER = "gitea";
|
||||
# Application run mode, affects performance and debugging: "dev" or "prod", default is
|
||||
# "prod". Mode "dev" makes Gitea easier to develop and debug, values other than "dev" are
|
||||
# treated as "prod" which is for production use.
|
||||
FORGEJO__RUN_MODE = "prod";
|
||||
# The working directory.
|
||||
#WORK_PATH = "";
|
||||
|
||||
# Disable SSH feature when not available.
|
||||
FORGEJO__server__DISABLE_SSH = "false";
|
||||
# Whether to use the builltin SSH server or not.
|
||||
FORGEJO__server__START_SSH_SERVER = "true";
|
||||
# Username to use for the builtin SSH server. If blank, then it is the value of RUN_USER.
|
||||
#FORGEJO__server__BUILTIN_SSH_SERVER_USER = "git";
|
||||
# Domain to be exposed in clone URL.
|
||||
#FORGEJO__server__SSH_DOMAIN = "";
|
||||
# SSH username displayed in clone URLs.
|
||||
#FORGEJO__server__SSH_USER = "git";
|
||||
# The network interface the builtin SSH server should listen on.
|
||||
#FORGEJO__server__SSH_LISTEN_HOST = "ens18";
|
||||
# Port number to be exposed in clone URL.
|
||||
FORGEJO__server__SSH_PORT = "22";
|
||||
# Port number the builtin SSH server should listen on.
|
||||
FORGEJO__server__SSH_LISTEN_PORT = toString sshPort;
|
||||
# Root path of SSH directory, default is '~/.ssh', but you have to use '/home/git/.ssh'.
|
||||
FORGEJO__server__SSH_ROOT_PATH = "/var/lib/gitea/ssh";
|
||||
# Gitea will create a authorized_keys file by default when it is not using the internal ssh server
|
||||
# If you intend to use the AuthorizedKeysCommand functionality then you should turn this off.
|
||||
#FORGEJO__server__SSH_CREATE_AUTHORIZED_KEYS_FILE = "true";
|
||||
# Gitea will create a authorized_principals file by default when it is not using the internal ssh server
|
||||
# If you intend to use the AuthorizedPrincipalsCommand functionality then you should turn this off.
|
||||
#FORGEJO__server__SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE = "true";
|
||||
# For the built-in SSH server, choose the ciphers to support for SSH connections,
|
||||
# for system SSH this setting has no effect
|
||||
#FORGEJO__server__SSH_SERVER_CIPHERS = "chacha20-poly1305@openssh.com, aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, aes256-gcm@openssh.com";
|
||||
# For the built-in SSH server, choose the key exchange algorithms to support for SSH connections,
|
||||
# for system SSH this setting has no effect
|
||||
#FORGEJO__server__SSH_SERVER_KEY_EXCHANGES = "curve25519-sha256, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1";
|
||||
# For the built-in SSH server, choose the MACs to support for SSH connections,
|
||||
# for system SSH this setting has no effect
|
||||
#FORGEJO__server__SSH_SERVER_MACS = "hmac-sha2-256-etm@openssh.com, hmac-sha2-256, hmac-sha1";
|
||||
# For the built-in SSH server, choose the keypair to offer as the host key
|
||||
# The private key should be at SSH_SERVER_HOST_KEY and the public SSH_SERVER_HOST_KEY.pub
|
||||
# relative paths are made absolute relative to the %(APP_DATA_PATH)s
|
||||
FORGEJO__server__SSH_SERVER_HOST_KEYS = "/var/lib/gitea/ssh/forgejo.ed25519";
|
||||
# Directory to create temporary files in when testing public keys using ssh-keygen,
|
||||
# default is the system temporary directory.
|
||||
#FORGEJO__server__SSH_KEY_TEST_PATH = "";
|
||||
# Use `ssh-keygen` to parse public SSH keys. The value is passed to the shell. By default, Gitea does the parsing itself.
|
||||
#FORGEJO__server__SSH_KEYGEN_PATH = "";
|
||||
# Enable SSH Authorized Key Backup when rewriting all keys, default is false
|
||||
FORGEJO__server__SSH_AUTHORIZED_KEYS_BACKUP = "false";
|
||||
# ...
|
||||
# Enable exposure of SSH clone URL to anonymous visitors, default is false.
|
||||
FORGEJO__server__EXPOSE_ANONYMOUS = "false";
|
||||
# ...
|
||||
# Enables git-lfs support. true or false, default is false.
|
||||
FORGEJO__server__LFS_START_SERVER = "false";
|
||||
# ...
|
||||
|
||||
# Database to use. Either "mysql", "postgres" or "sqlite3".
|
||||
FORGEJO__database__DB_TYPE = "postgres";
|
||||
FORGEJO__database__HOST = "gitea-db:${toString dbPort}";
|
||||
FORGEJO__database__NAME = "gitea";
|
||||
FORGEJO__database__USER = "gitea";
|
||||
FORGEJO__database__PASSWD = postgresPassword;
|
||||
#FORGEJO__database__SCHEMA = "";
|
||||
#FORGEJO__database__SSL_MODE = "disable";
|
||||
|
||||
# Whether the installer is disabled (set to true to disable the installer).
|
||||
#FORGEJO__security__INSTALL_LOCK = "false";
|
||||
# Global security key that will be used.
|
||||
# This key is VERY IMPORTANT. If you lose it, the data encrypted by it can't be decrypted anymore.
|
||||
#FORGEJO__security__SECRET_KEY = "";
|
||||
# Alternatively, specify the location of the secret key.
|
||||
#FORGEJO__security__SECRET_KEY_URI = "file:/etc/gitea/secret_key";
|
||||
# ...
|
||||
|
||||
# IF the camo is enabled.
|
||||
#FORGEJO__camo__ENABLED = "false";
|
||||
# ....
|
||||
|
||||
# Enables OAuth2 provider
|
||||
FORGEJO__oauth2__ENABLED = "false";
|
||||
# ...
|
||||
|
||||
# Root path for the log files - defaults to %(GITEA_WORK_DIR)/log
|
||||
#FORGEJO__log__ROOT_PATH = "";
|
||||
# Either "console", "file" or "conn", default is "console"
|
||||
FORGEJO__log__MODE = "file";
|
||||
# Either "Trace", "Debug", "Info", "Warn", "Error" or "None", default is "Info".
|
||||
FORGEJO__log__LEVEL = "Warn";
|
||||
# ...
|
||||
# Collect SSH logs (Creates logs from ssh git requests)
|
||||
FORGEJO__log__ENABLE_SSH_LOG = "true";
|
||||
# ...
|
||||
|
||||
# The path of git executable. If empty, Gitea searches through the PATH environment.
|
||||
#FORGEJO__git__PATH = "";
|
||||
# ...
|
||||
FORGEJO__git_0x2E_timeout__MIGRATE = "600";
|
||||
FORGEJO__git_0x2E_timeout__MIRROR = "600";
|
||||
|
||||
# Time limit to confirm account/email registration.
|
||||
#FORGEJO__service__ACTIVE_CODE_LIVE_MINUTES = "180";
|
||||
# Time limit to perform the reset of a forgotten password.
|
||||
#FORGEJO__service__RESET_PASSWD_CODE_LIVE_MINUTES = "180";
|
||||
# Whether a new user needs to confirm their email when registering.
|
||||
FORGEJO__service__REGISTER_EMAIL_CONFIRM = "true";
|
||||
# Whether a new user needs to be confirmed manually after registration.
|
||||
FORGEJO__service__REGISTER_MANUAL_CONFIRM = "true";
|
||||
# List of domain names that are allowed to be used to register on a Gitea instance, wildcard is supported.
|
||||
#FORGEJO__service__EMAIL_DOMAIN_ALLOWLIST = "";
|
||||
# Comma-separated list of domain names that are not allowed to be used to register on a Gitea instance, wildcard is supported.
|
||||
#FORGEJO__service__EMAIL_DOMAIN_BLOCKLIST = "";
|
||||
# Disallow registration, only allow admins to create accounts.
|
||||
FORGEJO__service__DISABLE_REGISTRATION = "true";
|
||||
# Allow registration only using gitea itself, it works only when DISABLE_REGISTRATION is false.
|
||||
FORGEJO__service__ALLOW_ONLY_INTERNAL_REGISTRATION = "true";
|
||||
# Allow registration only using third-party services, it works only when DISABLE_REGISTRATION is false.
|
||||
FORGEJO__service__ALLOW_ONLY_EXTERNAL_REGISTRATION = "false";
|
||||
# User must sign in to view anything.
|
||||
FORGEJO__service__REQUIRE_SIGNIN_VIEW = "false";
|
||||
# Mail notification
|
||||
FORGEJO__service__ENABLE_NOTIFY_MAIL = "true";
|
||||
# This setting enables gitea to be signed in with HTTP BASIC Authentication using the user's password.
|
||||
# If you set this to false you will not be able to access the tokens endpoints on the API with your password.
|
||||
# Please note that setting this to false will not disable OAuth Basic or Basic authentication using a token.
|
||||
FORGEJO__service__ENABLE_BASIC_AUTHENTICATION = "false";
|
||||
# ...
|
||||
# Enable captcha validation for registration.
|
||||
FORGEJO__service__ENABLE_CAPTCHA = "true";
|
||||
# Enable this to require captcha validation for login.
|
||||
FORGEJO__service__REQUIRE_CAPTCHA_FOR_LOGIN = "true";
|
||||
# Requires captcha for external registrations
|
||||
#FORGEJO__service__REQUIRE_EXTERNAL_REGISTRATION_CAPTCHA = "false";
|
||||
# Requires a password for external registrations.
|
||||
#FORGEJO__service__REQUIRE_EXTERNAL_REGISTRATION_PASSWORD = "false";
|
||||
# Type of captcha you want to use. Options: image, recaptcha, hcaptcha, mcaptcha, cfturnstile.
|
||||
FORGEJO__service__CAPTCHA_TYPE = "image";
|
||||
# ...
|
||||
# Default value for KeepEmailPrivate
|
||||
# Each new user will get the value of this setting copied into their profile
|
||||
FORGEJO__service__DEFAULT_KEEP_EMAIL_PRIVATE = "true";
|
||||
# Default value for AllowCreateOrganization
|
||||
# Every new user will have rights set to create organizations depending on this setting.
|
||||
FORGEJO__service__DEFAULT_ALLOW_CREATE_ORGANIZATION = "true";
|
||||
# Default value for IsRestricted
|
||||
# Every new user will have restricted permissions depending on this setting.
|
||||
FORGEJO__service__DEFAULT_USER_IS_RESTRICTED = "false";
|
||||
# Users will be able to use dots when choosing their username. Disabling this is
|
||||
# helpful if your usersare having issues with e.g. RSS feeds or advanced third-party
|
||||
# extensions that use strange regex patterns.
|
||||
FORGEJO__service__ALLOW_DOTS_IN_USERNAMES = "false";
|
||||
# Either "public", "limited" or "private", default is "public".
|
||||
# Limited is for users visible only to signed users.
|
||||
# Private is for users visible only to members of their organizations
|
||||
# Public is for users visible for everyone
|
||||
FORGEJO__service__DEFAULT_USER_VISIBILITY = "limited";
|
||||
# Set which visibility modes a user can have
|
||||
FORGEJO__service__ALLOWED_USER_VISIBILITY_MODES = "public,limited,private";
|
||||
# Either "public", "limited" or "private", default is "public".
|
||||
# Limited is for organizations visible only to signed users
|
||||
# Private is for organizations visible only to members of the organization
|
||||
# Public is for organizations visible to everyone
|
||||
FORGEJO__service__DEFAULT_ORG_VISIBILITY = "limited";
|
||||
# Default value for DefaultOrgMemberVisible
|
||||
# True will make the membership of the users visible when added to the organisation
|
||||
FORGEJO__service__DEFAULT_ORG_MEMBER_VISIBLE = "false";
|
||||
# Default value for EnableDependencies
|
||||
# Repositories will use dependencies by default depending on this setting
|
||||
#FORGEJO__service__DEFAULT_ENABLE_DEPENDENCIES = "true";
|
||||
# Dependencies can be added from any repository where the user is granted access or only from the current repository depending on this setting.
|
||||
#FORGEJO__service__ALLOW_CROSS_REPOSITORY_DEPENDENCIES = "true";
|
||||
# Default map service. No external API support has been included. A service has to allow
|
||||
# searching using URL parameters, the location will be appended to the URL as escaped query parameter.
|
||||
# Some example values are:
|
||||
# - OpenStreetMap: https://www.openstreetmap.org/search?query=
|
||||
# - Google Maps: https://www.google.com/maps/place/
|
||||
# - MapQuest: https://www.mapquest.com/search/
|
||||
# - Bing Maps: https://www.bing.com/maps?where1=
|
||||
#FORGEJO__service__USER_LOCATION_MAP_URL = "https://www.openstreetmap.org/search?query=";
|
||||
# Enable heatmap on users profiles.
|
||||
FORGEJO__service__ENABLE_USER_HEATMAP = "true";
|
||||
# Enable Timetracking
|
||||
FORGEJO__service__ENABLE_TIMETRACKING = "true";
|
||||
# Default value for EnableTimetracking
|
||||
# Repositories will use timetracking by default depending on this setting
|
||||
FORGEJO__service__DEFAULT_ENABLE_TIMETRACKING = "false";
|
||||
# Default value for AllowOnlyContributorsToTrackTime
|
||||
# Only users with write permissions can track time if this is true
|
||||
#FORGEJO__service__DEFAULT_ALLOW_ONLY_CONTRIBUTORS_TO_TRACK_TIME = "true";
|
||||
# Value for the domain part of the user's email address in the git log if user
|
||||
# has set KeepEmailPrivate to true. The user's email will be replaced with a
|
||||
# concatenation of the user name in lower case, "@" and NO_REPLY_ADDRESS. Default
|
||||
# value is "noreply." + DOMAIN, where DOMAIN resolves to the value from server.DOMAIN
|
||||
# Note: do not use the <DOMAIN> notation below
|
||||
FORGEJO__service__NO_REPLY_ADDRESS = "noreply.depeuter.dev";
|
||||
# Show Registration button.
|
||||
FOGEJO__service__SHOW_REGISTRATION_BUTTON = "false";
|
||||
# Show milestones dashboard page - a view of all the user's milestones.
|
||||
#FORGEJO__service__SHOW_MILESTONES_DASHBOARD_PAGE = "true";
|
||||
# Default value for AutoWatchNewRepos
|
||||
# When adding a repo to a team or creating a new repo all team members will watch the
|
||||
# repo automatically if enabled
|
||||
#FORGEJO__service__AUTO_WATCH_NEW_REPOS = "true";
|
||||
# Default value for AutoWatchOnChanges
|
||||
# Make the user watch a repository When they commit for the first time
|
||||
#FORGEJO__service__AUTO_WATCH_ON_CHANGES = "false";
|
||||
# Minimum amount of time a user must exist before comments are kept when the user is deleted.
|
||||
#FORGEJO__service__USER_DELETE_WITH_COMMENTS_MAX_TIME = "0";
|
||||
# Valid site url schemes for user profiles
|
||||
#FORGEJO__service__VALID_SITE_URL_SCHEMES = "http,https";
|
||||
|
||||
# Enable repository badges (via shields.io or a similar generator)
|
||||
#FORGEJO__badges__ENABLED = "true";
|
||||
# ...
|
||||
|
||||
# Root path for storing all repository data. By default, it is set to %(APP_DATA_PATH)s/gitea-repositories.
|
||||
# A relative path is interpreted as _`AppWorkPath`_/%(ROOT)s
|
||||
FORGEJO__repository__ROOT = repoDir;
|
||||
# ...
|
||||
# Force every new repository to be private.
|
||||
FORGEJO__repository__FORCE_PRIVATE = "false";
|
||||
# Default private when creating a new repository with push-to-create.
|
||||
FORGEJO__repository__DEFAULT_PUSH_TO_CREATE = "true";
|
||||
# ...
|
||||
# Allow users to push local repositories to Forgejo and have them automatically created for a user.
|
||||
FORGEJO__repository__ENABLE_PUSH_CREATE_USER = "true";
|
||||
# Allow users to push local repositories to Forgejo and have them automatically created for an org.
|
||||
FORGEJO__repository__ENABLE_PUSH_CREATE_ORG = "false";
|
||||
# Comma separated list of globally disabled repo units.
|
||||
FORGEJO__repository__DISABLED_REPO_UNITS = "";
|
||||
# Comma separated list of default new repo units.
|
||||
FORGEJO__repository__DEFAULT_REPO_UNITS = "repo.code,repo.issues,repo.pulls,repo.releases,repo.actions";
|
||||
# Comma separated list of default forked repo units.
|
||||
FORGEJO__repository__DEFAULT_FORK_REPO_UNITS = "repo.code,repo.pulls";
|
||||
# Prefix archive files by placing them in a directory named after the repository.
|
||||
FORGEJO__repository__PREFIX_ARCHIVE_FILES = "true";
|
||||
# Disable migrating feature.
|
||||
FORGEJO__repository__DISABLE_MIGRATIONS = "false";
|
||||
# Disable stars feature.
|
||||
FORGEJO__repository__DISABLE_STARS = "true";
|
||||
# Disable repository forking.
|
||||
#FORGEJO__repository__DISABLE_FORKS = "false";
|
||||
# The default branch name of new repositories
|
||||
FORGEJO__repository__DEFAULT_BRANCH = "main";
|
||||
# ...
|
||||
|
||||
# List of prefixes used in Pull Request title to mark them as Work In Progress (matched in a case-insensitive manner)
|
||||
FORGEJO__repository_0x2E_pull_0X2D_request__WORK_IN_PROGRESS_PREFIXES = "WIP:,[WIP],WIP";
|
||||
# ...
|
||||
# In the default merge message for squash commits walk all commits to include all authors in the Co-authored-by otherwise just use those in the limited list.
|
||||
FORGEJO__repository_0x2E_pull_0X2D_request__DEFAULT_MERGE_MESSAGE_ALL_AUTHORS = "true";
|
||||
# ...
|
||||
|
||||
# Enable cors headers (disabled by default)
|
||||
FORGEJO__cors__ENABLED = "true";
|
||||
# list of requesting origins that are allowed, eg: "https://*.example.com".
|
||||
FORGEJO__cors__ALLOW_DOMAINS = "https://git.depeuter.dev,http://192.168.0.24:${toString webPort}";
|
||||
|
||||
# Set the default theme for the Gitea install.
|
||||
FORGEJO__ui__DEFAULT_THEME = "gitea-auto";
|
||||
# All available themes. Allow users to select personalized themes regardless of `DEFAULT_THEME`.
|
||||
FORGEJO__ui__THEMES = "gitea-auto,gitea-light,gitea-dark,forgejo-auto,forgejo-light,forgejo-dark,forgejo-auto-deuteranopia-protanopia,forgejo-light-deuteranopia-protanopia,forgejo-dark-deuteranopia-protanopia,forgejo-auto-tritanopia,forgejo-light-tritanopia-forgejo-dark-tritanopia,github-auto,github,github-dark,edge-auto,edge-light,edge-dark,everforest-auto,everforest-light,everforest-dark,gruvbox-auto,gruvbox-light,gruvbox-dark,gruvbox-material-auto,grubox-material-dark,gruvbox-material-light,sonokai-andromeda,sonokai-atlantis,sonokai-espresso,sonokai-maia,sonokai-shusia,sonokai,catppuccin-frappe-green,catppuccin-frappe-teal,catppuccin-frappe-sky,catppuccin-frappe-sapphire,catppuccin-frappe-blue,catppuccin-frappe-lavender,catppuccin-macchiato-green,catppuccin-macchiato-teal,catppuccin-macchiato-sky,catppuccin-macchiato-sapphire,catppuccin-macchiato-blue,catppuccin-macchiato-lavender,catppuccin-mocha-green,catppuccin-mocha-teal,catppuccin-mocha-sky,catppuccin-mocha-sapphire,catppuccin-mocha-blue,catppuccin-mocha-lavender,nord,pitchblack,matrix,dark-arc";
|
||||
|
||||
FORGEJO__ui_0x2E_meta__AUTHOR = "${title} - ${slogan}";
|
||||
FORGEJO__ui_0x2E_meta__DESCRIPTION = description;
|
||||
FORGEJO__ui_0x2E_meta__KEYWORDS = "git,self-hosted,projects,code";
|
||||
|
||||
# Whether to render SVG files as images. If SVG rendering is disabled, SVG files are displayed as text and cannot be embedded in markdown files as images.
|
||||
FORGEJO__ui_0x2E_svg__ENABLE_RENDER = "true";
|
||||
|
||||
# ...
|
||||
# Enables math inline and block detection
|
||||
FORGEJO__markdown__ENABLE_MATH = "true";
|
||||
|
||||
# Define allowed algorithms and their minimum key length (use -1 to disable a type)
|
||||
#FORGEJO__ssh__0x2E__minimum_key_sizes__ED25519 = "256";
|
||||
#FORGEJO__ssh__0x2E__minimum_key_sizes__ECDSA = "256";
|
||||
FORGEJO__ssh_0x2E_minimum_key_sizes__RSA = "-1";
|
||||
FORGEJO__ssh_0x2E_minimum_key_sizes__DSA = "-1";
|
||||
|
||||
# ... indexer
|
||||
|
||||
# ... queue
|
||||
|
||||
# Disallow regular (non-admin) users from creating organizations.
|
||||
#FORGEJO__admin__DISABLE_REGULAR_ORG_CREATION = "false";
|
||||
# Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled
|
||||
FORGEJO__admin__DEFAULT_EMAIL_NOTIFICATIONS = "enabled";
|
||||
# Send an email to all admins when a new user signs up to inform the admins about this act. Options: true, false
|
||||
FORGEJO__admin__SEND_NOTIFICATION_EMAIL_ON_NEW_USER = "true";
|
||||
# Disabled features for users, could be "deletion", "manage_ssh_keys","manage_gpg_keys" more features can be disabled in future
|
||||
# - deletion: a user cannot delete their own account
|
||||
# - manage_ssh_keys: a user cannot configure ssh keys
|
||||
# - manage_gpg_keys: a user cannot configure gpg keys
|
||||
#FORGEJO__admin__USER_DISABLED_FEATURES = "";
|
||||
# Comma separated list of disabled features ONLY if the user has an external login type (eg. LDAP, Oauth, etc.), could be `deletion`, `manage_ssh_keys`, `manage_gpg_keys`. This setting is independent from `USER_DISABLED_FEATURES` and supplements its behavior.
|
||||
# - deletion: a user cannot delete their own account
|
||||
# - manage_ssh_keys: a user cannot configure ssh keys
|
||||
# - manage_gpg_keys: a user cannot configure gpg keys
|
||||
#FORGEJO__admin__EXTERNAL_USER_DISABLE_FEATURES = "";
|
||||
|
||||
# Whether to allow signin in via OpenID
|
||||
FORGEJO__openid__ENABLE_OPENID_SIGNIN = "false";
|
||||
# Whether to allow registering via OpenID
|
||||
# Do not include to rely on rhw DISABLE_REGISTRATION setting
|
||||
FORGEJO__openid__ENABLE_OPENID_SIGNUP = "false";
|
||||
# ...
|
||||
|
||||
# ... oath2_client
|
||||
|
||||
# ... webhook
|
||||
|
||||
FORGEJO__mailer__ENABLED = "true";
|
||||
# Buffer length of channel, keep it as it is if you don't know what it is.
|
||||
#FORGEJO__mailer__SEND_BUFFER_LEN = "100";
|
||||
# Prefix displayed before subject in mail.
|
||||
#FORGEJO__mailer__SUBJECT_PREFIX = "";
|
||||
# Mail server protocol. One of "smtp", "smtps", "smtp+starttls", "smtp+unix", "sendmail", "dummy"
|
||||
FORGEJO__mailer__PROTOCOL = "smtps";
|
||||
# Mail server address
|
||||
FORGEJO__mailer__SMTP_ADDR = "smtp.gmail.com";
|
||||
# Mail server port. If no protocol is specified, it will be inferred by this setting.
|
||||
FORGEJO__mailer__SMTP_PORT = "465";
|
||||
# Enable HELO operation. Defaults to true.
|
||||
#FORGEJO__mailer__ENABLE_HELO = "true";
|
||||
# Custom hostname fo the HELO operation. If no value is provided, one is retrieved from
|
||||
# the system.
|
||||
#FORGEJO__mailer__HELO_HOSTNAME = "";
|
||||
# If set to 'true', completely ignores server certificate validation errors. UNSAFE!
|
||||
#FORGEJO__mailer__FORCE_TRUST_SERVER_CERT = "false";
|
||||
# Use client certificate in connection.
|
||||
#FORGEJO__mailer__USE_CLIENT_CERT = "false";
|
||||
#FORGEJO__mailer__CLIENT_CERT_FILE = "custom/mailer/cert.pem";
|
||||
#FORGEJO__mailer__CLIENT_KEY_FILE = "custom/mailer/key.pem";
|
||||
# Mail from address, RFC 5322. This can be just an email address, or the
|
||||
# `"Name" <email@example.com>` format.
|
||||
FORGEJO__mailer__FROM = ''"${title}" <git@depeuter.dev>'';
|
||||
# Sometimes it is helpful to use a different address on the envelope. Set this to use
|
||||
# ENVELOPE_FROM as the from on the envelope. Set to `<>` to send an empty address.
|
||||
#FORGEJO__mailer__ENVELOPE_FROM = "";
|
||||
# If gitea sends mails on behave of users, it will just use the name also displayed in the
|
||||
# WebUI. If you want e.g. `Mister X (by CodeIt) <gitea@codeit.net>`, set it to
|
||||
# `{{ .DisplayName }} (by {{ .AppName }})`.
|
||||
# Available Variables: `.DisplayName`, `.AppName` and `.Domain`.
|
||||
#FORGEJO__mailer__FROM_DISPLAY_NAME_FORMAT = "{{ .DisplayName }}";
|
||||
# Mailer user name and password, if required by provider.
|
||||
#FORGEJO__mailer__USER = "";
|
||||
# Use PASSWD = `your password` for quoting if you use special characters in the password.
|
||||
#FORGEJO__mailer__PASSWD = "";
|
||||
# Send mails only in plain text, without HTML alternative
|
||||
#FORGEJO__mailer__SEND_AS_PLAIN_TEXT = "false";
|
||||
# Specify an alternative sendmail binary
|
||||
#FORGEJO__mailer__SENDMAIL_PATH = "sendmail";
|
||||
# Specify any extra sendmail arguments
|
||||
# WARNING: if your sendmail program interprets options you should set this to "--" or terminate these args with "--"
|
||||
#FORGEJO__mailer__SENDMAIL_ARGS = "";
|
||||
# Timeout for Sendmail
|
||||
#FORGEJO__mailer__SENDMAIL_TIMEOUT = "5m";
|
||||
# convert \r\n to \n for Sendmail
|
||||
#FORGEJO__mailer__SENDMAIL_CONVERT_CRLF = "true";
|
||||
|
||||
# ... email.incoming
|
||||
|
||||
# Either "memory", "redis", "memcache", or "twoqueue". default is "memory"
|
||||
FORGEJO__cache__ADAPTER = "redis";
|
||||
# For "memory" only, GC interval in seconds, default is 60.
|
||||
#FORGEJO__cache__INTERVAL = "60";
|
||||
# For "redis" and "memcache", connection host address
|
||||
# redis: `redis://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s` (or `redis+cluster://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s` for a Redis cluster)
|
||||
# memcache: `127.0.0.1:11211`
|
||||
# twoqueue: `{"size":50000,"recent_ratio":0.25,"ghost_ratio":0.5}` or `50000`
|
||||
FORGEJO__cache__HOST = "redis://gitea-redis:${toString redisPort}/0?pool_size=100&idle_timeout=180s";
|
||||
# Time to keep items in cache if not used, default is 16 hours.
|
||||
# Setting it to -1 disables caching
|
||||
FORGEJO__cache__ITEM_TTL = "16h";
|
||||
# Time to keep items in cache if not used, default is 8760 hours.
|
||||
# Setting it to -1 disables caching
|
||||
FORGEJO__cache_0X2E_last_0X2D_commit__ITEM_TTL = "8760h";
|
||||
# Only enable the cache when repository's commits count great than
|
||||
FORGEJO__cache_0X2E_last_0X2D_commit__COMMITS_COUNT = "100";
|
||||
|
||||
# Either "memory", "file", "redis", "db", "mysql", "couchbase", "memcache" or "postgres"
|
||||
# Default is "memory". "db" will reuse the configuration in [database]
|
||||
#FORGEJO__session__PROVIDER = "memory";
|
||||
# Provider config options
|
||||
# memory: doesn't have any config yet
|
||||
# file: session file path, e.g. `data/sessions`
|
||||
# redis: `redis://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s` (or `redis+cluster://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s` for a Redis cluster)
|
||||
# mysql: go-sql-driver/mysql dsn config string, e.g. `root:password@/session_table`
|
||||
#FORGEJO__session__PROVIDER_CONFIG = "data/sessions"; # Relative paths will be made absolute against _`AppWorkPath`_.
|
||||
# Session cookie name
|
||||
FORGEJO__session__COOKIE_NAME = "i_like_tibo";
|
||||
# If you use session in https only: true or false. If not set, it defaults to `true` if the ROOT_URL is an HTTPS URL.
|
||||
FORGEJO__session__COOKIE_SECURE = "true";
|
||||
# Session GC time interval in seconds, default is 86400 (1 day)
|
||||
#FORGEJO__session__GC_0X2E_INTERVAL_0X2E_TIME = "86400";
|
||||
# Session life time in seconds, default is 86400 (1 day)
|
||||
#FORGEJO__session__SESSION_0X2E_LIFE_0X2E_TIME = "86400";
|
||||
# Cookie domain name. Default is empty
|
||||
FORGEJO__session__DOMAIN = "git.depeuter.dev";
|
||||
# SameSite settings. Either "none", "lax", or "strict"
|
||||
FORGEJO__session__SAME_SITE = "strict";
|
||||
|
||||
# How Gitea deals with missing repository avatars
|
||||
# none = no avatar will be displayed; random = random avatar will be displayed; image = default image will be used
|
||||
#FORGEJO__picture__REPOSITORY_AVATAR_FALLBACK = "none";
|
||||
#FORGEJO__picture__REPOSITORY_AVATAR_FALLBACK_IMAGE = "/img/repo_default.png";
|
||||
# Max Width and Height of uploaded avatars.
|
||||
# This is to limit the amount of RAM used when resizing the image.
|
||||
FORGEJO__picture__AVATAR_MAX_WIDTH = "10000";
|
||||
FORGEJO__picture__AVATAR_MAX_HEIGTH = "10000";
|
||||
# The multiplication factor for rendered avatar images.
|
||||
# Larger values result in finer rendering on HiDPI devices.
|
||||
#FORGEJO__picture__AVATAR_RENDERED_SIZE_FACTOR = "2";
|
||||
# Maximum allowed file size for uploaded avatars.
|
||||
# This is to limit the amount of RAM used when resizing the image.
|
||||
FORGEJO__picture__AVATAR_MAX_FILE_SIZE = "1048576";
|
||||
# If the uploaded file is not larger than this byte size, the image will be used as is, without resizing/converting.
|
||||
#FORGEJO__picture__AVATAR_MAX_ORIGIN_SIZE = "262144";
|
||||
# Chinese users can choose "duoshuo"
|
||||
# or a custom avatar source, like: http://cn.gravatar.com/avatar/
|
||||
#FORGEJO__picture__GRAVATAR_SOURCE = "gravatar";
|
||||
# This value will always be true in offline mode.
|
||||
#FORGEJO__picture__DISABLE_GRAVATAR = "false";
|
||||
# Federated avatar lookup uses DNS to discover avatar associated.
|
||||
# with emails, see https://www.libravatar.org
|
||||
# This value will always be false in offline mode or when Gravatar is disabled.
|
||||
#FORGEJO__picture__ENABLE_FEDERATED_AVATAR = "false";
|
||||
|
||||
# ... attachment
|
||||
|
||||
# ... time
|
||||
|
||||
# ... cron
|
||||
|
||||
# Enables the mirror functionality. Set to **false** to disable all mirrors. Pre-existing mirrors remain valid but won't be updated; may be converted to regular repo.
|
||||
FORGEJO__mirror__ENABLED = "true";
|
||||
# Disable the creation of **new** pull mirrors. Pre-existing mirrors remain valid. Will be ignored if `mirror.ENABLED` is `false`.
|
||||
FORGEJO__mirror__DISABLE_NEW_PULL = "false";
|
||||
# Disable the creation of **new** push mirrors. Pre-existing mirrors remain valid. Will be ignored if `mirror.ENABLED` is `false`.
|
||||
FORGEJO__mirror__DISABLE_NEW_PUSH = "false";
|
||||
# Default interval as a duration between each check
|
||||
FORGEJO__mirror__DEFAULT_INTERVAL = "1h";
|
||||
# Min interval as a duration must be > 1m
|
||||
FORGEJO__mirror__MIN_INTERVAL = "5m";
|
||||
|
||||
# ... api
|
||||
|
||||
# ... i18n
|
||||
|
||||
# .. highlight.mapping
|
||||
|
||||
# Show version information about Gitea and Go in the footer
|
||||
FORGEJO__other__SHOW_FOOTER_VERSION = "false";
|
||||
# Show template execution time in the footer
|
||||
FORGEJO__other__SHOW_FOOTER_TEMPLATE_LOAD_TIME = "false";
|
||||
# Show the "powered by" text in the footer
|
||||
FORGEJO__other__SHOW_FOOTER_POWERED_BY = "false";
|
||||
# Generate sitemap. Defaults to `true`.
|
||||
FORGEJO__other__ENABLE_SITEMAP = "true";
|
||||
# Enable/Disable RSS/Atom feed
|
||||
FORGEJO__other__ENABLE_FEED = "true";
|
||||
|
||||
# ... markup
|
||||
|
||||
# ... metrics
|
||||
|
||||
# ... migrations
|
||||
|
||||
# ... f3
|
||||
|
||||
# Enable/Disable federation capabilities
|
||||
FORGEJO__federation_ENABLED = "false";
|
||||
# ...
|
||||
|
||||
# Enable/Disable package registry capabilities
|
||||
FORGEJO__packages__ENABLED = "true";
|
||||
|
||||
# ... storage
|
||||
|
||||
# Repo-archive storage will override storage.
|
||||
#FORGEJO__repo_0X2D_archive__STORAGE_TYPE = "local";
|
||||
# Where your lfs files reside, default is data/lfs
|
||||
FORGEJO__repo_0X2D_archive__PATH = "";
|
||||
# Override the minio base path if storage type is minio.
|
||||
#FORGEJO__repo_0X2D_archive__MINIO_BASE_PATH = "";
|
||||
|
||||
# lfs storage will override storage.
|
||||
#FORGEJO__lfs__STORAGE_TYPE = "local";
|
||||
# Where your lfs files reside, default is data/lfs
|
||||
FORGEJO__lfs__PATH = "";
|
||||
# Override the minio base path if storage is set to minio.
|
||||
#FORGEJO__lfs__MINIO_BASE_PATH = "lfs/";
|
||||
|
||||
# Enable the proxy, all requests to external via HTTP will be affected
|
||||
FORGEJO__proxy__PROXY_ENABLED = "false";
|
||||
# Proxy server URL, support http://, https//, socks://, blank will follow environment http_proxy/https_proxy/no_proxy
|
||||
#FORGEJO__proxy__PROXY_URL = "";
|
||||
# Comma separated list of host names requiring proxy. Glob patterns (*) are accepted; use ** to match all hosts.
|
||||
#FORGEJO__proxy__PROXY_HOSTS = "";
|
||||
|
||||
# Enable/Disable actions capabilities
|
||||
FORGEJO__actions__ENABLED = "true";
|
||||
# Default address to get action plugins, e.g. the default value means downloading from "https://code.forgejo.org/actions/checkout" for "uses: actions/checkout@v3"
|
||||
#FORGEJO__actions__DEFAULT_ACTIONS_URL = "https://code.forgejo.org";
|
||||
# ...
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
cfg = config.homelab.apps.homepage;
|
||||
|
||||
PUID = toString config.users.users.homepage.uid;
|
||||
PGID = toString config.users.groups.apps.gid;
|
||||
|
||||
homepage-config = "/srv/homepage-config";
|
||||
|
||||
proxyNet = config.homelab.apps.traefik.sharedNetworkName;
|
||||
in {
|
||||
options.homelab.apps.homepage = {
|
||||
enable = lib.mkEnableOption "homepage";
|
||||
port = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
default = 3000;
|
||||
description = "homepage WebUI port";
|
||||
};
|
||||
exposePort = lib.mkEnableOption "expose homepage port";
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
homelab = {
|
||||
users.apps.enable = true;
|
||||
virtualisation.containers.enable = true;
|
||||
};
|
||||
|
||||
users.users.homepage = {
|
||||
uid = lib.mkForce 3018;
|
||||
isSystemUser = true;
|
||||
group = config.users.groups.apps.name;
|
||||
home = "/var/empty";
|
||||
shell = null;
|
||||
};
|
||||
|
||||
fileSystems."${homepage-config}" = {
|
||||
device = "192.168.0.11:/mnt/SMALL/CONFIG/HOMEPAGE";
|
||||
fsType = "nfs";
|
||||
options = [
|
||||
"rw"
|
||||
"auto"
|
||||
"nfsvers=4.2"
|
||||
"async" "soft" "timeo=100" "retry=50" "actimeo=1800" "lookupcache=all"
|
||||
"nosuid" "tcp"
|
||||
];
|
||||
};
|
||||
|
||||
virtualisation.oci-containers.containers.homepage = let
|
||||
host = "homepage.${config.networking.domain}";
|
||||
in {
|
||||
hostname = "homepage";
|
||||
image = "ghcr.io/gethomepage/homepage:v1.10.1";
|
||||
autoStart = true;
|
||||
user = "${toString PUID}:${toString PGID}";
|
||||
ports = lib.mkIf cfg.exposePort [
|
||||
"${toString cfg.port}:3000/tcp"
|
||||
];
|
||||
networks = [
|
||||
proxyNet
|
||||
];
|
||||
volumes = [
|
||||
"${homepage-config}:/app/config"
|
||||
# "/var/run/docker.sock:/var/run/docker.sock:ro" # For docker integrations
|
||||
];
|
||||
labels = {
|
||||
"traefik.enable" = "true";
|
||||
"traefik.docker.network" = proxyNet;
|
||||
"traefik.http.routers.homepage.rule" = "Host(`${host}`)";
|
||||
"traefik.http.services.homepage.loadbalancer.server.port" = toString cfg.port;
|
||||
};
|
||||
environment = {
|
||||
inherit PUID PGID;
|
||||
|
||||
HOMEPAGE_ALLOWED_HOSTS = "${host},192.168.0.91:3000";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,169 +0,0 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.homelab.apps.jellyfin;
|
||||
|
||||
networkName = "jellyfin";
|
||||
inherit (config.homelab.fileSystems) media;
|
||||
|
||||
UID = 3008;
|
||||
GID = config.users.groups.media.gid;
|
||||
in {
|
||||
options.homelab.apps.jellyfin.enable = lib.mkEnableOption "Jellyfin using Docker";
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
homelab = {
|
||||
fileSystems.media.video = {
|
||||
enable = true;
|
||||
permissions = [ "read" ];
|
||||
};
|
||||
|
||||
users = {
|
||||
apps.enable = true;
|
||||
media.enable = true;
|
||||
};
|
||||
virtualisation.containers.enable = true;
|
||||
};
|
||||
|
||||
fileSystems = {
|
||||
"/srv/audio" = {
|
||||
device = "192.168.0.11:/mnt/SMALL/MEDIA/AUDIO";
|
||||
fsType = "nfs";
|
||||
options = [
|
||||
"ro"
|
||||
"nfsvers=4.2"
|
||||
"async" "soft"
|
||||
"timeo=100" "retry=50" "actimeo=1800" "lookupcache=all"
|
||||
"nosuid" "tcp"
|
||||
];
|
||||
};
|
||||
|
||||
"/srv/homevideo" = {
|
||||
device = "192.168.0.11:/mnt/BIG/MEDIA/HOMEVIDEO/ARCHIVE";
|
||||
fsType = "nfs";
|
||||
options = [
|
||||
"ro"
|
||||
"nfsvers=4.2"
|
||||
"async" "soft"
|
||||
"timeo=100" "retry=50" "actimeo=1800" "lookupcache=all"
|
||||
"nosuid" "tcp"
|
||||
];
|
||||
};
|
||||
|
||||
"/srv/photo" = {
|
||||
device = "192.168.0.11:/mnt/BIG/MEDIA/PHOTO/ARCHIVE";
|
||||
fsType = "nfs";
|
||||
options = [
|
||||
"ro"
|
||||
"nfsvers=4.2"
|
||||
"async" "soft"
|
||||
"timeo=100" "retry=50" "actimeo=1800" "lookupcache=all"
|
||||
"nosuid" "tcp"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
users.users.jellyfin = {
|
||||
uid = lib.mkForce UID;
|
||||
isSystemUser = true;
|
||||
group = config.users.groups.apps.name;
|
||||
extraGroups = [
|
||||
config.users.groups.media.name
|
||||
];
|
||||
home = "/var/empty";
|
||||
shell = null;
|
||||
};
|
||||
|
||||
# Make sure the Docker network exists.
|
||||
systemd.services."docker-${networkName}-create-network" = {
|
||||
description = "Create Docker network for ${networkName}";
|
||||
requiredBy = [
|
||||
"docker-jellyfin.service"
|
||||
"docker-feishin.service"
|
||||
];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
script = ''
|
||||
if ! ${pkgs.docker}/bin/docker network ls | grep -q ${networkName}; then
|
||||
${pkgs.docker}/bin/docker network create ${networkName}
|
||||
fi
|
||||
'';
|
||||
};
|
||||
|
||||
virtualisation.oci-containers.containers = {
|
||||
jellyfin = {
|
||||
hostname = "jellyfin";
|
||||
image = "jellyfin/jellyfin:10.10.7";
|
||||
user = "${toString UID}:${toString GID}";
|
||||
autoStart = true;
|
||||
ports = [
|
||||
"8096:8096/tcp"
|
||||
# "8920:8920/tcp"
|
||||
];
|
||||
extraOptions = [
|
||||
"--network=${networkName}"
|
||||
"--device=nvidia.com/gpu=all" # Equivalent to --gpus=all
|
||||
];
|
||||
volumes = [
|
||||
"jellyfin-config:/config"
|
||||
"cache:/cache"
|
||||
|
||||
"/srv/audio:/media/audio"
|
||||
"${media.video.hostPath}:/media/video"
|
||||
"/srv/homevideo:/media/homevideo"
|
||||
"/srv/photo:/media/photo"
|
||||
];
|
||||
environment = {
|
||||
# TODO
|
||||
};
|
||||
};
|
||||
|
||||
jellyfin-vue = {
|
||||
hostname = "jellyfin-vue";
|
||||
image = "ghcr.io/jellyfin/jellyfin-vue:unstable";
|
||||
autoStart = true;
|
||||
ports = [
|
||||
"8080:80/tcp"
|
||||
];
|
||||
extraOptions = [
|
||||
"--network=${networkName}"
|
||||
];
|
||||
labels = {
|
||||
};
|
||||
};
|
||||
|
||||
feishin = let
|
||||
feishinPort = "9180";
|
||||
in {
|
||||
hostname = "feishin";
|
||||
image = "ghcr.io/jeffvli/feishin:0.19.0";
|
||||
autoStart = true;
|
||||
ports = [
|
||||
"${feishinPort}:9180/tcp" # Web player (HTTP)
|
||||
];
|
||||
extraOptions = [
|
||||
"--network=${networkName}"
|
||||
];
|
||||
environment = {
|
||||
# pre defined server name
|
||||
SERVER_NAME = "Hugo";
|
||||
# When true AND name/type/url are set, only username/password can be toggled
|
||||
SERVER_LOCK = "true";
|
||||
# Either "jellyfin" or "navidrome"
|
||||
SERVER_TYPE = "jellyfin";
|
||||
# http://address:port
|
||||
SERVER_URL= "https://jelly.depeuter.dev";
|
||||
TZ = config.time.timeZone;
|
||||
};
|
||||
labels = {
|
||||
"traefik.enable" = "true";
|
||||
"traefik.http.routers.feishin.rule" = "Host(`play.jelly.depeuter.dev`)";
|
||||
"traefik.http.services.feishin.loadbalancer.server.port" = feishinPort;
|
||||
"traefik.tls.options.default.minVersion" = "VersionTLS13";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
cfg = config.homelab.apps.plex;
|
||||
in {
|
||||
options.homelab.apps.plex.enable = lib.mkEnableOption "Plex";
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
homelab = {
|
||||
users = {
|
||||
apps.enable = true;
|
||||
media.enable = true;
|
||||
};
|
||||
fileSystems.media.video.enable = true;
|
||||
virtualisation.containers.enable = true;
|
||||
};
|
||||
|
||||
users.users.plex = {
|
||||
uid = lib.mkForce 3009;
|
||||
isSystemUser = true;
|
||||
group = config.users.groups.apps.name;
|
||||
extraGroups = [
|
||||
config.users.groups.media.name
|
||||
];
|
||||
home = "/var/empty";
|
||||
shell = null;
|
||||
};
|
||||
|
||||
virtualisation.oci-containers.containers.plex = let
|
||||
videoHostPath = config.homelab.fileSystems.media.video.hostPath;
|
||||
in {
|
||||
hostname = "plex";
|
||||
image = "plexinc/pms-docker:1.41.6.9685-d301f511a";
|
||||
autoStart = true;
|
||||
ports = [
|
||||
"32400:32400/tcp" # Plex Media Server
|
||||
"1900:1900/udp" # Plex DLNA Server
|
||||
"32469:32469/tcp" # Plex DLNA Server
|
||||
"32410:32410/udp" # GDM network discovery
|
||||
"32412:32412/udp" # GDM network discovery
|
||||
"32413:32413/udp" # GDM network discovery
|
||||
"32414:32414/udp" # GDM network discovery
|
||||
# "8324:8324/tcp" # Controlling Plex for Roku via Plex Companion
|
||||
];
|
||||
environment = {
|
||||
#ADVERTISE_AP = "..."; # TODO Configure ip
|
||||
ALLOWED_NETWORKS = "192.168.0.0/24,172.16.0.0/16";
|
||||
CHANGE_CONFIG_DIR_OWNERSHIP = "false";
|
||||
HOSTNAME = "Hugo-Plex";
|
||||
PLEX_CLAIM = "claim-d5MqsjMeCZrUF6oUvssr";
|
||||
PLEX_UID = toString config.users.users.plex.uid;
|
||||
PLEX_GID = toString config.users.groups.media.gid;
|
||||
TZ = config.time.timeZone;
|
||||
};
|
||||
volumes = [
|
||||
# TODO Backup over NFS
|
||||
"plex-config:/config"
|
||||
"plex-transcode:/transcode"
|
||||
|
||||
"${videoHostPath}:/data/video:ro"
|
||||
];
|
||||
labels = {
|
||||
"traefik.enable" = "true";
|
||||
"traefik.http.routers.plex.rule" = "Host(`plex.depeuter.dev`)";
|
||||
"traefik.http.services.plex.loadbalancer.server.port" = "32400";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,278 +0,0 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.homelab.apps.solidtime;
|
||||
|
||||
networkName = "solidtime";
|
||||
internalNetworkName = "solidtime-internal";
|
||||
proxyNet = config.homelab.apps.traefiik.sharedNetworkName;
|
||||
|
||||
user = "1000:1000";
|
||||
|
||||
# dbExternalPort = ...;
|
||||
dbInternalPort = 5432;
|
||||
|
||||
gotenbergPort = 3000;
|
||||
|
||||
inherit (config.virtualisation.oci-containers) containers;
|
||||
|
||||
solidtimeImageName = "solidtime/solidtime";
|
||||
version = "0.10.0";
|
||||
solidtimeImage = "${solidtimeImageName}:${version}";
|
||||
solidtimeImageFile = pkgs.dockerTools.pullImage {
|
||||
imageName = solidtimeImageName;
|
||||
finalImageTag = version;
|
||||
imageDigest = "sha256:817d3a366ecc39f0473d7154372afa82dd4e6e50c66d70be45804892c8421cbb";
|
||||
sha256 = "sha256-h5aCKaquUF/EVsOHaLOHrn1HAoXZYPhAbJ+e4cmjSA8=";
|
||||
};
|
||||
|
||||
volumes = [
|
||||
"solidtime-storage:/var/www/html/storage"
|
||||
"solidtime-logs:/var/www/html/storage/logs"
|
||||
"solidtime-app:/var/www/html/storage/app"
|
||||
];
|
||||
|
||||
# laravel.env
|
||||
laravelEnv = {
|
||||
APP_NAME = "Solidtime";
|
||||
VITE_APP_NAME = laravelEnv.APP_NAME;
|
||||
APP_ENV = "production";
|
||||
APP_DEBUG = "false";
|
||||
APP_URL = "http://localhost:${toString cfg.port}";
|
||||
APP_FORCE_HTTPS = "false";
|
||||
APP_ENABLE_REGISTRATION = "false";
|
||||
TRUSTED_PROXIES = "0.0.0.0/0,2000:0:0:0:0:0:0:0/3";
|
||||
|
||||
# Logging
|
||||
LOG_CHANNEL = "stderr_daily";
|
||||
LOG_LEVEL = "debug";
|
||||
|
||||
# Database
|
||||
DB_CONNECTION = "pgsql";
|
||||
DB_HOST = containers.solidtimeDb.hostname;
|
||||
DB_PORT = toString dbInternalPort;
|
||||
DB_SSL_MODE = "require";
|
||||
DB_DATABASE = "solidtime";
|
||||
DB_USERNAME = "solidtime";
|
||||
DB_PASSWORD = "ChangeMe";
|
||||
|
||||
# Mail
|
||||
#MAIL_MAILER = "smtp";
|
||||
#MAIL_HOST = "smtp.gmail.com";
|
||||
#MAIL_PORT = "465";
|
||||
#MAIL_ENCRYPTION = "tls";
|
||||
#MAIL_FROM_ADDRESS = "no-reply@time.depeuter.dev";
|
||||
MAIL_FROM_NAME = laravelEnv.APP_NAME;
|
||||
#MAIL_USERNAME = "kmtl.hugo@gmail.com";
|
||||
#MAIL_PASSWORD = "fhfxoequhhqidrhd";
|
||||
|
||||
# Queue
|
||||
QUEUE_CONNECTION = "database";
|
||||
|
||||
# File storage
|
||||
FILESYSTEM_DISK = "local";
|
||||
PUBLIC_FILESYSTEM_DISK = "public";
|
||||
|
||||
# Services
|
||||
GOTENBERG_URL = "http://${containers.solidtimeGotenberg.hostname}:${toString gotenbergPort}";
|
||||
};
|
||||
|
||||
in {
|
||||
options.homelab.apps.solidtime = {
|
||||
enable = lib.mkEnableOption "Solidtime time tracker using Docker";
|
||||
port = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
default = 8000;
|
||||
description = "Solidtime WebUI port";
|
||||
};
|
||||
exposePort = lib.mkEnableOption "Expose Soldtime port";
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
homelab.virtualisation.containers.enable = true;
|
||||
|
||||
# Make sure the Docker network exists.
|
||||
systemd.services = {
|
||||
"docker-${networkName}-create-network" = {
|
||||
description = "Create Docker network for ${networkName}";
|
||||
requiredBy = [
|
||||
"${containers.solidtime.serviceName}.service"
|
||||
];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
script = ''
|
||||
if ! ${pkgs.docker}/bin/docker network ls | grep -q ${networkName}; then
|
||||
${pkgs.docker}/bin/docker network create ${networkName}
|
||||
fi
|
||||
'';
|
||||
};
|
||||
"docker-${internalNetworkName}-create-network" = {
|
||||
description = "Create Docker network for ${internalNetworkName}";
|
||||
requiredBy = [
|
||||
"${containers.solidtime.serviceName}.service"
|
||||
"${containers.solidtimeScheduler.serviceName}.service"
|
||||
"${containers.solidtimeQueue.serviceName}.service"
|
||||
"${containers.solidtimeDb.serviceName}.service"
|
||||
"${containers.solidtimeGotenberg.serviceName}.service"
|
||||
];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
script = ''
|
||||
if ! ${pkgs.docker}/bin/docker network ls | grep -q ${internalNetworkName}; then
|
||||
${pkgs.docker}/bin/docker network create ${internalNetworkName}
|
||||
fi
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
virtualisation.oci-containers.containers = {
|
||||
solidtime = {
|
||||
hostname = "solidtime";
|
||||
image = solidtimeImage;
|
||||
imageFile = solidtimeImageFile;
|
||||
inherit user;
|
||||
autoStart = true;
|
||||
dependsOn = [
|
||||
"solidtimeDb"
|
||||
];
|
||||
ports = [
|
||||
# Open ports if you don't use Traefik
|
||||
"${toString cfg.port}:8000"
|
||||
];
|
||||
networks = [
|
||||
networkName
|
||||
internalNetworkName
|
||||
];
|
||||
extraOptions = [
|
||||
# Healthecks
|
||||
# test: [ "CMD", "curl", "--fail", "http://localhost:8000/health-check/up" ]
|
||||
''--health-cmd=curl --fail http://localhost:8000/health-check/up''
|
||||
];
|
||||
inherit volumes;
|
||||
labels = {
|
||||
"traefik.enable" = "true";
|
||||
"traefik.http.routers.solidtime.rule" = "Host(`time.${config.networking.hostName}.depeuter.dev`)";
|
||||
"traefik.http.services.solidtime.loadbalancer.server.port" = toString cfg.port;
|
||||
};
|
||||
environmentFiles = [
|
||||
"/home/admin/.solidtime.env"
|
||||
];
|
||||
environment = laravelEnv // {
|
||||
CONTAINER_MODE = "http";
|
||||
};
|
||||
};
|
||||
solidtimeScheduler = {
|
||||
hostname = "scheduler";
|
||||
image = solidtimeImage;
|
||||
imageFile = solidtimeImageFile;
|
||||
inherit user;
|
||||
autoStart = true;
|
||||
dependsOn = [
|
||||
"solidtimeDb"
|
||||
];
|
||||
networks = [
|
||||
internalNetworkName
|
||||
];
|
||||
extraOptions = [
|
||||
# Healthchecks
|
||||
# test: [ "CMD", "healthcheck" ]
|
||||
''--health-cmd="healthcheck"''
|
||||
];
|
||||
inherit volumes;
|
||||
environmentFiles = [
|
||||
"/home/admin/.solidtime.env"
|
||||
];
|
||||
environment = laravelEnv // {
|
||||
CONTAINER_MODE = "scheduler";
|
||||
};
|
||||
};
|
||||
solidtimeQueue = {
|
||||
hostname = "queue";
|
||||
image = solidtimeImage;
|
||||
imageFile = solidtimeImageFile;
|
||||
inherit user;
|
||||
autoStart = true;
|
||||
networks = [
|
||||
internalNetworkName
|
||||
];
|
||||
extraOptions = [
|
||||
# Healthchecks
|
||||
# test: [ "CMD", "healthcheck" ]
|
||||
''--health-cmd="healthcheck"''
|
||||
];
|
||||
inherit volumes;
|
||||
dependsOn = [
|
||||
"solidtimeDb"
|
||||
];
|
||||
environmentFiles = [
|
||||
"/home/admin/.solidtime.env"
|
||||
];
|
||||
environment = laravelEnv // {
|
||||
CONTAINER_MODE = "worker";
|
||||
WORKER_COMMAND = "php /var/www/html/artisan queue:work";
|
||||
};
|
||||
};
|
||||
solidtimeDb = let
|
||||
imageName = "postgres";
|
||||
finalImageTag = "15";
|
||||
in {
|
||||
hostname = "database";
|
||||
image = "${imageName}:${finalImageTag}";
|
||||
imageFile = pkgs.dockerTools.pullImage {
|
||||
inherit imageName finalImageTag;
|
||||
imageDigest = "sha256:98fe06b500b5eb29e45bf8c073eb0ca399790ce17b1d586448edc4203627d342";
|
||||
sha256 = "sha256-AZ4VkOlROX+nR/MjDjsA4xdHzmtKjiBAtsp2Q6IdOvg=";
|
||||
};
|
||||
autoStart = true;
|
||||
ports = [
|
||||
# "${toString dbExternalPort}:${toString dbInternalPort}"
|
||||
];
|
||||
networks = [
|
||||
internalNetworkName
|
||||
];
|
||||
extraOptions = [
|
||||
# Healthchecks
|
||||
# test: - CMD - pg_isready - '-q' - '-d' - '${DB_DATABASE}' - '-U' - '${DB_USERNAME}' retries: 3 timeout: 5s
|
||||
''--health-cmd="pg_isready -q -d ${laravelEnv.DB_DATABASE} -U ${laravelEnv.DB_USERNAME}"''
|
||||
"--health-retries=3"
|
||||
"--health-timeout=5s"
|
||||
];
|
||||
volumes = [
|
||||
"solidtime-db:/var/lib/postgresql/data"
|
||||
];
|
||||
environment = {
|
||||
PGPASSWORD = laravelEnv.DB_PASSWORD;
|
||||
POSTGRES_DB = laravelEnv.DB_DATABASE;
|
||||
POSTGRES_USER = laravelEnv.DB_USERNAME;
|
||||
POSTGRES_PASSWORD = laravelEnv.DB_PASSWORD;
|
||||
};
|
||||
};
|
||||
solidtimeGotenberg = let
|
||||
imageName = "gotenberg/gotenberg";
|
||||
finalImageTag = "8.26.0";
|
||||
in {
|
||||
hostname = "gotenberg";
|
||||
image = "${imageName}:${finalImageTag}";
|
||||
imageFile = pkgs.dockerTools.pullImage {
|
||||
inherit imageName finalImageTag;
|
||||
imageDigest = "sha256:328551506b3dec3ff6381dd47e5cd72a44def97506908269e201a8fbfa1c12c0";
|
||||
sha256 = "sha256-1zz4xDAgXxHUnkCVIfjHTgXb82EFEx+5am6Cu9+eZj4=";
|
||||
};
|
||||
autoStart = true;
|
||||
networks = [
|
||||
internalNetworkName
|
||||
];
|
||||
extraOptions = [
|
||||
# Healthchecks
|
||||
# test: [ "CMD", "curl", "--silent", "--fail", "http://localhost:3000/health" ]
|
||||
''--health-cmd="curl --silent --fail http://localhost:${toString gotenbergPort}/health"''
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
cfg = config.homelab.apps.speedtest;
|
||||
in {
|
||||
options.homelab.apps.speedtest.enable = lib.mkEnableOption "Speedtest";
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
homelab.virtualisation.containers.enable = true;
|
||||
|
||||
virtualisation.oci-containers.containers.speedtest = {
|
||||
hostname = "speedtest";
|
||||
image = "openspeedtest/latest:v2.0.5";
|
||||
ports = [
|
||||
"3000:3000"
|
||||
"3001:3001"
|
||||
];
|
||||
labels = {
|
||||
"traefik.enable" = "true";
|
||||
"traefik.http.routers.speedtest.rule" = "Host(`speedtest.${config.networking.hostName}.${config.networking.domain}`)";
|
||||
"traefik.http.services.speedtest.loadbalancer.server.port" = "9090";
|
||||
"traefik.tls.options.default.minVersion" = "VersionTLS13";
|
||||
};
|
||||
autoStart = true;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
cfg = config.homelab.apps.technitiumDNS;
|
||||
in {
|
||||
options.homelab.apps.technitiumDNS.enable = lib.mkEnableOption "Technitium DNS";
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
homelab.virtualisation.containers.enable = true;
|
||||
|
||||
virtualisation.oci-containers.containers.technitium-dns = {
|
||||
hostname = "technitium-dns";
|
||||
image = "technitium/dns-server:12.1";
|
||||
ports = [
|
||||
# "5380:5380/tcp" #DNS web console (HTTP)
|
||||
# "53443:53443/tcp" #DNS web console (HTTPS)
|
||||
"53:53/udp" #DNS service
|
||||
"53:53/tcp" #DNS service
|
||||
# "853:853/udp" #DNS-over-QUIC service
|
||||
# "853:853/tcp" #DNS-over-TLS service
|
||||
# "443:443/udp" #DNS-over-HTTPS service (HTTP/3)
|
||||
# "443:443/tcp" #DNS-over-HTTPS service (HTTP/1.1, HTTP/2)
|
||||
# "80:80/tcp" #DNS-over-HTTP service (use with reverse proxy or certbot certificate renewal)
|
||||
# "8053:8053/tcp" #DNS-over-HTTP service (use with reverse proxy)
|
||||
# "67:67/udp" #DHCP service
|
||||
];
|
||||
environment = {
|
||||
# The primary domain name used by this DNS Server to identify itself.
|
||||
DNS_SERVER_DOMAIN = config.networking.hostName;
|
||||
# DNS Server will use IPv6 for querying whenever possible with this option enabled.
|
||||
DNS_SERVER_PREFER_IPV6 = "true";
|
||||
# The TCP port number for the DNS web console over HTTP protocol.
|
||||
# DNS_SERVER_WEB_SERVICE_HTTP_PORT=5380
|
||||
# The TCP port number for the DNS web console over HTTPS protocol.
|
||||
# DNS_SERVER_WEB_SERVICE_HTTPS_PORT=53443
|
||||
# Enables HTTPS for the DNS web console.
|
||||
# DNS_SERVER_WEB_SERVICE_ENABLE_HTTPS=false
|
||||
# Enables self signed TLS certificate for the DNS web console.
|
||||
# DNS_SERVER_WEB_SERVICE_USE_SELF_SIGNED_CERT=false
|
||||
# Enables DNS server optional protocol DNS-over-HTTP on TCP port 8053 to be used with a TLS terminating reverse proxy like nginx.
|
||||
# DNS_SERVER_OPTIONAL_PROTOCOL_DNS_OVER_HTTP=false
|
||||
# Recursion options: Allow, Deny, AllowOnlyForPrivateNetworks, UseSpecifiedNetworks.
|
||||
#nDNS_SERVER_RECURSION=AllowOnlyForPrivateNetworks
|
||||
# Comma separated list of IP addresses or network addresses to deny recursion. Valid only for `UseSpecifiedNetworks` recursion option.
|
||||
# DNS_SERVER_RECURSION_DENIED_NETWORKS=1.1.1.0/24
|
||||
# Comma separated list of IP addresses or network addresses to allow recursion. Valid only for `UseSpecifiedNetworks` recursion option.
|
||||
# DNS_SERVER_RECURSION_ALLOWED_NETWORKS=127.0.0.1, 192.168.1.0/24
|
||||
# Sets the DNS server to block domain names using Blocked Zone and Block List Zone.
|
||||
DNS_SERVER_ENABLE_BLOCKING = "false";
|
||||
# Specifies if the DNS Server should respond with TXT records containing a blocked domain report for TXT type requests.
|
||||
# DNS_SERVER_ALLOW_TXT_BLOCKING_REPORT=false
|
||||
# A comma separated list of block list URLs.
|
||||
# DNS_SERVER_BLOCK_LIST_URLS=
|
||||
#Comma separated list of forwarder addresses.
|
||||
DNS_SERVER_FORWARDERS="195.130.130.2,195.130.131.2";
|
||||
# Forwarder protocol options: Udp, Tcp, Tls, Https, HttpsJson.
|
||||
# DNS_SERVER_FORWARDER_PROTOCOL=Tcp
|
||||
# Enable this option to use local time instead of UTC for logging.
|
||||
# DNS_SERVER_LOG_USING_LOCAL_TIME=true
|
||||
};
|
||||
volumes = [
|
||||
"technitium_dns:/etc/dns"
|
||||
];
|
||||
labels = {
|
||||
"traefik.enable" = "true";
|
||||
"traefik.http.routers.technitium-dns.rule" = "Host(`dns.${config.networking.hostName}.${config.networking.domain}`)";
|
||||
"traefik.http.services.technitium-dns.loadbalancer.server.port" = "5380";
|
||||
"traefik.tls.options.default.minVersion" = "VersionTLS13";
|
||||
};
|
||||
autoStart = true;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.homelab.apps.traefik;
|
||||
|
||||
port = 8080;
|
||||
in {
|
||||
options.homelab.apps.traefik = {
|
||||
enable = lib.mkEnableOption "Traefik Reverse Proxy";
|
||||
sharedNetworkName = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "traefik";
|
||||
description = "The name of the shared network to connect the container to.";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
homelab.virtualisation.containers.enable = true;
|
||||
|
||||
# Make sure the Docker network exists.
|
||||
systemd.services."docker-${cfg.sharedNetworkName}-create-network" = {
|
||||
description = "Create Docker network for ${cfg.sharedNetworkName}";
|
||||
requiredBy = [
|
||||
"docker-traefik.service"
|
||||
];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
script = ''
|
||||
if ! ${pkgs.docker}/bin/docker network ls | grep -q ${cfg.sharedNetworkName}; then
|
||||
${pkgs.docker}/bin/docker network create ${cfg.sharedNetworkName}
|
||||
fi
|
||||
'';
|
||||
};
|
||||
|
||||
virtualisation.oci-containers.containers.traefik = {
|
||||
hostname = "traefik";
|
||||
image = "traefik:v3.4.3";
|
||||
autoStart = true;
|
||||
ports = [
|
||||
"80:80/tcp"
|
||||
"443:443/tcp"
|
||||
"${toString port}:${toString port}/tcp" # Web UI (enabled by --api.insecure=true)
|
||||
];
|
||||
extraOptions = [
|
||||
"--network=${cfg.sharedNetworkName}"
|
||||
];
|
||||
environmentFiles = [
|
||||
/home/admin/.cloudflare.secret
|
||||
];
|
||||
cmd = [
|
||||
"--api.insecure=true"
|
||||
|
||||
# Add Docker provider
|
||||
"--providers.docker=true"
|
||||
"--providers.docker.exposedByDefault=false"
|
||||
|
||||
# Add web entrypoint
|
||||
"--entrypoints.web.address=:80/tcp"
|
||||
"--entrypoints.web.http.redirections.entrypoint.to=websecure"
|
||||
"--entrypoints.web.http.redirections.entrypoint.scheme=https"
|
||||
|
||||
# Add websecure entrypoint
|
||||
"--entrypoints.websecure.address=:443/tcp"
|
||||
"--entrypoints.websecure.http.tls=true"
|
||||
"--entrypoints.websecure.http.tls.certResolver=letsencrypt"
|
||||
"--entrypoints.websecure.http.tls.domains[0].main=depeuter.dev"
|
||||
"--entrypoints.websecure.http.tls.domains[0].sans=*.depeuter.dev"
|
||||
"--entrypoints.websecure.http.tls.domains[1].sans=*.${config.networking.hostName}.depeuter.dev"
|
||||
|
||||
# Certificates
|
||||
"--certificatesresolvers.letsencrypt.acme.dnschallenge=true"
|
||||
"--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare"
|
||||
"--certificatesresolvers.letsencrypt.acme.email=tibo.depeuter@telenet.be"
|
||||
"--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
|
||||
];
|
||||
volumes = [
|
||||
"letsencryp:/letsencrypt"
|
||||
|
||||
"/var/run/docker.sock:/var/run/docker.sock:ro"
|
||||
];
|
||||
labels = {
|
||||
"traefik.enable" = "true";
|
||||
"traefik.http.routers.traefik.rule" = "Host(`traefik.${config.networking.hostName}.depeuter.dev`)";
|
||||
"traefik.http.services.traefik.loadbalancer.server.port" = toString port;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,656 +0,0 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.homelab.apps.vaultwarden;
|
||||
|
||||
networkName = "vaultwarden";
|
||||
in {
|
||||
options.homelab.apps.vaultwarden = {
|
||||
enable = lib.mkEnableOption "Vaultwarden";
|
||||
port = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
default = 10102;
|
||||
description = "Vaultwarden WebUI port";
|
||||
};
|
||||
domain = lib.mkOption {
|
||||
type = lib.types.string;
|
||||
example = "https://vault.depeuter.dev";
|
||||
description = "Domain to configure Vaultwarden on";
|
||||
};
|
||||
name = lib.mkOption {
|
||||
type = lib.types.string;
|
||||
example = "Hugo's Vault";
|
||||
description = "Service name to use for invitations and mail";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
homelab = {
|
||||
# Allow remote backups.
|
||||
users.backup.enable = true;
|
||||
|
||||
virtualisation.containers.enable = true;
|
||||
};
|
||||
|
||||
# Make sure the Docker network exists.
|
||||
systemd.services."docker-${networkName}-create-network" = {
|
||||
description = "Create Docker network for ${networkName}";
|
||||
requiredBy = [
|
||||
"docker-vaultwarden-db.service"
|
||||
"docker-vaultwarden.service"
|
||||
];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
script = ''
|
||||
if ! ${pkgs.docker}/bin/docker network ls | grep -q ${networkName}; then
|
||||
${pkgs.docker}/bin/docker network create ${networkName}
|
||||
fi
|
||||
'';
|
||||
};
|
||||
|
||||
virtualisation.oci-containers.containers = let
|
||||
dbHostname = "vaultwarden-db";
|
||||
dbPort = 5432;
|
||||
in {
|
||||
vaultwardenDb = {
|
||||
hostname = dbHostname;
|
||||
image = "postgres:15.8-alpine";
|
||||
autoStart = true;
|
||||
ports = [
|
||||
"${toString dbPort}:5432/tcp"
|
||||
];
|
||||
extraOptions = [
|
||||
"--network=${networkName}"
|
||||
];
|
||||
environment = {
|
||||
POSTGRES_PASSWORD = "ChangeMe";
|
||||
PGDATA = "/var/lib/postgresql/data/pgdata";
|
||||
};
|
||||
volumes = [
|
||||
"vaultwarden-db:/var/lib/postgresql/data"
|
||||
];
|
||||
};
|
||||
|
||||
vaultwarden = let
|
||||
dataDir = "/data";
|
||||
in {
|
||||
hostname = "vaultwarden";
|
||||
image = "vaultwarden/server:1.34.3-alpine";
|
||||
autoStart = true;
|
||||
ports = [
|
||||
"${toString cfg.port}:80/tcp"
|
||||
];
|
||||
extraOptions = [
|
||||
"--network=${networkName}"
|
||||
];
|
||||
dependsOn = [
|
||||
"vaultwardenDb"
|
||||
];
|
||||
volumes = [
|
||||
"vaultwarden:${dataDir}"
|
||||
];
|
||||
environmentFiles = [
|
||||
# NOTE Don't forget to create this file
|
||||
# TODO Put in place using age(nix)?
|
||||
"/var/lib/vaultwarden.env"
|
||||
];
|
||||
environment = {
|
||||
####################
|
||||
### Data folders ###
|
||||
####################
|
||||
|
||||
## Main data folder
|
||||
DATA_FOLDER = dataDir;
|
||||
|
||||
## Individual folders, these override %DATA_FOLDER%
|
||||
# ICON_CACHE_FOLDER=data/icon_cache
|
||||
# ATTACHMENTS_FOLDER=data/attachments
|
||||
# SENDS_FOLDER=data/sends
|
||||
# TMP_FOLDER=data/tmp
|
||||
|
||||
## Templates data folder, by default uses embedded templates
|
||||
## Check source code to see the format
|
||||
# TEMPLATES_FOLDER=data/templates
|
||||
## Automatically reload the templates for every request, slow, use only for development
|
||||
# RELOAD_TEMPLATES=false
|
||||
|
||||
## Web vault settings
|
||||
# WEB_VAULT_FOLDER=web-vault/
|
||||
# WEB_VAULT_ENABLED=true
|
||||
|
||||
#########################
|
||||
### Database settings ###
|
||||
#########################
|
||||
|
||||
## Database URL
|
||||
## When using SQLite, this is the path to the DB file, default to %DATA_FOLDER%/db.sqlite3
|
||||
# DATABASE_URL=data/db.sqlite3
|
||||
## When using MySQL, specify an appropriate connection URI.
|
||||
## Details: https://docs.diesel.rs/2.1.x/diesel/mysql/struct.MysqlConnection.html
|
||||
# DATABASE_URL=mysql://user:password@host[:port]/database_name
|
||||
## When using PostgreSQL, specify an appropriate connection URI (recommended)
|
||||
## or keyword/value connection string.
|
||||
## Details:
|
||||
## - https://docs.diesel.rs/2.1.x/diesel/pg/struct.PgConnection.html
|
||||
## - https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING
|
||||
DATABASE_URL = "postgresql://vaultwarden:ChangeMe@${dbHostname}:${toString dbPort}/vaultwarden";
|
||||
|
||||
## Enable WAL for the DB
|
||||
## Set to false to avoid enabling WAL during startup.
|
||||
## Note that if the DB already has WAL enabled, you will also need to disable WAL in the DB,
|
||||
## this setting only prevents Vaultwarden from automatically enabling it on start.
|
||||
## Please read project wiki page about this setting first before changing the value as it can
|
||||
## cause performance degradation or might render the service unable to start.
|
||||
# ENABLE_DB_WAL=true
|
||||
|
||||
## Database connection retries
|
||||
## Number of times to retry the database connection during startup, with 1 second delay between each retry, set to 0 to retry indefinitely
|
||||
# DB_CONNECTION_RETRIES=15
|
||||
|
||||
## Database timeout
|
||||
## Timeout when acquiring database connection
|
||||
# DATABASE_TIMEOUT=30
|
||||
|
||||
## Database max connections
|
||||
## Define the size of the connection pool used for connecting to the database.
|
||||
# DATABASE_MAX_CONNS=10
|
||||
|
||||
## Database connection initialization
|
||||
## Allows SQL statements to be run whenever a new database connection is created.
|
||||
## This is mainly useful for connection-scoped pragmas.
|
||||
## If empty, a database-specific default is used:
|
||||
## - SQLite: "PRAGMA busy_timeout = 5000; PRAGMA synchronous = NORMAL;"
|
||||
## - MySQL: ""
|
||||
## - PostgreSQL: ""
|
||||
# DATABASE_CONN_INIT=""
|
||||
|
||||
#################
|
||||
### WebSocket ###
|
||||
#################
|
||||
|
||||
## Enable websocket notifications
|
||||
# ENABLE_WEBSOCKET=true
|
||||
|
||||
##########################
|
||||
### Push notifications ###
|
||||
##########################
|
||||
|
||||
## Enables push notifications (requires key and id from https://bitwarden.com/host)
|
||||
## Details about mobile client push notification:
|
||||
## - https://github.com/dani-garcia/vaultwarden/wiki/Enabling-Mobile-Client-push-notification
|
||||
# PUSH_ENABLED=false
|
||||
# PUSH_INSTALLATION_ID=CHANGEME
|
||||
# PUSH_INSTALLATION_KEY=CHANGEME
|
||||
|
||||
# WARNING: Do not modify the following settings unless you fully understand their implications!
|
||||
# Default Push Relay and Identity URIs
|
||||
# PUSH_RELAY_URI=https://push.bitwarden.com
|
||||
# PUSH_IDENTITY_URI=https://identity.bitwarden.com
|
||||
# European Union Data Region Settings
|
||||
# If you have selected "European Union" as your data region, use the following URIs instead.
|
||||
# PUSH_RELAY_URI=https://api.bitwarden.eu
|
||||
# PUSH_IDENTITY_URI=https://identity.bitwarden.eu
|
||||
|
||||
#####################
|
||||
### Schedule jobs ###
|
||||
#####################
|
||||
|
||||
## Job scheduler settings
|
||||
##
|
||||
## Job schedules use a cron-like syntax (as parsed by https://crates.io/crates/cron),
|
||||
## and are always in terms of UTC time (regardless of your local time zone settings).
|
||||
##
|
||||
## The schedule format is a bit different from crontab as crontab does not contains seconds.
|
||||
## You can test the the format here: https://crontab.guru, but remove the first digit!
|
||||
## SEC MIN HOUR DAY OF MONTH MONTH DAY OF WEEK
|
||||
## "0 30 9,12,15 1,15 May-Aug Mon,Wed,Fri"
|
||||
## "0 30 * * * * "
|
||||
## "0 30 1 * * * "
|
||||
##
|
||||
## How often (in ms) the job scheduler thread checks for jobs that need running.
|
||||
## Set to 0 to globally disable scheduled jobs.
|
||||
# JOB_POLL_INTERVAL_MS=30000
|
||||
##
|
||||
## Cron schedule of the job that checks for Sends past their deletion date.
|
||||
## Defaults to hourly (5 minutes after the hour). Set blank to disable this job.
|
||||
# SEND_PURGE_SCHEDULE="0 5 * * * *"
|
||||
##
|
||||
## Cron schedule of the job that checks for trashed items to delete permanently.
|
||||
## Defaults to daily (5 minutes after midnight). Set blank to disable this job.
|
||||
# TRASH_PURGE_SCHEDULE="0 5 0 * * *"
|
||||
##
|
||||
## Cron schedule of the job that checks for incomplete 2FA logins.
|
||||
## Defaults to once every minute. Set blank to disable this job.
|
||||
# INCOMPLETE_2FA_SCHEDULE="30 * * * * *"
|
||||
##
|
||||
## Cron schedule of the job that sends expiration reminders to emergency access grantors.
|
||||
## Defaults to hourly (3 minutes after the hour). Set blank to disable this job.
|
||||
# EMERGENCY_NOTIFICATION_REMINDER_SCHEDULE="0 3 * * * *"
|
||||
##
|
||||
## Cron schedule of the job that grants emergency access requests that have met the required wait time.
|
||||
## Defaults to hourly (7 minutes after the hour). Set blank to disable this job.
|
||||
# EMERGENCY_REQUEST_TIMEOUT_SCHEDULE="0 7 * * * *"
|
||||
##
|
||||
## Cron schedule of the job that cleans old events from the event table.
|
||||
## Defaults to daily. Set blank to disable this job. Also without EVENTS_DAYS_RETAIN set, this job will not start.
|
||||
# EVENT_CLEANUP_SCHEDULE="0 10 0 * * *"
|
||||
## Number of days to retain events stored in the database.
|
||||
## If unset (the default), events are kept indefinitely and the scheduled job is disabled!
|
||||
# EVENTS_DAYS_RETAIN=
|
||||
##
|
||||
## Cron schedule of the job that cleans old auth requests from the auth request.
|
||||
## Defaults to every minute. Set blank to disable this job.
|
||||
# AUTH_REQUEST_PURGE_SCHEDULE="30 * * * * *"
|
||||
##
|
||||
## Cron schedule of the job that cleans expired Duo contexts from the database. Does nothing if Duo MFA is disabled or set to use the legacy iframe prompt.
|
||||
## Defaults to every minute. Set blank to disable this job.
|
||||
# DUO_CONTEXT_PURGE_SCHEDULE="30 * * * * *"
|
||||
|
||||
########################
|
||||
### General settings ###
|
||||
########################
|
||||
|
||||
## Domain settings
|
||||
## The domain must match the address from where you access the server
|
||||
## It's recommended to configure this value, otherwise certain functionality might not work,
|
||||
## like attachment downloads, email links and U2F.
|
||||
## For U2F to work, the server must use HTTPS, you can use Let's Encrypt for free certs
|
||||
## To use HTTPS, the recommended way is to put Vaultwarden behind a reverse proxy
|
||||
## Details:
|
||||
## - https://github.com/dani-garcia/vaultwarden/wiki/Enabling-HTTPS
|
||||
## - https://github.com/dani-garcia/vaultwarden/wiki/Proxy-examples
|
||||
## For development
|
||||
# DOMAIN=http://localhost
|
||||
## For public server
|
||||
DOMAIN = cfg.domain;
|
||||
## For public server (URL with port number)
|
||||
# DOMAIN=https://vw.domain.tld:8443
|
||||
## For public server (URL with path)
|
||||
# DOMAIN=https://domain.tld/vw
|
||||
|
||||
## Controls whether users are allowed to create Bitwarden Sends.
|
||||
## This setting applies globally to all users.
|
||||
## To control this on a per-org basis instead, use the "Disable Send" org policy.
|
||||
# SENDS_ALLOWED=true
|
||||
|
||||
## HIBP Api Key
|
||||
## HaveIBeenPwned API Key, request it here: https://haveibeenpwned.com/API/Key
|
||||
# HIBP_API_KEY=
|
||||
|
||||
## Per-organization attachment storage limit (KB)
|
||||
## Max kilobytes of attachment storage allowed per organization.
|
||||
## When this limit is reached, organization members will not be allowed to upload further attachments for ciphers owned by that organization.
|
||||
# ORG_ATTACHMENT_LIMIT=
|
||||
## Per-user attachment storage limit (KB)
|
||||
## Max kilobytes of attachment storage allowed per user.
|
||||
## When this limit is reached, the user will not be allowed to upload further attachments.
|
||||
# USER_ATTACHMENT_LIMIT=
|
||||
## Per-user send storage limit (KB)
|
||||
## Max kilobytes of send storage allowed per user.
|
||||
## When this limit is reached, the user will not be allowed to upload further sends.
|
||||
# USER_SEND_LIMIT=
|
||||
|
||||
## Number of days to wait before auto-deleting a trashed item.
|
||||
## If unset (the default), trashed items are not auto-deleted.
|
||||
## This setting applies globally, so make sure to inform all users of any changes to this setting.
|
||||
# TRASH_AUTO_DELETE_DAYS=
|
||||
|
||||
## Number of minutes to wait before a 2FA-enabled login is considered incomplete,
|
||||
## resulting in an email notification. An incomplete 2FA login is one where the correct
|
||||
## master password was provided but the required 2FA step was not completed, which
|
||||
## potentially indicates a master password compromise. Set to 0 to disable this check.
|
||||
## This setting applies globally to all users.
|
||||
# INCOMPLETE_2FA_TIME_LIMIT=3
|
||||
|
||||
## Disable icon downloading
|
||||
## Set to true to disable icon downloading in the internal icon service.
|
||||
## This still serves existing icons from $ICON_CACHE_FOLDER, without generating any external
|
||||
## network requests. $ICON_CACHE_TTL must also be set to 0; otherwise, the existing icons
|
||||
## will be deleted eventually, but won't be downloaded again.
|
||||
# DISABLE_ICON_DOWNLOAD=false
|
||||
|
||||
## Controls if new users can register
|
||||
SIGNUPS_ALLOWED = "false";
|
||||
|
||||
## Controls if new users need to verify their email address upon registration
|
||||
## Note that setting this option to true prevents logins until the email address has been verified!
|
||||
## The welcome email will include a verification link, and login attempts will periodically
|
||||
## trigger another verification email to be sent.
|
||||
SIGNUPS_VERIFY = "false";
|
||||
|
||||
## If SIGNUPS_VERIFY is set to true, this limits how many seconds after the last time
|
||||
## an email verification link has been sent another verification email will be sent
|
||||
# SIGNUPS_VERIFY_RESEND_TIME=3600
|
||||
|
||||
## If SIGNUPS_VERIFY is set to true, this limits how many times an email verification
|
||||
## email will be re-sent upon an attempted login.
|
||||
# SIGNUPS_VERIFY_RESEND_LIMIT=6
|
||||
|
||||
## Controls if new users from a list of comma-separated domains can register
|
||||
## even if SIGNUPS_ALLOWED is set to false
|
||||
# SIGNUPS_DOMAINS_WHITELIST=example.com,example.net,example.org
|
||||
|
||||
## Controls whether event logging is enabled for organizations
|
||||
## This setting applies to organizations.
|
||||
## Disabled by default. Also check the EVENT_CLEANUP_SCHEDULE and EVENTS_DAYS_RETAIN settings.
|
||||
# ORG_EVENTS_ENABLED=false
|
||||
|
||||
## Controls which users can create new orgs.
|
||||
## Blank or 'all' means all users can create orgs (this is the default):
|
||||
# ORG_CREATION_USERS=
|
||||
## 'none' means no users can create orgs:
|
||||
# ORG_CREATION_USERS=none
|
||||
## A comma-separated list means only those users can create orgs:
|
||||
# ORG_CREATION_USERS=admin1@example.com,admin2@example.com
|
||||
|
||||
## Invitations org admins to invite users, even when signups are disabled
|
||||
# INVITATIONS_ALLOWED=true
|
||||
## Name shown in the invitation emails that don't come from a specific organization
|
||||
INVITATION_ORG_NAME = cfg.name;
|
||||
|
||||
## The number of hours after which an organization invite token, emergency access invite token,
|
||||
## email verification token and deletion request token will expire (must be at least 1)
|
||||
# INVITATION_EXPIRATION_HOURS=120
|
||||
|
||||
## Controls whether users can enable emergency access to their accounts.
|
||||
## This setting applies globally to all users.
|
||||
# EMERGENCY_ACCESS_ALLOWED=true
|
||||
|
||||
## Controls whether users can change their email.
|
||||
## This setting applies globally to all users
|
||||
# EMAIL_CHANGE_ALLOWED=true
|
||||
|
||||
## Number of server-side passwords hashing iterations for the password hash.
|
||||
## The default for new users. If changed, it will be updated during login for existing users.
|
||||
# PASSWORD_ITERATIONS=600000
|
||||
|
||||
## Controls whether users can set password hints. This setting applies globally to all users.
|
||||
# PASSWORD_HINTS_ALLOWED=true
|
||||
|
||||
## Controls whether a password hint should be shown directly in the web page if
|
||||
## SMTP service is not configured. Not recommended for publicly-accessible instances
|
||||
## as this provides unauthenticated access to potentially sensitive data.
|
||||
SHOW_PASSWORD_HINT = "false";
|
||||
|
||||
#########################
|
||||
### Advanced settings ###
|
||||
#########################
|
||||
|
||||
## Client IP Header, used to identify the IP of the client, defaults to "X-Real-IP"
|
||||
## Set to the string "none" (without quotes), to disable any headers and just use the remote IP
|
||||
# IP_HEADER=X-Real-IP
|
||||
|
||||
## Icon service
|
||||
## The predefined icon services are: internal, bitwarden, duckduckgo, google.
|
||||
## To specify a custom icon service, set a URL template with exactly one instance of `{}`,
|
||||
## which is replaced with the domain. For example: `https://icon.example.com/domain/{}`.
|
||||
##
|
||||
## `internal` refers to Vaultwarden's built-in icon fetching implementation.
|
||||
## If an external service is set, an icon request to Vaultwarden will return an HTTP
|
||||
## redirect to the corresponding icon at the external service. An external service may
|
||||
## be useful if your Vaultwarden instance has no external network connectivity, or if
|
||||
## you are concerned that someone may probe your instance to try to detect whether icons
|
||||
## for certain sites have been cached.
|
||||
# ICON_SERVICE=internal
|
||||
|
||||
## Icon redirect code
|
||||
## The HTTP status code to use for redirects to an external icon service.
|
||||
## The supported codes are 301 (legacy permanent), 302 (legacy temporary), 307 (temporary), and 308 (permanent).
|
||||
## Temporary redirects are useful while testing different icon services, but once a service
|
||||
## has been decided on, consider using permanent redirects for cacheability. The legacy codes
|
||||
## are currently better supported by the Bitwarden clients.
|
||||
# ICON_REDIRECT_CODE=302
|
||||
|
||||
## Cache time-to-live for successfully obtained icons, in seconds (0 is "forever")
|
||||
## Default: 2592000 (30 days)
|
||||
# ICON_CACHE_TTL=2592000
|
||||
## Cache time-to-live for icons which weren't available, in seconds (0 is "forever")
|
||||
## Default: 2592000 (3 days)
|
||||
# ICON_CACHE_NEGTTL=259200
|
||||
|
||||
## Icon download timeout
|
||||
## Configure the timeout value when downloading the favicons.
|
||||
## The default is 10 seconds, but this could be to low on slower network connections
|
||||
# ICON_DOWNLOAD_TIMEOUT=10
|
||||
|
||||
## Block HTTP domains/IPs by Regex
|
||||
## Any domains or IPs that match this regex won't be fetched by the internal HTTP client.
|
||||
## Useful to hide other servers in the local network. Check the WIKI for more details
|
||||
## NOTE: Always enclose this regex withing single quotes!
|
||||
# HTTP_REQUEST_BLOCK_REGEX='^(192\.168\.0\.[0-9]+|192\.168\.1\.[0-9]+)$'
|
||||
|
||||
## Enabling this will cause the internal HTTP client to refuse to connect to any non global IP address.
|
||||
## Useful to secure your internal environment: See https://en.wikipedia.org/wiki/Reserved_IP_addresses for a list of IPs which it will block
|
||||
# HTTP_REQUEST_BLOCK_NON_GLOBAL_IPS=true
|
||||
|
||||
## Client Settings
|
||||
## Enable experimental feature flags for clients.
|
||||
## This is a comma-separated list of flags, e.g. "flag1,flag2,flag3".
|
||||
##
|
||||
## The following flags are available:
|
||||
## - "autofill-overlay": Add an overlay menu to form fields for quick access to credentials.
|
||||
## - "autofill-v2": Use the new autofill implementation.
|
||||
## - "browser-fileless-import": Directly import credentials from other providers without a file.
|
||||
## - "fido2-vault-credentials": Enable the use of FIDO2 security keys as second factor.
|
||||
# EXPERIMENTAL_CLIENT_FEATURE_FLAGS=fido2-vault-credentials
|
||||
|
||||
## Require new device emails. When a user logs in an email is required to be sent.
|
||||
## If sending the email fails the login attempt will fail!!
|
||||
# REQUIRE_DEVICE_EMAIL=false
|
||||
|
||||
## Enable extended logging, which shows timestamps and targets in the logs
|
||||
# EXTENDED_LOGGING=true
|
||||
|
||||
## Timestamp format used in extended logging.
|
||||
## Format specifiers: https://docs.rs/chrono/latest/chrono/format/strftime
|
||||
# LOG_TIMESTAMP_FORMAT="%Y-%m-%d %H:%M:%S.%3f"
|
||||
|
||||
## Logging to Syslog
|
||||
## This requires extended logging
|
||||
# USE_SYSLOG=false
|
||||
|
||||
## Logging to file
|
||||
# LOG_FILE=/path/to/log
|
||||
|
||||
## Log level
|
||||
## Change the verbosity of the log output
|
||||
## Valid values are "trace", "debug", "info", "warn", "error" and "off"
|
||||
## Setting it to "trace" or "debug" would also show logs for mounted routes and static file, websocket and alive requests
|
||||
## For a specific module append a comma separated `path::to::module=log_level`
|
||||
## For example, to only see debug logs for icons use: LOG_LEVEL="info,vaultwarden::api::icons=debug"
|
||||
LOG_LEVEL = "warn";
|
||||
|
||||
## Token for the admin interface, preferably an Argon2 PCH string
|
||||
## Vaultwarden has a built-in generator by calling `vaultwarden hash`
|
||||
## For details see: https://github.com/dani-garcia/vaultwarden/wiki/Enabling-admin-page#secure-the-admin_token
|
||||
## If not set, the admin panel is disabled
|
||||
## New Argon2 PHC string
|
||||
## Note that for some environments, like docker-compose you need to escape all the dollar signs `$` with an extra dollar sign like `$$`
|
||||
## Also, use single quotes (') instead of double quotes (") to enclose the string when needed
|
||||
# ADMIN_TOKEN='$argon2id$v=19$m=65540,t=3,p=4$MmeKRnGK5RW5mJS7h3TOL89GrpLPXJPAtTK8FTqj9HM$DqsstvoSAETl9YhnsXbf43WeaUwJC6JhViIvuPoig78'
|
||||
## Old plain text string (Will generate warnings in favor of Argon2)
|
||||
# ADMIN_TOKEN=Vy2VyYTTsKPv8W5aEOWUbB/Bt3DEKePbHmI4m9VcemUMS2rEviDowNAFqYi1xjmp
|
||||
|
||||
## Enable this to bypass the admin panel security. This option is only
|
||||
## meant to be used with the use of a separate auth layer in front
|
||||
# DISABLE_ADMIN_TOKEN=false
|
||||
|
||||
## Number of seconds, on average, between admin login requests from the same IP address before rate limiting kicks in.
|
||||
# ADMIN_RATELIMIT_SECONDS=300
|
||||
## Allow a burst of requests of up to this size, while maintaining the average indicated by `ADMIN_RATELIMIT_SECONDS`.
|
||||
# ADMIN_RATELIMIT_MAX_BURST=3
|
||||
|
||||
## Set the lifetime of admin sessions to this value (in minutes).
|
||||
# ADMIN_SESSION_LIFETIME=20
|
||||
|
||||
## Allowed iframe ancestors (Know the risks!)
|
||||
## https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors
|
||||
## Allows other domains to embed the web vault into an iframe, useful for embedding into secure intranets
|
||||
## This adds the configured value to the 'Content-Security-Policy' headers 'frame-ancestors' value.
|
||||
## Multiple values must be separated with a whitespace.
|
||||
# ALLOWED_IFRAME_ANCESTORS=
|
||||
|
||||
## Number of seconds, on average, between login requests from the same IP address before rate limiting kicks in.
|
||||
# LOGIN_RATELIMIT_SECONDS=60
|
||||
## Allow a burst of requests of up to this size, while maintaining the average indicated by `LOGIN_RATELIMIT_SECONDS`.
|
||||
## Note that this applies to both the login and the 2FA, so it's recommended to allow a burst size of at least 2.
|
||||
# LOGIN_RATELIMIT_MAX_BURST=10
|
||||
|
||||
## BETA FEATURE: Groups
|
||||
## Controls whether group support is enabled for organizations
|
||||
## This setting applies to organizations.
|
||||
## Disabled by default because this is a beta feature, it contains known issues!
|
||||
## KNOW WHAT YOU ARE DOING!
|
||||
# ORG_GROUPS_ENABLED=false
|
||||
|
||||
## Increase secure note size limit (Know the risks!)
|
||||
## Sets the secure note size limit to 100_000 instead of the default 10_000.
|
||||
## WARNING: This could cause issues with clients. Also exports will not work on Bitwarden servers!
|
||||
## KNOW WHAT YOU ARE DOING!
|
||||
# INCREASE_NOTE_SIZE_LIMIT=false
|
||||
|
||||
## Enforce Single Org with Reset Password Policy
|
||||
## Enforce that the Single Org policy is enabled before setting the Reset Password policy
|
||||
## Bitwarden enforces this by default. In Vaultwarden we encouraged to use multiple organizations because groups were not available.
|
||||
## Setting this to true will enforce the Single Org Policy to be enabled before you can enable the Reset Password policy.
|
||||
# ENFORCE_SINGLE_ORG_WITH_RESET_PW_POLICY=false
|
||||
|
||||
########################
|
||||
### MFA/2FA settings ###
|
||||
########################
|
||||
|
||||
## Yubico (Yubikey) Settings
|
||||
## Set your Client ID and Secret Key for Yubikey OTP
|
||||
## You can generate it here: https://upgrade.yubico.com/getapikey/
|
||||
## You can optionally specify a custom OTP server
|
||||
# YUBICO_CLIENT_ID=11111
|
||||
# YUBICO_SECRET_KEY=AAAAAAAAAAAAAAAAAAAAAAAA
|
||||
# YUBICO_SERVER=http://yourdomain.com/wsapi/2.0/verify
|
||||
|
||||
## Duo Settings
|
||||
## You need to configure the DUO_IKEY, DUO_SKEY, and DUO_HOST options to enable global Duo support.
|
||||
## Otherwise users will need to configure it themselves.
|
||||
## Create an account and protect an application as mentioned in this link (only the first step, not the rest):
|
||||
## https://help.bitwarden.com/article/setup-two-step-login-duo/#create-a-duo-security-account
|
||||
## Then set the following options, based on the values obtained from the last step:
|
||||
# DUO_IKEY=<Client ID>
|
||||
# DUO_SKEY=<Client Secret>
|
||||
# DUO_HOST=<API Hostname>
|
||||
## After that, you should be able to follow the rest of the guide linked above,
|
||||
## ignoring the fields that ask for the values that you already configured beforehand.
|
||||
##
|
||||
## If you want to attempt to use Duo's 'Traditional Prompt' (deprecated, iframe based) set DUO_USE_IFRAME to 'true'.
|
||||
## Duo no longer supports this, but it still works for some integrations.
|
||||
## If you aren't sure, leave this alone.
|
||||
# DUO_USE_IFRAME=false
|
||||
|
||||
## Email 2FA settings
|
||||
## Email token size
|
||||
## Number of digits in an email 2FA token (min: 6, max: 255).
|
||||
## Note that the Bitwarden clients are hardcoded to mention 6 digit codes regardless of this setting!
|
||||
# EMAIL_TOKEN_SIZE=6
|
||||
##
|
||||
## Token expiration time
|
||||
## Maximum time in seconds a token is valid. The time the user has to open email client and copy token.
|
||||
# EMAIL_EXPIRATION_TIME=600
|
||||
##
|
||||
## Maximum attempts before an email token is reset and a new email will need to be sent.
|
||||
# EMAIL_ATTEMPTS_LIMIT=3
|
||||
##
|
||||
## Setup email 2FA regardless of any organization policy
|
||||
# EMAIL_2FA_ENFORCE_ON_VERIFIED_INVITE=false
|
||||
## Automatically setup email 2FA as fallback provider when needed
|
||||
# EMAIL_2FA_AUTO_FALLBACK=false
|
||||
|
||||
## Other MFA/2FA settings
|
||||
## Disable 2FA remember
|
||||
## Enabling this would force the users to use a second factor to login every time.
|
||||
## Note that the checkbox would still be present, but ignored.
|
||||
# DISABLE_2FA_REMEMBER=false
|
||||
##
|
||||
## Authenticator Settings
|
||||
## Disable authenticator time drifted codes to be valid.
|
||||
## TOTP codes of the previous and next 30 seconds will be invalid
|
||||
##
|
||||
## According to the RFC6238 (https://tools.ietf.org/html/rfc6238),
|
||||
## we allow by default the TOTP code which was valid one step back and one in the future.
|
||||
## This can however allow attackers to be a bit more lucky with there attempts because there are 3 valid codes.
|
||||
## You can disable this, so that only the current TOTP Code is allowed.
|
||||
## Keep in mind that when a sever drifts out of time, valid codes could be marked as invalid.
|
||||
## In any case, if a code has been used it can not be used again, also codes which predates it will be invalid.
|
||||
# AUTHENTICATOR_DISABLE_TIME_DRIFT=false
|
||||
|
||||
###########################
|
||||
### SMTP Email settings ###
|
||||
###########################
|
||||
|
||||
## Mail specific settings, set SMTP_FROM and either SMTP_HOST or USE_SENDMAIL to enable the mail service.
|
||||
## To make sure the email links are pointing to the correct host, set the DOMAIN variable.
|
||||
## Note: if SMTP_USERNAME is specified, SMTP_PASSWORD is mandatory
|
||||
SMTP_HOST = "smtp.gmail.com";
|
||||
SMTP_FROM = "vault@depeuter.dev";
|
||||
SMTP_FROM_NAME = cfg.name;
|
||||
# SMTP_USERNAME=username
|
||||
# SMTP_PASSWORD=password
|
||||
# SMTP_TIMEOUT=15
|
||||
|
||||
## Choose the type of secure connection for SMTP. The default is "starttls".
|
||||
## The available options are:
|
||||
## - "starttls": The default port is 587.
|
||||
## - "force_tls": The default port is 465.
|
||||
## - "off": The default port is 25.
|
||||
## Ports 587 (submission) and 25 (smtp) are standard without encryption and with encryption via STARTTLS (Explicit TLS). Port 465 (submissions) is used for encrypted submission (Implicit TLS).
|
||||
SMTP_SECURITY = "starttls";
|
||||
SMTP_PORT = "587";
|
||||
|
||||
# Whether to send mail via the `sendmail` command
|
||||
# USE_SENDMAIL=false
|
||||
# Which sendmail command to use. The one found in the $PATH is used if not specified.
|
||||
# SENDMAIL_COMMAND="/path/to/sendmail"
|
||||
|
||||
## Defaults for SSL is "Plain" and "Login" and nothing for Non-SSL connections.
|
||||
## Possible values: ["Plain", "Login", "Xoauth2"].
|
||||
## Multiple options need to be separated by a comma ','.
|
||||
SMTP_AUTH_MECHANISM = "Login";
|
||||
|
||||
## Server name sent during the SMTP HELO
|
||||
## By default this value should be is on the machine's hostname,
|
||||
## but might need to be changed in case it trips some anti-spam filters
|
||||
# HELO_NAME=
|
||||
|
||||
## Embed images as email attachments
|
||||
# SMTP_EMBED_IMAGES=true
|
||||
|
||||
## SMTP debugging
|
||||
## When set to true this will output very detailed SMTP messages.
|
||||
## WARNING: This could contain sensitive information like passwords and usernames! Only enable this during troubleshooting!
|
||||
# SMTP_DEBUG=false
|
||||
|
||||
## Accept Invalid Certificates
|
||||
## DANGEROUS: This option introduces significant vulnerabilities to man-in-the-middle attacks!
|
||||
## Only use this as a last resort if you are not able to use a valid certificate.
|
||||
## If the Certificate is valid but the hostname doesn't match, please use SMTP_ACCEPT_INVALID_HOSTNAMES instead.
|
||||
# SMTP_ACCEPT_INVALID_CERTS=false
|
||||
|
||||
## Accept Invalid Hostnames
|
||||
## DANGEROUS: This option introduces significant vulnerabilities to man-in-the-middle attacks!
|
||||
## Only use this as a last resort if you are not able to use a valid certificate.
|
||||
# SMTP_ACCEPT_INVALID_HOSTNAMES=false
|
||||
|
||||
#######################
|
||||
### Rocket settings ###
|
||||
#######################
|
||||
|
||||
## Rocket specific settings
|
||||
## See https://rocket.rs/v0.5/guide/configuration/ for more details.
|
||||
# ROCKET_ADDRESS=0.0.0.0
|
||||
## The default port is 8000, unless running in a Docker container, in which case it is 80.
|
||||
# ROCKET_PORT=8000
|
||||
# ROCKET_TLS={certs="/path/to/certs.pem",key="/path/to/key.pem"}
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,10 +1,7 @@
|
|||
{
|
||||
imports = [
|
||||
./apps
|
||||
./fileSystems
|
||||
./common
|
||||
./services
|
||||
./virtualisation
|
||||
|
||||
./common
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
imports = [
|
||||
./media
|
||||
];
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
imports = [
|
||||
./video
|
||||
];
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
cfg = config.homelab.fileSystems.media.video;
|
||||
|
||||
remotePath = "/mnt/SMALL/MEDIA/VIDEO";
|
||||
|
||||
maxPermissions = permissions:
|
||||
if builtins.elem "write" permissions then "rw"
|
||||
else "ro";
|
||||
permissionsOption = maxPermissions cfg.permissions;
|
||||
in {
|
||||
options.homelab.fileSystems.media.video = {
|
||||
enable = lib.mkEnableOption "MEDIA/VIDEO dataset";
|
||||
hostPath = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
default = "/srv/video";
|
||||
description = "Mountpath on host";
|
||||
};
|
||||
permissions = lib.mkOption {
|
||||
type = lib.types.listOf (lib.types.enum [ "read" "write" ]);
|
||||
default = [ "read" ];
|
||||
description = "Mount options permissions";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
fileSystems."${cfg.hostPath}" = {
|
||||
device = "192.168.0.11:${remotePath}";
|
||||
fsType = "nfs";
|
||||
options = [
|
||||
permissionsOption
|
||||
"auto"
|
||||
"nfsvers=4.2"
|
||||
"async" "soft"
|
||||
"rsize=1048576" "wsize=1048576"
|
||||
"timeo=600" "retry=50" "retrans=2" "actimeo=1800" "lookupcache=all"
|
||||
"_netdev" "nosuid" "tcp"
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.homelab.services.actions;
|
||||
in {
|
||||
options.homelab.services.actions.enable = lib.mkEnableOption "Actions runner";
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
homelab.virtualisation.containers.enable = true;
|
||||
|
||||
services.gitea-actions-runner = {
|
||||
instances.depeuter-dev = {
|
||||
enable = true;
|
||||
url = "https://git.depeuter.dev";
|
||||
tokenFile = "/etc/runner/depeuter-dev";
|
||||
name = config.networking.hostName;
|
||||
labels = [
|
||||
"debian-11:docker://debian:11"
|
||||
"debian-12:docker://debian:12"
|
||||
"debian-latest:docker://debian:latest"
|
||||
"docker:host"
|
||||
"Linux:host"
|
||||
"self-hosted:host"
|
||||
"ubuntu-22.04:docker://ubuntu:22.04"
|
||||
"ubuntu-24.04:docker://ubuntu:24.04"
|
||||
"ubuntu-latest:docker://ubuntu:latest"
|
||||
];
|
||||
settings = {
|
||||
cache.enabled = true;
|
||||
container.privileged = true;
|
||||
};
|
||||
hostPackages = with pkgs; [
|
||||
bash
|
||||
cmake
|
||||
coreutils
|
||||
curl
|
||||
docker
|
||||
gawk
|
||||
git
|
||||
gnused
|
||||
nodejs
|
||||
openssh
|
||||
wget
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
{
|
||||
imports = [
|
||||
./actions
|
||||
./openssh
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.homelab.virtualisation.containers;
|
||||
in {
|
||||
options.homelab.virtualisation.containers.enable = lib.mkEnableOption "OCI containers";
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
environment.systemPackages = with pkgs; [
|
||||
nfs-utils
|
||||
];
|
||||
|
||||
virtualisation = {
|
||||
docker = {
|
||||
enable = true;
|
||||
enableOnBoot = true;
|
||||
autoPrune.enable = true;
|
||||
};
|
||||
|
||||
oci-containers.backend = "docker";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
{
|
||||
imports = [
|
||||
./containers
|
||||
./guest
|
||||
];
|
||||
}
|
||||
|
|
|
|||
35
scripts/control-center-bootstrap.sh
Executable file
35
scripts/control-center-bootstrap.sh
Executable file
|
|
@ -0,0 +1,35 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
echo "=========================================="
|
||||
echo " Bootstrapping Control Center VM "
|
||||
echo "=========================================="
|
||||
|
||||
# Ensure script is run with appropriate permissions (this script might run as part of Cloud-Init or manually)
|
||||
|
||||
if ! command -v terraform &> /dev/null; then
|
||||
echo "Terraform not found. Please ensure it is installed via Nix."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "1. Initializing proxmox-bootstrap state..."
|
||||
cd terraform/proxmox-bootstrap
|
||||
terraform init
|
||||
|
||||
echo "2. Applying proxmox-bootstrap (Requires manual approval if not passing -auto-approve)"
|
||||
echo " This will configure the restricted terraform@pve user on the Proxmox host."
|
||||
# Uncomment the following line to auto-approve in a fully automated environment
|
||||
# terraform apply -auto-approve
|
||||
terraform apply
|
||||
|
||||
echo "3. Fetching the generated API token..."
|
||||
TF_TOKEN=$(terraform output -raw terraform_api_token)
|
||||
|
||||
echo "=========================================="
|
||||
echo " IMPORTANT: Save the following token into "
|
||||
echo " Forgejo Secrets as PROXMOX_TOKEN_SECRET "
|
||||
echo "=========================================="
|
||||
echo "$TF_TOKEN"
|
||||
echo "=========================================="
|
||||
|
||||
echo "Control Center Bootstrap Complete."
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
users:
|
||||
admin:
|
||||
authorized_keys:
|
||||
NixOS: ENC[AES256_GCM,data:sj2hkUkWp628KuXp+AnncLdawHpxb9fH1ZHnIisP0x9Tght9+/X2sWHpuMSeqi2i/R8B+Wgte66QkuwAOB0j+oB9N+66EhehmWZlK5hD/22p,iv:z18U+LvAQgPDfBBewE3lJmWZd0NGCPwJIe/h3tupuZc=,tag:ZJar3spO66JbDXygdTHh2w==,type:str]
|
||||
sops:
|
||||
age:
|
||||
- recipient: age1qzutny0mqpcccqw6myyfntu6wcskruu9ghzvt6r4te7afkqwnguq05ex37
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBjUSt2REk2Mmd0bk9ubjJk
|
||||
dXFiY2JNR1dyZW9qTUdzaWZhY3c3amVwQzA0CkZHNVpZVjhsWXhVQVNaR0xONzhh
|
||||
Y0lQaWNaNmpYYVdrRnZIZUhvUFUzcWMKLS0tIDAvSmF0VmpxcnZEQStXUjNCUE5Z
|
||||
RnA2Lzk2WHFxOEh6dHN0aGhVSVpLTW8KA7IOvGDMBtgo4pe0Sw3Lol243xCDAJ4i
|
||||
PhcJFiUObVRFZN7ISlULnOlTO3pT9jWvvmC5rDZWId3PQ8qjPvnOUg==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
lastmodified: "2025-10-04T17:33:22Z"
|
||||
mac: ENC[AES256_GCM,data:I7I7uDFEWfw9+4KROtjHMVhaxYrVK5QmLfFZShSajF0A2Zxu9lg+fDGiMHk40JC5zD31P70QS/ipye1mBGQbCbLEA7uBUhNzZ7G1g58cIXF6vSGmt0fovm0MVSxEJ44r05fx6uT4OJu5BYVxYSlG84gTj9rCFXxxcBJMrh+6yaI=,iv:c1vudsp9bg0Pc2ddRyvWn6Tf0LhqNuEjxG9D4PpHqxs=,tag:K/1PSHhrTdsNPcPmRv/2Ew==,type:str]
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.10.2
|
||||
71
terraform/proxmox-bootstrap/main.tf
Normal file
71
terraform/proxmox-bootstrap/main.tf
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
terraform {
|
||||
required_providers {
|
||||
proxmox = {
|
||||
source = "bpg/proxmox"
|
||||
version = "~> 0.61.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# This bootstrap state must be run manually ONCE with the root@pam credentials
|
||||
# to establish the restricted terraform@pve user for the rest of the CI pipeline.
|
||||
provider "proxmox" {
|
||||
# Configuration can be passed via environment variables:
|
||||
# PROXMOX_VE_ENDPOINT=https://your-proxmox-ip:8006/
|
||||
# PROXMOX_VE_USERNAME=root@pam
|
||||
# PROXMOX_VE_PASSWORD=your-root-password
|
||||
# PROXMOX_VE_INSECURE=true # If using self-signed certs
|
||||
}
|
||||
|
||||
resource "proxmox_virtual_environment_role" "terraform_prov" {
|
||||
role_id = "TerraformProv"
|
||||
|
||||
privileges = [
|
||||
"VM.Allocate",
|
||||
"VM.Audit",
|
||||
"VM.Clone",
|
||||
"VM.Config.CDROM",
|
||||
"VM.Config.CPU",
|
||||
"VM.Config.Disk",
|
||||
"VM.Config.HWType",
|
||||
"VM.Config.Memory",
|
||||
"VM.Config.Network",
|
||||
"VM.Config.Options",
|
||||
"VM.Monitor",
|
||||
"VM.PowerMgmt",
|
||||
"Datastore.AllocateSpace",
|
||||
"Datastore.Audit",
|
||||
"SDN.Use"
|
||||
]
|
||||
}
|
||||
|
||||
resource "proxmox_virtual_environment_user" "terraform_user" {
|
||||
user_id = "terraform@pve"
|
||||
comment = "Managed by Terraform (proxmox-bootstrap) for GitOps CI/CD"
|
||||
}
|
||||
|
||||
resource "proxmox_virtual_environment_acl" "terraform_vms" {
|
||||
user_id = proxmox_virtual_environment_user.terraform_user.user_id
|
||||
role_id = proxmox_virtual_environment_role.terraform_prov.role_id
|
||||
path = "/vms"
|
||||
}
|
||||
|
||||
resource "proxmox_virtual_environment_acl" "terraform_storage" {
|
||||
user_id = proxmox_virtual_environment_user.terraform_user.user_id
|
||||
role_id = proxmox_virtual_environment_role.terraform_prov.role_id
|
||||
# Update this path to match your actual local-zfs or TrueNAS mounted storage
|
||||
path = "/storage/local-zfs"
|
||||
}
|
||||
|
||||
resource "proxmox_virtual_environment_user_token" "terraform_token" {
|
||||
user_id = proxmox_virtual_environment_user.terraform_user.user_id
|
||||
token_id = "tf-automation"
|
||||
privsep = false
|
||||
comment = "Token for Forgejo CI/CD to provision VMs"
|
||||
}
|
||||
|
||||
output "terraform_api_token" {
|
||||
value = proxmox_virtual_environment_user_token.terraform_token.value
|
||||
sensitive = true
|
||||
description = "The secret API token for terraform@pve. Save this to Forgejo Secrets as PROXMOX_VE_API_TOKEN."
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
cfg = config.homelab.users.apps;
|
||||
in {
|
||||
options.homelab.users.apps.enable = lib.mkEnableOption "user Apps";
|
||||
|
||||
config.users = lib.mkIf cfg.enable {
|
||||
groups.apps.gid = lib.mkForce 568;
|
||||
users.apps = {
|
||||
uid = lib.mkForce 568;
|
||||
isSystemUser = true;
|
||||
group = config.users.groups.apps.name;
|
||||
home = "/var/empty";
|
||||
shell = null;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
cfg = config.homelab.users.backup;
|
||||
in {
|
||||
options.homelab.users.backup.enable = lib.mkEnableOption "user Backup";
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
users.users.backup = {
|
||||
description = "Backup User";
|
||||
isNormalUser = true;
|
||||
extraGroups = [
|
||||
"docker" # Allow access to the docker socket.
|
||||
];
|
||||
openssh.authorizedKeys.keys = [
|
||||
# Hugo
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICms6vjhE9kOlqV5GBPGInwUHAfCSVHLI2Gtzee0VXPh"
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,9 +1,5 @@
|
|||
{
|
||||
imports = [
|
||||
./admin
|
||||
./apps
|
||||
./backup
|
||||
./deploy
|
||||
./media
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,49 +0,0 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.homelab.users.deploy;
|
||||
in {
|
||||
options.homelab.users.deploy.enable = lib.mkEnableOption "user Deploy";
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
users = {
|
||||
groups.deploy = { };
|
||||
|
||||
# The user used to deploy rebuilds without password authentication
|
||||
users.deploy = {
|
||||
group = config.users.groups.deploy.name;
|
||||
isSystemUser = true;
|
||||
home = "/var/empty";
|
||||
shell = pkgs.bashInteractive;
|
||||
openssh.authorizedKeys.keys = [
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPrG+ldRBdCeHEXrsy/qHXIJYg8xQXVuiUR0DxhFjYNg"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
security.sudo.extraRules = [
|
||||
{
|
||||
groups = [
|
||||
config.users.groups.deploy.name
|
||||
];
|
||||
commands = [
|
||||
{
|
||||
command = "/nix/store/*-nix-*/bin/nix-env -p /nix/var/nix/profile/system --set /nix/store/*-*";
|
||||
options = [ "NOPASSWD" ];
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
groups = [
|
||||
config.users.groups.deploy.name
|
||||
];
|
||||
commands = [
|
||||
{
|
||||
command = "/nix/store/*/bin/switch-to-configuration";
|
||||
options = [ "NOPASSWD" ];
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
cfg = config.homelab.users.media;
|
||||
in {
|
||||
options.homelab.users.media.enable = lib.mkEnableOption "user Media";
|
||||
|
||||
config.users = lib.mkIf cfg.enable {
|
||||
groups.media.gid = lib.mkForce 3000;
|
||||
users.media = {
|
||||
uid = lib.mkForce 3001;
|
||||
isSystemUser = true;
|
||||
group = config.users.groups.media.name;
|
||||
home = "/var/empty";
|
||||
shell = null;
|
||||
};
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue