feat: add French SEO descriptions to all pages and fix canonical URL
This commit is contained in:
+1
-1
@@ -25,7 +25,7 @@ export default defineAppConfig({
|
|||||||
docus: {
|
docus: {
|
||||||
title: 'Docudjeex',
|
title: 'Docudjeex',
|
||||||
description: 'La doc de mes expériences',
|
description: 'La doc de mes expériences',
|
||||||
url: 'http://docus.dev',
|
url: 'https://docu.djeex.fr/fr',
|
||||||
image: '/img/social.png',
|
image: '/img/social.png',
|
||||||
socials: {
|
socials: {
|
||||||
github:'',
|
github:'',
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: Accueil
|
title: Accueil
|
||||||
|
description: Documentation homelab par Djeex — guides d'auto-hébergement pour Debian, Docker, réseau, stockage et bien plus.
|
||||||
navigation: false
|
navigation: false
|
||||||
layout: page
|
layout: page
|
||||||
main:
|
main:
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
---
|
||||||
|
icon: lucide:home
|
||||||
|
title: Welcome
|
||||||
|
description: Introduction à Docudjeex — un site de documentation personnelle sur les services auto-hébergés, Debian et l'infrastructure Docker.
|
||||||
|
main:
|
||||||
|
fluid: false
|
||||||
|
---
|
||||||
|
:ellipsis{right=0px width=75% blur=150px}
|
||||||
|
|
||||||
|
# docu[·]{style="color: #1ad6ff"}what?
|
||||||
|
|
||||||
|
__Docu[·]{style="color: #1ad6ff"}djeex__ is a site containing the documentation of my personal servers, originally created to easily keep track of my configurations and commands.
|
||||||
|
My infrastructure is built around the Debian 13 + Docker combo, making exporting and deployment simpler.
|
||||||
|
Special thanks to __Nipah__, __Xenio__, and others for their patience and support. Most of this content comes directly from them.
|
||||||
|
|
||||||
|
## About the documentation
|
||||||
|
|
||||||
|
The documentation provided here is experimental and shared in a spirit of open knowledge and experience.
|
||||||
|
It is not intended to build production-grade or industrialized infrastructure.
|
||||||
|
It may contain mistakes and/or approximations.
|
||||||
|
|
||||||
|
Naturally, this documentation should only be used within a strictly legal framework.
|
||||||
|
|
||||||
|
::card-grid
|
||||||
|
#title
|
||||||
|
Available or Upcoming Documentation
|
||||||
|
|
||||||
|
#root
|
||||||
|
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||||
|
|
||||||
|
#default
|
||||||
|
::card{icon=noto:microscope}
|
||||||
|
#title
|
||||||
|
Serveex
|
||||||
|
#description
|
||||||
|
[Step-by-step Homelab Deployment Guide](/serveex/introduction)
|
||||||
|
::
|
||||||
|
|
||||||
|
::card{icon=noto:computer-disk}
|
||||||
|
#title
|
||||||
|
Stockeex
|
||||||
|
#description
|
||||||
|
*(coming soon)* Build your own home NAS to store your data and media
|
||||||
|
::
|
||||||
|
::
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
---
|
||||||
|
navigation: true
|
||||||
|
title: NAT & DHCP
|
||||||
|
description: Comprendre le NAT, la redirection de ports et le DHCP sur un routeur domestique. Configurer des baux IP fixes et exposer des services locaux.
|
||||||
|
main:
|
||||||
|
fluid: false
|
||||||
|
---
|
||||||
|
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||||
|
|
||||||
|
# Router and NAT
|
||||||
|
|
||||||
|
::alert{type="info"}
|
||||||
|
🎯 __Goals:__
|
||||||
|
- Understand how port forwarding works
|
||||||
|
- Learn how to configure router NAT
|
||||||
|
- Learn how to issue DHCP leases (fixed IPs)
|
||||||
|
::
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## What is a "port"?
|
||||||
|
---
|
||||||
|
Ports are different channels through which your router sends and receives data. This allows multiple services to run simultaneously.
|
||||||
|
When it receives data through a port, your router forwards that data to the machine that:
|
||||||
|
- either initiated the request,
|
||||||
|
- or is configured to receive data on a specific port.
|
||||||
|
|
||||||
|
Your router has over 65,000 ports available.
|
||||||
|
|
||||||
|
Some programs and applications are designed to use specific ports. For example, when your network sends data from an HTML page, the router receives it through port 80 (non-secure) or port `443` (secure via SSL).
|
||||||
|
|
||||||
|
So, your router acts as a data dispatcher between the internet and your local machines.
|
||||||
|
|
||||||
|
## Port Forwarding
|
||||||
|
---
|
||||||
|
Forwarding a `port` means setting a rule that specifies which `source` can send data to which `port` on your router, which will then forward it to a specific `port` on a specific `machine`. The `sources` and `destination machine` are identified by their IP addresses.
|
||||||
|
|
||||||
|
| Variable | Description | Example |
|
||||||
|
|------------------------|---------------------------------------------------------|-------------------------|
|
||||||
|
| `source machine` | IP of the source machine (from the internet) | `All`<br>`123.45.67.89` |
|
||||||
|
| `source port` | Incoming port on the router | `443` |
|
||||||
|
| `destination port` | Port on the destination machine | `3000` |
|
||||||
|
| `destination machine` | IP of the target machine (on your local network) | `192.168.1.50` |
|
||||||
|
|
||||||
|
According to the table:
|
||||||
|
If we remove `All` and keep the IP `123.45.67.89`, all traffic from this IP sent to port `443` on your router will be forwarded to port `3000` on the local IP `192.168.1.50`.
|
||||||
|
|
||||||
|
If we remove the IP and keep `All`, then all traffic from the internet on port `443` will be redirected to port `3000` on `192.168.1.50`.
|
||||||
|
|
||||||
|
This is useful when you have a server that must be accessible from the internet. For instance, a website uses port `80` (non-secure) or `443` (SSL-secured).
|
||||||
|
To make the website accessible, you'll configure your router to redirect the domain request to your local server.
|
||||||
|
Assume your service runs on port `3000` locally (`http://192.168.1.50:3000`), you would redirect all traffic from port `443` on the router to port `3000` on the local server.
|
||||||
|
|
||||||
|
::alert{type="warning"}
|
||||||
|
:::list{type="warning"}
|
||||||
|
- __Warning:__ If you have multiple services to expose like `subdomain1.mydomain.com` and `subdomain2.mydomain.com`, your router cannot differentiate requests and forward to different ports.
|
||||||
|
You must use a [Reverse Proxy](../../serveex/core/swag) to route traffic based on the request.
|
||||||
|
:::
|
||||||
|
::
|
||||||
|
|
||||||
|
## DHCP
|
||||||
|
---
|
||||||
|
Every time a device connects to your local network, your router assigns it an IP address using DHCP rules.
|
||||||
|
This IP is randomly selected from a predefined pool.
|
||||||
|
At every device reboot, the IP may change — which is problematic if you're forwarding ports, as the target IP may no longer be valid.
|
||||||
|
|
||||||
|
To avoid this, use your router's DHCP server to assign a static IP address.
|
||||||
|
|
||||||
|
Each device has a physical "MAC address".
|
||||||
|
To assign a fixed IP, you must know your device's MAC address (visible in your router when it's connected), and assign it a static IP.
|
||||||
|
This is called a "static DHCP lease."
|
||||||
|
|
||||||
|
That way, your machine's IP never changes and your port forwarding rules remain effective.
|
||||||
|
|
||||||
|
| Variable | Description | Example |
|
||||||
|
|---------------|----------------------------------|---------------------|
|
||||||
|
| `IP` | Fixed local IP to assign | `192.168.1.50` |
|
||||||
|
| `MAC Address` | Physical address of the device | `5E:FF:56:A2:AF:15` |
|
||||||
|
|
||||||
|
For more information, refer to your router's documentation.
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
---
|
||||||
|
navigation: true
|
||||||
|
title: DNS Zone
|
||||||
|
description: Comprendre le fonctionnement du DNS, lire et éditer une zone DNS, et configurer des noms de domaine pour vos services auto-hébergés.
|
||||||
|
main:
|
||||||
|
fluid: false
|
||||||
|
---
|
||||||
|
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||||
|
# Domain Names and DNS Zones
|
||||||
|
|
||||||
|
::alert{type="info"}
|
||||||
|
🎯 __Objectives:__
|
||||||
|
- Understand how a DNS server works
|
||||||
|
- Learn how to edit a DNS zone
|
||||||
|
::
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
---
|
||||||
|
When you browse a website or use an app, requests are made to one or more domains to fetch content for the page. Your device doesn't know the IP addresses of these servers, so it contacts a _name server_ (Domain Name Server), which responds with the most up-to-date IP address for the domain being requested.
|
||||||
|
|
||||||
|
The DNS zone is like a registry with signposts that direct your requests to the correct destination.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## The DNS Zone
|
||||||
|
---
|
||||||
|
When you purchase a domain from a registrar (Cloudflare, OVH, etc.), the registrar assigns you a DNS zone that you can customize.
|
||||||
|
|
||||||
|
You can enter _records_ into this DNS zone to direct requests properly. You can find [more information here](https://help.ovhcloud.com/csm/fr-dns-servers-general-information?id=kb_article_view&sysparm_article=KB0051661).
|
||||||
|
|
||||||
|
Example of a DNS zone for the domain `mydomain.com`:
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
@ IN SOA ns1.dns.me. dns.net. (2024051800 86400 3600 3600000 60)
|
||||||
|
IN NS ns1.dns.me.
|
||||||
|
IN NS ns2.dns.me.
|
||||||
|
IN A 203.0.113.0
|
||||||
|
www IN CNAME mydomain.com
|
||||||
|
sousdomaine IN CNAME mydomain.com
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
In this example:
|
||||||
|
|
||||||
|
- `$TTL 3600` tells global name servers that the records are valid for 1 hour (after which they need to re-check).
|
||||||
|
- `IN SOA ns1.dns.me. dns.net. (...)` indicates `ns1.dns.me` as the primary DNS server, with refresh intervals.
|
||||||
|
- `IN NS` records define the authoritative name servers for the domain.
|
||||||
|
- `IN A 203.0.113.0` means `mydomain.com` points to IP `203.0.113.0`.
|
||||||
|
- `subdomain IN CNAME mydomain.com` means `subdomain.mydomain.com` points to the same destination as `mydomain.com`.
|
||||||
|
|
||||||
|
So, if you want to point `mydomain.com` to your server, you can do it by adding an `A` record pointing to your server's public IP address.
|
||||||
|
|
||||||
|
::alert{type="warning"}
|
||||||
|
:::list{type="warning"}
|
||||||
|
- __Warning:__ If your server is hosted at home:
|
||||||
|
:::
|
||||||
|
- Your public IP is the one assigned to your home router. Make sure it's static, or configure [DDNS](https://aws.amazon.com/fr/what-is/dynamic-dns/).
|
||||||
|
- Make sure you've [set up port 443 forwarding to your server's listening port](/general/networking/nat).
|
||||||
|
::
|
||||||
|
|
||||||
|
If you're adding a subdomain that should also point to your server, use a `CNAME` record pointing to `mydomain.com`.
|
||||||
|
|
||||||
|
::alert{type="info"}
|
||||||
|
:::list{type="info"}
|
||||||
|
- __Why not use an `A` record for the subdomain?__ If your subdomain points to the same server as `mydomain.com`, it's better to use a `CNAME` record because if the server's IP changes, you won’t need to update the subdomain record.
|
||||||
|
:::
|
||||||
|
::
|
||||||
|
|
||||||
|
Most registrars offer user-friendly interfaces to manage DNS records. Refer to your registrar’s documentation for specific instructions.
|
||||||
@@ -0,0 +1,229 @@
|
|||||||
|
---
|
||||||
|
navigation: true
|
||||||
|
title: Samba
|
||||||
|
description: Configurer Samba sur Debian pour partager des dossiers sur votre réseau local et y accéder depuis Windows, macOS ou Linux.
|
||||||
|
main:
|
||||||
|
fluid: false
|
||||||
|
---
|
||||||
|
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||||
|
# Samba
|
||||||
|
|
||||||
|
Samba is a protocol that allows access to a folder located on a network drive. It can be configured on macOS, Windows, or Linux.
|
||||||
|
|
||||||
|
There are many tutorials for setting up Samba on Windows or on NAS systems like Synology, but here we focus on Debian.
|
||||||
|
|
||||||
|
::alert{type="info"}
|
||||||
|
🎯 __Objectives:__
|
||||||
|
- Create a network folder on a remote machine
|
||||||
|
- Access the network folder from our server
|
||||||
|
::
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Sharing a Network Folder
|
||||||
|
---
|
||||||
|
::alert{type="info"}
|
||||||
|
:::list{type="info"}
|
||||||
|
- In this example, we will share the `/video` folder from a remote machine called `remote-machine`. We will access this folder from a machine called `local-machine`. The user connecting to the network drive will be `sambauser`.
|
||||||
|
:::
|
||||||
|
::
|
||||||
|
|
||||||
|
### Install Samba Server
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo apt update && sudo apt upgrade
|
||||||
|
sudo apt install samba smbclient cifs-utils
|
||||||
|
```
|
||||||
|
|
||||||
|
### Create the `/video` Folder
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo mkdir /video
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configure the Share
|
||||||
|
|
||||||
|
Now, edit the file `/etc/samba/smb.conf`.
|
||||||
|
|
||||||
|
::alert{type="success"}
|
||||||
|
✨ __Tip:__ You can use [File Browser](/serveex/files/file-browser) to navigate and edit your files instead of using terminal commands.
|
||||||
|
::
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo vim /etc/samba/smb.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
Find the `workgroup` variable, press `i` to enter insert mode, and name your workgroup (e.g., `workgroup = WORKGROUP`).
|
||||||
|
|
||||||
|
Then scroll to the end of the file and add the following configuration:
|
||||||
|
|
||||||
|
```properties
|
||||||
|
[video]
|
||||||
|
comment = Video folder
|
||||||
|
path = /video
|
||||||
|
writable = yes
|
||||||
|
guest ok = no
|
||||||
|
valid users = @smbshare
|
||||||
|
force create mode = 770
|
||||||
|
force directory mode = 770
|
||||||
|
inherit permissions = yes
|
||||||
|
```
|
||||||
|
|
||||||
|
Press `Esc` to exit insert mode, then type `:x` and press `Enter` to save and exit.
|
||||||
|
|
||||||
|
### Create a Samba User and Group
|
||||||
|
|
||||||
|
Since we're using a secured share, we need to create a user and group to access it remotely.
|
||||||
|
|
||||||
|
Create the group:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo groupadd smbshare
|
||||||
|
```
|
||||||
|
|
||||||
|
Give the group control over the `/video` folder:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo chgrp -R smbshare /video
|
||||||
|
```
|
||||||
|
|
||||||
|
Set inherited permissions:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo chmod 2775 /video
|
||||||
|
```
|
||||||
|
|
||||||
|
Now add a no-login user — this user cannot log into the server but can access Samba.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo useradd -M -s /sbin/nologin sambauser
|
||||||
|
```
|
||||||
|
|
||||||
|
Add the user to the `smbshare` group:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo usermod -aG smbshare sambauser
|
||||||
|
```
|
||||||
|
|
||||||
|
Set a Samba password:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo smbpasswd -a sambauser
|
||||||
|
```
|
||||||
|
|
||||||
|
Enable the Samba account:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo smbpasswd -e sambauser
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo ufw allow from remote-ip to any app Samba
|
||||||
|
::
|
||||||
|
```
|
||||||
|
|
||||||
|
## Accessing a Shared Folder
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
\::
|
||||||
|
|
||||||
|
### Install Required Packages
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo apt update && sudo apt upgrade
|
||||||
|
sudo apt install cifs-utils
|
||||||
|
```
|
||||||
|
|
||||||
|
### Create the Mount Destination
|
||||||
|
|
||||||
|
We will create a folder on our local machine where the remote `/video` folder will be mounted — e.g., `/mnt/video`.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo mkdir /mnt/video
|
||||||
|
```
|
||||||
|
|
||||||
|
### Prepare the .credentials File
|
||||||
|
|
||||||
|
To avoid typing our username and password every time, create a `.credentials` file storing the login info.
|
||||||
|
|
||||||
|
Create it in the `/smb` folder:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo mkdir /smb
|
||||||
|
sudo vi /smb/.credentials
|
||||||
|
```
|
||||||
|
|
||||||
|
Enter insert mode (`i`) and write:
|
||||||
|
|
||||||
|
```properties
|
||||||
|
username=smbuser
|
||||||
|
password=password
|
||||||
|
```
|
||||||
|
|
||||||
|
* `smbuser`: the user we created on the `remote-machine`
|
||||||
|
* `password`: the password set earlier
|
||||||
|
|
||||||
|
Press `Esc`, then `:x` and `Enter` to save and exit.
|
||||||
|
|
||||||
|
Set proper file permissions:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo chmod 600 /smb/.credentials
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mount the Shared Folder
|
||||||
|
|
||||||
|
Now mount the folder:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo mount -t cifs -o credentials=/smb/.credentials //remote-ip/video /mnt/video
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace `remote-ip` with your `remote-machine`'s IP address.
|
||||||
|
|
||||||
|
Verify the mount:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo mount -t cifs
|
||||||
|
```
|
||||||
|
|
||||||
|
You’ll see details confirming the mount is successful.
|
||||||
|
|
||||||
|
Now you can access the `/video` folder of the `remote-machine` from your `local-machine`!
|
||||||
|
|
||||||
|
### Auto-mount on Boot
|
||||||
|
|
||||||
|
By default, shares aren't auto-mounted after reboot. To automate this, edit the `/etc/fstab` file.
|
||||||
|
|
||||||
|
First, back it up:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo cp /etc/fstab /etc/fstab.bak
|
||||||
|
```
|
||||||
|
|
||||||
|
Then add the mount configuration line:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo echo //remote-ip/video /mnt/video cifs _netdev,nofail,credentials=/smb/.credentials,x-systemd.automount,x-systemd.device-timeout=15 0 0 >> /etc/fstab
|
||||||
|
```
|
||||||
|
|
||||||
|
Reboot the machine:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo reboot
|
||||||
|
```
|
||||||
|
|
||||||
|
After rebooting, verify the mount:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo mount -t cifs
|
||||||
|
```
|
||||||
|
|
||||||
|
And done!
|
||||||
|
|
||||||
|
### Unmount the Shared Folder
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo umount -t cifs /mnt/video
|
||||||
|
```
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
---
|
||||||
|
navigation: true
|
||||||
|
title: RAID
|
||||||
|
description: Comprendre le RAID — matériel vs logiciel, niveaux RAID, et comment mettre en place des grappes de disques redondants pour votre homelab.
|
||||||
|
main:
|
||||||
|
fluid: false
|
||||||
|
---
|
||||||
|
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||||
|
# RAID
|
||||||
|
|
||||||
|
_Redundant Array of Independent Disks_
|
||||||
|
|
||||||
|
In computing, RAID (Redundant Array of Independent Disks) is a system that allows multiple hard drives to be combined to improve performance and/or reliability. It works by restructuring and distributing data blocks across the drives.
|
||||||
|
|
||||||
|
Originally, RAID systems were hardware-based, meaning a dedicated controller (a specific chip) managed data distribution and RAID operations. Today, most RAID systems (or their equivalents) are software-based. In fact, many software technologies can create RAID-like systems with features not available in hardware RAID, such as automatic repair (data scrubbing), snapshots, and more.
|
||||||
|
|
||||||
|
## Different Types of RAID
|
||||||
|
|
||||||
|
There are several types of RAID, each offering its own pros and cons. In general, RAID impacts the following five factors:
|
||||||
|
|
||||||
|
- Number of drives
|
||||||
|
- Total storage capacity
|
||||||
|
- Read speed
|
||||||
|
- Write speed
|
||||||
|
- Fault tolerance (resistance to hardware failure)
|
||||||
|
|
||||||
|
::alert{type="warning"}
|
||||||
|
:::list{type="warning"}
|
||||||
|
- RAID is not a backup system but a service continuity system! It only allows hot-swapping of drives without interrupting your server or restoring from backup. You still need an external backup system.
|
||||||
|
::
|
||||||
|
|
||||||
|
### No RAID
|
||||||
|
---
|
||||||
|
|
||||||
|
<div style="display: flex; align-items: center;">
|
||||||
|
<img src="/img/global/no-raid.svg" alt="Image" style="max-width: 30%; max-height:230px; margin-right: 20px;">
|
||||||
|
<ul>
|
||||||
|
<li>Just your disks, without RAID. Data is stored disk by disk.</li>
|
||||||
|
<li>If you lose a disk, only its data is lost.</li>
|
||||||
|
<li>Total capacity is the sum of all disks.</li>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
Use your disks without RAID when you're not afraid of data loss and can tolerate service interruptions between failure and backup restoration.
|
||||||
|
|
||||||
|
### RAID 0
|
||||||
|
---
|
||||||
|
|
||||||
|
<div style="display: flex; align-items: center;">
|
||||||
|
<img src="/img/global/raid0.svg" alt="Image" style="max-width: 30%; max-height:230px; margin-right: 20px;">
|
||||||
|
<ul>
|
||||||
|
<li>OS sees 1 drive.</li>
|
||||||
|
<li>Data is striped across all disks.</li>
|
||||||
|
<li>If you lose one disk, you lose all data.</li>
|
||||||
|
<li>High read and write performance (multiplied by number of disks).</li>
|
||||||
|
<li>Total capacity is the sum of all disks.</li>
|
||||||
|
<li>Minimum of 2 disks required.</li>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
Use RAID 0 when you prioritize performance and are not concerned about data loss. Ideal for temporary, high-speed storage (video editing, AI workloads, etc). Not suitable for long-term storage, as one failure means total data loss.
|
||||||
|
|
||||||
|
### RAID 1
|
||||||
|
---
|
||||||
|
|
||||||
|
<div style="display: flex; align-items: center;">
|
||||||
|
<img src="/img/global/raid1.svg" alt="Image" style="max-width: 30%; max-height:230px; margin-right: 20px;">
|
||||||
|
<ul>
|
||||||
|
<li>OS sees 1 drive.</li>
|
||||||
|
<li>All disks contain identical data.</li>
|
||||||
|
<li>You can lose all but one disk.</li>
|
||||||
|
<li>Improved read speed (scales with number of disks).</li>
|
||||||
|
<li>Total capacity is equal to one disk (e.g., 2×10TB = 10TB).</li>
|
||||||
|
<li>Minimum of 2 disks required.</li>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
Use RAID 1 for strong redundancy. Each disk contains all data, so performance remains unaffected during a failure. Once failed disks are replaced, data is quickly restored. However, usable storage is limited to one disk’s capacity, making it an expensive solution.
|
||||||
|
|
||||||
|
::alert{type="success"}
|
||||||
|
✨ __Tip:__ You can combine RAID 1 with other RAID types to create mirrored arrays.
|
||||||
|
::
|
||||||
|
|
||||||
|
### RAID 5
|
||||||
|
---
|
||||||
|
<p align="center">
|
||||||
|
<img src="/img/global/raid5.svg" alt="Image" style="max-width: 40%; margin-right: 20px;">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
- OS sees 1 drive.
|
||||||
|
- Data is striped with parity blocks for redundancy.
|
||||||
|
- You can lose 1 disk and recover data.
|
||||||
|
- Improved read speed (scales with number of disks).
|
||||||
|
- Total capacity is the sum of all disks minus one (e.g., 3×10TB = 20TB).
|
||||||
|
- Minimum of 3 disks (4 recommended to reduce capacity loss).
|
||||||
|
|
||||||
|
Use RAID 5 when you want reliable storage with 3 to 5 disks and minimal space loss. It tolerates one disk failure but may have degraded performance during recovery, which can take days.
|
||||||
|
|
||||||
|
### RAID 6
|
||||||
|
---
|
||||||
|
<p align="center">
|
||||||
|
<img src="/img/global/raid6.svg" alt="Image" style="max-width: 50%; margin-right: 20px;">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
- OS sees 1 drive.
|
||||||
|
- Data is striped with dual parity blocks.
|
||||||
|
- You can lose 2 disks and still recover data.
|
||||||
|
- Improved read speed (scales with number of disks).
|
||||||
|
- Total capacity is the sum of all disks minus two (e.g., 4×10TB = 20TB).
|
||||||
|
- Minimum of 4 disks (6 recommended to minimize space loss).
|
||||||
|
|
||||||
|
Use RAID 6 in similar situations as RAID 5, especially with 6 or more disks. More disks mean higher failure risk. RAID 6 offers peace of mind by tolerating two simultaneous failures.
|
||||||
|
|
||||||
|
## Software RAID
|
||||||
|
(coming soon)
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
---
|
||||||
|
navigation: true
|
||||||
|
title: ZFS
|
||||||
|
description: Introduction à ZFS — système de fichiers et gestionnaire de volumes combinés avec snapshots, checksums et redondance intégrée pour un stockage fiable.
|
||||||
|
main:
|
||||||
|
fluid: false
|
||||||
|
---
|
||||||
|
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||||
|
# ZFS
|
||||||
|
|
||||||
|
::alert{type="info"}
|
||||||
|
🎯 __Objectives:__
|
||||||
|
- Understand what ZFS is and why it's useful
|
||||||
|
::
|
||||||
|
|
||||||
|
ZFS is widely used in the world of servers, NAS systems (like FreeNAS / TrueNAS), virtualization, and even by tech-savvy individuals who want reliable storage. It is both a _file system_ (like NTFS for Windows, EXT4, FAT32, etc.) and a _volume manager_ (similar to LVM).
|
||||||
|
|
||||||
|
To put it simply:
|
||||||
|
- A **volume manager** organizes physical storage (like one or more hard drives).
|
||||||
|
- A **file system** organizes how data blocks are written, read, and deleted within those volumes.
|
||||||
|
|
||||||
|
ZFS goes far beyond traditional file systems in terms of performance and features.
|
||||||
|
Here’s what we’re most interested in:
|
||||||
|
- Its __snapshot management__ features, allowing you to quickly roll back in case of issues.
|
||||||
|
- Its support for disk groupings and [__RAID-like structures__](/general/storage/raid) (Z-Mirror, RAIDZ1, RAIDZ2, RAIDZ3).
|
||||||
|
- Its __automatic recovery of corrupted data__ (through scrubbing).
|
||||||
|
- Its performance, enhanced by RAM caching (ZFS ARC).
|
||||||
|
- Its robust error notifications and monitoring.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
---
|
||||||
|

|
||||||
|
|
||||||
|
ZFS has a unique structure:
|
||||||
|
|
||||||
|
- **vdev** (virtual device): a group of physical or virtual disks.
|
||||||
|
- **zpool**: a collection of vdevs configured as a single storage pool. A zpool can contain multiple vdevs, but a vdev belongs to only one zpool.
|
||||||
|
- **dataset**: a logical data container within a zpool. Each dataset can have its own settings (compression, quotas, permissions, etc.).
|
||||||
|
|
||||||
|
There are several dataset types:
|
||||||
|
- **file system**: a standard ZFS filesystem, mounted without storage quotas.
|
||||||
|
- **zvol**: a "virtual disk" with a defined size, which you can format and partition as if it were a physical disk.
|
||||||
|
- **snapshot**: a frozen-in-time version of another dataset. Snapshots can be created manually or through backup tools. They can be mounted to browse data as it was at the snapshot time.
|
||||||
|
|
||||||
|
## Why ZFS over others?
|
||||||
|
---
|
||||||
|
### Data Integrity
|
||||||
|
|
||||||
|
ZFS continuously checks that your stored data hasn't become corrupted. Every block of data is associated with a checksum, allowing ZFS to detect even the smallest alteration. If corruption is found and a healthy copy exists elsewhere, ZFS can repair the data automatically.
|
||||||
|
|
||||||
|
### Built-in RAID
|
||||||
|
|
||||||
|
ZFS includes its own volume management system (vdevs). You can build a zpool using multiple disks—similar to traditional [RAID](/general/storage/raid) setups—but with more flexibility. For example:
|
||||||
|
- **Z-mirror** → equivalent to RAID 1
|
||||||
|
- **RAIDZ1** → equivalent to RAID 5 (tolerates 1 disk failure)
|
||||||
|
- **RAIDZ2** → equivalent to RAID 6 (tolerates 2 disk failures)
|
||||||
|
- **RAIDZ3** → tolerates up to 3 disk failures
|
||||||
|
|
||||||
|
ZFS handles all this natively—no external RAID software needed.
|
||||||
|
|
||||||
|
::alert{type="info"}
|
||||||
|
:::list{type="info"}
|
||||||
|
- Check out the [article on RAID](/general/storage/raid) to find the right solution for your needs.
|
||||||
|
:::
|
||||||
|
::
|
||||||
|
|
||||||
|
### Snapshots and Clones
|
||||||
|
|
||||||
|
ZFS allows you to create snapshots—instantaneous images of a dataset's state. Snapshots take up minimal space and can be scheduled frequently. You can also create clones: writable copies of snapshots.
|
||||||
|
|
||||||
|
### Compression and Deduplication
|
||||||
|
|
||||||
|
ZFS can compress data on the fly (transparently to the user), saving disk space. It also supports deduplication (removing duplicate data), though this feature requires a lot of memory and is not recommended for all use cases.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Now you know why ZFS is *the* file system to deploy on your NAS.
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
---
|
||||||
|
navigation: true
|
||||||
|
title: The Basics
|
||||||
|
description: Les fondamentaux du matériel serveur — CPU, RAM, stockage et facteurs de forme pour bien démarrer votre homelab.
|
||||||
|
main:
|
||||||
|
fluid: false
|
||||||
|
---
|
||||||
|
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||||
|
# Server Basics
|
||||||
|
|
||||||
|
::alert{type="info"}
|
||||||
|
🎯 __Objectives:__
|
||||||
|
- Understand the fundamentals of server hardware
|
||||||
|
::
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
A __server__ is essentially a computer dedicated to specific tasks, designed to remain accessible at all times. Structurally, it's not much different from a regular computer. Depending on its intended use, some components may vary. This article serves as a reference to help you understand the essential components of a server and how their roles adapt based on your needs.
|
||||||
|
|
||||||
|
## Motherboard
|
||||||
|
---
|
||||||
|
The __motherboard__ is the foundation of your machine. It's the component that connects all others together. It enables communication between components and interaction with peripherals (keyboard, mouse, etc.). Choose it based on your I/O (Input/Output) needs like USB ports, network ports, speed, etc., and ensure compatibility with the components you plan to install.
|
||||||
|
|
||||||
|
Key components connected to the motherboard:
|
||||||
|
- CPU
|
||||||
|
- RAM
|
||||||
|
- Storage (HDD and/or SSD)
|
||||||
|
- Optional dedicated GPU
|
||||||
|
|
||||||
|
Common consumer motherboard formats:
|
||||||
|
- E-ATX: largest
|
||||||
|
- ATX: standard
|
||||||
|
- Micro-ATX: smaller
|
||||||
|
- Mini-ITX: smallest
|
||||||
|
|
||||||
|
Larger boards generally offer more ports and features. Pre-built systems might use proprietary formats.
|
||||||
|
|
||||||
|
## CPU
|
||||||
|
---
|
||||||
|
<div style="display: flex; align-items: center;">
|
||||||
|
<img src="/img/global/cpu.svg" alt="Image" style="max-width: 25%; max-height:230px; margin-right: 20px;">
|
||||||
|
<p>The <strong>CPU</strong> (Central Processing Unit) is the computer's calculator. It processes most software tasks. Modern CPUs have multiple cores, often with virtual threads, to better handle workloads. They need to be cooled using either an active cooler (with a fan) or a passive one (fanless), depending on power consumption (watts). Choose your CPU based on how you plan to use the server.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
::alert{type="warning"}
|
||||||
|
:::list{type="warning"}
|
||||||
|
- __Caution:__ Ensure third-party coolers are compatible with the CPU socket and always apply thermal paste before installing the cooler.
|
||||||
|
:::
|
||||||
|
::
|
||||||
|
|
||||||
|
Consider:
|
||||||
|
- Number of cores (more cores = better multitasking)
|
||||||
|
- Clock speed in GHz
|
||||||
|
- Power consumption in Watts
|
||||||
|
|
||||||
|
For low-power home servers or NAS (non-intensive computing), consider Intel N100/150 (4 cores) or N305/N355 (8 cores)—efficient and low power (ideal for 24/7 uptime).
|
||||||
|
|
||||||
|
## RAM
|
||||||
|
---
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="/img/global/ram.svg" alt="Image" style="max-width: 65%;">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
__RAM__ (Random Access Memory) is fast, temporary memory used by the CPU (and iGPU if applicable) for quick access during execution. It clears periodically and when the machine powers down. Better RAM = better CPU performance.
|
||||||
|
|
||||||
|
Comes as sticks installed on the motherboard. Varies by format and generation (currently DDR5).
|
||||||
|
|
||||||
|
## GPU
|
||||||
|
---
|
||||||
|
|
||||||
|
The __GPU__ (Graphics Processing Unit) handles graphical, video, and sometimes AI-related processing. Its main theoretical use is to display the image on your screen. In servers, it's useful for media centers (e.g. [Plex](/serveex/media/plex)) and for accelerating AI tasks like facial recognition or photo indexing (e.g. [Immich](/serveex/cloud/immich)).
|
||||||
|
|
||||||
|
Depending on the required performance, one can choose between a dedicated GPU with its own VRAM (a graphics card connected to a PCIe slot on the motherboard), or an iGPU—an integrated GPU built into the CPU (such as the N100/N150 or N305/N355), which uses the system’s shared RAM.
|
||||||
|
|
||||||
|
### HDD(s)
|
||||||
|
---
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="/img/global/hdd.svg" alt="Image" style="max-width: 50%; margin-right: 20px;">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
An __HDD__ (Hard Disk Drive), or hard drive, is a component used to store data. It was once the standard storage device in computers. HDDs consist of one or more stacked platters and read/write heads—somewhat like a vinyl record player.
|
||||||
|
|
||||||
|
Today, HDDs can store enormous amounts of data (up to 30TB, or 30,000 gigabytes, for consumer models), but their read and write speeds are limited due to their mechanical nature. They are also bulky and heavy.
|
||||||
|
|
||||||
|
Generally, HDDs are best suited for storing data that doesn’t require frequent access or fast write speeds, such as media files (videos, photos), cloud drives, or archived data. They perform well in these scenarios and, most importantly, are significantly cheaper than SSDs for the same amount of storage.
|
||||||
|
|
||||||
|
::alert{type="success"}
|
||||||
|
✨ __Tip:__ Use multiple HDDs in [RAID](/general/storage/raid) to enhance performance and redundancy.
|
||||||
|
::
|
||||||
|
|
||||||
|
Comes in 3.5" and 2.5" formats; servers usually favor the more reliable 3.5".
|
||||||
|
|
||||||
|
### SSD(s)
|
||||||
|
---
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="/img/global/nvme.svg" alt="Image" style="max-width: 50%; margin-right: 20px;">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
An __SSD__ (Solid State Drive) is a small circuit board with memory chips soldered onto it, used to store information. Unlike RAM, these chips retain data even when not powered, meaning the information is preserved after a reboot. SSDs are generally used as the main storage medium for your server.
|
||||||
|
|
||||||
|
Unlike HDDs, SSDs have no moving parts, are highly compact, and most importantly, are extremely fast—offering speeds of several gigabytes per second for high-performance models.
|
||||||
|
|
||||||
|
SSDs come in various formats, but today the preferred choice is the M.2 NVMe version, as it is the smallest, fastest, and has become the standard on modern motherboards.
|
||||||
|
|
||||||
|
However, SSDs are significantly more expensive than hard drives for the same storage capacity. Typically, the operating system (OS) is installed on the SSD to ensure fast performance. In a server environment, it's also ideal to store [Docker containers](/serveex/core/docker) and databases on the SSD. More broadly, any data that needs to be accessed frequently and quickly—such as websites, applications, or processing workloads—should be stored on an SSD.
|
||||||
|
|
||||||
|
### Network Card
|
||||||
|
---
|
||||||
|
|
||||||
|
A __network card__ allows your machine to communicate with your network (including the internet). It consists of a controller chip and one or more network ports. These ports—often Ethernet ports—can come in different physical formats and support various data transfer standards:
|
||||||
|
|
||||||
|
- __RJ45 Gigabit Ethernet (10/100/1000):__ The standard RJ45 connector, supporting speeds from 10 Mbps (0.125 MB/s) up to 1000 Mbps (125 MB/s).
|
||||||
|
- __RJ45 2.5G:__ Same connector type, supporting up to 2.5 Gbps (2,500 Mbps or 312.5 MB/s).
|
||||||
|
- __RJ45 5G:__ Same connector, supporting up to 5 Gbps (625 MB/s).
|
||||||
|
- __RJ45 10G Base-T:__ Same RJ45 format, supporting up to 10 Gbps (1.25 GB/s).
|
||||||
|
- __SFP 1G:__ SFP port, commonly used for fiber optic connections, supporting speeds up to 1 Gbps.
|
||||||
|
- __SFP+ 10G:__ An enhanced version of the SFP port, also used for fiber optics, supporting up to 10 Gbps.
|
||||||
|
|
||||||
|
::alert{type="warning"}
|
||||||
|
:::list{type="warning"}
|
||||||
|
- __Caution:__ Match network gear (router, switch, cables) to your desired speed. For most uses, CAT5E cables are enough; use CAT6A beyond 10 Gbps. Fiber requires additional care (simplex, duplex, transceivers...).
|
||||||
|
:::
|
||||||
|
::
|
||||||
|
|
||||||
|
The network card is usually built directly into the motherboard, but you can also use dedicated network cards, for example via USB or a PCIe expansion slot.
|
||||||
|
|
||||||
|
In general, for a server setup, it's recommended to have at least two Ethernet ports to ensure redundancy in case one connection fails.
|
||||||
|
|
||||||
|
### Input/Output Ports
|
||||||
|
---
|
||||||
|
|
||||||
|
__I/O__ ports allow communication with external devices (displays, keyboard, mouse, network...). Motherboards typically offer:
|
||||||
|
- Ethernet ports
|
||||||
|
- USB ports (varied types/speeds)
|
||||||
|
- Video ports
|
||||||
|
- Audio jacks
|
||||||
|
|
||||||
|
Choose a motherboard and expansions based on your I/O needs.
|
||||||
|
|
||||||
|
### Power Supply
|
||||||
|
---
|
||||||
|
|
||||||
|
The __power supply unit__ (PSU) is the component that provides electrical power to your machine’s components. It connects to the wall via a power cord and has several output cables that plug into the motherboard and various peripherals, such as hard drives or dedicated graphics cards.
|
||||||
|
|
||||||
|
A power supply is defined by several key characteristics:
|
||||||
|
|
||||||
|
- Wattage (its total power output),
|
||||||
|
- Modularity (whether the cables are fixed or detachable),
|
||||||
|
- Efficiency (measured as a percentage). For example, a 500W PSU with 80% efficiency will actually draw 625W from the wall to deliver 500W to the system.
|
||||||
|
|
||||||
|
Another important factor is the form factor. There are several standard sizes, from ATX L (for larger cases) to SFX (for compact builds). There are also specialized models for rack-mounted servers, which are typically flat and space-efficient.
|
||||||
|
|
||||||
|
To choose the right PSU, a common rule of thumb is to estimate your system’s power needs based on usage, and then double that value. This is because most power supplies operate at optimal efficiency around 50% of their maximum load.
|
||||||
|
|
||||||
|
### Case
|
||||||
|
---
|
||||||
|
|
||||||
|
<div style="display: flex; align-items: center;">
|
||||||
|
<img src="/img/global/case.svg" alt="Image" style="max-width: 25%; max-height:230px; margin-right: 20px;">
|
||||||
|
<p>The <strong>case</strong> is also an essential component of your machine. It plays a key role in cooling, through its fans and airflow design, and it determines the form factor compatibility for your motherboard, power supply, and any dedicated GPU you may install.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
Additionally, the case dictates how many HDDs you can install and what formats they support. Some cases are rack-mountable, meaning they can be installed in server racks (server cabinets).
|
||||||
|
|
||||||
|
Choose your case carefully based on your specific needs and the hardware you plan to use.
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
---
|
||||||
|
navigation: true
|
||||||
|
title: Network
|
||||||
|
description: Aperçu du matériel réseau pour homelab — switches, cartes réseau, câbles et comment connecter efficacement vos serveurs.
|
||||||
|
main:
|
||||||
|
fluid: false
|
||||||
|
---
|
||||||
|
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||||
|
# Network
|
||||||
|
|
||||||
|
::alert{type="info"}
|
||||||
|
🎯 __Objectives:__
|
||||||
|
- Understand the basics of networking hardware
|
||||||
|
::
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
A computer network cannot exist without the hardware required to build it. Hardware determines the size of the network, communication speeds, and its overall performance. In this article, we will focus on the simplest types of networks, typically found in home environments.
|
||||||
|
|
||||||
|
## The Router
|
||||||
|
---
|
||||||
|
The __router__ is the central hub of your network. It directs __packets__—the blocks of data that travel across your network—from the sender to the appropriate recipient. It manages the routing of data both within your local network and to/from external networks. In short, it enables devices to communicate with each other and with the internet.
|
||||||
|
|
||||||
|
Everyone has a router at home—it's the __internet box__ provided by your ISP (Internet Service Provider).
|
||||||
|
|
||||||
|
In general, a router consists of:
|
||||||
|
- a WAN (Wide Area Network) port that receives data from the internet (or from a higher-level network). For example, it could be a port for a fiber optic connection from your ISP, or an SFP+/RJ45 port for a third-party router.
|
||||||
|
- a switch, i.e., a hub with several __LAN__ (Local Area Network) ports allowing multiple devices to connect to your network. These ports can be RJ45 or SFP/SFP+.
|
||||||
|
- sometimes a built-in WiFi transmitter/receiver.
|
||||||
|
|
||||||
|
A router may also include _firewall_ capabilities, allowing you to restrict traffic from specific devices, as well as _[NAT (Network Address Translation)](/general/networking/nat)_ for port forwarding. It generally includes a _[DHCP (Dynamic Host Configuration Protocol)](/general/networking/nat#dhcp)_ server to automatically assign _IP addresses_ to devices connected to the network.
|
||||||
|
|
||||||
|
The router directly affects communication speeds between devices. The WAN port limits the maximum internet speed you can receive from your ISP. For example, if your subscription offers 5 Gb/s, you’ll need a WAN port that supports at least 5 Gb/s. Likewise, internal device-to-device communication is limited by the speed of the switch. If your devices communicate at 5 Gb/s, the router’s switch must have 5 Gb/s ports. If you're using WiFi 7 equipment and want to enjoy its full speed, your router must support it as well. If you’re using a separate WiFi access point, make sure its network port matches or exceeds the speed of the WiFi it broadcasts—and that the router supports it too.
|
||||||
|
|
||||||
|
Internet speed, number of devices, WiFi speed, and internal network speed—these are the four key factors to consider when choosing an internet box or buying your own router.
|
||||||
|
|
||||||
|
::alert{type="success"}
|
||||||
|
✨ __Tip:__
|
||||||
|
You can easily use a third-party router to manage your network if your ISP’s internet box supports _bridge mode_. In France, only the provider Free offers this option. It is technically possible with other providers that do not support bridge mode, but it can be quite difficult and may prevent you from using all the features a third-party router provides.
|
||||||
|
::
|
||||||
|
|
||||||
|
## The Switch
|
||||||
|
---
|
||||||
|
|
||||||
|
The __switch__, or network switch, is a device that allows multiple devices to connect to the network. It acts as a literal hub, connecting directly to the router or to another switch upstream. It helps avoid overloading the switch ports on your router or relocating devices to another room without running a cable from each one back to the router. Another common use case is to segment multiple networks that are managed by the same router.
|
||||||
|
|
||||||
|
There are generally two types of switches:
|
||||||
|
- **Unmanaged switches**, the most common. These are plug-and-play: you just plug them in and everything works automatically.
|
||||||
|
- **Managed switches**. These offer a configuration interface (via command line or web UI), allowing you to fine-tune routing rules under the control of the router. They are powerful for creating virtual networks between your devices, but usually require more setup time and are less convenient than simple unmanaged switches.
|
||||||
|
|
||||||
|
::alert{type="warning"}
|
||||||
|
:::list{type="warning"}
|
||||||
|
- __Warning:__ Make sure to use a switch with ports that match the speeds supported by your network devices.
|
||||||
|
:::
|
||||||
|
::
|
||||||
|
|
||||||
|
## Cables
|
||||||
|
---
|
||||||
|
|
||||||
|
Cables are essential components of your network. Depending on their type and category, they can limit the bandwidth between devices, so they must be chosen to match your network's specifications. They also need to be compatible with your devices' ports.
|
||||||
|
|
||||||
|
Here’s a quick reference of the most common cable and port standards:
|
||||||
|
|
||||||
|
- **RJ45 Gigabit Ethernet 10/100/1000**: The standard RJ45 connector, supporting speeds from 10 Mbps (0.125 MB/s) to 1000 Mbps (125 MB/s)
|
||||||
|
- **RJ45 2.5G**: Same connector, supporting speeds up to 2.5 Gbps (312.5 MB/s)
|
||||||
|
- **RJ45 5G**: Same connector, supporting speeds up to 5 Gbps (625 MB/s)
|
||||||
|
- **RJ45 10GBase-T**: Same connector, supporting speeds up to 10 Gbps (1.25 GB/s)
|
||||||
|
- **SFP 1G**: SFP port, typically used for fiber optics, supporting up to 1 Gbps
|
||||||
|
- **SFP+ 10G**: Enhanced SFP port, also for fiber, supporting up to 10 Gbps
|
||||||
|
|
||||||
|
### Ethernet Cables
|
||||||
|
|
||||||
|
These copper cables usually use the standard `RJ45` connector. It's the most common network connector found on routers and switches.
|
||||||
|
|
||||||
|
Ethernet cables are divided into categories that define their maximum speed based on distance:
|
||||||
|
|
||||||
|
| Speed | Cable Type | Max Distance |
|
||||||
|
|-----------|------------|--------------|
|
||||||
|
| 10 Gb/s | CAT 6A | 100 m |
|
||||||
|
| | CAT 6 | 55 m |
|
||||||
|
| | CAT 5e | 30 m |
|
||||||
|
| 5 Gb/s | CAT 6 | 100 m |
|
||||||
|
| | CAT 5e | 30 m |
|
||||||
|
| 2.5 Gb/s | CAT 5e | 100 m |
|
||||||
|
| 1 Gb/s | CAT 5e | 100 m |
|
||||||
|
| 100 Mb/s | CAT 5 | 100 m |
|
||||||
|
|
||||||
|
Some of these cables are flat, round, shielded (requiring grounding), etc. Choose based on your setup. What matters is that, for example, if you want to connect a device with a 2.5 Gb/s RJ45 port to a 2.5 Gb/s router, you’ll need at least a `CAT 5e` cable.
|
||||||
|
|
||||||
|
On the other hand, if your device is limited to 100 Mb/s, a simple `CAT 5` cable will suffice.
|
||||||
|
|
||||||
|
Nowadays, in new buildings, it is standard practice to install `CAT 6A` cables inside walls. This way, wall ports are ready to support 10 Gb/s over 100 meters.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Optical Cables
|
||||||
|
|
||||||
|
Very thin but fragile, optical cables are increasingly appearing in home networks. It often starts with the fiber cable connecting your ISP’s outlet to your box/router. They have several advantages:
|
||||||
|
- Extremely compact
|
||||||
|
- Zero electrical consumption (unlike copper, which loses energy as heat)
|
||||||
|
- No electromagnetic radiation (no shielding needed, no signal interference)
|
||||||
|
- Very high speeds over long distances
|
||||||
|
|
||||||
|
For local networking, it's important to understand that several types of fiber cables exist. Their performance depends on both distance and compatibility with the appropriate `transceiver`. Fiber cables connect to your devices' SFP+ ports via a small device called a transceiver, which converts electrical signals to light (and vice versa).
|
||||||
|
|
||||||
|
For local networks, the recommended standard is a **multimode OM3 fiber with LC connectors**, paired with a **10G LC SFP+ transceiver**. This setup allows 10 Gb/s connections and is compatible with most devices featuring SFP+ ports.
|
||||||
|
|
||||||
|
::alert{type="warning"}
|
||||||
|
:::list{type="warning"}
|
||||||
|
- __Warning:__ Make sure to use transceivers that are compatible with your devices (routers, switches, or other hardware). There is no universal standard yet, and manufacturers usually specify which brands are supported.
|
||||||
|
:::
|
||||||
|
::
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### DAC Cables
|
||||||
|
|
||||||
|
These are copper cables with integrated `transceivers`. They allow two SFP/SFP+ ports to communicate over short distances without using fragile fiber or RJ45 adapters. However, they consume more energy due to natural copper loss, which is non-negligible.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### SFP+ Transceivers
|
||||||
|
|
||||||
|
These let you connect different types of cables to your SFP/SFP+ ports. Variants are available for:
|
||||||
|
- Fiber optic
|
||||||
|
- DAC
|
||||||
|
- RJ45
|
||||||
|
|
||||||
|
::alert{type="warning"}
|
||||||
|
:::list{type="warning"}
|
||||||
|
- RJ45 transceivers consume a lot of energy due to copper signal loss and can generate significant heat. Low-power models (under 2W) exist and are generally rated for longer cables (e.g., 80m instead of 30m). Surprisingly, these are preferred over short-distance models because they generate less heat and consume less energy—making them more compatible with sensitive devices. Using the wrong type can cause network degradation or even outages.
|
||||||
|
:::
|
||||||
|
::
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
---
|
||||||
|
navigation: true
|
||||||
|
title: The ProloNAS
|
||||||
|
description: Construire un serveur domestique performant avec un mini PC Intel N100 — guide pratique pour démarrer l'auto-hébergement pour moins de 130 €.
|
||||||
|
fluid: false
|
||||||
|
---
|
||||||
|
|
||||||
|
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||||
|
|
||||||
|
# The ProloNAS
|
||||||
|
|
||||||
|
When you decide to dive into the adventure of running your own home server, the same questions usually come up: _“Where should I start?”_, _“Isn’t it expensive?”_. And often, people either give up or end up buying a proprietary NAS that they’ll throw away a year later once they realize it only brings headaches and wasted money.
|
||||||
|
|
||||||
|
A server isn’t a piece of furniture. It’s simply any computer capable of running Linux.That’s why mini PCs powered by **Intel N100** processors are so popular: for around $100–130 on Chinese platforms, you can get a machine that runs **24/7** for years, capable of handling everything you’d expect from a home server or personal cloud without sacrificing performance.
|
||||||
|
|
||||||
|
It’s **objectively inexpensive**, and anyone with a bit of curiosity can get started.
|
||||||
|
|
||||||
|
A mini PC for $100 + a USB dock for $50 that holds multiple hard drives = a complete platform for $150, versus **$350–1200** for branded NAS systems.
|
||||||
|
|
||||||
|
That’s all a **ProloNAS** is. It’s then up to you to scale your storage capacity according to your needs.
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
## Example Hardware
|
||||||
|
|
||||||
|
- Mini PC — **Note: choose 16 GB / 512 GB**: [Aliexpress](https://fr.aliexpress.com/item/1005008477986765.html)
|
||||||
|
- DAS (Direct Attached Storage) — **Note: select “EU plug”**: [Aliexpress](https://fr.aliexpress.com/item/1005007933987260.html)
|
||||||
|
- More refined alternative with a fan: [Amazon](https://www.amazon.fr/Boîtier-Disque-Ventilateur-Supportant-Capacité/dp/B0DD3GSSCX)
|
||||||
|
|
||||||
|
> *These are not affiliate links — buy wherever you prefer.*
|
||||||
|
|
||||||
|
|
||||||
|
## Why a NAS?
|
||||||
|
|
||||||
|
A **NAS** (Network Attached Storage) is a machine centered around storage, designed to be shared over a network.The idea is to have a **reliable and secure** storage space that serves as the backbone for your personal services and apps such as a self-hosted cloud like [Nextcloud](/serveex/cloud/nextcloud), a photo sync tool like [Immich](/serveex/cloud/immich), or a media server like [Plex](/serveex/media/plex). You can also store camera footage, backups, or even development projects on it.
|
||||||
|
|
||||||
|
### But why not just use a mini PC with an external hard drive?
|
||||||
|
|
||||||
|
Sure, a simple mini PC with 1–2 TB of storage will do for most people.And your movie collection might fit on an external drive of a few extra terabytes. But that’s **neither reliable nor scalable** a single shock or hardware failure could permanently destroy your data.
|
||||||
|
|
||||||
|
A real NAS is built around **storage reliability**. It uses redundancy strategies like [RAID](/general/storage/raid) to protect against drive failure, and snapshot systems like [ZFS](/general/storage/zfs) to guard against corruption.
|
||||||
|
|
||||||
|
In short, a NAS lets you **host everything yourself** that you currently entrust to third parties while maintaining control, reliability, and data safety.
|
||||||
|
|
||||||
|
|
||||||
|
## The Problem with Consumer NAS Systems
|
||||||
|
|
||||||
|
Many brands offer “ready-to-use” NAS platforms: Synology, QNAP, Ugreen, and others. They promise simplicity and sleek web interfaces, but the reality is quite different.
|
||||||
|
|
||||||
|
### First, the price.
|
||||||
|
|
||||||
|
$350 is the starting price for a 2-bay NAS (without drives) from Synology. For that, you get a 2019 processor, no SSD slot for the OS, and a measly 2 GB of RAM.
|
||||||
|
|
||||||
|
Now, compare that to the **ProloNAS**: an N100 (4 cores), 16 GB RAM, a 512 GB SSD for $100–130, plus a 4-bay DAS for $55. That’s **half the price** of a 2-bay Synology, and **a quarter of the price** of a 4-bay one.
|
||||||
|
|
||||||
|
### Locked-Down Operating Systems
|
||||||
|
|
||||||
|
“Yeah, but at least with a Synology, you plug it in and everything just works.”
|
||||||
|
|
||||||
|
One year.
|
||||||
|
That’s how long it took before I threw away my Synology and realized I should have started with a **ProloNAS** (which later became a full-fledged server).
|
||||||
|
|
||||||
|
Manufacturers ship heavily customized Linux-based OSes: ancient kernels, limited app repositories, and complete dependence on their proprietary tools. As a result, you can’t fully tailor your NAS to your needs, and many Docker containers simply won’t run because the kernel is too old.
|
||||||
|
|
||||||
|
### Total Vendor Lock-In
|
||||||
|
|
||||||
|
“I’m fine with the built-in apps.”
|
||||||
|
Yeah, I thought so too… until my needs exploded: media center, password manager, Git hosting, strong authentication, web hosting, and more.
|
||||||
|
|
||||||
|
Why stay stuck with half-baked proprietary tools when you can rely on **open-source projects** that are regularly updated and interoperable?
|
||||||
|
|
||||||
|
And what happens when the manufacturer decides to drop support or limit hardware compatibility? It’s already happened, Synology made certain drives **incompatible** unless they were “certified” by them. They even **disabled hardware transcoding** on their NAS units: [see here](https://www.cachem.fr/synology-desactive-transcodage-materiel-nas/).
|
||||||
|
|
||||||
|
In short, you have **no control** over a product that isn’t open, nor truly yours.
|
||||||
|
|
||||||
|
## OK, but how do I turn my Mini PC Serveex into a ProloNAS?
|
||||||
|
|
||||||
|
As mentioned earlier: by adding a **DAS (drive hub)** and setting up a redundant storage system with [RAID](/general/storage/raid) and [ZFS](/general/storage/zfs), you can transform your mini PC into a robust and scalable NAS.
|
||||||
|
|
||||||
|
Enjoy !
|
||||||
|
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
icon: lucide:bookmark
|
icon: lucide:bookmark
|
||||||
navigation: true
|
navigation: true
|
||||||
title: Introduction
|
title: Introduction
|
||||||
|
description: Introduction à Serveex — un projet homelab pour auto-héberger ses services du quotidien avec Debian et Docker, sans dépendre de Google, Apple ou Netflix.
|
||||||
main:
|
main:
|
||||||
fluid: false
|
fluid: false
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
---
|
||||||
|
navigation: true
|
||||||
|
title: Debian 13
|
||||||
|
description: Guide pas à pas pour installer Debian 13 sur un serveur et configurer l'accès SSH, les paquets essentiels et un système de base prêt à l'emploi.
|
||||||
|
main:
|
||||||
|
fluid: false
|
||||||
|
---
|
||||||
|
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||||
|
# Debian 13
|
||||||
|
::alert{type="info"}
|
||||||
|
🎯 __Goal:__ Install Debian 13 and the main dependencies to have a ready-to-use OS, accessible via SSH.
|
||||||
|
::
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Installation
|
||||||
|
---
|
||||||
|
1. [BIOS Setup]((https://www.debian.org/releases/stable/i386/ch03s06.en.html#bios-setup)
|
||||||
|
2. [Download Debian Image](https://www.debian.org/download.en.html)
|
||||||
|
3. [Create Bootable USB (Rufus)](https://dev.to/devops2808/how-to-create-bootable-usb-installer-for-debian-12-4f66)
|
||||||
|
4. [Install Debian and Set Up SSH](https://www.howtoforge.com/tutorial/debian-minimal-server/)
|
||||||
|
5. Install sudo and add a user to the sudo group for administrative privileges.
|
||||||
|
Log in as root:
|
||||||
|
```sh
|
||||||
|
su -
|
||||||
|
```
|
||||||
|
Enter your password, then type:
|
||||||
|
```sh
|
||||||
|
apt install sudo
|
||||||
|
```
|
||||||
|
Add the user to the sudo group:
|
||||||
|
```sh
|
||||||
|
adduser <username> sudo
|
||||||
|
```
|
||||||
|
|
||||||
|
Next time the user logs in, they will be able to use the `sudo` command to execute commands with administrative privileges.
|
||||||
|
|
||||||
|
6. [Everything About Remote Console Access (SSH)](https://www.digitalocean.com/community/tutorials/ssh-essentials-working-with-ssh-servers-clients-and-keys)
|
||||||
|
7. Optional - [UPS Client in Case of Power Outage](https://www.sindastra.de/p/2078/how-to-connect-linux-server-to-synology-ups-server) / [also here](https://www.reddit.com/r/synology/comments/gtkjam/use_synology_nas_as_ups_server_to_safely_power/)
|
||||||
|
8. Optional - Wake up after power outage → configure BIOS S0 state
|
||||||
|
9. Optional - [Wake Server Remotely (WoW - WoL)](https://dev.to/zakery1369/enable-wake-on-lan-on-debian-4ljd)
|
||||||
|
|
||||||
|
## Must-Have CLI Apps
|
||||||
|
---
|
||||||
|
Some essential apps you’ll likely need at some point, so might as well install them early:
|
||||||
|
```sh
|
||||||
|
sudo apt update
|
||||||
|
sudo apt upgrade
|
||||||
|
sudo apt install vim btop ranger git duf neofetch samba cifs-utils tree unzip
|
||||||
|
```
|
||||||
|
|
||||||
|
Additionally:
|
||||||
|
|
||||||
|
- [gping](https://www.linode.com/docs/guides/how-to-use-gping-on-linux/) - Graphical ping tool
|
||||||
|
- [lazydocker](https://github.com/jesseduffield/lazydocker) - CLI Docker container manager
|
||||||
|
|
||||||
|
## Useful Features
|
||||||
|
---
|
||||||
|
### Firewall
|
||||||
|
- [ufw](https://www.zenarmor.com/docs/network-security-tutorials/how-to-set-up-a-firewall-with-ufw-on-debian)
|
||||||
|
- [Firewalld](https://linuxcapable.com/how-to-install-firewalld-on-debian-linux/)
|
||||||
|
|
||||||
|
### Samba Sharing (Access a Remote Network Disk)
|
||||||
|
- [Create and Access a Samba Share](/general/networking/samba)
|
||||||
|
|
||||||
|
|
||||||
|
### File Transfer via rsync
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo rsync -avhHSP /source /destination
|
||||||
|
```
|
||||||
|
::alert{type="info" icon="exclamation-circle"}
|
||||||
|
:::list{type="info"}
|
||||||
|
- Add ` --exclude @eaDir`{lang=shell} if the source is a Synology NAS
|
||||||
|
:::
|
||||||
|
::
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
---
|
||||||
|
navigation: true
|
||||||
|
title: Docker
|
||||||
|
description: Installer Docker et Dockge sur Debian pour déployer et gérer des services auto-hébergés avec de simples stacks de conteneurs.
|
||||||
|
main:
|
||||||
|
fluid: false
|
||||||
|
---
|
||||||
|
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||||
|
# Docker
|
||||||
|
|
||||||
|
Docker, to install deployable services in seconds and manage them with just a few commands or clicks.
|
||||||
|
|
||||||
|
::alert{type="info"}
|
||||||
|
🎯 __Goals:__
|
||||||
|
- Install [Docker](https://www.docker.com/)
|
||||||
|
- Install [Dockge](https://github.com/louislam/dockge) to manage stacks
|
||||||
|
- Install [Watchtower](https://github.com/containrrr/watchtower) to update containers
|
||||||
|
::
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Install Docker
|
||||||
|
---
|
||||||
|
Add the Docker repositories and GPG key:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Add Docker's official GPG key:
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install ca-certificates curl
|
||||||
|
sudo install -m 0755 -d /etc/apt/keyrings
|
||||||
|
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
|
||||||
|
sudo chmod a+r /etc/apt/keyrings/docker.asc
|
||||||
|
|
||||||
|
# Add the repository to Apt sources:
|
||||||
|
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||||
|
sudo apt-get update
|
||||||
|
```
|
||||||
|
|
||||||
|
Install the packages:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it!
|
||||||
|
|
||||||
|
**More options:** [Install Docker for Debian 13](https://docs.docker.com/engine/install/debian/)
|
||||||
|
|
||||||
|
::alert{type="info" icon="exclamation-circle"}
|
||||||
|
:::list{type="info"}
|
||||||
|
- From here on, we assume the stacks are installed in the `/docker` folder, created using the command:
|
||||||
|
:::
|
||||||
|
```sh
|
||||||
|
sudo mkdir /docker
|
||||||
|
::
|
||||||
|
|
||||||
|
## Install [Dockge](https://github.com/louislam/dockge) to manage and deploy containers
|
||||||
|
---
|
||||||
|
[Dockge](https://github.com/louislam/dockge) is a web tool to create, configure, launch, and manage Docker containers. It's a simple, intuitive interface that’s lighter and easier for beginners than using the CLI or Portainer.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
File structure we will create:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
root
|
||||||
|
└── docker
|
||||||
|
└── dockge
|
||||||
|
└── compose.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
Create the stack folder:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd /docker
|
||||||
|
sudo mkdir dockge
|
||||||
|
```
|
||||||
|
|
||||||
|
Then create the `compose.yml` file in this folder using `vim`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd /docker/dockge
|
||||||
|
sudo vi compose.yml
|
||||||
|
```
|
||||||
|
Press `i` to enter insert mode and paste the following:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
services:
|
||||||
|
dockge:
|
||||||
|
image: louislam/dockge:1
|
||||||
|
restart: unless-stopped
|
||||||
|
container_name: dockge
|
||||||
|
ports:
|
||||||
|
- 3555:5001 # LAN-accessible port will be 3555
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
- /docker/dockge/data:/app/data
|
||||||
|
- /docker:/docker
|
||||||
|
environment:
|
||||||
|
- DOCKGE_STACKS_DIR=/docker
|
||||||
|
```
|
||||||
|
|
||||||
|
Press `Esc` and type `:x` to save and exit.
|
||||||
|
|
||||||
|
To launch the container:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd /docker/dockge
|
||||||
|
sudo docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Then go to `http://yourserverip:3555` in your browser to access the login page.
|
||||||
|
|
||||||
|
More info on [Dockge and how to use it](https://github.com/louislam/dockge)
|
||||||
|
|
||||||
|
And there you go — Docker and a tool to easily manage your containers are ready!
|
||||||
|
|
||||||
|
## [Watchtower](https://watchtower.nickfedor.com/), to auto-update containers
|
||||||
|
---
|
||||||
|
Watchtower is a container that checks for updates and pulls new images automatically, just by adding a label in your containers’ `compose.yml` files.
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
- Open Dockge in your browser
|
||||||
|
- Click `compose`
|
||||||
|
- Name the stack `watchtower`
|
||||||
|
- Paste the config below into the default config area in Dockge
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
services:
|
||||||
|
watchtower:
|
||||||
|
container_name: watchtower
|
||||||
|
image: ghcr.io/nicholas-fedor/watchtower:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
environment:
|
||||||
|
- TZ=Europe/Paris
|
||||||
|
- WATCHTOWER_SCHEDULE=${SCHEDULE}
|
||||||
|
- WATCHTOWER_LABEL_ENABLE=true
|
||||||
|
- WATCHTOWER_CLEANUP=true
|
||||||
|
- WATCHTOWER_REMOVE_VOLUMES=true
|
||||||
|
# Discord notifications - uncomment if used
|
||||||
|
#- WATCHTOWER_NOTIFICATIONS=slack
|
||||||
|
#- WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER=Watchtower
|
||||||
|
#- WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL=${WH_URL}
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
```
|
||||||
|
|
||||||
|
Then fill in the `.env` section in Dockge with the following:
|
||||||
|
|
||||||
|
```properties
|
||||||
|
SCHEDULE=
|
||||||
|
WH_URL=
|
||||||
|
```
|
||||||
|
|
||||||
|
| Property | Value | Examples |
|
||||||
|
|----------------|--------------------------------------------------------------------|----------------------------------------------|
|
||||||
|
| `SCHEDULE` | Cron format | `0 0 6 * * *` (every day at 6 AM) |
|
||||||
|
| `WH_URL` | Your Discord webhook URL - append `/slack` at the end | `https://yourdiscordserver/webhook/slack` |
|
||||||
|
|
||||||
|
To have Watchtower monitor your other containers, add this to their `compose.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
labels:
|
||||||
|
- com.centurylinklabs.watchtower.enable=true
|
||||||
|
```
|
||||||
|
|
||||||
|
Then restart the modified stacks. And that's it — you now have a solid base to start deploying the services you want!
|
||||||
@@ -0,0 +1,380 @@
|
|||||||
|
---
|
||||||
|
navigation: true
|
||||||
|
title: SWAG
|
||||||
|
description: Configurer SWAG comme reverse proxy avec SSL automatique, exposer vos services en toute sécurité et activer le blocage géographique sur votre homelab.
|
||||||
|
main:
|
||||||
|
fluid: false
|
||||||
|
---
|
||||||
|
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||||
|
# SWAG
|
||||||
|
|
||||||
|
::alert{type="info"}
|
||||||
|
🎯 __Objectives:__
|
||||||
|
- Install Swag
|
||||||
|
- Enable SSL
|
||||||
|
- Access the dashboard
|
||||||
|
- Configure regional blocking
|
||||||
|
- Expose Dockge
|
||||||
|
::
|
||||||
|
|
||||||
|
[Swag](https://docs.linuxserver.io/general/swag/) is the core of this homelab. It’s a powerful reverse proxy that allows you to expose services on the internet using domain names, handling SSL certificate issuance (for encrypted connections), request routing, and access security (via HTTP auth or SSO like Authelia or Authentik). All the necessary documentation is [available here](https://docs.linuxserver.io/general/swag).
|
||||||
|
|
||||||
|
::alert{type="warning"}
|
||||||
|
:::list{type="warning"}
|
||||||
|
- SWAG is only useful for exposing your services to the internet—i.e., accessing them via a public URL like `https://service.mydomain.com`. If you don’t want to expose your services and prefer to always use a VPN to connect remotely, you can go [here instead](/serveex/security/wireguard).
|
||||||
|
:::
|
||||||
|
::
|
||||||
|
|
||||||
|
Below is an example exposing Dockge. We will install SWAG along with the dbip mod for geolocation-based blocking, and the dashboard mod for managing swag, fail2ban, and geolocation.
|
||||||
|
|
||||||
|
**Reverse proxy principle and its application in our case:**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Installation
|
||||||
|
---
|
||||||
|
|
||||||
|
::alert{type="info" icon="exclamation-circle"}
|
||||||
|
:::list{type="info"}
|
||||||
|
- This tutorial assumes you have a domain name pointing to your server, and that your router has a NAT rule forwarding port `443` to your server's IP and port `443`. The example domain will be `mydomain.com`.
|
||||||
|
:::
|
||||||
|
::
|
||||||
|
|
||||||
|
File structure to be modified:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
root
|
||||||
|
└── docker
|
||||||
|
└── swag
|
||||||
|
├── config
|
||||||
|
│ ├── dns-conf
|
||||||
|
│ │ └── ovh.ini
|
||||||
|
│ └── nginx
|
||||||
|
│ ├── dbip.conf
|
||||||
|
│ ├── nginx.conf
|
||||||
|
│ └── proxy-confs
|
||||||
|
│ └── dockge.subdomain.conf
|
||||||
|
├── compose.yml
|
||||||
|
└── .env
|
||||||
|
```
|
||||||
|
|
||||||
|
Open Dockge in your browser, click on `compose`, name the stack `swag`, and copy the following config:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
services:
|
||||||
|
swag:
|
||||||
|
image: lscr.io/linuxserver/swag:latest
|
||||||
|
container_name: swag
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
environment:
|
||||||
|
- TZ=Europe/Paris
|
||||||
|
- URL=${DOMAIN}
|
||||||
|
- EXTRA_DOMAINS=${DOMAINS}
|
||||||
|
- SUBDOMAINS=wildcard
|
||||||
|
- VALIDATION=dns
|
||||||
|
- DNSPLUGIN=${PLUGIN}
|
||||||
|
- EMAIL=${EMAIL}
|
||||||
|
- DOCKER_MODS=linuxserver/mods:swag-dbip|linuxserver/mods:swag-dashboard|linuxserver/mods:swag-auto-reload
|
||||||
|
volumes:
|
||||||
|
- /docker/swag/config:/config
|
||||||
|
ports:
|
||||||
|
- 80:80
|
||||||
|
- 443:443
|
||||||
|
- 81:81
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- swag
|
||||||
|
|
||||||
|
networks:
|
||||||
|
swag:
|
||||||
|
name: swag_default
|
||||||
|
```
|
||||||
|
|
||||||
|
::alert{type="success"}
|
||||||
|
✨ __Tip:__
|
||||||
|
Add the watchtower label to each container to enable automatic updates
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
swag:
|
||||||
|
#...
|
||||||
|
labels:
|
||||||
|
- com.centurylinklabs.watchtower.enable=true
|
||||||
|
```
|
||||||
|
::
|
||||||
|
|
||||||
|
Then in the `.env` file:
|
||||||
|
|
||||||
|
```properties
|
||||||
|
DOMAIN=
|
||||||
|
DOMAINS=
|
||||||
|
EMAIL=
|
||||||
|
PLUGIN=
|
||||||
|
```
|
||||||
|
|
||||||
|
Fill out the variables as follows:
|
||||||
|
|
||||||
|
| Property | Value | Examples |
|
||||||
|
|-------------------------|---------------------------------------------------------------------------|-----------------------|
|
||||||
|
| `DOMAIN` | Your domain (covers all subdomains too) | `mydomain.com` |
|
||||||
|
| `DOMAINS` | Any additional domains | `myseconddomain.com` |
|
||||||
|
| `EMAIL` | Your email for generating the certificate | `[email protected]` |
|
||||||
|
| `PLUGIN` | Plugin for certificate generation—depends on your [DNS provider](https://docs.linuxserver.io/general/swag/) | `ovh`<br>`cloudflare` |
|
||||||
|
|
||||||
|
Assuming your DNS zone is managed by OVH, deploy the stack once. The logs will show a failure in creating the SSL certificate due to a missing `ovh.ini` configuration. Stop the stack.
|
||||||
|
|
||||||
|
In CLI, go to the dns-conf folder and edit the `ovh.ini` file:
|
||||||
|
|
||||||
|
::alert{type="success"}
|
||||||
|
✨ __Tip for terminal-shy users:__
|
||||||
|
You can use [File Browser](/serveex/files/file-browser) to browse and edit files instead of using terminal commands.
|
||||||
|
::
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo vi /docker/swag/config/dns-conf/ovh.ini
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see:
|
||||||
|
|
||||||
|
```properties
|
||||||
|
# Instructions: https://github.com/certbot/certbot/blob/master/certbot-dns-ovh/certbot_dns_ovh/__init__.py#L20
|
||||||
|
# Replace with your values
|
||||||
|
dns_ovh_endpoint = ovh-eu
|
||||||
|
dns_ovh_application_key =
|
||||||
|
dns_ovh_application_secret =
|
||||||
|
dns_ovh_consumer_key =
|
||||||
|
```
|
||||||
|
|
||||||
|
Authenticate and create [your token here](https://www.ovh.com/auth/?onsuccess=https%3A%2F%2Fwww.ovh.com%2Fauth%2Fapi%2FcreateToken).
|
||||||
|
|
||||||
|
Set the following permissions:
|
||||||
|
|
||||||
|
* `GET /domain/zone/*`
|
||||||
|
* `PUT /domain/zone/*`
|
||||||
|
* `POST /domain/zone/*`
|
||||||
|
* `DELETE /domain/zone/*`
|
||||||
|
|
||||||
|
Note the 3 keys temporarily and enter them in `ovh.ini`. (In vim, press `i` to edit, `Esc` when done, `:x` to save and exit)
|
||||||
|
|
||||||
|
Save and exit the file.
|
||||||
|
|
||||||
|
Now configure swag to access DBIP, the geolocation-based access control module. Open the `nginx.conf` file:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo vi /docker/swag/config/nginx/nginx.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
Add the following line below the `http` section:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
include /config/nginx/dbip.conf;
|
||||||
|
```
|
||||||
|
|
||||||
|
Restart the stack in Dockge. This time, the SSL certificate should be successfully generated! Check the logs to confirm the server is ready.
|
||||||
|
|
||||||
|
## Dashboard
|
||||||
|
---
|
||||||
|
Access the dashboard locally by going to `http://yourserverip:81`
|
||||||
|
On the left, you'll see a list of currently "proxied" services (none yet). On the right, the list of banned IPs. Below, various indicators. For more details, [click here](https://www.linuxserver.io/blog/introducing-swag-dashboard).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
## DBIP
|
||||||
|
---
|
||||||
|
DBIP allows you to block connections based on countries. It relies on the configuration file named `dbip.conf` located in `/docker/swag/config/nginx`. [More info here](https://virtualize.link/secure/).
|
||||||
|
|
||||||
|
In this example, we’ll configure it to block a list of countries known to be the source of most malicious traffic. We’ll also configure a variable to allow internal server traffic, your box’s local network, and a potential VPN in the 10.x.x.x range to access your services — but not the open internet.
|
||||||
|
|
||||||
|
This configuration can be enabled or disabled per service (see the Dockge example below).
|
||||||
|
|
||||||
|
Open `dbip.conf`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo vi /docker/swag/config/nginx/dbip.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
Make your changes ([see documentation](https://github.com/linuxserver/docker-mods/tree/swag-dbip)), or use the following example:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
geoip2 /config/geoip2db/dbip-country-lite.mmdb {
|
||||||
|
auto_reload 1w;
|
||||||
|
$geoip2_data_continent_code continent code;
|
||||||
|
$geoip2_data_country_iso_code country iso_code;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Country Codes: https://en.wikipedia.org/wiki/ISO_3166-2
|
||||||
|
|
||||||
|
map $geoip2_data_country_iso_code $geo-whitelist {
|
||||||
|
default no;
|
||||||
|
FR yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
map $geoip2_data_country_iso_code $geo-blacklist {
|
||||||
|
default yes;
|
||||||
|
CN no; #China
|
||||||
|
RU no; #Russia
|
||||||
|
HK no; #Hong Kong
|
||||||
|
IN no; #India
|
||||||
|
IR no; #Iran
|
||||||
|
VN no; #Vietnam
|
||||||
|
TR no; #Turkey
|
||||||
|
EG no; #Egypt
|
||||||
|
MX no; #Mexico
|
||||||
|
JP no; #Japan
|
||||||
|
KR no; #South Korea
|
||||||
|
KP no; #North Korea
|
||||||
|
PE no; #Peru
|
||||||
|
BR no; #Brazil
|
||||||
|
UA no; #Ukraine
|
||||||
|
ID no; #Indonesia
|
||||||
|
TH no; #Thailand
|
||||||
|
}
|
||||||
|
|
||||||
|
geo $lan-ip {
|
||||||
|
default no;
|
||||||
|
10.0.0.0/8 yes;
|
||||||
|
172.16.0.0/12 yes;
|
||||||
|
192.168.0.0/16 yes;
|
||||||
|
127.0.0.1 yes;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Save and close the file. Restart the stack.
|
||||||
|
|
||||||
|
In the domain config files (see next section), you can enable or disable the whitelist or blacklist ([see documentation here](https://www.forum-nas.fr/threads/tuto-installer-swag-en-docker-reverse-proxy.15057/)). In our case, the whitelist allows only French requests. The blacklist blocks only the listed countries. We'll use the blacklist, like so:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
listen [::]:443 ssl;
|
||||||
|
|
||||||
|
server_name some-app.*;
|
||||||
|
include /config/nginx/ssl.conf;
|
||||||
|
client_max_body_size 0;
|
||||||
|
|
||||||
|
if ($geo-blacklist = no) { return 404; }
|
||||||
|
|
||||||
|
location / {
|
||||||
|
```
|
||||||
|
|
||||||
|
## Exposing Dockge
|
||||||
|
---
|
||||||
|
::alert{type="info"}
|
||||||
|
📋 __Prerequisite:__ <br/><br/>
|
||||||
|
We assume that you have created a subdomain like `dockge.mydomain.com` in your [DNS zone](/general/networking/dns), with a `CNAME` pointing to `mydomain.com` and — unless you're using [Cloudflare Zero Trust](/serveex/security/cloudflare) — that you've forwarded port `443` from your router to the server's `443` in [your NAT rules](/general/networking/nat).
|
||||||
|
::
|
||||||
|
|
||||||
|
Now it's time to expose Dockge on the internet so you can access and manage your containers remotely. We assume you've set up the subdomain `dockge.mydomain.com` with a `CNAME` pointing to `mydomain.com`.
|
||||||
|
|
||||||
|
::alert{type="warning"}
|
||||||
|
:::list{type="warning"}
|
||||||
|
- Dockge does not support multi-factor authentication. Exposing it online could compromise all connected machines. Only do this if you're using an MFA solution like [Authentik](/serveex/security/authentik/). Otherwise, don’t expose it with SWAG — use a VPN like [Wireguard](/serveex/security/wireguard) instead.
|
||||||
|
:::
|
||||||
|
::
|
||||||
|
|
||||||
|
Open the `dockge.subdomain.conf` file:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo vi /docker/swag/config/nginx/proxy-confs/dockge.subdomain.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
Configure it like this:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
## Version 2023/12/19
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
listen [::]:443 ssl;
|
||||||
|
|
||||||
|
server_name dockge.*; # define the subdomain to redirect
|
||||||
|
|
||||||
|
include /config/nginx/ssl.conf;
|
||||||
|
|
||||||
|
client_max_body_size 0;
|
||||||
|
|
||||||
|
#if ($lan-ip = yes) { set $geo-whitelist yes; }
|
||||||
|
#if ($geo-whitelist = no) { return 404; }
|
||||||
|
if ($geo-blacklist = no) { return 404; } # all countries un blacklist are forbidden
|
||||||
|
|
||||||
|
#include /config/nginx/ldap-server.conf;
|
||||||
|
#include /config/nginx/authelia-server.conf;
|
||||||
|
#include /config/nginx/authentik-server.conf;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
#auth_basic "Restricted";
|
||||||
|
#auth_basic_user_file /config/nginx/.htpasswd;
|
||||||
|
|
||||||
|
#include /config/nginx/ldap-location.conf;
|
||||||
|
#include /config/nginx/authelia-location.conf;
|
||||||
|
#include /config/nginx/authentik-location.conf;
|
||||||
|
|
||||||
|
include /config/nginx/proxy.conf;
|
||||||
|
include /config/nginx/resolver.conf;
|
||||||
|
|
||||||
|
set $upstream_app dockge; # container name
|
||||||
|
set $upstream_port 5001; # internal container port (not exposed port)
|
||||||
|
set $upstream_proto http;
|
||||||
|
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Save and exit. The configuration will update within a few seconds.
|
||||||
|
|
||||||
|
::alert{type="info"}
|
||||||
|
:::list{type="info"}
|
||||||
|
- By default, SWAG doesn’t recognize the name "dockge". You’ll need to add Dockge’s network to SWAG’s `compose.yml`.
|
||||||
|
:::
|
||||||
|
::
|
||||||
|
|
||||||
|
Go to the SWAG stack, click `edit`, and modify the config file like this (note the `networks` section):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
swag:
|
||||||
|
container_name: #...
|
||||||
|
# ...
|
||||||
|
networks: # Link the container to the custom network
|
||||||
|
- dockge # Network name as defined in the stack
|
||||||
|
|
||||||
|
networks: # Define the custom network
|
||||||
|
# ...
|
||||||
|
dockge: # Network name as defined in the stack
|
||||||
|
name: dockge_default # True external network name
|
||||||
|
external: true
|
||||||
|
```
|
||||||
|
|
||||||
|
::alert{type="info"}
|
||||||
|
:::list{type="info"}
|
||||||
|
- We assume the Dockge network is named `dockge_default`. You can verify the setup works by checking the SWAG dashboard at `http://yourserverip:81`.
|
||||||
|
:::
|
||||||
|
::
|
||||||
|
|
||||||
|
Redeploy the SWAG stack.
|
||||||
|
|
||||||
|
Wait a moment, then visit `https://dockge.mydomain.com` in your browser — you should be redirected to Dockge. You can also check the service status from the dashboard (`http://yourserverip:81` on your local network).
|
||||||
|
|
||||||
|
## Exposing Another Service with SWAG
|
||||||
|
---
|
||||||
|
SWAG includes templates for most known services, named `servicename.subdomain.conf.sample`. Just create the subdomain in your registrar's DNS zone (like OVH), point it to your main domain via a CNAME, then copy and rename the sample file:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd /docker/swag/config/proxy-confs
|
||||||
|
sudo cp servicename.subdomain.conf.sample servicename.subdomain.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
::alert{type="danger"}
|
||||||
|
:::list{type="danger"}
|
||||||
|
- __If the subdomain is not redirected properly__
|
||||||
|
:::
|
||||||
|
- Open the file and verify the container name in `set $upstream_app containername;`{lang=nginx}
|
||||||
|
- Make sure you added the container's network in SWAG’s `compose.yml`
|
||||||
|
::
|
||||||
|
|
||||||
|
You can also customize the subdomain by editing `server_name yoursubdomain.*;`{lang=nginx} and renaming the file to `yoursubdomain.subdomain.conf`.
|
||||||
@@ -0,0 +1,249 @@
|
|||||||
|
---
|
||||||
|
navigation: true
|
||||||
|
title: Wireguard
|
||||||
|
description: Installer et configurer WireGuard VPN pour accéder à votre homelab de n'importe où et connecter tous vos appareils à votre réseau privé.
|
||||||
|
main:
|
||||||
|
fluid: false
|
||||||
|
---
|
||||||
|
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||||
|
# Wireguard
|
||||||
|
|
||||||
|
::alert{type="info"}
|
||||||
|
🎯 __Goals:__
|
||||||
|
- Install Wireguard
|
||||||
|
- Configure clients
|
||||||
|
- Access the secure network
|
||||||
|
::
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
---
|
||||||
|
Using a VPN allows remote access to a server’s local resources without exposing them to the internet. It’s a clean and secure way to access services like SSH without exposing the port publicly. With a VPN, you can securely connect to your network from anywhere and make devices on different networks communicate.
|
||||||
|
|
||||||
|
Here we will use [Wireguard](https://www.wireguard.com/), a secure and high-performance VPN server, using containers:
|
||||||
|
|
||||||
|
- [wg-easy](https://github.com/wg-easy/wg-easy) as the server, providing a very simple web UI to manage connections and download config files (including QR codes for phones)
|
||||||
|
- [Wireguard](https://docs.linuxserver.io/images/docker-wireguard/?h=wireguard) as the client for Linux systems
|
||||||
|
|
||||||
|
Clients are also available for Windows, macOS, iOS, and Android.
|
||||||
|
|
||||||
|
The concept:
|
||||||
|
|
||||||
|
- On the internet, anyone can reach any internet box and thus any exposed server.
|
||||||
|
- Your server is on your local network. It is accessible only locally unless services are explicitly exposed (as we did with Dockge). To access non-exposed resources, you must be on the same local network.
|
||||||
|
- We want to securely access these unexposed services (like SSH) from anywhere.
|
||||||
|
- We also want to connect services between servers, like linking two Dockge instances securely.
|
||||||
|
|
||||||
|
To achieve this, we’ll create a **Virtual Private Network** (VPN), i.e., a secure tunnel that only connected machines can use. They’ll appear to be on the same private network.
|
||||||
|
|
||||||
|
Additionally, you can add your phone, laptop, or other devices to the VPN and securely access your server resources wherever you are.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
In this diagram, machine 1 is part of two networks:
|
||||||
|
|
||||||
|
- Its local network (devices behind the same router, e.g. `192.168.x.x` – machines 1 and 2)
|
||||||
|
- The VPN network (VPN devices with a second IP, e.g. `10.8.x.x` – machines 1 and 4)
|
||||||
|
|
||||||
|
You *can* allow VPN clients to share access to their local networks, but we won’t do that here for security and subnet conflict reasons (e.g., if two remote machines use the same local IP like `192.168.1.1`).
|
||||||
|
|
||||||
|
So only VPN-connected devices can communicate with each other on the VPN, not with other local devices outside the VPN.
|
||||||
|
|
||||||
|
## Server Setup
|
||||||
|
---
|
||||||
|
::alert{type="info"}
|
||||||
|
📋 **Pre-flight Checklist:**
|
||||||
|
- Ensure port `51820 UDP` is free on your server and correctly forwarded from your router (`51820 UDP -> Server`).
|
||||||
|
- Ensure port `51821 TCP` is free for the web UI.
|
||||||
|
::
|
||||||
|
|
||||||
|
::alert{type="warning"}
|
||||||
|
:::list{type="warning"}
|
||||||
|
- __Warning__: If your IP is not static, use a Dynamic DNS service ([DynDNS](https://en.wikipedia.org/wiki/Dynamic_DNS)). If your ISP uses [CGNAT](https://en.wikipedia.org/wiki/Carrier-grade_NAT), you’ll need to use an external VPS and connect your local server as a client.
|
||||||
|
:::
|
||||||
|
::
|
||||||
|
|
||||||
|
### Folder Structure
|
||||||
|
|
||||||
|
```sh
|
||||||
|
root
|
||||||
|
└── docker
|
||||||
|
└── wg-easy
|
||||||
|
├── config
|
||||||
|
│ └── etc_wireguard
|
||||||
|
├── compose.yaml
|
||||||
|
└── .env
|
||||||
|
```
|
||||||
|
|
||||||
|
Open Dockge, click **Compose**, and name the stack `wg_easy`.
|
||||||
|
|
||||||
|
Copy the following configuration:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
services:
|
||||||
|
wg-easy:
|
||||||
|
environment:
|
||||||
|
- INSECURE=true
|
||||||
|
image: ghcr.io/wg-easy/wg-easy:15
|
||||||
|
container_name: wg-easy
|
||||||
|
networks:
|
||||||
|
wg:
|
||||||
|
ipv4_address: 10.42.42.42
|
||||||
|
ipv6_address: fdcc:ad94:bacf:61a3::2a
|
||||||
|
volumes:
|
||||||
|
- ./etc_wireguard:/etc/wireguard
|
||||||
|
- /lib/modules:/lib/modules:ro
|
||||||
|
ports:
|
||||||
|
- "51820:51820/udp"
|
||||||
|
- "51821:51821/tcp"
|
||||||
|
restart: unless-stopped
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
- SYS_MODULE
|
||||||
|
sysctls:
|
||||||
|
- net.ipv4.ip_forward=1
|
||||||
|
- net.ipv4.conf.all.src_valid_mark=1
|
||||||
|
- net.ipv6.conf.all.disable_ipv6=0
|
||||||
|
- net.ipv6.conf.all.forwarding=1
|
||||||
|
- net.ipv6.conf.default.forwarding=1
|
||||||
|
|
||||||
|
networks:
|
||||||
|
wg:
|
||||||
|
driver: bridge
|
||||||
|
enable_ipv6: true
|
||||||
|
ipam:
|
||||||
|
driver: default
|
||||||
|
config:
|
||||||
|
- subnet: 10.42.42.0/24
|
||||||
|
- subnet: fdcc:ad94:bacf:61a3::/64
|
||||||
|
```
|
||||||
|
|
||||||
|
::alert{type="success"}
|
||||||
|
✨ **Tip:**
|
||||||
|
- You can customize WireGuard and web UI ports.
|
||||||
|
- Add a Watchtower label for automatic updates:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
wg-easy:
|
||||||
|
# ...
|
||||||
|
labels:
|
||||||
|
- com.centurylinklabs.watchtower.enable=true
|
||||||
|
```
|
||||||
|
::
|
||||||
|
|
||||||
|
Deploy the stack and access the local web UI at `http://server-ip:51821`.
|
||||||
|
|
||||||
|
::alert{type="danger"}
|
||||||
|
:::list{type="danger"}
|
||||||
|
- If the deployment fails, check your firewall rules.
|
||||||
|
:::
|
||||||
|
::
|
||||||
|
|
||||||
|
Once connected, follow the web UI instructions to:
|
||||||
|
- Create your admin account and password.
|
||||||
|
- Set the host field (use your public IP or domain name).
|
||||||
|
|
||||||
|
Then go to *Administrator → Admin Panel → Config*:
|
||||||
|
- Change `Allowed IPs` from `0.0.0.0/24` to `10.8.0.0/24` for **split tunneling**.
|
||||||
|
- Remove IPv6 (it often causes unnecessary issues).
|
||||||
|
|
||||||
|
### Retrieve Configuration Files
|
||||||
|
|
||||||
|
To configure clients:
|
||||||
|
1. Access the web UI: `http://server-ip:51821`
|
||||||
|
2. Create a new client
|
||||||
|
3. Edit the client and add `10.8.0.0/24` to `Server Allowed IPs`
|
||||||
|
4. (Optional) Set `Persistent Keep Alive` to `25` if it’s a permanently connected client
|
||||||
|
5. Save, download, and rename the file to `wg0.conf` (or `wg1.conf`, etc.)
|
||||||
|
|
||||||
|
## Client Server Setup
|
||||||
|
---
|
||||||
|
::alert{type="info"}
|
||||||
|
:::list{type="info"}
|
||||||
|
- We assume the client server runs Linux with Docker installed.
|
||||||
|
:::
|
||||||
|
::
|
||||||
|
|
||||||
|
### Folder Structure
|
||||||
|
|
||||||
|
```sh
|
||||||
|
root
|
||||||
|
└── docker
|
||||||
|
└── wireguard
|
||||||
|
└── config
|
||||||
|
│ └── wg_confs
|
||||||
|
└── compose.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Create the folder:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo mkdir -p /docker/wireguard/config/wg_confs
|
||||||
|
```
|
||||||
|
|
||||||
|
::alert{type="success"}
|
||||||
|
✨ **Tip:** You can use [File Browser](/serveex/files/file-browser) instead of the terminal to edit and upload files.
|
||||||
|
::
|
||||||
|
|
||||||
|
Create the `wg0.conf` file:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo vi /docker/wireguard/config/wg_confs/wg0.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
Enter insert mode (`i`), paste the downloaded configuration, then save (`Esc` → `:x`).
|
||||||
|
|
||||||
|
::alert{type="success"}
|
||||||
|
✨ **Alternative method:** Transfer the file via SFTP and move it:
|
||||||
|
```sh
|
||||||
|
sudo cp ~/wg0.conf /docker/wireguard/config/wg_confs
|
||||||
|
```
|
||||||
|
::
|
||||||
|
|
||||||
|
Create the `compose.yaml` file in `/docker/wireguard`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
wireguard:
|
||||||
|
image: lscr.io/linuxserver/wireguard:latest
|
||||||
|
container_name: wireguard
|
||||||
|
network_mode: host
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
- SYS_MODULE
|
||||||
|
environment:
|
||||||
|
- TZ=Europe/Paris
|
||||||
|
volumes:
|
||||||
|
- /docker/wireguard/config:/config
|
||||||
|
- /lib/modules:/lib/modules
|
||||||
|
restart: unless-stopped
|
||||||
|
```
|
||||||
|
|
||||||
|
Start the container:
|
||||||
|
```sh
|
||||||
|
cd /docker/wireguard
|
||||||
|
sudo docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
::alert{type="info"}
|
||||||
|
:::list{type="info"}
|
||||||
|
- Repeat this setup for each client.
|
||||||
|
:::
|
||||||
|
::
|
||||||
|
|
||||||
|
## Other Devices
|
||||||
|
---
|
||||||
|
- **Mobile:** Install WireGuard and scan the QR code via the web UI (`http://server-ip:51821`)
|
||||||
|
- **Desktop:** Install the WireGuard client and import the downloaded config file.
|
||||||
|
|
||||||
|
::alert{type="warning"}
|
||||||
|
:::list{type="warning"}
|
||||||
|
- **Note:** If the client machine is on the same local network as the server, edit the `wg0.conf` file to use the local server IP:
|
||||||
|
`Endpoint = server-local-ip:51820`
|
||||||
|
:::
|
||||||
|
::
|
||||||
|
|
||||||
|
And here’s the final setup overview:
|
||||||
|
|
||||||
|

|
||||||
@@ -0,0 +1,575 @@
|
|||||||
|
---
|
||||||
|
navigation: true
|
||||||
|
title: Authentik
|
||||||
|
description: Installer Authentik comme fournisseur d'identité auto-hébergé — configurer le MFA et protéger vos services avec du SSO et l'authentification via reverse proxy.
|
||||||
|
main:
|
||||||
|
fluid: false
|
||||||
|
---
|
||||||
|
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||||
|
# Authentik
|
||||||
|
|
||||||
|
::alert{type="info"}
|
||||||
|
🎯 __Objectives:__
|
||||||
|
- Install and expose Authentik
|
||||||
|
- Configure Multi-Factor Authentication (MFA)
|
||||||
|
- Protect a native app or an app behind a reverse proxy
|
||||||
|
::
|
||||||
|
|
||||||
|
[Authentik](https://goauthentik.io) is a single sign-on (SSO) tool that allows you to log in once to all platforms compatible with OpenID. It can also secure access to your exposed services by injecting itself via SWAG into requests to those services.
|
||||||
|
|
||||||
|
For example, if you're exposing Dockge online at `dockge.mydomain.com`, you’ll first land on an Authentik login page when accessing it. If you've already authenticated with another Authentik-protected service, you won’t need to log in again. This allows you to authenticate only once per day for all protected services.
|
||||||
|
|
||||||
|
Authentik also supports multi-factor authentication, including TOTP (a code generated by the authentication app of your choice). Additionally, it allows login through Microsoft or Google accounts, provided you've configured one of those applications.
|
||||||
|
|
||||||
|
It's a great alternative to VPNs for securely exposing services, especially ones that lack MFA or login protection (e.g., the SWAG dashboard).
|
||||||
|
|
||||||
|
Authentik has [extensive documentation](https://docs.goauthentik.io/docs/installation/docker-compose) and [great tutorials from Cooptonian](https://www.youtube.com/@cooptonian). Here, we’ll cover the basics using Dockge as an example.
|
||||||
|
|
||||||
|
There are two main modes you should know:
|
||||||
|
|
||||||
|
- The first allows apps with native support for OpenID-compatible SSO to connect directly to Authentik. This is the preferred method, as the app itself decides what’s public and what’s protected.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- The second method injects Authentik authentication through SWAG before reaching the target service.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Both modes can be configured on a per-application basis.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
---
|
||||||
|
Folder structure:
|
||||||
|
```sh
|
||||||
|
root
|
||||||
|
└── docker
|
||||||
|
└── authentik
|
||||||
|
├── .env
|
||||||
|
├── compose.yml
|
||||||
|
├── media
|
||||||
|
├── certs
|
||||||
|
├── custom-template
|
||||||
|
└── ssh
|
||||||
|
```
|
||||||
|
|
||||||
|
Create the folders:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo mkdir -p /docker/authentik/media /docker/authentik/certs /docker/authentik/custom-template /docker/authentik/ssh
|
||||||
|
```
|
||||||
|
|
||||||
|
Navigate to the `authentik` folder via `cd /docker/authentik` and generate a password and secret key to include in the `.env` file:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo echo "PG_PASS=$(openssl rand 36 | base64)" >> .env
|
||||||
|
sudo echo "AUTHENTIK_SECRET_KEY=$(openssl rand 60 | base64)" >> .env
|
||||||
|
```
|
||||||
|
|
||||||
|
::alert{type="info"}
|
||||||
|
:::list{type="info"}
|
||||||
|
- To generate the keys, we created the folders ahead of deployment using Dockge. Dockge will prevent you from creating a stack with the same name in these folders unless a `compose.yml` file exists. So, create an empty `compose.yml` so it appears as an inactive stack:
|
||||||
|
:::
|
||||||
|
```sh
|
||||||
|
sudo vi /docker/authentik/compose.yml
|
||||||
|
::
|
||||||
|
|
||||||
|
Open Dockge and search for "authentik" in the inactive stacks.
|
||||||
|
Name the stack `authentik` and paste the following configuration, replacing `{AUTHENTIK_TAG:-2026.2}`{lang=properties} with [the latest version of Authentik](https://goauthentik.io/docs/releases).
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
services:
|
||||||
|
|
||||||
|
postgresql:
|
||||||
|
image: docker.io/library/postgres:16-alpine
|
||||||
|
container_name: authentik-postgresql
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test:
|
||||||
|
- CMD-SHELL
|
||||||
|
- pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}
|
||||||
|
start_period: 20s
|
||||||
|
interval: 30s
|
||||||
|
retries: 5
|
||||||
|
timeout: 5s
|
||||||
|
volumes:
|
||||||
|
- database:/var/lib/postgresql/data
|
||||||
|
environment:
|
||||||
|
POSTGRES_PASSWORD: ${PG_PASS:?database password required}
|
||||||
|
POSTGRES_USER: ${PG_USER:-authentik}
|
||||||
|
POSTGRES_DB: ${PG_DB:-authentik}
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: docker.io/library/redis:alpine
|
||||||
|
container_name: authentik-redis
|
||||||
|
command: --save 60 1 --loglevel warning
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test:
|
||||||
|
- CMD-SHELL
|
||||||
|
- redis-cli ping | grep PONG
|
||||||
|
start_period: 20s
|
||||||
|
interval: 30s
|
||||||
|
retries: 5
|
||||||
|
timeout: 3s
|
||||||
|
volumes:
|
||||||
|
- redis:/data
|
||||||
|
|
||||||
|
server:
|
||||||
|
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2026.2}
|
||||||
|
container_name: authentik-server
|
||||||
|
restart: unless-stopped
|
||||||
|
command: server
|
||||||
|
environment:
|
||||||
|
AUTHENTIK_REDIS__HOST: redis
|
||||||
|
AUTHENTIK_POSTGRESQL__HOST: postgresql
|
||||||
|
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
|
||||||
|
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
|
||||||
|
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
|
||||||
|
volumes:
|
||||||
|
- ./media:/media
|
||||||
|
- ./custom-templates:/templates
|
||||||
|
- ./ssh:/authentik/.ssh
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
ports:
|
||||||
|
- ${COMPOSE_PORT_HTTP:-9000}:9000
|
||||||
|
- ${COMPOSE_PORT_HTTPS:-9443}:9443
|
||||||
|
depends_on:
|
||||||
|
- postgresql
|
||||||
|
- redis
|
||||||
|
|
||||||
|
worker:
|
||||||
|
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2026.2}
|
||||||
|
container_name: authentik-worker
|
||||||
|
restart: unless-stopped
|
||||||
|
command: worker
|
||||||
|
environment:
|
||||||
|
AUTHENTIK_REDIS__HOST: redis
|
||||||
|
AUTHENTIK_POSTGRESQL__HOST: postgresql
|
||||||
|
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
|
||||||
|
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
|
||||||
|
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
|
||||||
|
# `user: root` and the docker socket volume are optional.
|
||||||
|
# See more for the docker socket integration here:
|
||||||
|
# https://goauthentik.io/docs/outposts/integrations/docker
|
||||||
|
# Removing `user: root` also prevents the worker from fixing the permissions
|
||||||
|
# on the mounted folders, so when removing this make sure the folders have the correct UID/GID
|
||||||
|
# (1000:1000 by default)
|
||||||
|
user: root
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
- ./media:/media
|
||||||
|
- ./certs:/certs
|
||||||
|
- ./custom-templates:/templates
|
||||||
|
- ./ssh:/authentik/.ssh
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
depends_on:
|
||||||
|
- postgresql
|
||||||
|
- redis
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
database:
|
||||||
|
driver: local
|
||||||
|
redis:
|
||||||
|
driver: local
|
||||||
|
```
|
||||||
|
|
||||||
|
In the `.env` file, the `PG_PASS` and `AUTHENTIK_SECRET_KEY` variables are already set.
|
||||||
|
Deploy the stack.
|
||||||
|
|
||||||
|
You can then begin the initial setup by visiting:
|
||||||
|
`http://yourserverip:9000/if/flow/initial-setup/`
|
||||||
|
|
||||||
|
::alert{type="warning"}
|
||||||
|
:::list{type="warning"}
|
||||||
|
- __Warning:__ It’s recommended to create a new admin account and **disable** the default `akadmin` account.
|
||||||
|
:::
|
||||||
|
::
|
||||||
|
|
||||||
|
## Exposing Authentik
|
||||||
|
---
|
||||||
|
To use Authentik outside your local network, you must expose it.
|
||||||
|
|
||||||
|
::alert{type="info"}
|
||||||
|
📋 __Prerequisites:__ <br/><br/>
|
||||||
|
We assume you have already created a subdomain like `auth.mydomain.com` in your [DNS zone](/general/networking/dns), with a CNAME pointing to `mydomain.com`. Also, unless you're using [Cloudflare Zero Trust](/serveex/security/cloudflare), you must have already forwarded port `443` from your router to port `443` of your server in your [NAT rules](/general/networking/nat).
|
||||||
|
::
|
||||||
|
|
||||||
|
Open the `authentik-server.conf` file:
|
||||||
|
|
||||||
|
::alert{type="success"}
|
||||||
|
✨ __Tip for those who dislike terminals:__
|
||||||
|
You can use [File Browser](/serveex/files/file-browser) to navigate and edit files instead of using terminal commands.
|
||||||
|
::
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo vi /docker/swag/config/nginx/authentik-server.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify that the following variables are set correctly:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
set $upstream_authentik authentik-server;
|
||||||
|
proxy_pass http://$upstream_authentik:9000;
|
||||||
|
```
|
||||||
|
|
||||||
|
If not, press `i` to enter edit mode, make the necessary changes, then save and exit by pressing `Esc` followed by `:x`.
|
||||||
|
|
||||||
|
Create the `auth.subdomain.conf` file:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo vi /docker/swag/config/nginx/proxy-confs/auth.subdomain.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
Press `i` to enter edit mode and paste the following configuration:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
## Version 2023/05/31
|
||||||
|
# Ensure your authentik container is named authentik-server
|
||||||
|
# Ensure your DNS has a CNAME for authentik
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
listen [::]:443 ssl;
|
||||||
|
|
||||||
|
server_name auth.*;
|
||||||
|
|
||||||
|
include /config/nginx/ssl.conf;
|
||||||
|
|
||||||
|
client_max_body_size 0;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
include /config/nginx/proxy.conf;
|
||||||
|
include /config/nginx/resolver.conf;
|
||||||
|
set $upstream_app authentik-server;
|
||||||
|
set $upstream_port 9000;
|
||||||
|
set $upstream_proto http;
|
||||||
|
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ (/authentik)?/api {
|
||||||
|
include /config/nginx/proxy.conf;
|
||||||
|
include /config/nginx/resolver.conf;
|
||||||
|
set $upstream_app authentik-server;
|
||||||
|
set $upstream_port 9000;
|
||||||
|
set $upstream_proto http;
|
||||||
|
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Save and exit by pressing `Esc` then `:x`.
|
||||||
|
|
||||||
|
Go to Dockge, and edit the SWAG compose file to add the Authentik network:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
swag:
|
||||||
|
container_name: # ...
|
||||||
|
# ...
|
||||||
|
networks: # Attach the container to the custom network
|
||||||
|
# ...
|
||||||
|
- authentik # Name of the network declared in the stack
|
||||||
|
|
||||||
|
networks: # Define the custom network
|
||||||
|
# ...
|
||||||
|
authentik: # Name of the network declared in the stack
|
||||||
|
name: authentik_default # Actual name of the external network
|
||||||
|
external: true # Indicates it's an external network
|
||||||
|
```
|
||||||
|
|
||||||
|
Restart the stack and wait for SWAG to be fully operational.
|
||||||
|
|
||||||
|
Done! You can now access Authentik via `https://auth.mydomain.com`
|
||||||
|
|
||||||
|
## Enable Multifactor Authentication
|
||||||
|
---
|
||||||
|
The main value of Authentik is using multifactor authentication for all protected apps.
|
||||||
|
|
||||||
|
- Go to `https://auth.mydomain.com`
|
||||||
|
- Log in
|
||||||
|
- Go to _Settings_
|
||||||
|
- Click the _MFA_ section
|
||||||
|
- Click _Register_
|
||||||
|
- Choose a method like _TOTP device_ (you'll need an authenticator app like Google Authenticator)
|
||||||
|
- Follow the steps
|
||||||
|
|
||||||
|
You’ll now be prompted to enter a one-time code at every login.
|
||||||
|
|
||||||
|
## Protecting a Native App
|
||||||
|
---
|
||||||
|
Authentik is natively compatible with several applications. You can find the list and [support here](https://docs.goauthentik.io/integrations/services/).
|
||||||
|
|
||||||
|
## Protecting an App via Reverse Proxy
|
||||||
|
---
|
||||||
|
SWAG lets you insert Authentik’s login page between a request and access to your service. To do this:
|
||||||
|
|
||||||
|
- Configure the authentication provider in Authentik.
|
||||||
|
- Edit the domain proxy file so SWAG can intercept the request.
|
||||||
|
|
||||||
|
Why do this when Dockge already has authentication? Because Dockge uses weak HTTP authentication. With Authentik, you get strong MFA authentication and automatic login to all apps protected by Authentik. This secures access to Dockge and other apps without needing a VPN.
|
||||||
|
|
||||||
|
### Configuring Authentik
|
||||||
|
|
||||||
|
- Go to Authentik
|
||||||
|
- Open the admin panel
|
||||||
|
- Select _Applications_ then _Create with wizard_
|
||||||
|
- Fill in the fields as shown:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- At the next step, choose "Forward authentication (single application)" and configure it as shown (flows are important):
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- Next, go to the _Outposts_ menu on the left and edit _authentik Embedded Outpost_:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- Add the `dockge` application by moving it to the right column and save.
|
||||||
|
|
||||||
|
### Configuring SWAG
|
||||||
|
|
||||||
|
Edit the file `dockge.mydomain.com`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo vi /docker/swag/config/nginx/proxy-confs/dockge.subdomain.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
Press `i` to enter edit mode and uncomment the two lines `#include /config/nginx/authentik-server.conf;`
|
||||||
|
|
||||||
|
Press `Esc`, type `:x`, and press `Enter` to save and exit.
|
||||||
|
|
||||||
|
Done! Now when accessing `https://dockge.mydomain.com`, you’ll be redirected to the Authentik login screen.
|
||||||
|
|
||||||
|
::alert{type="success"}
|
||||||
|
✨ __Tip:__ In Dockge's settings, you can disable Dockge's authentication to avoid double login. **Warning**: this means if the port is open on your local network, there will be no authentication at all.
|
||||||
|
::
|
||||||
|
|
||||||
|
::alert{type="info"}
|
||||||
|
:::list{type="info"}
|
||||||
|
- Repeat this process for each app you want to protect (unless it has native integration with Authentik).
|
||||||
|
:::
|
||||||
|
::
|
||||||
|
|
||||||
|
Your new architecture looks like this:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Protecting a Remote Server Service
|
||||||
|
---
|
||||||
|
For a [native application](/serveex/security/authentik/#protecting-a-native-app) (via OAuth 2.0 or other), nothing changes.
|
||||||
|
|
||||||
|
For a non-native app behind a reverse proxy, you must deploy an __Outpost__. An Outpost is a container acting as a local proxy — it's the target of your app's auth requests and the only one authorized to communicate with your Authentik API.
|
||||||
|
|
||||||
|
::alert{type="info"}
|
||||||
|
Prerequisites:
|
||||||
|
- Install [Docker](/serveex/core/docker) on the remote server hosting the service.
|
||||||
|
- If the app has no native integration, use a compatible reverse proxy. We will use [SWAG](/serveex/core/swag) here.
|
||||||
|
::
|
||||||
|
|
||||||
|
This container will forward requests to your main [Authentik](/serveex/security/authentik#authentik) instance over the internet (or your local network). The server will perform checks and respond to the Outpost, which will allow or block access accordingly.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Configuring Authentik
|
||||||
|
|
||||||
|
Create your [providers and applications](/serveex/security/authentik/#protecting-a-native-app) as shown earlier.
|
||||||
|
|
||||||
|
Then, in the admin panel, go to _Applications > Outposts_, and create a new outpost.
|
||||||
|
|
||||||
|
Fill in as follows:
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
|----------------|------------------------------------------------------------------------|
|
||||||
|
| `Name` | Your preferred name |
|
||||||
|
| `Type` | `Proxy` |
|
||||||
|
| `Integration` | Leave empty |
|
||||||
|
| `Applications` | Select the applications you previously created |
|
||||||
|
|
||||||
|
In the `Advanced settings` section, clear the existing content and enter:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
log_level: info
|
||||||
|
docker_labels: null
|
||||||
|
authentik_host: https://your_authentik_server_domain/
|
||||||
|
object_naming_template: ak-outpost-%(name)s
|
||||||
|
authentik_host_insecure: false
|
||||||
|
container_image:
|
||||||
|
docker_network: null
|
||||||
|
docker_map_ports: true
|
||||||
|
docker_labels: null
|
||||||
|
```
|
||||||
|
|
||||||
|
Save and exit.
|
||||||
|
|
||||||
|
On the list of created outposts, locate the new one and click _Show details_ at the end of the line. Carefully copy the access token.
|
||||||
|
|
||||||
|
|
||||||
|
### Configuring the Remote Machine
|
||||||
|
|
||||||
|
We assume you’ve already installed [Docker](/serveex/core/docker) and [SWAG](/serveex/core/swag) on this remote machine.
|
||||||
|
|
||||||
|
On your remote machine, use [Dockge](/serveex/core/docker/#installer-dockge-pour-gérer-et-déployer-les-conteneurs) to create a stack named `authentik-outpost`.
|
||||||
|
|
||||||
|
If you haven’t installed [Dockge](/serveex/core/docker/#installer-dockge-pour-gérer-et-déployer-les-conteneurs), create a folder `/docker/authentik-outpost`, or directly via command line:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo mkdir -P /docker/authentik-outpost
|
||||||
|
```
|
||||||
|
|
||||||
|
::alert{type="success"}
|
||||||
|
✨ __Tip for terminal-averse users:__
|
||||||
|
You can use [File Browser](/serveex/files/file-browser) to navigate and edit your files instead of using terminal commands.
|
||||||
|
::
|
||||||
|
|
||||||
|
Create the `compose.yaml` file or paste the configuration directly into Dockge if installed.
|
||||||
|
|
||||||
|
Via command line:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo vi /docker/authentik-outpost/compose.yaml
|
||||||
|
```
|
||||||
|
Enter edit mode by pressing `i` and paste the following configuration, updating the version in `{AUTHENTIK_TAG:proxy:2024.2.3}`{lang=properties} to match your Authentik server version.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: "3.5"
|
||||||
|
services:
|
||||||
|
authentik_proxy:
|
||||||
|
container_name: authentik-outpost
|
||||||
|
image: ghcr.io/goauthentik/proxy:2024.2.3
|
||||||
|
# Optionally specify which networks the container should be
|
||||||
|
# might be needed to reach the core authentik server
|
||||||
|
restart: unless-stopped
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
ports:
|
||||||
|
- 9000:9000
|
||||||
|
- 9443:9443
|
||||||
|
environment:
|
||||||
|
AUTHENTIK_HOST: ${HOST}
|
||||||
|
AUTHENTIK_INSECURE: "false"
|
||||||
|
AUTHENTIK_TOKEN: ${TOKEN}
|
||||||
|
```
|
||||||
|
|
||||||
|
Go to the SWAG stack on the remote machine (or edit directly using Dockge) and add the authentik-outpost network in the configuration file like this (see `networks` section):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo vi /docker/swag/compose.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
swag:
|
||||||
|
container_name: #...
|
||||||
|
# ...
|
||||||
|
networks: # Attach the container to the custom network
|
||||||
|
- authentik-outpost # Network name as declared in the stack
|
||||||
|
|
||||||
|
networks: # Define the custom network
|
||||||
|
#...
|
||||||
|
authentik-outpost: # Name of the network declared in the stack
|
||||||
|
name: authentik-outpost_default # Actual name of the external network
|
||||||
|
external: true # Marks it as an external network
|
||||||
|
```
|
||||||
|
|
||||||
|
Press `Esc`, then type `:x` and press `Enter` to save and exit.
|
||||||
|
|
||||||
|
::alert{type="info"}
|
||||||
|
:::list{type="info"}
|
||||||
|
- We assume the Dockge network name is `authentik-outpost_default`.
|
||||||
|
:::
|
||||||
|
::
|
||||||
|
|
||||||
|
If using [Dockge](/serveex/core/docker/#installer-dockge-pour-gérer-et-déployer-les-conteneurs), restart SWAG.
|
||||||
|
|
||||||
|
Otherwise, via terminal:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd /docker/swag/
|
||||||
|
sudo docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Create (or fill using Dockge) the `.env` file in the `authentik-outpost` directory:
|
||||||
|
|
||||||
|
Via command line:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo vi /docker/authentik-outpost/.env
|
||||||
|
```
|
||||||
|
|
||||||
|
Enter edit mode with `i` and paste the following configuration:
|
||||||
|
|
||||||
|
```properties
|
||||||
|
HOST=
|
||||||
|
TOKEN=
|
||||||
|
```
|
||||||
|
|
||||||
|
Fill in the values:
|
||||||
|
|
||||||
|
| Variable | Value | Example |
|
||||||
|
|----------|-------|---------|
|
||||||
|
| `HOST`{lang=properties} | The URL of your Authentik server | `https://auth.domain.com` |
|
||||||
|
| `TOKEN`{lang=properties} | The previously copied access token | `Q2pVEqsTNRkJSO9SkJzU3KZ2` |
|
||||||
|
|
||||||
|
Press `Esc`, then type `:x` and press `Enter` to save and exit.
|
||||||
|
|
||||||
|
If using Dockge, deploy the stack.
|
||||||
|
|
||||||
|
Otherwise, via terminal:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd /docker/authentik-outpost/
|
||||||
|
sudo docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
The container is now running. You can verify its status from your Authentik instance admin panel under _Applications > Outposts_.
|
||||||
|
|
||||||
|
Now, let’s configure SWAG.
|
||||||
|
|
||||||
|
Open the `authentik-server.conf` file:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo vi /docker/swag/config/nginx/authentik-server.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
In the file, press `i` to enter edit mode and change `authentik-server` to `authentik-outpost` as shown:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
set $upstream_authentik authentik-outpost;
|
||||||
|
proxy_pass http://$upstream_authentik:9000;
|
||||||
|
```
|
||||||
|
|
||||||
|
Save and exit with `Esc`, then `:x` and `Enter`.
|
||||||
|
|
||||||
|
Then configure the applications to protect as you did on your main server, whether they are [native](/serveex/security/authentik/#protecting-a-native-app) or protected via [reverse proxy](/serveex/security/authentik#protecting-an-app-via-reverse-proxy).
|
||||||
|
|
||||||
|
## Migrating an Authentik Database
|
||||||
|
---
|
||||||
|
On the source machine, dump the database:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo docker exec authentik-postgres pg_dump -U authentik -F t authentik > /path/to/mydb.tar
|
||||||
|
```
|
||||||
|
|
||||||
|
Then transfer it to the target machine. On the target machine, copy the file into the Docker container:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cp /path/to/mydb.tar authentik-postgres:/path/to/wherever
|
||||||
|
```
|
||||||
|
|
||||||
|
(Optional) Purge existing tables:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo docker exec -i authentik-postgres psql -U authentik -c "SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = 'authentik' AND pid <> pg_backend_pid();" && sudo docker exec -i authentik-postgres psql -U authentik -d postgres -c "DROP DATABASE IF EXISTS authentik;" && sudo docker exec -i authentik-postgres psql -U authentik -d postgres -c "CREATE DATABASE authentik;"
|
||||||
|
```
|
||||||
|
|
||||||
|
Restore the database:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo docker exec authentik-postgresql pg_restore -U authentik -d authentik /path/to/wherever/mydb.tar
|
||||||
|
```
|
||||||
@@ -0,0 +1,264 @@
|
|||||||
|
---
|
||||||
|
navigation: true
|
||||||
|
title: Cloudflare Zero Trust
|
||||||
|
description: Utiliser les tunnels Cloudflare et Zero Trust pour exposer des services sans ouvrir de ports — configurer SWAG et gérer plusieurs tunnels.
|
||||||
|
main:
|
||||||
|
fluid: false
|
||||||
|
---
|
||||||
|
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||||
|
# Cloudflare Zero Trust
|
||||||
|
|
||||||
|
::alert{type="info"}
|
||||||
|
🎯 __Goals:__
|
||||||
|
- Understand the concept of Cloudflare Tunnels
|
||||||
|
- Configure your Cloudflare account
|
||||||
|
- Configure SWAG
|
||||||
|
- Manage multiple tunnels
|
||||||
|
::
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
---
|
||||||
|
The _Zero Trust_ architecture is the practice of designing systems based on the principle of __"never trust, always verify"__, as opposed to the traditional principle of __"trust, but verify"__. This concept has become increasingly popular recently due to the growing number of attacks targeting user data. It’s a broad concept, but we’ll focus on how to apply _Zero Trust_ to the web services we host.
|
||||||
|
|
||||||
|
_Cloudflare tunnels_ offer a simple way to implement _Zero Trust_, using [SWAG](/serveex/core/swag) and [Authentik](/serveex/security/authentik).
|
||||||
|
|
||||||
|
Simply put, Cloudflare Tunnels allow you to:
|
||||||
|
|
||||||
|
- Hide your server’s IP (and your home IP if it's self-hosted)
|
||||||
|
- Authenticate traffic
|
||||||
|
- Benefit from Cloudflare protections (DDoS attacks, blacklists, malicious requests, etc.)
|
||||||
|
- Use Cloudflare's CDN to cache and speed up your websites
|
||||||
|
- Avoid opening router ports for services exposed by SWAG
|
||||||
|
|
||||||
|
Here we’ll explain how to integrate SWAG with Cloudflare tunnels.
|
||||||
|
|
||||||
|
::alert{type="warning"}
|
||||||
|
:::list{type="warning"}
|
||||||
|
- __Warning:__
|
||||||
|
:::
|
||||||
|
- Do not use Cloudflare tunnels to expose a mail server
|
||||||
|
- Do not use Cloudflare tunnels to expose a video service like Plex (if you followed [this guide](/serveex/media/plex), Plex is not exposed, so it’s fine)
|
||||||
|
- Do not use Cloudflare tunnels for the BitTorrent protocol (if you followed [this guide](/serveex/media/qbittorrent), everything is fine)
|
||||||
|
::
|
||||||
|
|
||||||
|
## Cloudflare Configuration
|
||||||
|
---
|
||||||
|
### DNS Zone
|
||||||
|
|
||||||
|
First, you need to set Cloudflare as your [DNS zone](/general/networking/dns) manager. If you bought your domain from Cloudflare, that’s already done. Otherwise, check with your registrar how to add external DNS servers. Cloudflare provides [step-by-step documentation](https://developers.cloudflare.com/dns/zone-setups/full-setup/setup/) on how to configure a DNS Zone, whether your domain is external or registered with Cloudflare.
|
||||||
|
|
||||||
|
If you only have one server to protect behind Cloudflare, you can delete all existing DNS records. By default, your domain and all its subdomains will be redirected to the tunnel.
|
||||||
|
|
||||||
|
If you have subdomains pointing to other servers, you can still define them in the DNS zone using A records.
|
||||||
|
|
||||||
|
If you have several servers and tunnels under one domain, [see here](http://192.168.7.80:8005/serveex/cloudflare/#gerer-plusieurs-tunnels-pour-plusieurs-serveurs).
|
||||||
|
|
||||||
|
### API Key
|
||||||
|
|
||||||
|
Start by creating a new Cloudflare API token and retrieving your zone and account IDs.
|
||||||
|
|
||||||
|
On your Cloudflare dashboard, on your domain overview page, you’ll see the `zone` and `account` IDs at the bottom right. Save both securely.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Just below that is a link titled _Get your API token_. Click it. The token scope must include `Zone:DNS:Edit` and `Account:Cloudflare Tunnel:Edit`. Your page should look like the screenshot below.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Once created, your token will only be shown once. Save it securely, as it cannot be viewed again later.
|
||||||
|
|
||||||
|
### Cloudflare Zero Trust
|
||||||
|
|
||||||
|
You must register for _Cloudflare Teams_ to access the _Zero Trust_ dashboard that manages tunnels and access policies. This is a premium service, but there’s a free plan for up to 50 users—perfect for a home lab. Keep in mind that a valid credit card is required to register, but the free plan incurs no charges.
|
||||||
|
|
||||||
|
Register [via this link](https://dash.teams.cloudflare.com/).
|
||||||
|
|
||||||
|
## SWAG Configuration
|
||||||
|
---
|
||||||
|
::alert{type="info"}
|
||||||
|
:::list{type="info"}
|
||||||
|
- This guide assumes you own `mondomaine.fr` and that its DNS is correctly pointing to Cloudflare, as described above.
|
||||||
|
:::
|
||||||
|
::
|
||||||
|
|
||||||
|
SWAG supports two Docker Mods:
|
||||||
|
|
||||||
|
- __Cloudflared__, the container used to create and manage tunnels
|
||||||
|
- __Cloudflared Real IP__, which allows SWAG to receive the true source IP of incoming requests instead of Docker’s internal IP (important for IP geolocation mods like DBIP).
|
||||||
|
|
||||||
|
These two mods, merged into the SWAG container, require some configuration.
|
||||||
|
|
||||||
|
### Tunnel Configuration
|
||||||
|
|
||||||
|
Create a file `tunnelconfig.yml` to reference in your SWAG `compose.yaml`.
|
||||||
|
|
||||||
|
::alert{type="success"}
|
||||||
|
✨ __Tip:__ Use [File Browser](/serveex/files/file-browser) to navigate and edit files instead of using the terminal.
|
||||||
|
::
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo vi /docker/swag/config/tunnelconfig.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
Press `i` to enter insert mode and paste:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
ingress:
|
||||||
|
- hostname: mondomaine.fr
|
||||||
|
service: https://mondomaine.fr
|
||||||
|
- hostname: "*.mondomaine.fr"
|
||||||
|
service: https://mondomaine.fr
|
||||||
|
- service: http_status:404
|
||||||
|
```
|
||||||
|
|
||||||
|
Press `Esc`, then save and exit with `:x` and `Enter`.
|
||||||
|
|
||||||
|
### Cloudflare Real IP Configuration
|
||||||
|
|
||||||
|
Now configure _Cloudflare Real IP_.
|
||||||
|
|
||||||
|
Open the `nginx.conf` file:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo vi /docker/swag/config/nginx/nginx.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
Press `i` and add the following at the end of the `http` section:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
real_ip_header X-Forwarded-For;
|
||||||
|
real_ip_recursive on;
|
||||||
|
include /config/nginx/cf_real-ip.conf;
|
||||||
|
set_real_ip_from 127.0.0.1;
|
||||||
|
```
|
||||||
|
|
||||||
|
Save and exit with `:x`.
|
||||||
|
|
||||||
|
### Docker Compose
|
||||||
|
|
||||||
|
In Dockge, edit your SWAG stack with this:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
services:
|
||||||
|
swag:
|
||||||
|
image: lscr.io/linuxserver/swag:latest
|
||||||
|
container_name: swag
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
environment:
|
||||||
|
- DOCKER_MODS=linuxserver/mods:swag-dbip|linuxserver/mods:swag-dashboard|linuxserver/mods:swag-auto-reload|linuxserver/mods:universal-cloudflared|linuxserver/mods:swag-cloudflare-real-ip
|
||||||
|
- PUID=${PUID}
|
||||||
|
- PGID=${PGID}
|
||||||
|
- TZ=Europe/Paris
|
||||||
|
- URL=${DOMAIN}
|
||||||
|
- SUBDOMAINS=wildcard
|
||||||
|
- VALIDATION=dns
|
||||||
|
- DNSPLUGIN=${PLUGIN}
|
||||||
|
- EMAIL=${EMAIL}
|
||||||
|
- CF_ZONE_ID=${ZONE_ID}
|
||||||
|
- CF_ACCOUNT_ID=${ACCOUNT_ID}
|
||||||
|
- CF_API_TOKEN=${API_TOKEN}
|
||||||
|
- CF_TUNNEL_NAME=${TUNNEL_NAME}
|
||||||
|
- CF_TUNNEL_PASSWORD=${TUNNEL_PW}
|
||||||
|
- FILE__CF_TUNNEL_CONFIG=/config/tunnelconfig.yml
|
||||||
|
extra_hosts:
|
||||||
|
- ${DOMAIN}:127.0.0.1
|
||||||
|
ports:
|
||||||
|
- 81:81
|
||||||
|
volumes:
|
||||||
|
- /docker/swag/config:/config
|
||||||
|
- /docker/swag/config/fail2ban/fail2ban.sqlite3:/dashboard/fail2ban.sqlite3:ro
|
||||||
|
restart: unless-stopped
|
||||||
|
```
|
||||||
|
|
||||||
|
::alert{type="success"}
|
||||||
|
✨ __Tip:__ Add a Watchtower label to automate updates:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
labels:
|
||||||
|
- com.centurylinklabs.watchtower.enable=true
|
||||||
|
```
|
||||||
|
::
|
||||||
|
|
||||||
|
Fill in your `.env` file:
|
||||||
|
|
||||||
|
```properties
|
||||||
|
PUID=
|
||||||
|
PGID=
|
||||||
|
DOMAIN=
|
||||||
|
PLUGIN=
|
||||||
|
EMAIL=
|
||||||
|
ZONE_ID=
|
||||||
|
ACCOUNT_ID=
|
||||||
|
API_TOKEN=
|
||||||
|
TUNNEL_NAME=
|
||||||
|
TUNNEL_PW=
|
||||||
|
```
|
||||||
|
|
||||||
|
| Variable | Value | Example |
|
||||||
|
|----------------|-------------------------------------------------------------|--------------------------------|
|
||||||
|
| `PUID` | User ID (`id username`) | `1000` |
|
||||||
|
| `GUID` | Group ID (`id username`) | `1000` |
|
||||||
|
| `DOMAIN` | Your reserved domain | `mondomaine.fr` |
|
||||||
|
| `PLUGIN` | DNS provider (also configure `cloudflare.ini`) | `cloudflare` |
|
||||||
|
| `EMAIL` | Email for the certificate | `[email protected]` |
|
||||||
|
| `ZONE_ID` | Cloudflare Zone ID | `aNhcz1l3JfWbFZo2XMpzQlP2iOqk` |
|
||||||
|
| `ACCOUNT_ID` | Cloudflare Account ID | `buKsjNHLyzKMM1qYnzOy4s7SHfly` |
|
||||||
|
| `API_TOKEN` | API token | `53ydYus9TFFk1DOXNdP87iIcJtQjoW` |
|
||||||
|
| `TUNNEL_NAME` | Tunnel name | `my_tunnel` |
|
||||||
|
| `TUNNEL_PW` | Strong, random password | `iSzKRmP4VbnlsMvdSdgBEJiJi` |
|
||||||
|
|
||||||
|
Once done, deploy the stack. Check the logs—you should reach `server ready`.
|
||||||
|
|
||||||
|
Then confirm your tunnel appears under _Networks > Tunnels_ in [Cloudflare Zero Trust](https://one.dash.cloudflare.com/). By default, all subdomains will be routed through the tunnel—no need to define them [in your DNS zone](/general/networking/dns).
|
||||||
|
|
||||||
|
::alert{type="success"}
|
||||||
|
✨ __Tip:__ If you want to expose a service without a tunnel, just define an A record [in your DNS zone](/general/networking/dns). If resolution fails, disable the proxy function for that record—e.g., for `sub.mondomaine.fr`.
|
||||||
|

|
||||||
|
::
|
||||||
|
|
||||||
|
## Managing Multiple Tunnels for Multiple Servers
|
||||||
|
---
|
||||||
|
By default, all subdomains of your domain are routed through the single tunnel. But if you have a second server, just change the tunnel name in that SWAG instance.
|
||||||
|
|
||||||
|
In your DNS zone, redirect subdomains to the correct tunnel.
|
||||||
|
|
||||||
|
Go to _Networks > Tunnels_ in [Cloudflare Zero Trust](https://one.dash.cloudflare.com/).
|
||||||
|
|
||||||
|
Note the tunnel IDs:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Then in the [Cloudflare DNS dashboard](https://dash.cloudflare.com/), click your domain name.
|
||||||
|
|
||||||
|
Click `Add Record` and add these two CNAME records (include `.cfargotunnel.com`):
|
||||||
|
|
||||||
|
| Type | Name | Target |
|
||||||
|
|---------|--------------|----------------------------------------|
|
||||||
|
| `CNAME` | `subdomain1` | `yourtunnelid1.cfargotunnel.com` |
|
||||||
|
| `CNAME` | `subdomain2` | `yourtunnelid2.cfargotunnel.com` |
|
||||||
|
|
||||||
|
If you have many subdomains, point them to the above reference subdomains.
|
||||||
|
|
||||||
|
This way, if a tunnel ID changes, you only update one DNS record.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
- `sub1` and `sub2` also point to the server behind `subdomain1`:
|
||||||
|
|
||||||
|
| Type | Name | Target |
|
||||||
|
|---------|--------|---------------|
|
||||||
|
| `CNAME` | `sub1` | `subdomain1` |
|
||||||
|
| `CNAME` | `sub2` | `subdomain1` |
|
||||||
|
|
||||||
|
- `sub3` and `sub4` point to the server behind `subdomain2`:
|
||||||
|
|
||||||
|
| Type | Name | Target |
|
||||||
|
|---------|--------|---------------|
|
||||||
|
| `CNAME` | `sub3` | `subdomain2` |
|
||||||
|
| `CNAME` | `sub4` | `subdomain2` |
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
navigation: true
|
navigation: true
|
||||||
title: Uptime-Kuma
|
title: Uptime-Kuma
|
||||||
|
description: Installer Uptime-Kuma pour surveiller la disponibilité de vos services auto-hébergés, configurer des alertes et protéger le tableau de bord avec Authentik.
|
||||||
main:
|
main:
|
||||||
fluid: false
|
fluid: false
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
navigation: true
|
navigation: true
|
||||||
title: Dozzle
|
title: Dozzle
|
||||||
|
description: Installer Dozzle pour surveiller les logs des conteneurs Docker en temps réel depuis une interface web épurée, exposée via SWAG.
|
||||||
main:
|
main:
|
||||||
fluid: false
|
fluid: false
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
navigation: true
|
navigation: true
|
||||||
title: Speedtest Tracker
|
title: Speedtest Tracker
|
||||||
|
description: Installer Speedtest Tracker pour mesurer et enregistrer automatiquement la vitesse de votre connexion internet dans le temps, exposé avec SWAG.
|
||||||
main:
|
main:
|
||||||
fluid: false
|
fluid: false
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
navigation: true
|
navigation: true
|
||||||
title: Beszel
|
title: Beszel
|
||||||
|
description: Installer Beszel pour surveiller CPU, RAM, disques et réseau de vos serveurs — y compris à distance — via un tableau de bord web léger.
|
||||||
main:
|
main:
|
||||||
fluid: false
|
fluid: false
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
navigation: true
|
navigation: true
|
||||||
title: UpSnap
|
title: UpSnap
|
||||||
|
description: Installer UpSnap pour allumer à distance des machines de votre réseau local via Wake-on-LAN, exposé avec SWAG.
|
||||||
main:
|
main:
|
||||||
fluid: false
|
fluid: false
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
navigation: true
|
navigation: true
|
||||||
title: Plex
|
title: Plex
|
||||||
|
description: Installer Plex Media Server avec Tautulli sur votre homelab pour streamer films et séries depuis n'importe où sur tous vos appareils.
|
||||||
main:
|
main:
|
||||||
fluid: false
|
fluid: false
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
navigation: true
|
navigation: true
|
||||||
title: Qbittorrent
|
title: Qbittorrent
|
||||||
|
description: Installer qBittorrent avec Gluetun et ProtonVPN pour télécharger des torrents de manière sécurisée derrière un VPN sur votre serveur auto-hébergé.
|
||||||
main:
|
main:
|
||||||
fluid: false
|
fluid: false
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
---
|
---
|
||||||
navigation: true
|
navigation: true
|
||||||
title: Automatisation
|
title: Automatisationdescription: Automatiser les téléchargements de médias avec la suite Servarr — Radarr, Sonarr, Bazarr, Prowlarr et Overseerr pour films et séries.main:
|
||||||
main:
|
|
||||||
fluid: false
|
fluid: false
|
||||||
---
|
---
|
||||||
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
navigation: true
|
navigation: true
|
||||||
title: Immich
|
title: Immich
|
||||||
|
description: Installer Immich, une alternative auto-hébergée à Google Photos et iCloud avec reconnaissance faciale, géolocalisation et synchronisation multi-appareils.
|
||||||
main:
|
main:
|
||||||
fluid: false
|
fluid: false
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
navigation: true
|
navigation: true
|
||||||
title: Nextcloud
|
title: Nextcloud
|
||||||
|
description: Installer Nextcloud pour auto-héberger vos fichiers, photos et agenda — une alternative respectueuse de la vie privée à Google Drive, OneDrive et iCloud.
|
||||||
main:
|
main:
|
||||||
fluid: false
|
fluid: false
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
navigation: true
|
navigation: true
|
||||||
title: File Browser
|
title: File Browser
|
||||||
|
description: Installer File Browser pour parcourir et gérer les fichiers de votre serveur depuis une interface web, exposée de manière sécurisée avec SWAG.
|
||||||
main:
|
main:
|
||||||
fluid: false
|
fluid: false
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
navigation: true
|
navigation: true
|
||||||
title: Pingvin
|
title: Pingvin
|
||||||
|
description: Installer Pingvin Share, une plateforme d'envoi de fichiers auto-hébergée pour partager des fichiers de façon sécurisée sans WeTransfer ni Google Drive.
|
||||||
main:
|
main:
|
||||||
fluid: false
|
fluid: false
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
navigation: true
|
navigation: true
|
||||||
title: Code-Serveur
|
title: Code-Serveur
|
||||||
|
description: Installer code-server pour utiliser VS Code dans votre navigateur depuis votre homelab — montez des dossiers et exposez-le de manière sécurisée avec SWAG.
|
||||||
main:
|
main:
|
||||||
fluid: false
|
fluid: false
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
navigation: true
|
navigation: true
|
||||||
title: Gitea
|
title: Gitea
|
||||||
|
description: Installer Gitea, un service Git auto-hébergé léger pour gérer vos dépôts de code de façon privée sur votre propre serveur.
|
||||||
main:
|
main:
|
||||||
fluid: false
|
fluid: false
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
---
|
---
|
||||||
navigation: true
|
navigation: true
|
||||||
title: IT-Tools
|
title: IT-Toolsdescription: Installer IT Tools, une collection auto-hébergée d'utilitaires pratiques pour développeurs — convertisseurs, encodeurs, formateurs et plus encore.main:
|
||||||
main:
|
|
||||||
fluid: false
|
fluid: false
|
||||||
---
|
---
|
||||||
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
navigation: true
|
navigation: true
|
||||||
title: Adguard Home
|
title: Adguard Home
|
||||||
|
description: Installer AdGuard Home pour bloquer publicités et trackers à l'échelle du réseau avec DNS-over-HTTPS, gestion des clients et règles de filtrage personnalisées.
|
||||||
main:
|
main:
|
||||||
fluid: false
|
fluid: false
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
navigation: true
|
navigation: true
|
||||||
title: Vaultwarden
|
title: Vaultwarden
|
||||||
|
description: Installer Vaultwarden, un gestionnaire de mots de passe auto-hébergé compatible Bitwarden pour remplacer les gestionnaires Google ou Apple sur tous vos appareils.
|
||||||
main:
|
main:
|
||||||
fluid: false
|
fluid: false
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
icon: lucide:bookmark
|
icon: lucide:bookmark
|
||||||
navigation: true
|
navigation: true
|
||||||
title: Introduction
|
title: Introduction
|
||||||
|
description: Introduction à Stockeex — un projet personnel de gestion de stock et d'inventaire. Documentation en cours de rédaction.
|
||||||
main:
|
main:
|
||||||
fluid: false
|
fluid: false
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
navigation: true
|
||||||
|
title: Nvidia Stock Bot
|
||||||
|
description: Un bot Python qui surveille la disponibilité des GPU en temps réel et envoie des alertes Discord — créé lors de la pénurie de la série RTX 5000.
|
||||||
|
main:
|
||||||
|
fluid: false
|
||||||
|
---
|
||||||
|
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||||
|
|
||||||
|
# 🤖 Nvidia Stock Bot
|
||||||
|
---
|
||||||
|
|
||||||
|
For the past four years, the electronics hardware shortage has been relentless. Graphics cards are no exception. In 2020, I had to wait two months to get my RTX 3080. To manage it, I joined [JV Hardware](https://discord.gg/gxffg3GA96), where a small group of geeks had set up a bot that pinged users when GPUs became available.
|
||||||
|
|
||||||
|
Four years later and with 5,000 members on the server, the RTX 5000 series is being released. Yet, no working stock bot seems to exist. Not to mention a certain “influencer” who charges users for access to a bot that doesn’t even work. He manually copies alerts from other servers like ours, which have already solved the issue.
|
||||||
|
|
||||||
|
Anyway, eager to get an RTX 5090 for my AI-dedicated machine, I decided it was time to dive into Python—with a little help from ChatGPT. Along with another member, KevOut, who helped guide me through the APIs and initial architecture, I ended up building a clean and functional bot that sends different kinds of Discord alerts—all deployable in a simple Docker container.
|
||||||
|
|
||||||
|
After many setbacks, I went from this:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
To this:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
And more recently :
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
And I was also lucky enough to be referenced in the famous [selfhost newsletter](https://selfh.st/weekly/2025-07-11/) !
|
||||||
|
|
||||||
|
More info directly on the repo:
|
||||||
|
|
||||||
|
::card
|
||||||
|
#title
|
||||||
|
🐋 __Nvidia Stock Bot__
|
||||||
|
#description
|
||||||
|
[Nvidia GPU stock alert bot](https://git.djeex.fr/Djeex/nvidia-stock-bot)
|
||||||
|
::
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
navigation: true
|
||||||
|
title: Adguard CIDRE
|
||||||
|
description: Un script Python pour synchroniser automatiquement les listes CIDR d'AdGuard Home et sécuriser votre serveur DNS auto-hébergé exposé sur internet.
|
||||||
|
main:
|
||||||
|
fluid: false
|
||||||
|
---
|
||||||
|
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||||
|
|
||||||
|
# 🤖 Adguard CIDRE Sync
|
||||||
|
---
|
||||||
|
|
||||||
|
Adguard Home is a fantastic solution for DNS-level ad blocking and rewriting requests—perfect for removing ISP DNS trackers or intrusive ads.
|
||||||
|
|
||||||
|
It works great locally, but if you want all your devices (even on the go) to benefit, you’ll need to expose Adguard to the internet. Unfortunately, that means anyone can use it, potentially overloading your €1/month remote VPS.
|
||||||
|
|
||||||
|
Adguard allows whitelisting or blacklisting clients. The problem? To whitelist a client, you need their IP—but for mobile phones, that IP changes often. Instead of trying to whitelist ever-changing IPs, the better approach is to block broader IP ranges by region.
|
||||||
|
|
||||||
|
CIDRE is a tool that syncs geo-targeted IP ranges with firewalls. Instead of running CIDRE with a full firewall stack on the remote server, I figured I could just import those regularly updated IP ranges into Adguard’s blocklist.
|
||||||
|
|
||||||
|
Thus, Adguard CIDRE Sync was born: a container that syncs Adguard’s blocklist with CIDRE’s updated IP ranges on a schedule of your choosing.
|
||||||
|
|
||||||
|
The idea is to:
|
||||||
|
- Backup Adguard’s config file on first run (original untouched version saved)
|
||||||
|
- Download selected country IP ranges via an environment variable
|
||||||
|
- Let you manually add custom IPs via a file
|
||||||
|
- Concatenate, backup the config again (as the updated version), and inject the list into the correct blocklist section
|
||||||
|
- Reload Adguard by restarting the container (using Docker socket proxy for limited permissions)
|
||||||
|
|
||||||
|
All fully autonomous, with frequency set via environment variable in the `docker-compose` config.
|
||||||
|
|
||||||
|
More info directly on the repo:
|
||||||
|
|
||||||
|
::card
|
||||||
|
#title
|
||||||
|
🐋 __Adguard CIDRE Sync__
|
||||||
|
#description
|
||||||
|
[Adguard blocklist sync bot](https://git.djeex.fr/Djeex/adguard-cidre)
|
||||||
|
::
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
---
|
||||||
|
navigation: true
|
||||||
|
title: Lumeex
|
||||||
|
description: Lumeex est un générateur de galerie photo statique en Python — minimaliste, léger et entièrement personnalisable sans CMS.
|
||||||
|
main:
|
||||||
|
fluid: false
|
||||||
|
---
|
||||||
|
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<img src="https://git.djeex.fr/Djeex/lumeex/raw/branch/main/illustration/logo.svg" alt="Lumeex Screenshot" width="300"/>
|
||||||
|
</div>
|
||||||
|
<p/>
|
||||||
|
<div align="center">
|
||||||
|
<p>Yet another minimalist, lightweight photo gallery static site generator.</p>
|
||||||
|
</div>
|
||||||
|
</p>
|
||||||
|
<div align="center">
|
||||||
|
<img src="https://git.djeex.fr/Djeex/lumeex/raw/branch/main/illustration/lumeex.png" alt="Lumeex Screenshot" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Amateur photographer that I am, I spent several weeks looking for a framework with a photo gallery that could outshine Instagram. I wanted something that showcased the photos rather than the author, and that made every visit unique by loading the images in random order—while still allowing filtering and sorting by tag or combinations of tags.
|
||||||
|
|
||||||
|
In the end, I found nothing that did exactly what I wanted. And when something came close, it was always through heavy, bloated CMS platforms. So I decided to make a static site by hand, the old-school way, with Notepad++. Being fairly comfortable with HTML/CSS and a bit of JavaScript, I quickly came up with something nice during my vacation, between beach sessions. After all, a good craftsman should have good tools—and there’s no better tool than one you make yourself.
|
||||||
|
|
||||||
|
Then I thought it might be a good idea to automate certain tasks—like generating favicon formats, resizing and converting images, creating the gallery automatically instead of entering everything by hand, and generating `robots.txt` and `sitemap` files… so I turned back to Python.
|
||||||
|
|
||||||
|
Eventually, after getting good results, I figured I might as well go all the way: build a complete framework for generating a static site photo gallery, where all you need to do is fill in your site’s information in a config file and tweak the visuals a bit—without touching the code.
|
||||||
|
|
||||||
|
That’s how **Lum[eex]{style="color: #1ad6ff"}** was born.
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<img src="https://git.djeex.fr/Djeex/lumeex/raw/branch/main/illustration/lumeex-webui.png" alt="Lumeex Screenshot" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
::card-grid{grid-template-columns="repeat(2, minmax(0, 1fr));"}
|
||||||
|
#title
|
||||||
|
Et voilà!
|
||||||
|
|
||||||
|
#root
|
||||||
|
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||||
|
|
||||||
|
#default
|
||||||
|
|
||||||
|
::card{icon=noto:open-book}
|
||||||
|
#title
|
||||||
|
__Documentation__
|
||||||
|
#description
|
||||||
|
[Check out the doc](https://lumeex.djeex.fr)
|
||||||
|
::
|
||||||
|
|
||||||
|
::card{icon=simple-icons:gitea style="color: #9ee773;"}
|
||||||
|
#title
|
||||||
|
__Repository__
|
||||||
|
#description
|
||||||
|
[See the repo](https://git.djeex.fr/Djeex/lumeex)
|
||||||
|
::
|
||||||
|
|
||||||
|
::card{icon=fluent-color:design-ideas-48}
|
||||||
|
#title
|
||||||
|
__Demo__
|
||||||
|
#description
|
||||||
|
[Explore the demo](https://modern.djeex.fr)
|
||||||
|
::
|
||||||
|
::
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
---
|
||||||
|
navigation: true
|
||||||
|
title: Instameex
|
||||||
|
description: Instameex est un outil Docker pour fusionner des exports SDR et HDR en un JPEG avec gain map prêt pour l'upload HDR sur Instagram.
|
||||||
|
main:
|
||||||
|
fluid: false
|
||||||
|
---
|
||||||
|
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<img src="https://git.djeex.fr/Djeex/instameex/raw/branch/main/src/assets/img/logo-long.svg" alt="Lumeex Screenshot" width="300"/>
|
||||||
|
</div>
|
||||||
|
<p/>
|
||||||
|
<div align="center">
|
||||||
|
<p>Mix your SDR and HDR exports into an Instagram-ready HDR photo.</p>
|
||||||
|
</div>
|
||||||
|
</p>
|
||||||
|
<div align="center">
|
||||||
|
<img src="https://git.djeex.fr/Djeex/instameex/raw/branch/main/illustration/instameex-illustration.png" width="640" alt="Instameex Screenshot" />
|
||||||
|
</div>
|
||||||
|
---
|
||||||
|
|
||||||
|
Nothing is more frustrating than Instagram's HDR handling. It compresses and destroys gain maps, and the slightest change in aspect ratio or size simply strips HDR out entirely. As for Lightroom, its "SDR preview" system is frankly unacceptable, it makes it impossible to get consistent results. Until now, posting on Instagram meant choosing between decent SDR with broken HDR, or the other way around.
|
||||||
|
|
||||||
|
Why not simply edit your SDR file to perfection on one side, your HDR file on the other, and then recalculate a gain map from those two perfect files?
|
||||||
|
A few pioneers have already gone down that road, notably with an [Adobe Lightroom Classic](https://github.com/karachungen/lightroom-plugin-export-hdr) plugin. Judge me if you want, but I only use Lightroom CC, which does not support plugins.
|
||||||
|
|
||||||
|
I drew inspiration from a [fork of the original project](https://github.com/kostis-kounadis/instagram-hdr-assembler), the one that eventually became the LrC plugin, to build a frontend that can be easily deployed with Docker. Let's be honest: it was also a great excuse to put my Claude Code subscription to the test. And I have to say, watching it spin up its own environments, run end-to-end tests, self-correct its code, and write detailed summaries is genuinely impressive. I still reviewed everything myself, don't worry. I also learned a great deal about HDR fundamentals, gain maps, HLG/PQ tone curves, color spaces, and more.
|
||||||
|
|
||||||
|
In short, here is what my workflow now looks like for posting on Instagram:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Allow me to introduce **Instam[eex]{style="color: #1ad6ff"}**
|
||||||
|
|
||||||
|
---
|
||||||
|
::card-grid{grid-template-columns="repeat(2, minmax(0, 1fr));"}
|
||||||
|
#title
|
||||||
|
And here is the result
|
||||||
|
|
||||||
|
#root
|
||||||
|
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||||
|
|
||||||
|
#default
|
||||||
|
::card
|
||||||
|
#title
|
||||||
|
🐋 __Instameex__
|
||||||
|
#description
|
||||||
|
[Open the repository](https://git.djeex.fr/Djeex/instameex)
|
||||||
|
::
|
||||||
|
|
||||||
|
::card
|
||||||
|
#title
|
||||||
|
🌍 __Online version__
|
||||||
|
#description
|
||||||
|
[Convert online](https://instameex.djeex.fr)
|
||||||
|
::
|
||||||
|
::
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
---
|
||||||
|
navigation: true
|
||||||
|
title: Servarr corrector
|
||||||
|
description: Un script bash pour détecter et corriger les fichiers médias en double dans les bibliothèques Sonarr et Radarr en remplaçant les copies par des hardlinks.
|
||||||
|
main:
|
||||||
|
fluid: false
|
||||||
|
---
|
||||||
|
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||||
|
# Servarr duplicates corrector
|
||||||
|
---
|
||||||
|
|
||||||
|
Six months after downloading terabytes of media, I realized that Sonarr and Radarr were copying them into my Plex library instead of creating hardlinks. This happens due to a counterintuitive mechanism: if you mount multiple folders in Sonarr/Radarr, it sees them as different filesystems and thus cannot create hardlinks. That’s why you should mount only one parent folder containing all child folders (like `downloads`, `movies`, `tvseries` inside a `media` parent folder).
|
||||||
|
|
||||||
|
So I restructured my directories, manually updated every path in Qbittorrent, Plex, and others. The last challenge was finding a way to detect existing duplicates, delete them, and automatically create hardlinks instead—to save space.
|
||||||
|
|
||||||
|
My directory structure:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
.
|
||||||
|
└── media
|
||||||
|
├── seedbox
|
||||||
|
├── radarr
|
||||||
|
│ └── tv-radarr
|
||||||
|
├── movies
|
||||||
|
└── tvseries
|
||||||
|
```
|
||||||
|
|
||||||
|
The originals are in `seedbox` and must not be modified to keep seeding. The copies (duplicates) are in `movies` and `tvseries`. To complicate things, there are also unique originals in `movies` and `tvseries`. And within those, there can be subfolders, sub-subfolders, etc.
|
||||||
|
|
||||||
|
So the idea is to:
|
||||||
|
|
||||||
|
- list the originals in seedbox
|
||||||
|
- list files in movies and tvseries
|
||||||
|
- compare both lists and isolate duplicates
|
||||||
|
- delete the duplicates
|
||||||
|
- hardlink the originals to the deleted duplicate paths
|
||||||
|
|
||||||
|
Yes, I asked ChatGPT and Qwen3 (which I host on a dedicated AI machine). Naturally, they suggested tools like rfind, rdfind, dupes, rdupes, rmlint... But hashing 30TB of media would take days, so I gave up quickly.
|
||||||
|
|
||||||
|
In the end, I only needed to find `.mkv` files, and duplicates have the exact same name as the originals, which simplifies things a lot. A simple Bash script would do the job.
|
||||||
|
|
||||||
|
Spare you the endless Q&A with ChatGPT—I was disappointed. Qwen3 was much cleaner. ChatGPT kept pushing awk-based solutions, which fail on paths with spaces. With Qwen’s help and dropping awk, the results improved significantly.
|
||||||
|
|
||||||
|
To test, I first asked for a script that only lists and compares:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Create an associative array to store duplicates
|
||||||
|
declare -A seen
|
||||||
|
|
||||||
|
# Find all .mkv files only (exclude directories)
|
||||||
|
find /media/seedbox /media/movies /media/tvseries -type f -name "*.mkv" -print0 | \
|
||||||
|
while IFS= read -r -d '' file; do
|
||||||
|
# Get the file's inode and name
|
||||||
|
inode=$(stat --format="%i" "$file")
|
||||||
|
filename=$(basename "$file")
|
||||||
|
|
||||||
|
# If the filename has been seen before
|
||||||
|
if [[ -n "${seen[$filename]}" ]]; then
|
||||||
|
# Check if the inode is different from the previous one
|
||||||
|
if [[ "${seen[$filename]}" != "$inode" ]]; then
|
||||||
|
# Output the duplicates with full paths
|
||||||
|
echo "Duplicates for \"$filename\":"
|
||||||
|
echo "${seen["$filename"]} ${seen["$filename:full_path"]}"
|
||||||
|
echo "$inode $file"
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
seen[$filename]="$inode"
|
||||||
|
seen["$filename:full_path"]="$file"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
This gave me outputs like:
|
||||||
|
|
||||||
|
```
|
||||||
|
Duplicates for "episode1.mkv":
|
||||||
|
1234567 /media/seedbox/sonarr/Serie 1/Season1/episode1.mkv
|
||||||
|
2345678 /media/tvseries/Serie 1/Season1/episode1.mkv
|
||||||
|
```
|
||||||
|
|
||||||
|
With `awk`, it would’ve stopped at `/media/seedbox/sonarr/Serie`. I’m far from an expert, but Qwen3 performed better and explained everything clearly.
|
||||||
|
|
||||||
|
Once I verified the output, I asked for a complete script: compare, delete duplicates, create hardlinks.
|
||||||
|
|
||||||
|
Again, ChatGPT disappointed. Despite my requests, it created hardlinks *before* deleting the duplicates—effectively linking and then deleting the link (though the original is kept). Not helpful.
|
||||||
|
|
||||||
|
Quick stopover to Qwen3, RTX 5090 in overdrive, and bam—much better result. Yes, it kept ChatGPT-style emojis, but here it is:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo "🔍 Step 1: Indexing original files in /media/seedbox..."
|
||||||
|
declare -A seen
|
||||||
|
|
||||||
|
# Index all .mkv files in seedbox
|
||||||
|
while IFS= read -r -d '' file; do
|
||||||
|
filename=$(basename "$file")
|
||||||
|
seen["$filename"]="$file"
|
||||||
|
done < <(find /media/seedbox -type f -name "*.mkv" -print0)
|
||||||
|
|
||||||
|
echo "📦 Step 2: Automatically replacing duplicates..."
|
||||||
|
total_doublons=0
|
||||||
|
total_ko_saved=0
|
||||||
|
|
||||||
|
while IFS= read -r -d '' file; do
|
||||||
|
filename=$(basename "$file")
|
||||||
|
original="${seen[$filename]}"
|
||||||
|
|
||||||
|
if [[ -n "$original" && "$original" != "$file" ]]; then
|
||||||
|
inode_orig=$(stat -c %i "$original")
|
||||||
|
inode_dupe=$(stat -c %i "$file")
|
||||||
|
|
||||||
|
if [[ "$inode_orig" != "$inode_dupe" ]]; then
|
||||||
|
size_kb=$(du -k "$file" | cut -f1)
|
||||||
|
echo "🔁 Replacing:"
|
||||||
|
echo " Duplicate : $file"
|
||||||
|
echo " Original : $original"
|
||||||
|
echo " Size : ${size_kb} KB"
|
||||||
|
|
||||||
|
rm "$file" && ln "$original" "$file" && echo "✅ Hardlink created."
|
||||||
|
|
||||||
|
total_doublons=$((total_doublons + 1))
|
||||||
|
total_ko_saved=$((total_ko_saved + size_kb))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done < <(find /media/movies /media/tvseries -type f -name "*.mkv" -print0)
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "🧾 Summary:"
|
||||||
|
echo " 🔗 Duplicates replaced by hardlink: $total_doublons"
|
||||||
|
echo " 💾 Approx. disk space saved: ${total_ko_saved} KB (~$((total_ko_saved / 1024)) MB)"
|
||||||
|
echo "✅ Done."
|
||||||
|
```
|
||||||
|
|
||||||
|
So, in conclusion, I:
|
||||||
|
- Learned many Bash subtleties
|
||||||
|
- Learned never to blindly copy-paste a ChatGPT script without understanding and dry-running it
|
||||||
|
- Learned that Qwen on a RTX 5090 is more coherent than ChatGPT-4o on server farms (not even mentioning “normal” ChatGPT)
|
||||||
|
- Learned that even with 100TB of storage, monitoring it would’ve alerted me much earlier to the 12TB of duplicates lying around
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
---
|
||||||
|
navigation: true
|
||||||
|
title: LUKS Backup
|
||||||
|
description: Un script bash pour extraire automatiquement les headers LUKS de tous les disques chiffrés, les identifier par numéro de série et les archiver de façon chiffrée.
|
||||||
|
main:
|
||||||
|
fluid: false
|
||||||
|
---
|
||||||
|
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||||
|
|
||||||
|
# Backup of LUKS Headers for Encrypted Disks/Volumes
|
||||||
|
---
|
||||||
|
|
||||||
|
I recently realized that having just the password is not enough to unlock a LUKS volume after a failure or corruption. I learned how to dump the LUKS headers from disks/volumes and to use the serial numbers along with partition names to accurately identify which header corresponds to which disk/partition (I have 10 of them!).
|
||||||
|
|
||||||
|
After struggling to do this manually, I asked Qwen3 (an LLM running on my RTX 5090) to create a script that automates the listing and identification of disks, dumps the headers, and stores them in an encrypted archive ready to be backed up on my backup server.
|
||||||
|
|
||||||
|
This script:
|
||||||
|
* Lists and identifies disks with their serial numbers
|
||||||
|
* Lists partitions
|
||||||
|
* Dumps headers into a secured folder under `/root`
|
||||||
|
* Creates a temporary archive
|
||||||
|
* Prompts for a password
|
||||||
|
* Encrypts the archive with that password
|
||||||
|
* Deletes the unencrypted archive
|
||||||
|
|
||||||
|
```sh
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Directory where LUKS headers will be backed up
|
||||||
|
DEST="/root/luks-headers-backup"
|
||||||
|
mkdir -p "$DEST"
|
||||||
|
|
||||||
|
echo "🔍 Searching for LUKS containers on all partitions..."
|
||||||
|
|
||||||
|
# Loop through all possible disk partitions (including NVMe and SATA)
|
||||||
|
for part in /dev/sd? /dev/sd?? /dev/nvme?n?p?; do
|
||||||
|
# Skip if the device doesn't exist
|
||||||
|
if [ ! -b "$part" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if the partition is a LUKS encrypted volume
|
||||||
|
if cryptsetup isLuks "$part"; then
|
||||||
|
# Find the parent disk device (e.g. nvme0n1p4 → nvme0n1)
|
||||||
|
disk=$(lsblk -no pkname "$part" | head -n 1)
|
||||||
|
full_disk="/dev/$disk"
|
||||||
|
|
||||||
|
# Get the serial number of the parent disk
|
||||||
|
SERIAL=$(udevadm info --query=all --name="$full_disk" | grep ID_SERIAL= | cut -d= -f2)
|
||||||
|
if [ -z "$SERIAL" ]; then
|
||||||
|
SERIAL="unknown"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract the partition name (e.g. nvme0n1p4)
|
||||||
|
PART_NAME=$(basename "$part")
|
||||||
|
|
||||||
|
# Build the output filename with partition name and disk serial
|
||||||
|
OUTPUT="$DEST/luks-header-${PART_NAME}__${SERIAL}.img"
|
||||||
|
|
||||||
|
echo "🔐 Backing up LUKS header of $part (Serial: $SERIAL)..."
|
||||||
|
|
||||||
|
# Backup the LUKS header to the output file
|
||||||
|
cryptsetup luksHeaderBackup "$part" --header-backup-file "$OUTPUT"
|
||||||
|
if [[ $? -eq 0 ]]; then
|
||||||
|
echo "✅ Backup successful → $OUTPUT"
|
||||||
|
else
|
||||||
|
echo "❌ Backup failed for $part"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Create a timestamped compressed tar archive of all header backups
|
||||||
|
ARCHIVE_NAME="/root/luks-headers-$(date +%Y%m%d_%H%M%S).tar.gz"
|
||||||
|
echo "📦 Creating archive $ARCHIVE_NAME..."
|
||||||
|
tar -czf "$ARCHIVE_NAME" -C "$DEST" .
|
||||||
|
|
||||||
|
# Encrypt the archive symmetrically using GPG with AES256 cipher
|
||||||
|
echo "🔐 Encrypting the archive with GPG..."
|
||||||
|
gpg --symmetric --cipher-algo AES256 "$ARCHIVE_NAME"
|
||||||
|
if [[ $? -eq 0 ]]; then
|
||||||
|
echo "✅ Encrypted archive created: ${ARCHIVE_NAME}.gpg"
|
||||||
|
# Remove the unencrypted archive for security
|
||||||
|
rm -f "$ARCHIVE_NAME"
|
||||||
|
else
|
||||||
|
echo "❌ Encryption failed"
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
**Don’t forget to back up `/etc/fstab` and `/etc/crypttab` as well!**
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
---
|
||||||
|
navigation: true
|
||||||
|
title: Socat Proxy
|
||||||
|
description: Utiliser socat pour proxifier le socket Docker via Docker Socket Proxy, permettant à Beszel de collecter les stats des conteneurs sans exposer le socket complet.
|
||||||
|
main:
|
||||||
|
fluid: false
|
||||||
|
---
|
||||||
|
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||||
|
|
||||||
|
# Socat Proxy
|
||||||
|
---
|
||||||
|
|
||||||
|
This project addresses a common use case:
|
||||||
|
|
||||||
|
- I have [Beszel](https://beszel.dev/), a monitoring container running in host mode, which requires access to the Docker socket to collect container statistics.
|
||||||
|
- To avoid exposing the Docker socket fully to Beszel, I use [Docker Socket Proxy](https://github.com/Tecnativa/docker-socket-proxy), a container that sits between the Docker socket and the consuming container. It filters requests by setting appropriate permissions, preventing full exposure of the Docker socket.
|
||||||
|
|
||||||
|
The problem arises when **Beszel** runs in host mode. In that case, it must connect directly to **Docker Socket Proxy** on a host port, meaning the proxy’s port is exposed. This allows any container or application on the host to access it and use the Docker socket.
|
||||||
|
|
||||||
|
This is where [Socat Proxy](https://git.djeex.fr/Djeex/socat-proxy) comes in. It is a container that:
|
||||||
|
|
||||||
|
- Creates a UNIX socket
|
||||||
|
- Listens on this socket
|
||||||
|
- Forwards requests to Docker Socket Proxy and back
|
||||||
|
- Replaces the real Docker socket by exposing the proxy socket in the target container via a bind mount (in this case, Beszel)
|
||||||
|
|
||||||
|
With this setup, Docker Socket Proxy communicates with Socat Proxy in their isolated bridge network, while the UNIX socket bind-mounted on the host has restricted permissions, preventing access from other containers or applications.
|
||||||
|
|
||||||
|
In short:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
For example, with Beszel, the configuration would look like this:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
socat-proxy:
|
||||||
|
image: git.djeex.fr/djeex/socat-proxy:latest
|
||||||
|
container_name: socat-proxy-beszel
|
||||||
|
environment:
|
||||||
|
- TARGET_HOST=${TARGET_HOST}
|
||||||
|
- TARGET_PORT=${TARGET_PORT}
|
||||||
|
- UNIX_SOCKET_PATH=${UNIX_SOCKET_PATH}
|
||||||
|
- HOST_SOCKET_PATH=${HOST_SOCKET_PATH}
|
||||||
|
- UNIX_SOCKET_NAME=${UNIX_SOCKET_NAME}
|
||||||
|
volumes:
|
||||||
|
- ${HOST_SOCKET_PATH}:${UNIX_SOCKET_PATH}
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- ${TARGET_HOST}
|
||||||
|
|
||||||
|
socket-proxy:
|
||||||
|
image: lscr.io/linuxserver/socket-proxy:latest
|
||||||
|
container_name: ${TARGET_HOST}
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
environment:
|
||||||
|
- CONTAINERS=1
|
||||||
|
- INFO=1
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
restart: unless-stopped
|
||||||
|
read_only: true
|
||||||
|
tmpfs:
|
||||||
|
- /run
|
||||||
|
|
||||||
|
beszel-agent:
|
||||||
|
image: henrygd/beszel-agent:latest
|
||||||
|
container_name: beszel-agent
|
||||||
|
restart: unless-stopped
|
||||||
|
network_mode: host
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
volumes:
|
||||||
|
- ${HOST_SOCKET_PATH}/${UNIX_SOCKET_NAME}:/var/run/docker.sock:ro
|
||||||
|
environment:
|
||||||
|
- #... your Beszel environment variables
|
||||||
|
depends_on:
|
||||||
|
- socat-proxy
|
||||||
|
```
|
||||||
|
|
||||||
|
More information is available on the repository:
|
||||||
|
|
||||||
|
::card
|
||||||
|
#title
|
||||||
|
🐋 **Socat Proxy**
|
||||||
|
#description
|
||||||
|
[A lightweight bind-mount socket proxy](https://git.djeex.fr/Djeex/socat-proxy)
|
||||||
|
::
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
navigation: true
|
||||||
|
title: HotDisk
|
||||||
|
description: Un script bash qui surveille la température des disques durs et éteint automatiquement le serveur lorsqu'ils dépassent un seuil de sécurité trop longtemps.
|
||||||
|
main:
|
||||||
|
fluid: false
|
||||||
|
---
|
||||||
|
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||||
|
|
||||||
|
# HotDisk
|
||||||
|
---
|
||||||
|
|
||||||
|
When you have a NAS with several drives sitting in a laundry room, temperatures can quickly rise.
|
||||||
|
Hard drives are very sensitive to heat and can suffer serious damage if they exceed a certain temperature threshold for too long.
|
||||||
|
After a particularly hot summer that caused a few cold sweats while monitoring my drives’ temperatures, I started looking for a way to automatically shut down the server when disk temperatures stay above their safe limit for an extended period.
|
||||||
|
|
||||||
|
Since I couldn’t find a convincing solution, I decided to build my own.
|
||||||
|
|
||||||
|
- The script reads SMART temperature data from all SATA drives every minute.
|
||||||
|
- It counts the number of consecutive minutes the temperature stays above or below the threshold.
|
||||||
|
- It sends Discord notifications if the threshold is exceeded or when the temperature cools down.
|
||||||
|
- It triggers a system shutdown if the temperature stays above the limit for the configured duration.
|
||||||
|
- It logs all temperatures and counter states, and automatically rotates log files.
|
||||||
|
|
||||||
|
While I was at it, I also added an installation script that installs the main script, makes it executable, creates a systemd service and timer, and enables them automatically.
|
||||||
|
The installer also lets you configure various parameters:
|
||||||
|
|
||||||
|
| Variable | Description | Default Value |
|
||||||
|
|-----------------------|------------------------------------------------------------------------------|-----------------------------------------------|
|
||||||
|
| `MAX_TEMP` | Maximum allowed temperature (°C) before the shutdown countdown starts | `60` |
|
||||||
|
| `HOT_DURATION` | Consecutive minutes above `MAX_TEMP` before shutdown | `5` |
|
||||||
|
| `COOL_RESET_DURATION` | Consecutive minutes below `MAX_TEMP` to reset all counters | `5` |
|
||||||
|
| `LOG_FILE` | Path to the main log file | `/var/log/hdd_temp_monitor.log` |
|
||||||
|
| `LOG_ROTATE_COUNT` | Number of log files to keep | `7` |
|
||||||
|
| `LOG_ROTATE_PERIOD` | Log rotation period (`daily` or `weekly`) | `daily` |
|
||||||
|
| `DISCORD_WEBHOOK` | Discord webhook URL for notifications | _Required_ |
|
||||||
|
|
||||||
|
It also runs another script that configures **logrotate** with the parameters defined above.
|
||||||
|
Finally, the installer can even be executed directly via a simple `curl` command followed by one last setup script — perfect for the laziest of us.
|
||||||
|
|
||||||
|
I also had to handle several tricky cases: running as root without sudo, using sudo directly, running as a non-sudo user, missing dependencies, permission issues, file creation errors, disk data reading errors, and more.
|
||||||
|
|
||||||
|
Concurrent access to the status file also had to be managed carefully.
|
||||||
|
|
||||||
|
More details are available directly on the repository:
|
||||||
|
|
||||||
|
::card
|
||||||
|
#title
|
||||||
|
📜 __HotDisk__
|
||||||
|
#description
|
||||||
|
[Keep your drives cool!](https://git.djeex.fr/Djeex/hotdisk)
|
||||||
|
::
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
---
|
||||||
|
navigation: true
|
||||||
|
title: Backrest Docker Stop
|
||||||
|
description: Un script bash qui arrête les conteneurs Docker avant une sauvegarde Backrest et les redémarre après — pour des sauvegardes de bases de données sans dump complexe.
|
||||||
|
main:
|
||||||
|
fluid: false
|
||||||
|
---
|
||||||
|
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||||
|
|
||||||
|
# Backrest Docker Stop
|
||||||
|
---
|
||||||
|
|
||||||
|
[Backrest](https://github.com/garethgeorge/backrest) is a fantastic backup tool. In the case of [Serveex](https://docu.djeex.fr/serveex/introduction), most of the data that needs to be backed up consists of containers, and those containers often include databases.
|
||||||
|
|
||||||
|
The problem? You can’t safely back up a database while it’s running. There are plenty of complex solutions involving database dumps, but often the simplest method is to stop the containers, perform the backup, and then restart them.
|
||||||
|
|
||||||
|
**Backrest** doesn’t natively provide this functionality, but it does allow the execution of custom scripts triggered by events, for example, at the start and end of a backup plan. Our goal is to stop the containers whose databases need to be backed up when the backup plan starts, and restart them when the backup plan finishes.To achieve this, we’ll need a small Bash script and a secure connection between Backrest and the Docker socket, to enable the following sequence:
|
||||||
|
|
||||||
|
- The backup plan starts
|
||||||
|
- The event triggers the execution of a custom script
|
||||||
|
- The script contacts Docker and retrieves a list of containers labeled `backrest.backup.stop=true`
|
||||||
|
- It stops those containers
|
||||||
|
- The backup plan completes
|
||||||
|
- The event triggers another custom script
|
||||||
|
- The script contacts Docker again, retrieves the same list, and restarts those containers
|
||||||
|
|
||||||
|
|
||||||
|
## Securely Connecting Backrest and Docker
|
||||||
|
|
||||||
|
To allow **Backrest** to communicate securely with Docker, we’ll use [Docker Socket Proxy](https://github.com/linuxserver/docker-socket-proxy).
|
||||||
|
This avoids exposing the full Docker socket and grants only the necessary permissions.
|
||||||
|
Here’s an example Docker stack:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
services:
|
||||||
|
backrest:
|
||||||
|
image: garethgeorge/backrest:latest
|
||||||
|
container_name: backrest
|
||||||
|
hostname: backrest
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
volumes:
|
||||||
|
- ... # your volumes
|
||||||
|
environment:
|
||||||
|
- ... # your environment variables
|
||||||
|
- DOCKER_HOST=tcp://socket-proxy-backrest:2375
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- ... # your ports
|
||||||
|
depends_on:
|
||||||
|
- socket-proxy
|
||||||
|
|
||||||
|
socket-proxy:
|
||||||
|
image: lscr.io/linuxserver/socket-proxy:latest
|
||||||
|
container_name: socket-proxy-backrest
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
environment:
|
||||||
|
- CONTAINERS=1
|
||||||
|
- ALLOW_START=1
|
||||||
|
- ALLOW_STOP=1
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
restart: unless-stopped
|
||||||
|
read_only: true
|
||||||
|
tmpfs:
|
||||||
|
- /run
|
||||||
|
```
|
||||||
|
|
||||||
|
With this setup, Backrest can communicate with Docker safely and securely.
|
||||||
|
|
||||||
|
|
||||||
|
## The Scripts
|
||||||
|
|
||||||
|
Below are the scripts to use for **Backrest**’s *start* and *end* backup events.
|
||||||
|
|
||||||
|
::code-group
|
||||||
|
```sh [Stop]
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
BACKUP_LABEL="backrest.backup.stop=true"
|
||||||
|
BACKUP_CONTAINERS=$(docker ps -aqf "label=$BACKUP_LABEL")
|
||||||
|
for BC in $BACKUP_CONTAINERS
|
||||||
|
do
|
||||||
|
docker stop "$BC"
|
||||||
|
done
|
||||||
|
sleep 10
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh [Start]
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
BACKUP_LABEL="backrest.backup.stop=true"
|
||||||
|
BACKUP_CONTAINERS=$(docker ps -aqf "label=$BACKUP_LABEL")
|
||||||
|
for BC in $BACKUP_CONTAINERS
|
||||||
|
do
|
||||||
|
docker start "$BC"
|
||||||
|
done
|
||||||
|
sleep 10
|
||||||
|
```
|
||||||
|
::
|
||||||
|
|
||||||
|
|
||||||
|
## The Label
|
||||||
|
|
||||||
|
Once the scripts are in place and configured for the proper **Backrest** hooks, you just need to add the label `backrest.backup.stop=true` to the `compose.yaml` files of the containers that should stop and restart during backups:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
your_service:
|
||||||
|
...
|
||||||
|
labels:
|
||||||
|
- backrest.backup.stop=true
|
||||||
|
```
|
||||||
|
|
||||||
|
And that’s it!
|
||||||
|
At the next backup, all containers with the correct label will automatically stop during the backup and restart once it’s finished.
|
||||||
+2
-2
@@ -46,12 +46,12 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
|
|
||||||
site: {
|
site: {
|
||||||
url: 'https://docu.djeex.fr'
|
url: 'https://docu.djeex.fr/fr'
|
||||||
},
|
},
|
||||||
|
|
||||||
sitemap: {
|
sitemap: {
|
||||||
autoLastmod: true,
|
autoLastmod: true,
|
||||||
urls: ['https://docu.djeex.fr']
|
urls: ['https://docu.djeex.fr/fr']
|
||||||
},
|
},
|
||||||
|
|
||||||
compatibilityDate: '2024-10-24'
|
compatibilityDate: '2024-10-24'
|
||||||
|
|||||||
Reference in New Issue
Block a user