Projet

Général

Profil

Présentation personnelle et projet de stage

Je suis Nicolas Schmauch
Je suis un étudiant en Licence Pro ADSILLH (Administration et Développement de Systèmes Informatiques à base de Logiciels Libres et Hybrides).
Je suis en stage avec Yoan Gabriel (4 mois de stage du 30 mars au 31 juillet 2026).

Rapport

Jour 1: Lundi 30 mars 2026

Notre travail consiste à mettre à jour l’ancienne architecture vers une nouvelle, basée sur Debian Trixie stable, avec un hyperviseur permettant de gérer différentes machines virtuelles (une VM par service).

SCHEMA

Nous avons tout d’abord dû choisir un hyperviseur parmi les différentes solutions open source :

Caractéristique Incus (Mode VM) KVM/QEMU + libvirt Canonical LXD (Mode VM) Xen Project
Moteur QEMU / KVM QEMU / KVM QEMU / KVM Xen Hypervisor
Installation Debian 13 Native apt install incus Native apt install libvirt-daemon Via Snap snap install lxd Native apt install xen-hypervisor
RAM Très faible (~50 Mo) Faible (~100 Mo) Moyen (~200 Mo) Élevé (minimum 1024 Mo)
Déploiement OS Images Cloud et Manuel Manuel (Fichiers ISO) Images Cloud et Manuel Manuel (ISO ou debootstrap)
Sauvegardes / Snapshots Intégrés (incus snapshot / export) Manuels (virsh snapshot-create) Intégrés (lxc snapshot) Complexes (via LVM)
Score global / 5 5 / 5 4 / 5 3 / 5 1 / 5

sources :
https://linuxcontainers.org
https://libvirt.org
https://canonical.com/lxd
https://xenproject.org/resources

https://wiki.debian.org/Incus
https://wiki.debian.org/KVM
https://wiki.debian.org/QEMU
https://wiki.debian.org/libvirt
https://wiki.debian.org/Xen/InstallBootConfig
https://wiki.xenproject.org/wiki/Tuning_Xen_for_Performance

https://dev.to/rosgluk/linux-virtualization-solutions-a-complete-comparison-guide-196g
https://www.siberoloji.com/how-to-install-and-configure-lxd-on-debian-12

Nous avons ensuite calculé les ressources nécessaires pour savoir si le serveur a besoin d’une amélioration de ses ressources ou non :

Service RAM minimum RAM recommandée CPU minimum CPU recommandé
Apache 512 MB 1–4 GB 1 core 2+ cores
Nginx 512 MB 1 GB 1 core 2+ cores
Laravel 1 GB 2+ GB 1 core 2+ cores
Redmine 512 MB 1–2 GB 1 core 2 cores
Forgejo 512 MB 2 GB 1 core 2+ cores
NextCloud 512 MB 2 GB 1 core 2+ cores
total 3.5 GB 9-13+ GB 6 cores 12+ cores

sources :
https://httpd.apache.org/docs/2.4/misc/perf-tuning.html
https://docs.nginx.com/nginx-agent/technical-specifications
https://kinsta.com/fr/blog/installer-nginx
https://www.hostragons.com/fr/blog/exigences-dhebergement-pour-les-applications-laravel
https://redmine1click.com/requirements
https://blog.stephane-robert.info/docs/services/devops/forgejo/installation
https://docs.nextcloud.com/server/stable/admin_manual/installation/system_requirements.html

Critère clé Caddy Nginx Proxy Manager (NPM) Traefik
Consommation Mémoire (RAM) Très léger Lourd Léger
Automatisation SSL Natif, Zéro config Bouton dans l'interface Demande une config YAML
Adaptation à un réseau de VM Fichier texte très lisible Routage IP via interface Usine à gaz sans Docker
Facilité de prise en main Syntaxe minimaliste Interface web complète Courbe d'apprentissage rude
Score global / 5 4 / 5 3 / 5 1 / 5

sources :
https://kx.cloudingenium.com/en/caddy-reverse-proxy-automatic-https-zero-config-ssl-guide/
https://caddyserver.com/docs/quick-starts/reverse-proxy
https://caddyserver.com/docs/

https://stackoverflow.com/questions/63077100/how-much-memory-and-cpu-nginx-and-nodejs-in-each-container-needs
https://nginxproxymanager.com/

https://community.traefik.io/t/traefik-performance-lags-behind-nginx-and-caddy/28919
https://doc.traefik.io/traefik/routing/overview/

https://www.reddit.com/r/selfhosted/comments/1odh46j/nginx_vs_caddy_vs_traefik_benchmark_results/

Jour 2: Mardi 31 mars 2026

Mail pour Alexander :

Bonjour Alexander,

Nous sommes les nouveaux stagiaires ADSILLH.
Pour le projet du nouveau serveur, on a fait des comparatifs (Yoan et moi), pour la nouvelle architecture et on propose :

Hyperviseur : Incus (Mode VM), car il offre la consommation RAM la plus faible, s'installe nativement sur Debian et la gestion des snapshots et plus simple qu'avec KVM.
Reverse Proxy : Caddy, car très léger et automatise à 100% la gestion des certificats SSL avec Let's Encrypt. Il est plus adapté que Traefik ou Nginx Proxy Manager pour des VM fixes.

Voici nos comparatifs : https://projets.cohabit.fr/redmine/projects/accueil/wiki/Nicolas_Schmauch_2

Schmauch Nicolas

Jour 3: Mercredi 01 avril 2026

La réponse d'Alexander :

Du coup, les questions qui restent en suspens sont :
Il faut que cela gère le raid logiciel (1 ou 5).
Quelle technologie sont supportées pour les containers ?
Et les Snapshots ou image des VM sont-elles exportables dans un format qui est géré par la plupart des hyperviseurs en cas de soucis ?

Notre réponse :

Incus est compatible avec le raid logiciel (1 ou 5), il ne gère pas le matériel directement, il faut juste lui indiquer d'utiliser le volume créé.
Incus utilise des conteneurs système (LXC) et il gère Docker soit en l'installant à l'intérieur d'une VM/conteneur (mode nesting), soit en lançant des images Docker (images OCI).
Les VM Incus sont basées sur QEMU et sont exportées au format QCOW2 ou Raw. Donc c'est compatible avec la plupart des hyperviseurs comme Proxmox, KVM, VirtualBox, VMware, etc.

Conteneur + dir = Dossier 
VM QEMU + dir = raw
VM QEMU + lvm = qcow2

Commande Incus :
incus launch images:debian/13 ma-vm-debian --vm : Lance un VM Debian 13
incus remote add docker https://docker.io --protocol=oci : Pour ajouter le dépôt Docker Hub
incus launch docker:nginx mon-nginx : Lance l'image nginx

incus list : Voir toutes les instances
incus stop mon-instance : Arrête une instance
incus start mon-instance : Démarrer une instance
incus restart mon-instance : Redémarrer une instance

incus delete mon-instance : Supprimer une instance arrêtée
incus delete mon-instance --force : Forcer l'arrêt et la suppression

incus snapshot create mon-instance mon-snap1 : Créer un snapshot de l'instance
--stateful pour sauvegarder la RAM à chaud

fonctionne que si migration.stateful=true est activé, pour l'activer :
incus config set mon-instance migration.stateful=true

incus snapshot list mon-instance : Voir toutes les snapshots d'une instances

incus snapshot create mon-instance mon-snap1 --reuse
--reuse supprime l'ancien mon-snap1 et recréer un nouveau avec le même nom

incus snapshot restore mon-instance mon-snap1 : Restore un snapshot pour une instance
Recommandé d'arrêter l'instance avec incus stop avant de restaurer

incus snapshot delete mon-instance mon-snap1 : Supprimer un snapshot

incus launch images:debian/13 ma-vm --vm -c limits.cpu=4 -c limits.memory=8GiB -d root,size=50GiB : -c (la config) -d (le disque)

Modifier les ressources d'une instance déjà existante :
incus config set mon-instance limits.memory 4GiB : Modifier la RAM
incus config set mon-instance limits.memory.swap false : Désactiver le swap
incus config set mon-instance limits.cpu 2 : Alloue 2 coeurs cpu
incus config set mon-instance limits.cpu.allowance 50% : Bride la puissance des coeurs à 50% d'utilisation

Voir la config d'une instance :
incus config show mon-instance
incus config device show mon-instance
incus info mon-instance

Jour 4: Jeudi 02 avril 2026

Partition Taille Point de montage Rôle
1 512 Mo /boot/efi Pour le démarre en UEFI
2 1 Go /boot Pour le boot Debian
3 8 Go swap Sécurité si la RAM sature (à voir car jsp si c'est vraiment utile car on a 32go)
4 50 Go / (Racine) Le Debian de l'hyperviseur, Incus et logs
5 ~940 Go Aucun Espace brut (Ne pas formater car on initialisera avec Incus en ZFS)

Nouvelle mission, configurer un routeur OpenWrt one avec un PinePhone pour partager le réseau cellulaire du PinePhone avec le routeur :
code sim : 7826

PostMarketOS :
id : user
mdp : 147147

Mobian :
id : mobian
mdp : 1234

Flash l'os OpenWrt stable sur le routeur OpenWrt One :
https://firmware-selector.openwrt.org/?version=25.12.2&target=mediatek%2Ffilogic&id=openwrt_one
Se brancher avec le câble RJ45 au routeur en mode NOR, puis aller à l’adresse http://192.168.1.1
Ensuite, flasher la nouvelle version et décocher "keep settings" pour le reset.
Après cela, attendre que l’opération se termine, puis redémarrer en mode NAND pour pouvoir le configurer.

On a aussi créé un schéma :
SCHEMA

Jour 5: Vendredi 03 avril 2026

On a flash l'OS PostMarketOS sur le PinePhone dans la mémoire interne (eMMC) :

Pour flasher un OS, on utilise bmaptool ou balenaEtcher

Il faut d'abord flasher Jumpdrive sur une carte micro SD en utilisant :
pine64-pinephone.img.xz (version normale)
ou
pine64-pinephone-charging.img.xz (version si la batterie est complètement vide)

On insère ensuite cette carte micro SD dans le PinePhone.

On démarre le PinePhone sur Jumpdrive et on le branche au PC.

Pour flasher Mobian ou PostMarketOS sur le stockage eMMC du PinePhone :
Mobian pour le PinePhone classique : sunxi
Mobian pour le PinePhone Pro : rockchip

PostMarketOS pour le PinePhone classique : PINE64 PinePhone
PostMarketOS pour le PinePhone Pro : PINE64 PinePhone Pro

Une fois le flash terminé, on débranche et éteint le PinePhone, puis on retire la carte micro SD.

Il faut ensuite flasher Tow-Boot sur la carte micro SD (c'est le fichier avec "install" dans le nom qu'il faut flasher)
pour le PinePhone classique : pine64-pinephoneA64-xxxx.xx-xxx.tar.xz
pour le PinePhone Pro : pine64-pinephonePro-xxxx.xx-xxx.tar.xz

On insère à nouveau cette carte micro SD dans le PinePhone.

On démarre sur Tow-Boot avec le PinePhone pour l'installer dans la mémoire eMMC.
On retire enfin la carte micro SD du téléphone, on le rallume, et on a réussi à démarrer sur PostMarketOS.

Pour mettre le PinePhone en hotspot, il faut le faire graphiquement avec le bouton et activer les données cellulaires et aussi "Mobile Data On".

On récupère le hotspot avec le routeur qui prend le relai pour émettre le réseau.

En ssh sur le routeur openwrt one pour installer le relay bridge :
apk update
apk add relayd luci-proto-relay
reboot

On accède au menu Network, puis Wireless et on sélectionne Radio 0 pour scanner et rejoindre le réseau du PinePhone.
On est allé dans Network, puis Interfaces et on a édité l'interface Wi-Fi du PinePhone pour sélectionner le réseau sont dans l'onglet Device.
On a ajouté un pont relais depuis Network, puis Interfaces en sélectionnant Add et Relay bridge.
On a configuré le pont relais via l'option Relay between networks en associant l'interface du PinePhone et le réseau LAN.
On a créé un point d'accès depuis Network, puis Wireless et Radio 1 en cliquant sur Add.
On est revenu dans les paramètres du nouveau réseau Wi-Fi pour assigner le périphérique (Device) au réseau LAN.

Pour configurer le routeur, on s'est basé sur cette vidéo

Pour la mission du serveur, on a update le tableau et on a installé Debian 13 en fonction :
Partition Taille Point de montage
1 512 Mo /boot/efi (pour grub)
2 2 Go /boot
3 2 Go swap
4 50 Go / (Racine)
5 ~940 Go Aucun
Paramètre Valeur à mettre
Nom efi
Utiliser comme Partition système EFI
Indicateur d'amorçage présent
Taille 512 Mo
Paramètre Valeur à mettre
Nom boot
Utiliser comme ext4 journalisé
Point de montage /boot
Options de montage defaults
Étiquette aucune
Blocs réservés 5%
Utilisation habituelle standard
Indicateur d'amorçage absent
Taille 2 Go
Paramètre Valeur à mettre
Nom swap
Utiliser comme espace d'échange (swap)
Point de montage (aucun)
Options de montage (aucun)
Étiquette aucune
Indicateur d'amorçage absent
Taille 2 Go
Paramètre Valeur à mettre
Nom root
Utiliser comme ext4 journalisé
Point de montage /
Options de montage défauts
Étiquette aucune
Blocs réservés 5%
Utilisation habituelle standard
Indicateur d'amorçage absent
Taille 50 Go

Pour le reste du disque (~940 Go) on n'a rien touché

On a aussi créé quatre comptes (pierre, alexander, yoan et nicolas).
On a transféré les clés SSH avec cette commande : ssh-copy-id idcompte@ipserveur

Jour 6: Lundi 06 avril 2026

Férié

Jour 7: Mardi 07 avril 2026

Cette fois-ci, on veut transférer la connexion du PinePhone au routeur via l'adaptateur USB > RJ45 :

Pour router les paquets entre les interfaces :

sudo sysctl -w net.ipv4.ip_forward=1

Pour le rendre permanent :
echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.conf

Pour activer la 4G :

sudo nmtui

Puis allez dans "Activer une connexion" et choisir la connexion mobile (4G).

Pour vérifier que la 4G fonctionne :

ping 8.8.8.8

Pour voir l'interface filaire :

ip a

Pour créer le partage de connexion avec l'interface filaire :

sudo nmcli connection add type ethernet ifname enx00e07c4601d6 ipv4.method shared con-name PartageUsb

Pour l'activer :

sudo nmcli connection up PartageUsb

Pour le routeur, on a ajusté la configuration :
le téléphone est désormais connecté au routeur via l'adaptateur.

Le routeur a été modifié pour s’adapter à ce changement :
dans Network > Interfaces on edit le repeat_wifi (le bridge) pour choisir dans Relay between networks "Lan" et "Wan".

Pour le projet avec le serveur :
Mise en place des utilisateurs dans le groupe sudo et désactivation du mode veille :

Passer en root :

su -

Désactiver le mode veille et masquer les services systemd responsables de la veille :

systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target

Ajouter les utilisateurs au groupe sudo :

usermod -aG sudo yoan
usermod -aG sudo nicolas
usermod -aG sudo pierre
usermod -aG sudo alexander

Redémarrer pour appliquer les changements :

reboot

Mise en place du RAID 1 logiciel sur le serveur toutatis avec mdadm (sda = miroir de sdb) :

su -
apt install mdadm gdisk rsync

Copier la table de partitions de sdb vers sda, puis typer les partitions en Linux RAID :

sfdisk -d /dev/sdb | sfdisk /dev/sda
sgdisk --typecode=2:A19D880F-05FC-4D3B-A006-743F0F84911E /dev/sda
sgdisk --typecode=3:A19D880F-05FC-4D3B-A006-743F0F84911E /dev/sda
sgdisk --typecode=4:A19D880F-05FC-4D3B-A006-743F0F84911E /dev/sda

Créer les arrays RAID :

mdadm --create /dev/md2 --level=1 --raid-devices=2 --metadata=1.0 /dev/sda2 missing
mdadm --create /dev/md3 --level=1 --raid-devices=2 /dev/sda3 missing
mdadm --create /dev/md4 --level=1 --raid-devices=2 /dev/sda4 missing

Créer les systèmes de fichiers :

mkfs.ext4 /dev/md2
mkswap /dev/md3
mkfs.ext4 /dev/md4

Monter et copier le système :

mkdir -p /mnt/newroot
mount /dev/md4 /mnt/newroot
mkdir /mnt/newroot/boot
mount /dev/md2 /mnt/newroot/boot
rsync -aAXv --exclude={"/proc/","/sys/","/dev/","/run/","/mnt/","/tmp/"} / /mnt/newroot/

Récupérer les UUID des arrays et mettre à jour fstab :

blkid /dev/md2 /dev/md3 /dev/md4
vi /mnt/newroot/etc/fstab

Sauvegarder la config mdadm :

mdadm --detail --scan | tee /mnt/newroot/etc/mdadm/mdadm.conf

Chroot et mise à jour initramfs et installation de GRUB :

for dir in /dev /dev/pts /proc /sys /run; do mount --bind $dir /mnt/newroot$dir; done
mount /dev/sda1 /mnt/newroot/boot/efi
chroot /mnt/newroot
update-initramfs -u -k all
grub-install /dev/sda
update-grub
exit
reboot

Après reboot il faut ajouter sdb au RAID pour compléter le miroir :

mdadm /dev/md2 --add /dev/sda2
mdadm /dev/md3 --add /dev/sda3
mdadm /dev/md4 --add /dev/sda4
grub-install /dev/sdb
watch cat /proc/mdstat

Installation de Incus :

sudo apt install incus

Jour 8: Mercredi 08 avril 2026

Pour Incus :
installer Incus :

sudo apt install incus

Pour configurer et initialiser Incus

sudo incus admin init

Would you like to use clustering? (yes/no) [default=no]: no
Do you want to configure a new storage pool? (yes/no) [default=yes]: yes
Name of the new storage pool [default=default]: default
Where should this storage pool store its data? [default=/var/lib/incus/storage-pools/default]: 
Would you like to create a new local network bridge? (yes/no) [default=yes]: yes
What should the new bridge be called? [default=incusbr0]: 
What IPv4 address should be used? (CIDR subnet notation, “auto” or “none”) [default=auto]: auto
What IPv6 address should be used? (CIDR subnet notation, “auto” or “none”) [default=auto]: auto

We detected that you are running inside an unprivileged container.
This means that unless you manually configured your host otherwise,
you will not have enough uids and gids to allocate to your containers.

Your container's own allocation can be reused to avoid the problem.
Doing so makes your nested containers slightly less safe as they could
in theory attack their parent container and gain more privileges than
they otherwise would.

Would you like to have your containers share their parent's allocation? (yes/no) [default=yes]: yes
Would you like the server to be available over the network? (yes/no) [default=no]: no
Would you like stale cached images to be updated automatically? (yes/no) [default=yes]: yes
Would you like a YAML "init" preseed to be printed? (yes/no) [default=no]: no



Pour Redmine :
Pour créer le conteneur :
incus launch images:debian/13 redmine

pour ouvrir le terminal du conteneur :

incus exec redmine bash

pour installer docker :

apt update && apt install -y docker.io docker-compose

pour créer la config du docker pour Redmine et PostgreSQL :

mkdir -p /opt/redmine
cd /opt/redmine
vi docker-compose.yml

services:
  db:
    image: postgres:15
    restart: always
    environment:
      POSTGRES_USER: redmine
      POSTGRES_PASSWORD: "MDP" 
      POSTGRES_DB: redmine_db
    volumes:
      - ./pgdata:/var/lib/postgresql/data

  redmine:
    image: redmine:latest
    restart: always
    ports:
      - "80:3000" 
    environment:
      REDMINE_DB_POSTGRES: db
      REDMINE_DB_USERNAME: redmine
      REDMINE_DB_PASSWORD: "MDP" 
      REDMINE_DB_DATABASE: redmine_db
    volumes:
      - ./redmine_files:/usr/src/redmine/files
    depends_on:
      - db

pour lancer le docker :

docker compose up -d
exit

fixer l'ip :

incus config device override redmine eth0 ipv4.address=10.218.184.3

pour appliquer :

incus restart redmine

accessible à :
http://redmine.192.168.23.3.nip.io/



Pour NextCloud :
Installation de la vm nextcloud :
sudo incus launch images:debian/13 nextcloud --vm

On installe Apache, MariaDB, PHP et tous les modules requis par Nextcloud :

sudo apt install apache2 mariadb-server php libapache2-mod-php php-mysql php-xml php-mbstring php-zip php-gd php-curl php-intl php-bcmath php-gmp php-imagick php-redis unzip wget -y

On ouvre la console MariaDB pour créer la base de données et l'utilisateur :

sudo mysql -u root
CREATE DATABASE nextcloud;
USE nextcloud;
CREATE USER 'nc_user'@'localhost' IDENTIFIED BY 'MotDePasse';
GRANT ALL PRIVILEGES ON nextcloud.* TO 'nc_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;

On télécharge la dernière version de nextcloud et on l'extrait et on la place dans le dossier d'Apache :

wget https://download.nextcloud.com/server/releases/latest.zip
unzip latest.zip
sudo mv nextcloud /var/www/html/

On donne au serveur web Apache les droits sur le dossier Nextcloud pour qu'il puisse le lire et le modifier :

sudo chown -R www-data:www-data /var/www/html/nextcloud/
sudo chmod -R 755 /var/www/html/nextcloud/

On créer un VirtualHost :

vi /etc/apache2/sites-enabled/nextcloud.conf 
rm /etc/apache2/sites-enabled/000-default.conf 

<VirtualHost *:80>
    DocumentRoot /var/www/html/nextcloud/

    <Directory /var/www/html/nextcloud/>
        Require all granted
        AllowOverride All
        Options FollowSymLinks MultiViews

        <IfModule mod_dav.c>
            Dav off
        </IfModule>

        SetEnv HOME /var/www/html/nextcloud
        SetEnv HTTP_HOME /var/www/html/nextcloud
    </Directory>

    <IfModule mod_rewrite.c>
        RewriteEngine on
        RewriteRule ^/\.well-known/carddav /remote.php/dav/ [R=301,L]
        RewriteRule ^/\.well-known/caldav  /remote.php/dav/ [R=301,L]
        RewriteRule ^/\.well-known/webfinger /index.php/.well-known/webfinger [R=301,L]
        RewriteRule ^/\.well-known/nodeinfo  /index.php/.well-known/nodeinfo  [R=301,L]
    </IfModule>

    ErrorLog ${APACHE_LOG_DIR}/nextcloud_error.log
    CustomLog ${APACHE_LOG_DIR}/nextcloud_access.log combined
</VirtualHost>

On restart Apache :

sudo systemctl restart apache2

fixer l'ip :

incus config device override nextcloud eth0 ipv4.address=10.218.184.2

pour appliquer :

incus restart nextcloud

accessible à :
http://nextcloud.192.168.23.3.nip.io/



Pour site web Laravel :
pour créer la VM sous debian13
sudo incus launch images:debian/13 laravel-web --vm

fixer l'ip :

sudo incus config device override laravel-web eth0 ipv4.address=10.218.184.5

pour appliquer :

sudo incus restart laravel-web

pour acceder au shell de la VM :

sudo incus shell laravel-web

installer les fonctionnalités pour Laravel :

apt update && apt upgrade -y
apt install -y composer git unzip curl \
    php-xml php-mbstring php-curl php-zip php-bcmath php-intl

pour config le serveur :

vi /etc/nginx/sites-available/laravel

source
server {
    listen 80;
    listen [::]:80;
    server_name example.com;
    root /srv/example.com/public;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-Content-Type-Options "nosniff";

    index index.php;

    charset utf-8;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    error_page 404 /index.php;

    location ~ ^/index\.php(/|$) {
        fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_hide_header X-Powered-By;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}

pour activer le site et redémarrer Nginx :

ln -s /etc/nginx/sites-available/laravel /etc/nginx/sites-enabled/
systemctl restart nginx

accessible à :
http://laravel.192.168.23.3.nip.io/



Pour Git Forgejo :
Installation de la vm git :
sudo incus launch images:debian/13 git --vm

Installer les paquets requis Git et SQLite3 :

sudo apt install git git-lfs wget tar sqlite3 -y

Pour isoler et sécuriser le service créer un utilisateur système dédié nommé forgejo :

sudo adduser \
   --system \
   --shell /bin/bash \
   --gecos 'Forgejo Git Service' \
   --group \
   --disabled-password \
   --home /var/lib/forgejo \
   forgejo

Récupérer la version de Forgejo puis placer le binaire exécutable dans le dossier système approprié :

wget -O forgejo https://codeberg.org/forgejo/forgejo/releases/download/v14.0.3/forgejo-14.0.3-linux-amd64
sudo chmod +x forgejo
sudo mv forgejo /usr/local/bin/forgejo

Créer les dossiers où Forgejo stockera ses dépôts, ses configurations et ses logs, et donne la propriété à l'utilisateur forgejo :

sudo mkdir -p /var/lib/forgejo/{custom,data,log}
sudo chown -R forgejo:forgejo /var/lib/forgejo/
sudo chmod -R 750 /var/lib/forgejo/

sudo mkdir /etc/forgejo
sudo chown root:forgejo /etc/forgejo
sudo chmod 770 /etc/forgejo

Pour automatiser le démarrage de Forgejo :

sudo vi /etc/systemd/system/forgejo.service

[Unit]
Description=Forgejo (Self-Hosted Git Service)
After=network.target

[Service]
RestartSec=2s
Type=simple
User=forgejo
Group=forgejo
WorkingDirectory=/var/lib/forgejo
ExecStart=/usr/local/bin/forgejo web --config /etc/forgejo/app.ini
Restart=always
Environment=USER=forgejo HOME=/var/lib/forgejo GITEA_WORK_DIR=/var/lib/forgejo

[Install]
WantedBy=multi-user.target

Rechargez systemd puis activer et démarrer le service Forgejo :

sudo systemctl daemon-reload
sudo systemctl enable --now forgejo
sudo systemctl status forgejo

fixer l'ip :

sudo incus config device override git eth0 ipv4.address=10.218.184.4

pour appliquer :

sudo incus restart git

accessible à :
http://git.192.168.23.3.nip.io/



Pour Vaultwarden :
sudo incus launch images:debian/13 vaultwarden

sudo incus shell vaultwarden
apt update && apt install -y docker.io docker-compose
mkdir -p /opt/vaultwarden
cd /opt/vaultwarden
vi docker-compose.yml
services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: always
    environment:
      - WEBSOCKET_ENABLED=true
      - ADMIN_TOKEN="Fablab" 
    ports:
      - "80:80" 
    volumes:
      - ./vw-data:/data
docker compose up -d

fixer l'ip :

sudo incus config device override vaultwarden eth0 ipv4.address=10.218.184.6

pour appliquer :

sudo incus restart vaultwarden

accessible à :
http://vaultwarden.192.168.23.3.nip.io/



Pour Caddy :
apt install caddy

sudo vi /etc/caddy/Caddyfile
http://nextcloud.192.168.23.3.nip.io {
    tls internal
    reverse_proxy 10.218.184.2:80
}

http://redmine.192.168.23.3.nip.io {
    tls internal
    reverse_proxy 10.218.184.3:80
}

http://git.192.168.23.3.nip.io {
    tls internal
    reverse_proxy 10.218.184.4:3000
}

http://laravel.192.168.23.3.nip.io {
    tls internal
    reverse_proxy 10.218.184.5:80
}

http://vaultwarden.192.168.23.3.nip.io {
    tls internal
    reverse_proxy 10.218.184.6:80
}
sudo systemctl restart caddy

On a aussi modifié le tableau et le schema pour ajouté vaultwarden :

Service RAM minimum RAM recommandée CPU minimum CPU recommandé
Apache 512 MB 1–4 GB 1 core 2+ cores
Nginx 512 MB 1 GB 1 core 2+ cores
Laravel 1 GB 2+ GB 1 core 2+ cores
Redmine 512 MB 1–2 GB 1 core 2 cores
Forgejo 512 MB 2 GB 1 core 2+ cores
Nextcloud 512 MB 2 GB 1 core 2 cores
vaultwarden 512 MB 1 GB 1 core 2 cores
total 4 GB 10-14+ GB 7 cores 14+ cores
SCHEMA

Jour 9: Jeudi 09 avril 2026

Pour projet Routeur PinePhone WireGuard :
https://www.reddit.com/r/WireGuard/comments/18pee5i/wireguard_working_as_server_and_client_of_another/

Photo schema

Pour le projet serveur, on a maintenant accès en SSH et on fait la migration vers le nouveau serveur dans les VMs/conteneurs d'Incus.

Le travail est réparti entre le jeudi 9 et le vendredi 10, mais nous l’avons écrit dans la partie du vendredi 10.

Jour 10: Vendredi 10 avril 2026

Migration du site en Laravel sur le serveur incus :

sur le serveur cohabit :
tar -czvf ~/migration-complete.tar.gz \
  /etc/apache2 \
  /var/www/html

sur le serveur toutatis :
scp -P 55555 nicolas@projets.cohabit.fr:~/migration-complete.tar.gz ~/
scp -P 55555 nicolas@projets.cohabit.fr:/opt/laravel/laravel ~/

sudo incus file push -r ~/migration-complete.tar.gz laravel-web/
sudo incus file push -r ~/laravel laravel-web/
sudo incus shell laravel-web

Sur le conteneur laravel-web :
apt update
apt install -y apache2 postgresql postgresql-contrib git unzip curl php8.4 libapache2-mod-php8.4 php8.4-cli php8.4-pgsql php8.4-ldap php8.4-readline php8.4-opcache

mkdir /temp-web
tar -xzvf /migration-complete.tar.gz -C /temp-web
cp -r /temp-web/etc /.
cp -r /temp-web/var /.

mkdir /opt/laravel
cp -r /laravel /opt/laravel/.

a2enmod rewrite ssl proxy proxy_http headers

chown -R www-data:www-data /var/www/html
chown -R www-data:www-data /opt/laravel/laravel
chmod -R 775 /opt/laravel/laravel/storage
chmod -R 775 /opt/laravel/laravel/bootstrap/cache

systemctl restart apache2

mais j'ai cette erreur :
root@laravel-web:~# systemctl restart apache2
Job for apache2.service failed because the control process exited with error code.
See "systemctl status apache2.service" and "journalctl -xeu apache2.service" for details.

donc pour voir en détaille ce qui pose problème :
root@laravel-web:~# apache2ctl configtest
apache2: Syntax error on line 145 of /etc/apache2/apache2.conf: Syntax error on line 1 of /etc/apache2/mods-enabled/passenger.load: Cannot load /usr/lib/apache2/modules/mod_passenger.so into server: /usr/lib/apache2/modules/mod_passenger.so: cannot open shared object file: No such file or directory

Pour dire à Apache d'arrêter d'essayer de charger le module :
a2dismod passenger

mais :
apache2: Syntax error on line 145 of /etc/apache2/apache2.conf: Syntax error on line 3 of /etc/apache2/mods-enabled/php8.2.load: Cannot load /usr/lib/apache2/modules/libphp8.2.so into server: /usr/lib/apache2/modules/libphp8.2.so: cannot open shared object file: No such file or directory

donc on désactive le module PHP8.2 :
a2dismod php8.2

puis on active le bon module (PHP8.4) :
a2enmod php8.4

mais :
root@laravel-web:~# apache2ctl configtest
AH00526: Syntax error on line 17 of /etc/apache2/sites-enabled/forgejo-ssl.conf:
SSLCertificateFile: file '/cert/live/git.cohabit.fr/fullchain.pem' does not exist or is empty

il bloque car il y a un certificat SSL introuvable (de la conf de forgejo) donc :
a2dissite forgejo-ssl.conf

mais :
root@laravel-web:~# apache2ctl configtest
AH00526: Syntax error on line 9 of /etc/apache2/sites-enabled/laravel_accueil.conf:
SSLCertificateFile: file '/cert/live/cohabit.fr/fullchain.pem' does not exist or is empty

dans le fichier de configuration du site, il y a encore des traces de certificats ssl donc :
vi /etc/apache2/sites-enabled/laravel_accueil.conf

et on a mis en commentaire la partie pour les ssl :
#    SSLCertificateFile /cert/live/cohabit.fr/fullchain.pem
#    SSLCertificateKeyFile /cert/live/cohabit.fr/key.pem
...
#    SSLCertificateFile /cert/live/www.cohabit.fr/fullchain.pem
#    SSLCertificateKeyFile /cert/live/www.cohabit.fr/key.pem

et on a commenté la partie non commentée pour mettre cette config à la place (temporairement) dans le master.conf :
<VirtualHost *:80>
    DocumentRoot /opt/laravel/laravel/public/

    <Directory /opt/laravel/laravel/public/>
      AllowOverride All
      Require all granted
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/laravel_accueil_error.log
    CustomLog ${APACHE_LOG_DIR}/laravel_accueil_access.log combined
</VirtualHost>

systemctl reload apache2

mais :
root@laravel-web:~# apache2ctl configtest
AH00526: Syntax error on line 21 of /etc/apache2/sites-enabled/redmine-ssl.conf:
Invalid command 'RailsEnv', perhaps misspelled or defined by a module not included in the server configuration

meme chose que fogejo mais cette fois avec redmine donc :
a2dissite redmine-ssl.conf

mais sur la page web il y a :
Code d’erreur : SSL_ERROR_INTERNAL_ERROR_ALERT

Pour le moment, on va juste dire à caddy de ne pas demander de certificat, donc on ajoute (tls internal) sur le serveur toutatis donc :
exit
sudo vi /etc/caddy/Caddyfile 

laravel.192.168.23.3.nip.io {
    tls internal
    reverse_proxy 10.218.184.5:80
}

sudo systemctl restart caddy

mais sur la page du site :
La page n’est pas redirigée correctement
Firefox a détecté que le serveur redirige la demande pour cette adresse d’une manière qui n’aboutira pas.

La cause de ce problème peut être la désactivation ou le refus des cookies.

Par defaut laravel ne fait pas confiance à l'adresse ip de caddy donc pour qu'elle accepte toutes les requêtes du réseau local (Caddy) : 
vi /opt/laravel/laravel/app/Http/Middleware/TrustProxies.php
on modifie la ligne protected $proxies; pour mettre cela :
protected $proxies = '*';

cd /opt/laravel/laravel
php artisan optimize:clear

mais sur la page web "could not find driver" :
apt install -y php-pgsql
systemctl restart apache2

mais :
SQLSTATE[08006] [7] connection to server at "127.0.0.1", port 5432 failed: Connection refused Is the server running on that host and accepting TCP/IP connections?
donc :
apt install -y postgresql postgresql-contrib
sudo -u postgres psql
CREATE USER laravel WITH PASSWORD 'MDP';
CREATE DATABASE laravel OWNER laravel;
\q

maintenant il faut importer les données de l'ancien serveur dans la nouvelle base :
sur le serveur cohabit :
sudo -u postgres pg_dump laravel > /tmp/sauvegarde_laravel.sql

sur le serveur toutatis :
scp -P 55555 nicolas@projet.cohabit.fr:/tmp/sauvegarde_laravel.sql ~/.
incus file push ~/sauvegarde_laravel.sql laravel-web/

puis sur le conteneur laravel-web :
sudo -u postgres psql laravel -f /sauvegarde_laravel.sql

ou

sur le serveur cohabit :
echo '127.0.0.1:5432:laravel:laravel:MDP' > ~/.pgpass chmod 600 ~/.pgpass pg_dump -U laravel -h 127.0.0.1 -p 5432 -Fc --file=/tmp/laravel_db.dump laravel

sur le serveur toutatis :
scp -P 55555 nicolas@projet.cohabit.fr:/laravel_db.dump ~/.
incus file push ~/laravel_db.dump laravel-web/

puis sur le conteneur laravel-web :
pg_restore -U laravel -h localhost -d laravel --verbose --clean /laravel_db.dump

et c'est bon le site fonctionne.

Jour 11: Lundi 13 avril 2026

Jour 12: Mardi 14 avril 2026

Jour 13: Mercredi 15 avril 2026

Jour 14: Jeudi 16 avril 2026

Jour 15: Vendredi 17 avril 2026

Jour 16: Lundi 20 avril 2026

On a agrandi le / car c'est là où on stocke tout et on avait 900 Go sans partition

Partition Taille Point de montage Rôle
1 512 Mo /boot/efi Pour le démarrer en UEFI
2 2 Go /boot Pour le boot Debian
3 2 Go swap Sécurité si la RAM sature
4 ~995 Go / (Racine) La Debian de l'hyperviseur, Incus et logs

1. Voir l'espace disque actuel

df -h /

1. Voir la structure des disques et du RAID

lsblk -f

1. Voir les partitions exactes

sudo fdisk -l /dev/sda
sudo fdisk -l /dev/sdb

2. Agrandir la partition sda4

sudo fdisk /dev/sda

Dans fdisk, taper dans l'ordre :

d          # Supprimer une partition
4          # Numéro de partition 4
n          # Créer une nouvelle partition
4          # Numéro de partition 4
8814592    # Secteur de début
           # Entrée vide = utiliser tout l'espace restant
N          # Ne PAS supprimer la signature linux_raid_member
t          # Changer le type
4          # Partition 4
linux-raid # Type RAID Linux
w          # Écrire et quitter

3. Agrandir la partition sdb4

sudo fdisk /dev/sdb

Dans fdisk, taper dans l'ordre :

d          # Supprimer une partition
4          # Numéro de partition 4
n          # Créer une nouvelle partition
4          # Numéro de partition 4
8814592    # Secteur de début
           # Entrée vide = utiliser tout l'espace restant
N          # Ne PAS supprimer la signature linux_raid_member
t          # Changer le type
4          # Partition 4
linux-raid # Type RAID Linux
w          # Écrire et quitter

4. Vérifier que les deux partitions sont bien en "RAID Linux"

sudo fdisk -l /dev/sda | grep sda4
sudo fdisk -l /dev/sdb | grep sdb4

Les deux doivent afficher "RAID Linux" et ~927 Go

5. Agrandir le volume RAID mdadm

sudo mdadm --grow /dev/md4 --size=max

6. Surveiller la resynchronisation

watch cat /proc/mdstat

La synchronisation est terminée quand on voit :

md4 : active raid1 sda4[0] sdb4[2]
      972320768 blocks super 1.2 [2/2] [UU]

7. Étendre le système de fichiers ext4

sudo resize2fs /dev/md4

8. Vérifier le résultat final

df -h /

____________________________________________________________________________________________________________________
Agrandir l'espace de la vm git
sudo incus config device override git root size=100GiB

Entré dans la VM git

 sudo incus exec git -- bash

Installer gdisk dans la VM

apt install -y gdisk

Agrandir la partition

sgdisk -e /dev/sda
exit

Redémarrer la VM

sudo incus restart git

Agrandir le filesystem

sudo incus exec git -- bash
resize2fs /dev/sda2

Vérifier

df -h /

____________________________________________________________________________________________________________________
Transféré les données de l'ancien git vers le nouveau

Depuis l'ancien serveur
sudo zip -r ~/forgejo-repos.zip /var/lib/forgejo/data

Depuis le nouveau serveur

scp -P 55555 yoan@projets.cohabit.fr:~/forgejo-repos.zip ~/
sudo incus file push ~/forgejo-repos.zip git/root/forgejo-repos.zip
sudo incus exec git -- bash

Dans la VM git

Ne pas remplacer les fichiers du dossier indexés et queues

unzip /root/forgejo-repos.zip -d /
chown -R forgejo:forgejo /var/lib/forgejo/
systemctl restart forgejo
systemctl status forgejo

Pour sauvegarde :
https://www.perplexity.ai/search/j-ai-un-serveur-incus-et-j-ai-s9CK1LPMTfWnGEQLP74P6A

Jour 17: Mardi 21 avril 2026

Nous avons continué à faire le transférer entre les deux serveurs et nous avons changé/Maj la partie de laravel-web (voir vendredi 10 avril 2026)

et nous avons aussi fait Zotero :

sudo incus launch images:debian/13 zotero
sudo incus shell zotero
apt update && apt install -y docker.io docker-compose
docker run -d \
  --name=zotero \
  -e PUID=1000 \
  -e PGID=1000 \
  -e TZ=Etc/UTC \
  -p 3000:3000 \
  -p 3001:3001 \
  -v /path/to/config:/config \
  --shm-size="1gb" \
  --restart unless-stopped \
  lscr.io/linuxserver/zotero:latest

fixer l'ip :

sudo incus config device override zotero eth0 ipv4.address=10.218.184.7

Pour appliquer :

sudo incus restart zotero

Rajouter Zotero dans caddy

sudo vi /etc/caddy/Caddyfile
zotero.192.168.23.3.nip.io {
    tls internal
    reverse_proxy 10.218.184.7:3000
}
sudo systemctl restart caddy

Accéder à Zotero
http://zotero.192.168.23.3.nip.io

Redmine et en cours.

Jour 18: Mercredi 22 avril 2026

Composant Serveur cohabit Serveur laravel-web Commande pour vérifier
Apache2 2.4.66 2.4.66 apachectl -v
PHP 8.4.15 8.4.16 php -v
PostgreSQL 17.4 17.9 psql --version
Laravel 9.52.10 9.52.10 php artisan --version
Modules PHP (php -m) [PHP Modules] calendar Core ctype date exif FFI fileinfo filter ftp gettext hash iconv json ldap libxml openssl pcntl pcre PDO pdo_pgsql pgsql Phar posix random readline Reflection session shmop sockets sodium SPL standard sysvmsg sysvsem sysvshm tokenizer Zend OPcache zlib [Zend Modules] Zend OPcache [PHP Modules] calendar Core ctype date exif FFI fileinfo filter ftp gettext hash iconv json ldap libxml openssl pcntl pcre PDO pdo_pgsql pgsql Phar posix random readline Reflection session shmop sockets sodium SPL standard sysvmsg sysvsem sysvshm tokenizer Zend OPcache zlib [Zend Modules] Zend OPcache php -m

Jour 19: Jeudi 23 avril 2026

https://github.com/orgs/suitenumerique/repositories
Nous avons fait des recherches sur LaSuiteNumérique pour peut-être l'utiliser et du coup le mettre en place à la place de Nextcloud et aussi utiliser d'autre fonctionnalité.

Et pour Redmine, nous avons essayé de passer directement sur une version récente, mais on n'a pas réussi donc demain, on va juste essayer de l'importer sans changer de version.

Jour 20: Vendredi 24 avril 2026

Migration de Redmine sur le serveur incus (en gardant la même version)

Initialisation de la vm :

sudo incus init images:debian/13 redmine --vm
sudo incus config device override redmine root size=100GiB
sudo incus config device override redmine eth0 ipv4.address=10.218.184.3
sudo incus start redmine
sudo incus file push -r ~/redmine_db.dump redmine2/root/
sudo incus file push -r ~/migration-opt-redmine.tar.gz redmine2/root/
sudo incus shell redmine

Installation des dépendances système

apt update && apt upgrade -y
apt install -y ruby ruby-dev ruby-psych postgresql postgresql-contrib \
  postgresql-server-dev-all libxslt1-dev libyaml-dev libxml2-dev \
  libpq-dev libcurl4-openssl-dev zlib1g-dev apache2 apache2-dev \
  gcc g++ make patch imagemagick git

Création de la base de données PostgreSQL

su - postgres
createuser redmine
createdb redmine -O redmine
set +H
psql -c "ALTER USER redmine WITH PASSWORD 'MDP';" 
set -H
exit

Configuration de pg_hba.conf

vi /etc/postgresql/17/main/pg_hba.conf
local   all   all                  md5
host    all   all   127.0.0.1/32   md5
host    all   all   ::1/128        md5
systemctl restart postgresql

Restauration de la base de donnée

cp /root/redmine_db.dump /tmp/redmine_db.dump
su - postgres
dropdb redmine
createdb redmine -O redmine
pg_restore --no-owner -x -d redmine /tmp/redmine_db.dump
exit

Donner les droits à l'utilisateur redmine :

su - postgres
psql -d redmine
sql
GRANT ALL ON SCHEMA public TO redmine;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO redmine;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO redmine;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO redmine;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO redmine;
ALTER DATABASE redmine OWNER TO redmine;
\q
exit

Déploiement des fichiers Redmine

mkdir /var/www/redmine
tar -xzvf migration-opt-redmine.tar.gz -C /

Installation des gems Ruby

cd /opt/redmine/redmine-5.0.5/
vi Gemfile
Avant : ruby ">= 2.5.0", "< 3.2.0" 
Après : ruby ">= 2.5.0", "< 3.4.0" 

Avant : gem "pg", "~> 1.2.2", :platforms => [:mri, :mingw, :x64_mingw]
Après : gem "pg", "~> 1.5.0", :platforms => [:mri, :mingw, :x64_mingw]
gem install bundler
bundle config set --local without 'development test mysql sqlite'
bundle update pg
bundle install

Initialisation de Redmine

bundle exec rake generate_secret_token RAILS_ENV=production
bundle exec rake db:migrate RAILS_ENV=production
bundle exec rake redmine:plugins:migrate RAILS_ENV=production

Permissions des fichiers

chown -R www-data:www-data /opt/redmine/redmine-5.0.5
chmod -R 755 /opt/redmine/redmine-5.0.5
chmod -R 777 /opt/redmine/redmine-5.0.5/tmp \
             /opt/redmine/redmine-5.0.5/log \
             /opt/redmine/redmine-5.0.5/files \
             /opt/redmine/redmine-5.0.5/public/plugin_assets

Installation de Passenger via APT

apt install -y dirmngr gnupg apt-transport-https ca-certificates

curl https://oss-binaries.phusionpassenger.com/auto-software-signing-gpg-key.txt | \
  gpg --dearmor | tee /etc/apt/trusted.gpg.d/phusion.gpg > /dev/null

echo "deb https://oss-binaries.phusionpassenger.com/apt/passenger bookworm main" > \
  /etc/apt/sources.list.d/passenger.list

apt update
apt install -y libapache2-mod-passenger
a2enmod passenger
systemctl restart apache2

Configuration Apache

vi /etc/apache2/sites-enabled/redmine.conf
<VirtualHost *:80>
   DocumentRoot /opt/redmine/redmine-5.0.5/public
   PassengerRuby /usr/bin/ruby3.3

   <Directory /opt/redmine/redmine-5.0.5/public>
      Options FollowSymLinks
      AllowOverride All
      Require all granted
   </Directory>

   ErrorLog ${APACHE_LOG_DIR}/redmine_error.log
   CustomLog ${APACHE_LOG_DIR}/redmine_access.log combined
</VirtualHost>
a2ensite redmine
rm /etc/apache2/sites-enabled/default-ssl.conf
rm /etc/apache2/sites-enabled/000-default.conf
systemctl restart apache2
exit

Configuration de caddy

sudo vi /etc/caddy/Caddyfile
redmine.192.168.23.3.nip.io {
    tls internal
    reverse_proxy 10.218.184.3:80
}
systemctl restart caddy

Accéder au site :

https://redmine.192.168.23.3.nip.io/

Jour 21: Lundi 27 avril 2026

Nous avons upgradé redmine 5.0.5 vers redmine 5.1.12 et on a aussi un autre conteneur redmine pour tester la version 6.1.2, mais elle n'est pas totalement compatible.

On a deux conteneurs incus que l'on a renommé ainsi :
redmine-6-1-2
redmine-5-1-12

et on a créé des snapshots pour chacune :
pour redmine-6-1-2 elle s'appelle redmine-6-1-2
et pour redmine-5-1-12 elles s'appellent redmine-5-1-12 et aussi redmine-5-0-5

Pour le conteneur de redmine-6-1-2, on a donc mis à jour de 5.0.5 vers 6.1.2, mais les thèmes ne sont pas compatibles (icônes devenues noires et énormes), donc on les a retirés pour laisser le thème par défaut et pour les plugins, on a téléchargé des versions compatibles.

Pour le conteneur de redmine-5-1-12, on a donc mis à jour de 5.0.5 vers 5.1.12 (aucun problème de compatibilité) mais on a aussi laissé dessus la version 5.0.5 avec sa snapshot au cas de problème.

Upgrade redmine 5.0.5 vers 5.1.12

Télécharger Redmine

cd /opt/redmine
wget https://www.redmine.org/releases/redmine-5.1.12.tar.gz
tar -xzf redmine-5.1.12.tar.gz

Config base de données

cp redmine-5.0.5/config/database.yml redmine-5.1.12/config/

Config générale

cp redmine-5.0.5/config/configuration.yml redmine-5.1.12/config/

Fichiers des projets

cp -r redmine-5.0.5/files/* redmine-5.1.12/files/

Plugins

cp -r redmine-5.0.5/plugins/* redmine-5.1.12/plugins/

Thèmes

cp -r redmine-5.0.5/public/themes/* redmine-5.1.12/public/themes/

Modifier L'utilisateur pour le nouveau Redmine

sudo -u postgres psql -d redmine

Changer le propriétaire de toutes les tables

DO $$
DECLARE obj RECORD;
BEGIN
  FOR obj IN SELECT tablename FROM pg_tables WHERE schemaname = 'public' LOOP
    EXECUTE 'ALTER TABLE public.' || quote_ident(obj.tablename) || ' OWNER TO redmine';
  END LOOP;
END $$;

Changer le propriétaire de toutes les séquences

DO $$
DECLARE obj RECORD;
BEGIN
  FOR obj IN SELECT sequence_name FROM information_schema.sequences WHERE sequence_schema = 'public' LOOP
    EXECUTE 'ALTER SEQUENCE public.' || quote_ident(obj.sequence_name) || ' OWNER TO redmine';
  END LOOP;
END $$;
GRANT ALL ON SCHEMA public TO redmine;
\q

Installer Redmine

cd /opt/redmine/redmine-5.1.12
bundle config set --local without 'development test'
bundle install
bundle exec rake db:migrate RAILS_ENV=production

Régénérer le token secret

bundle exec rake generate_secret_token RAILS_ENV=production

Migration des plugins

bundle exec rake redmine:plugins:migrate RAILS_ENV=production

Recompiler les assets

bundle exec rake assets:clobber RAILS_ENV=production
bundle exec rake assets:precompile RAILS_ENV=production

Appliquer les permissions

chown -R www-data:www-data /opt/redmine/redmine-5.1.12
vi /etc/apache2/sites-enable/redmine.conf
<VirtualHost *:80>
   DocumentRoot /opt/redmine/redmine-5.1.12/public
   PassengerRuby /usr/bin/ruby3.3

   <Directory /opt/redmine/redmine-5.1.12/public>
      Options FollowSymLinks
      AllowOverride All
      Require all granted
   </Directory>

   ErrorLog ${APACHE_LOG_DIR}/redmine_error.log
   CustomLog ${APACHE_LOG_DIR}/redmine_access.log combined
</VirtualHost>

Redémarrer le service

systemctl restart apache2

Upgrade redmine 5.0.5 vers 6.1.2
C'est à peu près pareil qu'au-dessus, mais à la place d'écrire 5.1.12, c'est 6.1.2, mais il y a un problème avec cette version, car les thèmes et les plugins ne sont pas compatibles donc pour ces parties-là, nous avons fait :

Pour les thèmes :
Nous avons déplacé le dossier /public/themes/ à la racine du dossier Redmine parce que dans les versions 6.X, c'est comme ça donc /themes/ et on a regardé le résultat, mais les icônes sont surdimensionnées et toute noires donc comme nous ne savons pas régler cela, on a laissé le thème par défaut de Redmine.

Pour les plugins :
On a pu trouver les dernières versions compatibles pour les plugins redmine_wiki_extensions et redmine_agile mais pas pour redmine_embedded_video donc on l'a laissé, mais il ne fonctionne pas (nous avons l'impression que les plugins ne change rien au Redmine comme s'ils n'étaient pas chargés alors qu'ils le sont bien).

Migration des plugins

bundle exec rake redmine:plugins:migrate RAILS_ENV=production

Nous avons aussi envoyé un message à Alexander et Pierre :

Voici une mise à jour sur l'avancement de nos déploiements. Nous avons mis en place Vaultwarden et Zotero en conteneurs, alors que Git-Forgejo, Nextcloud, Redmine et Laravel sont installés sur des VMs. 
Si vous préférez qu'un de ces services soit sur une VM plutôt qu'un conteneur ou inversement, dites-le-nous et nous ferons la modification.

Nous avions également quelques questions pour la suite :

Pour Laravel, vous pouvez consulter la liste des services installés sur le conteneur et comparer leurs versions avec celles du serveur Cohabit actuel sur ce lien : https://projets.cohabit.fr/redmine/projects/accueil/wiki/Nicolas_Schmauch_2#Jour-18-Mercredi-22-avril-2026. 
Souhaitez-vous que nous procédions à leur mise à jour ?

Pour Redmine, nous avons déjà installé trois versions différentes pour faire des tests. Il ne nous reste plus qu'à choisir laquelle conserver.
Laquelle préférez-vous garder ?

- La version actuelle du serveur Cohabit (5.0.5).
- Une version mise à jour compatible (5.1.12).
- Une version plus récente, mais moins compatible (6.1.2).

Vous trouverez plus de détails sur ces versions de Redmine ici : https://projets.cohabit.fr/redmine/projects/accueil/wiki/Nicolas_Schmauch_2#Jour-21-Lundi-27-avril-2026.

Enfin, nous avons découvert Watchtower, un outil qui permet de maintenir automatiquement à jour les conteneurs Docker. Le projet officiel ayant été archivé (https://github.com/containrrr/watchtower), nous avons trouvé un fork qui est toujours maintenu (https://github.com/nicholas-fedor/watchtower/ https://watchtower.nickfedor.com/v1.16.1/). 
Si cette automatisation vous intéresse, nous pouvons vous la mettre en place.

Jour 22: Mardi 28 avril 2026

On nous a répondu :

Salut, automatiser la montée de version je ne suis pas sûr mais à voir pourquoi pas, en revanche surveiller et prévenir/alerter ça oui à 100% à minima.

Pour redmine le meilleur conseil que j'ai à donner c'est de ne jamais faire la montée de version sur le serveur actuel ou vous allez avoir des envies bien sombres derrière.

La version pleinement compatible semble déjà un bon compromis pour le moment, à la limite si vous trouvez d'autres thèmes sympa on peut regarder pour les mettre ou modifier aux couleurs du fablab

En revanche je vais faire l'embetant, je pourrais avoir en plus un petit graph de l'infra vm/kube avec les différents points d'entrée ? (Ou plus tard si vous voulez, si vous comptez encore modifier des choses après ces premiers tests :) )

Donc, nous avons demandé :

Nous pensons à automatiser la montée de version, surtout pour Vaultwarden, car souvent les applications et extensions Bitwarden refusent la connexion à un serveur Vaultwarden qui n'est pas à jour. Donc pourquoi pas mettre watchtower juste pour vaultwarden.

Il nous a dit :

Ça fait des Snapshot automatiquement avant la montée de version ? Si oui ça serait parfait du coup et possible de l'appliquer aux autres

Comme Watchtower ne permet pas de faire de snapshot, nous avons donc convenu de faire un script :

Non, Watchtower ne fait pas de snapshot automatique avant la mise à jour.
Le plus propre pour nous, ce serait un script côté Incus et quand une nouvelle version est disponible, il crée un snapshot (la nuit), lance la mise à jour (la nuit), puis envoie une alerte (les snapshots sont gérés avec incus).

On a fait un nouveau schéma du serveur pour répondre à Alexander.
Schéma serveur

Pour la recherche de theme compatible avec la version de Redmine-6.1.2, nous avons vu que le theme utilisé pour la version de Redmine-5.0.5 etait PurpleMine2 et on a trouvé Opale (qui un fork de PurpleMine2, car il est devenu "archived").

Le problème, c'est que le thème Opale n'a pas de "dark mode" donc il faut le modifier pour pouvoir en avoir un et aussi changer les couleurs du thème pour correspondre à l'ancien (le vert du fablab).

Commande pour changer le thème :

RAILS_ENV=production bundle exec rails console
Setting.ui_theme = 'opale-1.6.7'
exit

Pour appliquer :

bundle exec rake assets:precompile RAILS_ENV=production
systemctl restart apache2

https://github.com/fraoustin/redmine_dark
https://www.redmine.org/plugins/redmine_dark
https://github.com/gagnieray/opale
https://www.redmine.org/projects/redmine/wiki/Theme_List

Jour 23: Mercredi 29 avril 2026

On a mis à jour le schéma du serveur pour répondre à la demande d'Alexander :

Schéma serveur

Nous avons fait des ajustements sur le thème Opale pour le rendre sombre et vert comme l'ancien thème sur le Redmine-5.0.5 et nous avons aussi commencé à faire un script pour maintenir le serveur à jour tout en créant avant les mises à jour, des snapshots pour chaque service (puis plus tard des alertes, mais pas encore).

Jour 24: Jeudi 30 avril 2026

Début de script pour les snapshot :

#!/bin/bash
# ============================================================
# incus-auto-update.sh
# Snapshot + Mise à jour automatique des instances Incus
# Conteneurs (CT) : Docker Compose pull & up
# Machines Virtuelles (VM) : apt update & full-upgrade
# ============================================================

# ─── CONFIGURATION ──────────────────────────────────────────
# Durée de rétention des snapshots avant suppression automatique
SNAPSHOT_EXPIRY="7d" 

# Répertoire de logs
LOG_DIR="/var/log/incus-updates" 
LOG_FILE="${LOG_DIR}/update-$(date +%Y-%m-%d).log" 

# ─── INSTANCES ──────────────────────────────────────────────
# Conteneurs (CT) avec Docker — chemin vers le docker-compose.yml
declare -A CT_COMPOSE_PATHS=(
    ["vaultwarden"]="/opt/vaultwarden" 
    ["zotero"]="/opt/zotero" 
)

# Machines Virtuelles (VM) — mise à jour apt
VMS=("git" "laravel-web" "nextcloud" "redmine-6-1-2")

# ─── FONCTIONS ──────────────────────────────────────────────
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" 
}

send_notification() {
    local message="$1" 
    local payload

    curl -s -X POST -H "Content-Type: application/json" \
         -d "$payload" "$WEBHOOK_URL" > /dev/null 2>&1
}

create_snapshot() {
    local instance="$1" 
    local snap_name="$2" 

    log " Création du snapshot '$snap_name' pour '$instance'..." 
    if incus snapshot create "$instance" "$snap_name" --expiry "$SNAPSHOT_EXPIRY" >> "$LOG_FILE" 2>&1; then
        log " Snapshot '$snap_name' créé (expiration : $SNAPSHOT_EXPIRY)" 
        return 0
    else
        log " Échec de création du snapshot pour '$instance'" 
        return 1
    fi
}

update_container_docker() {
    local instance="$1" 
    local compose_path="$2" 
    local snap_name="$3" 

    log " [$instance] Vérification des mises à jour Docker..." 

    # Récupère les images (pull)
    incus exec "$instance" -- bash -c "cd '$compose_path' && docker compose pull" >> "$LOG_FILE" 2>&1
    PULL_STATUS=$?

    if [ $PULL_STATUS -ne 0 ]; then
        log "  [$instance] Impossible de pull les images Docker. Vérifiez la connexion." 
        send_notification " **[$instance]** Échec du pull Docker. Mise à jour annulée. Snapshot disponible : \`$snap_name\`" 
        return 1
    fi

    # Recrée les conteneurs si une nouvelle image est disponible
    incus exec "$instance" -- bash -c "cd '$compose_path' && docker compose up -d --remove-orphans" >> "$LOG_FILE" 2>&1
    UP_STATUS=$?

    if [ $UP_STATUS -eq 0 ]; then
        log " [$instance] Conteneurs Docker mis à jour avec succès." 
        # Nettoyage des anciennes images inutilisées
        incus exec "$instance" -- bash -c "docker image prune -f" >> "$LOG_FILE" 2>&1
        send_notification " **[$instance]** Mise à jour Docker réussie. Snapshot de secours : \`$snap_name\`" 
    else
        log " [$instance] Échec de la mise à jour Docker." 
        send_notification " **[$instance]** Échec mise à jour Docker.\nRestauration possible avec :\n\`incus restore $instance $snap_name\`" 
    fi

    return $UP_STATUS
}

update_vm_apt() {
    local instance="$1" 
    local snap_name="$2" 

    log " [$instance] Lancement de apt update & full-upgrade..." 

    incus exec "$instance" -- bash -c \
        "apt-get update -qq && apt-get full-upgrade -y && apt-get autoremove -y && apt-get clean" \
        >> "$LOG_FILE" 2>&1
    APT_STATUS=$?

    if [ $APT_STATUS -eq 0 ]; then
        log " [$instance] Paquets mis à jour avec succès." 
        send_notification " **[$instance]** apt full-upgrade réussi. Snapshot de secours : \`$snap_name\`" 
    else
        log " [$instance] Échec de apt full-upgrade." 
        send_notification " **[$instance]** Échec apt full-upgrade.\nRestauration possible avec :\n\`incus restore $instance $snap_name\`" 
    fi

    return $APT_STATUS
}

# ─── MAIN ───────────────────────────────────────────────────
mkdir -p "$LOG_DIR" 
DATE=$(date +%Y-%m-%d_%H-%M)

log "======================================================" 
log " Démarrage des mises à jour automatiques Incus" 
log "======================================================" 

send_notification " **Incus Auto-Update** démarré ($(date '+%d/%m/%Y %H:%M'))" 

# ── Conteneurs Docker (CT) ──────────────────────────────────
for INSTANCE in "${!CT_COMPOSE_PATHS[@]}"; do
    SNAP_NAME="pre-update-${DATE}" 
    COMPOSE_PATH="${CT_COMPOSE_PATHS[$INSTANCE]}" 
    log "" 
    log "── CT : $INSTANCE ──────────────────────────────────" 

    if create_snapshot "$INSTANCE" "$SNAP_NAME"; then
        update_container_docker "$INSTANCE" "$COMPOSE_PATH" "$SNAP_NAME" 
    else
        send_notification " **[$INSTANCE]** Snapshot impossible. Mise à jour annulée par sécurité." 
    fi
done

# ── Machines Virtuelles (VM) ────────────────────────────────
for INSTANCE in "${VMS[@]}"; do
    SNAP_NAME="pre-update-${DATE}" 
    log "" 
    log "── VM : $INSTANCE ──────────────────────────────────" 

    if create_snapshot "$INSTANCE" "$SNAP_NAME"; then
        update_vm_apt "$INSTANCE" "$SNAP_NAME" 
    else
        send_notification " **[$INSTANCE]** Snapshot impossible. Mise à jour annulée par sécurité." 
    fi
done

log "" 
log "======================================================" 
log " Traitement terminé." 
log "======================================================" 
send_notification "**Incus Auto-Update** terminé. Logs : \`$LOG_FILE\`" 

# Nettoyage des logs de plus de 30 jours
find "$LOG_DIR" -name "*.log" -mtime +30 -delete

Jour 25: Vendredi 01 mai 2026

Férié

Jour 26: Lundi 04 mai 2026

Nous avons essayé de modifier le thème en dark mode pour Redmine 6.1.2, mais en analysant plus en détaille, il y a des menus qui ont disparu et d'autre incompatibilité donc nous avons mis à jour le Redmine vers la 5.1.12 qui est compatible (l'ancienne version 5.0.5).

Nous travaillons maintenant sur le script qui permettra de faire des snapshots et mettre à jour les instance d'incus :

Pour Nextcloud :
Pour le mettre à jour :

sudo -u www-data php /var/www/html/nextcloud/updater/updater.phar

Pour pouvoir le mettre à jour, il faut l'installer depuis l'interface graphique sur la page web, mais pour cela, il faut donner les droits et créer la base avec un user :

chown -R www-data:www-data /var/www/html/nextcloud/
chmod -R 750 /var/www/html/nextcloud/config/
chmod 640 /var/www/html/nextcloud/config/config.php

sudo mysql -u root -p

CREATE DATABASE nextcloud CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE USER 'nextcloud'@'localhost' IDENTIFIED BY 'Fablab';
GRANT ALL PRIVILEGES ON nextcloud.* TO 'nextcloud'@'localhost';
FLUSH PRIVILEGES;
EXIT;

Pour connaitre sa version :

sudo -u www-data php /var/www/html/nextcloud/occ status

ou
cat /var/www/html/nextcloud/version.php

Pour Zotero :
Pour le mettre à jour :

docker pull lscr.io/linuxserver/zotero:latest
docker stop zotero
docker rm zotero
docker run -d --name=zotero -e PUID=1000 -e PGID=1000 -e TZ=Etc/UTC -p 3000:3000 -p 3001:3001 -v /opt/zotero:/config --shm-size="1gb" --restart unless-stopped lscr.io/linuxserver/zotero:latest

Pour connaitre sa version :

docker inspect -f '{{ index .Config.Labels "build_version" }}' zotero

Pour Vaultwarden :
Pour le mettre à jour :

cd /opt/vaultwarden && docker compose pull && docker compose up -d

Pour connaitre sa version :

docker exec -it vaultwarden /vaultwarden --version

Pour Laravel et Redmine :
Nous ne mettons que a jour les paquets Apt pour éviter de casser les deux services :

apt update && apt -y full-upgrade && apt autoremove -y && apt clean

Pour Git-Forgejo :
Pour le mettre à jour :

cd /opt/forgejo && docker compose pull && docker compose up -d

Pour connaitre sa version :

docker exec -it forgejo forgejo --version

Admin :
ID : Fablab
MdP : MDP

Et nous avons donc mis à jour le script des snapshots et maj :

#!/bin/bash
# ============================================================
#  incus-auto-update.sh
#  Snapshot automatique + mise à jour de toutes les instances
#  Planifier via crontab : 0 3 * * * /usr/local/bin/incus-auto-update.sh
# ============================================================

DATE=$(date +%Y-%m-%d_%H-%M)
LOG="/var/log/incus-updates.log" 

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG" 
}

update_instance() {
    local INSTANCE=$1
    local CMD=$2
    local SNAP_NAME="pre-update-${DATE}" 

    log "=== Traitement de $INSTANCE ===" 

    # 1. Snapshot avant mise à jour
    log "Création du snapshot $SNAP_NAME..." 
    if incus snapshot create "$INSTANCE" "$SNAP_NAME"; then
        log "Snapshot créé avec succès." 

        # 2. Mise à jour
        log "Lancement de la mise à jour..." 
        if incus exec "$INSTANCE" -- bash -c "$CMD"; then
            log "Mise à jour réussie pour $INSTANCE." 
        else
            log "Échec de la mise à jour pour $INSTANCE." 
            log "Pour restaurer : incus restore $INSTANCE $SNAP_NAME" 
        fi
    else
        log "Échec de création du snapshot pour $INSTANCE. Mise à jour annulée." 
    fi
}

# ============================================================
# Définition des instances et de leurs commandes de mise à jour
# ============================================================

# VM laravel
#update_instance "laravel" "apt-get update && apt-get -y full-upgrade && apt-get autoremove -y && apt-get clean" 

# VM Redmine
#update_instance "redmine" "apt-get update && apt-get -y full-upgrade && apt-get autoremove -y && apt-get clean" 

# VM nextcloud
#update_instance "nextcloud" "apt-get update && apt-get -y full-upgrade && apt-get autoremove -y && apt-get clean && sudo -u www-data php /var/www/html/nextcloud/updater/updater.phar" 

# Conteneur git-forgejo
#update_instance "git" "cd /opt/forgejo && docker compose pull && docker compose up -d" 

# Conteneur vaultwarden
#update_instance "vaultwarden" "cd /opt/vaultwarden && docker compose pull && docker compose up -d" 

# Conteneur zotero
#update_instance "zotero" 'docker pull lscr.io/linuxserver/zotero:latest && if [ "$(docker inspect --format "{{.Id}}" lscr.io/linuxserver/zotero:latest)" != "$(docker inspect --format "{{.Image}}" zotero)" ]; then docker stop zotero && docker rm zotero && docker run -d --name=zotero -e PUID=1000 -e PGID=1000 -e TZ=Etc/UTC -p 3000:3000 -p 3001:3001 -v /opt/zotero:/config --shm-size="1gb" --restart unless-stopped lscr.io/linuxserver/zotero:latest; else echo "Zotero est deja a jour"; fi'

log "=== Toutes les instances ont été traitées. ===" 

Jour 27: Mardi 05 mai 2026

Nous avons recréé git-forgejo mais en conteneur (car c'est plus simple pour le maintenir à jour) :

sudo incus shell git
apt update && apt install -y docker.io docker-compose
mkdir -p /opt/forgejo && cd /opt/forgejo
vi docker-compose.yml

services:
  server:
    image: codeberg.org/forgejo/forgejo:15
    container_name: forgejo
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - FORGEJO__database__DB_TYPE=postgres
      - FORGEJO__database__HOST=db:5432
      - FORGEJO__database__NAME=forgejo
      - FORGEJO__database__USER=forgejo
      - FORGEJO__database__PASSWD=Zo0EiPha
    restart: always
    volumes:
      - ./forgejo-data:/data
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "3000:3000" 
      - "2222:2222" 
    depends_on:
      - db

  db:
    image: postgres:14
    restart: always
    environment:
      - POSTGRES_USER=forgejo
      - POSTGRES_PASSWORD=MDP
      - POSTGRES_DB=forgejo
    volumes:
      - ./postgres-data:/var/lib/postgresql/data
docker compose up -d
docker compose stop server

exit
sudo incus file push forgejo-db.sql git/root/.
sudo incus push forgejo-repos.zip git/root/.
sudo incus push forgejo.zip git/root/.
sudo incus shell git

cp /root/forgejo-db.sql .
cat forgejo-db.sql | docker compose exec -T db psql -U forgejo -d forgejo
cd
unzip forgejo.zip 
unzip forgejo-repos.zip 
mv var/lib/forgejo/data/* /opt/forgejo/forgejo-data/
cd /opt/forgejo/
chown -R 1000:1000 forgejo-data
chown -R 1000:1000 forgejo-data/git/.ssh
docker compose start server
docker compose logs -f server

docker exec -it forgejo forgejo --version

En interface graphique :

Base de données :
Champ Valeur
Type PostgreSQL
Hôte bd:5432
Nom d'utilisateur forgejo
Mot de passe MDP
Nom de base de données forgejo
Configuration générale :
Champ Valeur
Titre du site Forgejo Fablab Cohabit
Emplacement racine des dépôts /data/git/gitea-repositories
Répertoire racine Git LFS /data/git/lfs
Exécuter avec le compte forgejo
Port SSH 2222
Port HTTP 3000
Compte administrateur (en bas de page)
Champ Valeur
Nom d'utilisateur Fablab
Email
Mot de passe MDP

Les maj pour passenger ne passé, car les clés ne sont plus signées donc nous avons fait cela :

curl https://oss-binaries.phusionpassenger.com/auto-software-signing-gpg-key-2025.txt \
  | gpg --dearmor \
  | sudo tee /etc/apt/trusted.gpg.d/phusion.gpg > /dev/null

sudo apt-get update

Nous avons mis à jour le script pour les maj auto avec leurs pre-snapshots :

#!/bin/bash
# ============================================================
#  incus-auto-update.sh
#  Snapshot automatique + mise à jour de toutes les instances
#  Planifier via crontab : 0 1 * * 6 /usr/local/bin/incus-auto-update.sh
# ============================================================

LOG="/var/log/incus-updates.log" 

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG" 
}

# Nettoie une version pour être valide dans un nom de snapshot Incus
# (supprime tout caractère autre que lettres, chiffres, point, tiret, underscore)
sanitize_version() {
    echo "$1" | sed 's/[^a-zA-Z0-9._-]/-/g'
}

# ============================================================
# Récupère la version d'une image Docker dans une instance
# Usage : get_docker_version <instance> <image_name>
# ============================================================
get_docker_version() {
    local INSTANCE=$1
    local IMAGE=$2
    local VERSION

    VERSION=$(incus exec "$INSTANCE" -- docker inspect --format '{{index .Config.Labels "org.opencontainers.image.version"}}' "$IMAGE" 2>/dev/null)

    if [ -z "$VERSION" ]; then
        VERSION=$(incus exec "$INSTANCE" -- docker inspect --format '{{.Config.Image}}' "$IMAGE" 2>/dev/null | awk -F: '{print $2}')
    fi

    if [ -z "$VERSION" ]; then
        VERSION=$(incus exec "$INSTANCE" -- docker inspect --format '{{slice .Id 0 12}}' "$IMAGE" 2>/dev/null)
    fi

    sanitize_version "${VERSION:-unknown}" 
}

# ============================================================
# Récupère la version de Forgejo via commande interne
# Usage : get_forgejo_version <instance>
# ============================================================
get_forgejo_version() {
    local INSTANCE=$1
    local VERSION

    VERSION=$(incus exec "$INSTANCE" -- docker exec forgejo forgejo --version 2>/dev/null \
        | grep -oP '(?<=version )\S+')

    sanitize_version "${VERSION:-unknown}" 
}

# ============================================================
# Récupère la version de Nextcloud via occ
# Usage : get_nextcloud_version <instance>
# ============================================================
get_nextcloud_version() {
    local INSTANCE=$1
    local VERSION

    VERSION=$(incus exec "$INSTANCE" -- sudo -u www-data php /var/www/html/nextcloud/occ status 2>/dev/null \
        | grep "version:" \
        | awk '{print $3}')

    sanitize_version "${VERSION:-unknown}" 
}

# ============================================================
# Fonction principale de mise à jour
# Usage : update_instance <instance> <snap_name> <commande_update>
# ============================================================
update_instance() {
    local INSTANCE=$1
    local SNAP_NAME=$2
    local CMD=$3

    log "=== Traitement de $INSTANCE (snapshot : $SNAP_NAME) ===" 

    # 1. Snapshot avant mise à jour
    log "Création du snapshot $SNAP_NAME..." 
    if incus snapshot create "$INSTANCE" "$SNAP_NAME"; then
        log "Snapshot créé avec succès." 

        # 2. Mise à jour
        log "Lancement de la mise à jour..." 
        if incus exec "$INSTANCE" -- bash -c "$CMD"; then
            log "Mise à jour réussie pour $INSTANCE." 
        else
            log "Échec de la mise à jour pour $INSTANCE." 
            log "Pour restaurer : incus restore $INSTANCE $SNAP_NAME" 
        fi
    else
        log "Échec de création du snapshot pour $INSTANCE. Mise à jour annulée." 
    fi
}

# ============================================================
# Définition des instances et de leurs commandes de mise à jour
# ============================================================

# VM laravel — version fixe
update_instance "laravel" "pre-update_laravel-9.52.10" \
    "apt-get update && apt-get -y full-upgrade && apt-get autoremove -y && apt-get clean" 

# VM redmine — version fixe
update_instance "redmine" "pre-update_redmine-5.1.12" \
    "apt-get update && apt-get -y full-upgrade && apt-get autoremove -y && apt-get clean" 

# VM nextcloud — version récupérée via occ
VERSION=$(get_nextcloud_version "nextcloud")
update_instance "nextcloud" "pre-update_nextcloud-${VERSION}" \
    "apt-get update && apt-get -y full-upgrade && apt-get autoremove -y && apt-get clean && sudo -u www-data php /var/www/html/nextcloud/updater/updater.phar" 

# Conteneur git-forgejo — version via commande interne
VERSION=$(get_forgejo_version "git")
update_instance "git" "pre-update_forgejo-${VERSION}" \
    "cd /opt/forgejo && docker compose pull && docker compose up -d" 

# Conteneur vaultwarden — version de l'image Docker
VERSION=$(get_docker_version "vaultwarden" "vaultwarden/server")
update_instance "vaultwarden" "pre-update_vaultwarden-${VERSION}" \
    "cd /opt/vaultwarden && docker compose pull && docker compose up -d" 

# Conteneur zotero — version de l'image Docker
VERSION=$(get_docker_version "zotero" "lscr.io/linuxserver/zotero")
update_instance "zotero" "pre-update_zotero-${VERSION}" \
    'docker pull lscr.io/linuxserver/zotero:latest && if [ "$(docker inspect --format "{{.Id}}" lscr.io/linuxserver/zotero:latest)" != "$(docker inspect --format "{{.Image}}" zotero)" ]; then docker stop zotero && docker rm zotero && docker run -d --name=zotero -e PUID=1000 -e PGID=1000 -e TZ=Etc/UTC -p 3000:3000 -p 3001:3001 -v /opt/zotero:/config --shm-size="1gb" --restart unless-stopped lscr.io/linuxserver/zotero:latest; else echo "Zotero est deja a jour"; fi'

log "=== Toutes les instances ont été traitées. ===" 

Nous avons aussi créé un script pour les snapshots hebdomadaire :

#!/bin/bash
# /usr/local/bin/incus-snapshot.sh
# Crée un snapshot horodaté sur toutes les VM/CT Incus
# Planifier via crontab : 0 0 * * 6 /usr/local/bin/incus-snapshot.sh

INSTANCES=(git laravel nextcloud redmine vaultwarden zotero)
DATE=$(date +"%Y%m%d-%H%M%S")
LOG="/var/log/incus-snapshot.log" 

echo "[$(date)] === Début des snapshots ===" >> "$LOG" 

for INSTANCE in "${INSTANCES[@]}"; do
    SNAPSHOT_NAME="hebdo-${DATE}" 
    if incus snapshot create "$INSTANCE" "$SNAPSHOT_NAME" --expiry=3w >> "$LOG" 2>&1; then
        echo "[$(date)] $INSTANCE -> $SNAPSHOT_NAME" >> "$LOG" 
    else
        echo "[$(date)] ERREUR sur $INSTANCE" >> "$LOG" 
    fi
done

echo "[$(date)] === Fin des snapshots ===" >> "$LOG" 

Commande crontab :

crontab -e
0 0 * * 6 /usr/local/bin/incus-snapshot.sh
0 1 * * 6 /usr/local/bin/incus-auto-update.sh

Jour 28: Mercredi 06 mai 2026

Pour mettre facilement a jour le conteneur zotero, nous avons créé un fichier docker-compose.yml (comme avec vaultwarden et git-forgejo) :

services:
  zotero:
    image: lscr.io/linuxserver/zotero:latest
    container_name: zotero
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Etc/UTC
    volumes:
      - /opt/zotero:/config
    ports:
      - 3000:3000
      - 3001:3001
    shm_size: "1gb" 
    restart: unless-stopped

Puis, nous avons adapté le script des updates pour retirer les snapshots car avec le script des snapshots hebdomadaire, nous avons déjà des snapshots avant les maj parce que le script des snapshots hebdomadaire sera lancé avant le script des majs (avec crontab) :

#!/bin/bash
# ============================================================
#  incus-update.sh
#  Mise à jour automatique des instances Incus
#  Planifier via crontab : 0 1 * * 6 /usr/local/bin/incus-update.sh
# ============================================================

LOG="/var/log/incus-updates.log" 

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG" 
}

# ============================================================
# Mise à jour d'une instance Docker Compose
# Logique : pull → comparer image locale vs image du conteneur actif
#           si différent → up -d
# ============================================================
update_docker_instance() {
    local INSTANCE=$1
    local IMAGE=$2
    local COMPOSE_DIR=$3
    local CONTAINER_NAME=$4

    log "=== Traitement de $INSTANCE ===" 
    log "Vérification des mises à jour disponibles..." 
    incus exec "$INSTANCE" -- bash -c "cd $COMPOSE_DIR && docker compose pull" >> "$LOG" 2>&1

    local IMAGE_DIGEST
    IMAGE_DIGEST=$(incus exec "$INSTANCE" -- docker inspect --format '{{.Id}}' "$IMAGE" 2>/dev/null)

    local CONTAINER_DIGEST
    CONTAINER_DIGEST=$(incus exec "$INSTANCE" -- docker inspect --format '{{.Image}}' "$CONTAINER_NAME" 2>/dev/null)

    if [ "$IMAGE_DIGEST" = "$CONTAINER_DIGEST" ]; then
        log "Aucune mise à jour disponible pour $INSTANCE. Rien à faire." 
        return 0
    fi

    log "Nouvelle version détectée et installée ! Redémarrage de $INSTANCE..." 
    if incus exec "$INSTANCE" -- bash -c "cd $COMPOSE_DIR && docker compose up -d --remove-orphans"; then
        log "Mise à jour réussie pour $INSTANCE." 
    else
        log "Échec de la mise à jour pour $INSTANCE." 
    fi
}

# ============================================================
# Mise à jour d'une instance système (APT)
# ============================================================
update_apt_instance() {
    local INSTANCE=$1

    log "=== Traitement de $INSTANCE ===" 

    local UPGRADABLE
    UPGRADABLE=$(incus exec "$INSTANCE" -- bash -c "apt-get update -qq && apt-get -s upgrade 2>/dev/null | grep -c '^Inst'" 2>/dev/null)

    if [ "$UPGRADABLE" -eq 0 ] 2>/dev/null; then
        log "Aucune mise à jour disponible pour $INSTANCE. Rien à faire." 
        return 0
    fi

    log "$UPGRADABLE paquet(s) à mettre à jour pour $INSTANCE." 
    if incus exec "$INSTANCE" -- bash -c "apt-get -y full-upgrade && apt-get autoremove -y && apt-get clean"; then
        log "Mise à jour réussie pour $INSTANCE." 
    else
        log "Échec de la mise à jour pour $INSTANCE." 
    fi
}

# ============================================================
# Définition des instances
# ============================================================

# VM laravel
update_apt_instance "laravel" 

# VM redmine
update_apt_instance "redmine" 

# VM nextcloud (APT + Nextcloud)
log "=== Traitement de nextcloud ===" 
UPGRADABLE=$(incus exec "nextcloud" -- bash -c "apt-get update -qq && apt-get -s upgrade 2>/dev/null | grep -c '^Inst'" 2>/dev/null)
if [ "$UPGRADABLE" -eq 0 ] 2>/dev/null; then
    log "Aucune mise à jour disponible pour nextcloud. Rien à faire." 
else
    log "$UPGRADABLE paquet(s) à mettre à jour pour nextcloud." 
    if incus exec "nextcloud" -- bash -c "apt-get -y full-upgrade && apt-get autoremove -y && apt-get clean && sudo -u www-data php /var/www/html/nextcloud/updater/updater.phar"; then
        log "Mise à jour réussie pour nextcloud." 
    else
        log "Échec de la mise à jour pour nextcloud." 
    fi
fi

# Conteneur git-forgejo
update_docker_instance "git" "codeberg.org/forgejo/forgejo:15" "/opt/forgejo" "forgejo" 

# Conteneur vaultwarden
update_docker_instance "vaultwarden" "vaultwarden/server" "/opt/vaultwarden" "vaultwarden" 

# Conteneur zotero
update_docker_instance "zotero"  "lscr.io/linuxserver/zotero:latest" "/opt/zotero" "zotero" 

log "=== Toutes les instances ont été traitées. ===" 

Jour 29: Jeudi 07 mai 2026

Pour pouvoir envoyer un mail des résultats des majs des instances incus, il nous faut postfix et nous avons récupéré la config du serveur cohabit donc dans /etc/postfix/ mais le problème, c'est que le pare-feu du réseau bloque :

sudo systemctl status postfix --no-pager >> log.log
cat log.log
...
mai 07 10:18:34 toutatis postfix/smtp[736103]: connect to mta-in02.u-bordeaux.fr[147.210.215.18]:25: Connection timed out

Nous avons modifié le script des updates pour faire l'envoie du mail :

#!/bin/bash
# ============================================================
#  incus-update.sh
#  Mise à jour automatique des instances Incus
#  Planifier via crontab : 0 1 * * 6 /usr/local/bin/incus-update.sh
# ============================================================

LOG="/var/log/incus-updates.log" 
SESSION_LOG=$(mktemp /tmp/incus-update-session.XXXXXX)

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG" | tee -a "$SESSION_LOG" > /dev/null
}

# ============================================================
# Mise à jour d'une instance Docker Compose
# Logique : pull → comparer image locale vs image du conteneur actif
#           si différent → up -d
# ============================================================
update_docker_instance() {
    local INSTANCE=$1
    local IMAGE=$2
    local COMPOSE_DIR=$3
    local CONTAINER_NAME=$4

    log "=== Traitement de $INSTANCE ===" 
    log "Vérification des mises à jour disponibles..." 
    incus exec "$INSTANCE" -- bash -c "cd $COMPOSE_DIR && docker compose pull" >> "$LOG" 2>&1

    local IMAGE_DIGEST
    IMAGE_DIGEST=$(incus exec "$INSTANCE" -- docker inspect --format '{{.Id}}' "$IMAGE" 2>/dev/null)

    local CONTAINER_DIGEST
    CONTAINER_DIGEST=$(incus exec "$INSTANCE" -- docker inspect --format '{{.Image}}' "$CONTAINER_NAME" 2>/dev/null)

    if [ "$IMAGE_DIGEST" = "$CONTAINER_DIGEST" ]; then
        log "Aucune mise à jour disponible pour $INSTANCE. Rien à faire." 
        return 0
    fi

    log "Nouvelle version détectée et installée ! Redémarrage de $INSTANCE..." 
    if incus exec "$INSTANCE" -- bash -c "cd $COMPOSE_DIR && docker compose up -d --remove-orphans"; then
        log "Mise à jour réussie pour $INSTANCE." 
    else
        log "Échec de la mise à jour pour $INSTANCE." 
    fi
}

# ============================================================
# Mise à jour d'une instance système (APT)
# ============================================================
update_apt_instance() {
    local INSTANCE=$1

    log "=== Traitement de $INSTANCE ===" 

    local UPGRADABLE
    UPGRADABLE=$(incus exec "$INSTANCE" -- bash -c "apt-get update -qq && apt-get -s upgrade 2>/dev/null | grep -c '^Inst'" 2>/dev/null)

    if [ "$UPGRADABLE" -eq 0 ] 2>/dev/null; then
        log "Aucune mise à jour disponible pour $INSTANCE. Rien à faire." 
        return 0
    fi

    log "$UPGRADABLE paquet(s) à mettre à jour pour $INSTANCE." 
    if incus exec "$INSTANCE" -- bash -c "apt-get -y full-upgrade && apt-get autoremove -y && apt-get clean"; then
        log "Mise à jour réussie pour $INSTANCE." 
    else
        log "Échec de la mise à jour pour $INSTANCE." 
    fi
}

# ============================================================
# Définition des instances
# ============================================================

# VM laravel
update_apt_instance "laravel" 

# VM redmine
update_apt_instance "redmine" 

# VM nextcloud (APT + Nextcloud)
log "=== Traitement de nextcloud ===" 
UPGRADABLE=$(incus exec "nextcloud" -- bash -c "apt-get update -qq && apt-get -s upgrade 2>/dev/null | grep -c '^Inst'" 2>/dev/null)
if [ "$UPGRADABLE" -eq 0 ] 2>/dev/null; then
    log "Aucune mise à jour disponible pour nextcloud. Rien à faire." 
else
    log "$UPGRADABLE paquet(s) à mettre à jour pour nextcloud." 
    if incus exec "nextcloud" -- bash -c "apt-get -y full-upgrade && apt-get autoremove -y && apt-get clean && sudo -u www-data php /var/www/html/nextcloud/updater/updater.phar"; then
        log "Mise à jour réussie pour nextcloud." 
    else
        log "Échec de la mise à jour pour nextcloud." 
    fi
fi

# Conteneur git-forgejo
update_docker_instance "git" "codeberg.org/forgejo/forgejo:15" "/opt/forgejo" "forgejo" 

# Conteneur vaultwarden
update_docker_instance "vaultwarden" "vaultwarden/server" "/opt/vaultwarden" "vaultwarden" 

# Conteneur zotero
update_docker_instance "zotero"  "lscr.io/linuxserver/zotero:latest" "/opt/zotero" "zotero" 

log "=== Toutes les instances ont été traitées. ===" 

# Envoi du rapport de la session par mail
MAIL_DEST="pierre.grange-praderas@u-bordeaux.fr" 
mail -s "[toutatis] Rapport mise à jour $(date '+%Y-%m-%d')" "$MAIL_DEST" < "$SESSION_LOG" 

# Nettoyage du fichier temporaire
rm -f "$SESSION_LOG" 

Nous avons aussi modifié le script des snapshots, car le --expiry n'existe pas sur les versions 6.0.x LTS de incus et nous avons aussi modifié le nom des snapshots pour plus de clarté (de hebdo-20260506-115242 à hebdo-2026-05-06_11:52:42) :

#!/bin/bash
# /usr/local/bin/incus-snapshot.sh
# Crée un snapshot horodaté sur toutes les VM/CT Incus
# Planifier via crontab : 0 0 * * 6 /usr/local/bin/incus-snapshot.sh

INSTANCES=(git laravel nextcloud redmine vaultwarden zotero)
DATE=$(date +"%d-%m-%Y_%H:%M:%S")
LOG="/var/log/incus-snapshot.log" 

echo "[$(date)] === Début des snapshots ===" >> "$LOG" 

for INSTANCE in "${INSTANCES[@]}"; do
    SNAPSHOT_NAME="hebdo-${DATE}" 
    if echo "expires_at: $(date -u -d '+2 weeks' '+%Y-%m-%dT%H:%M:%SZ')" \
    | incus snapshot create "$INSTANCE" "$SNAPSHOT_NAME" >> "$LOG" 2>&1; then
        echo "[$(date)] $INSTANCE -> $SNAPSHOT_NAME" >> "$LOG" 
    else
        echo "[$(date)] ERREUR sur $INSTANCE" >> "$LOG" 
    fi
done

echo "[$(date)] === Fin des snapshots ===" >> "$LOG" 

Commande crontab :

crontab -e
0 0 * * 6 /usr/local/bin/incus-snapshot.sh
0 1 * * 6 /usr/local/bin/incus-update.sh

Jour 30: Vendredi 08 mai 2026

Férié

Jour 31: Lundi 11 mai 2026

Ajout de deux nouveaux services (twiki et dokuwiki) :
dokuwiki :
id : fablab
mdp : MDP

twiki :
id : admin
mdp : MDP

Pour TWiki :

sudo incus launch images:debian/13 twiki
sudo incus config device override twiki eth0 ipv4.address=10.218.184.9
sudo incus restart twiki
sudo incus shell twiki

apt update && apt upgrade
apt install apache2 rcs wget make gcc libcgi-session-perl libdigest-sha-perl libhtml-parser-perl liberror-perl php wget zip
systemctl restart apache2
wget -O TWiki-6.1.0.zip "https://sourceforge.net/projects/twiki/files/TWiki%20for%20all%20Platforms/TWiki-6.1.0/TWiki-6.1.0.zip/download" 
unzip TWiki-6.1.0.zip
mv TWiki-6.1.0 /var/www/html/

Pour la configuration d'apache, TWiki ont un générateur de template : https://twiki.org/cgi-bin/view/TWiki/ApacheConfigGenerator?dir=%2Fvar%2Fwww%2Fhtml%2FTWiki-6.1.0&path=%2Fdo&puburl=%2Fpub&loginmanager=Template&phpinstalled=PHP4&secureattachments=no&errordocument=TWikiRegistration&execperl=mod_cgi&apachever24=on#TWikiConf

Mais pour que la page nous redirige automatiquement sur la page d'accueil du site, il faut ajouter cela :

RedirectMatch ^/$ /do/view/Main/WebHome

vi /etc/apache2/sites-enabled/twiki.conf
rm /etc/apache2/sites-enabled/000-default.conf
chown -R www-data:www-data /var/www/html/TWiki-6.1.0/
systemctl restart apache2
exit

Finaliser la configuration sur la page web
https://twiki.192.168.23.3.nip.io/do/configure

sudo vi /etc/caddy/Caddyfile

twiki.192.168.23.3.nip.io {
    tls internal
    reverse_proxy 10.218.184.9:80
}

TWiki est accessible sur : https://twiki.192.168.23.3.nip.io/

Pour DokuWiki :

sudo incus init images:debian/13 dokuwiki --vm
sudo incus config device override dokuwiki eth0 ipv4.address=10.218.184.8
sudo incus start dokuwiki
sudo incus shell dokuwiki
sudo apt update && sudo apt upgrade -y
sudo apt install apache2 php wget -y
sudo systemctl enable apache2
wget https://download.dokuwiki.org/out/dokuwiki-30e181c1af123e688bf57014595f64a4.tgz
tar xzvf dokuwiki-stable.tgz
sudo mkdir -p /var/www/html/dokuwiki
sudo mv dokuwiki-*/* /var/www/html/dokuwiki/
sudo chown -R www-data:www-data /var/www/html/dokuwiki
sudo chmod -R 755 /var/www/html/dokuwiki
sudo chmod -R 775 /var/www/html/dokuwiki/data
sudo chmod -R 775 /var/www/html/dokuwiki/conf
sudo vi /etc/apache2/sites-available/dokuwiki.conf
sudo rm /etc/apache2/sites-available/000-default.conf
<VirtualHost *:80>
    DocumentRoot /var/www/html/dokuwiki

    <Directory /var/www/html/dokuwiki>
        Options FollowSymLinks
        AllowOverride All
        Require all granted

        SetEnv HOME /var/www/html/dokuwiki
        SetEnv HTTP_HOME /var/www/html/dokuwiki

    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/dokuwiki-error.log
    CustomLog ${APACHE_LOG_DIR}/dokuwiki-access.log combined
</VirtualHost>
sudo systemctl restart apache2
exit

Finaliser l'installation sur la page web
https://dokuwiki.192.168.23.3.nip.io/install.php

sudo vi /etc/caddy/Caddyfile

dokuwiki.192.168.23.3.nip.io {
    tls internal
    reverse_proxy 10.218.184.8:80
}

DokuWiki est accessible sur : https://dokuwiki.192.168.23.3.nip.io/

Nous avons aussi mis à jour les scripts incus-update.sh et incus-snapshot.sh pour ajouter twiki et dokuwiki :

vi /usr/local/bin/incus-update.sh

...
# VM twiki
update_apt_instance "twiki" 

# VM dokuwiki
update_apt_instance "dokuwiki" 
...
vi /usr/local/bin/incus-snapshot.sh

...
INSTANCES=(git laravel nextcloud redmine vaultwarden zotero twiki dokuwiki)
...

Jour 32: Mardi 12 mai 2026

Création d'une page web de test dans TWiki avec mkdir /var/www/html/TWiki-6.1.0/data/Test puis création d’un topic avec vi data/Test/test.txt et application des droits avec chown www-data:www-data data/Test/test.txt.

Création correcte du web Test en s’appuyant sur le web modèle _default.

cp -r /var/www/html/TWiki-6.1.0/data/_default /var/www/html/TWiki-6.1.0/data/Test
chown -R www-data:www-data /var/www/html/TWiki-6.1.0/data/Test
vi /var/www/html/TWiki-6.1.0/data/Test/PageTest.txt
chown www-data:www-data /var/www/html/TWiki-6.1.0/data/Test/PageTest.txt

Test de contenu TWiki dans PageTest.txt : titres, listes, tableaux, liens, bloc verbatim, variables TOC, WEB, TOPIC, WIKINAME, SCRIPTURL.

Correction de l’encodage en UTF-8 :

vi /var/www/html/TWiki-6.1.0/lib/LocalSite.cfg

réglage de {Site}{CharSet} sur utf-8
réglage de {Site}{Locale} sur fr_FR.UTF-8

Et aussi dans apache ajoute de AddDefaultCharset UTF-8 :

vi /etc/apache2/conf-available/charset.conf
systemctl restart apache2

Page disponible ici : https://twiki.192.168.23.3.nip.io/do/view/Test/PageTest

Et pour DokuWiki : https://dokuwiki.192.168.23.3.nip.io/doku.php

Jour 33: Mercredi 13 mai 2026

On a envoyé le mail à la DSI pour débloquer le port pour le VPN WireGuard.

Sur le serveur cohabit, nous avons trouvé postfix, DMARC et DKIM :

/etc/postfix/*

/etc/opendkim.conf
/etc/default/opendkim
/etc/opendkim/*
/var/spool/postfix/opendkim/

/etc/opendmarc.conf
/etc/default/opendmarc
/etc/opendmarc/*
/var/spool/postfix/opendmarc/

Et nous avons regardé si caddy ne géré pas déjà cela, et nous avons trouvé maddy :
https://maddy.email/
https://github.com/foxcpp/maddy/
https://maddy.email/builds/

Jour 34: Jeudi 14 mai 2026

Férié

Jour 35: Vendredi 15 mai 2026

Pont

Jour 36: Lundi 18 mai 2026

Nous avons envoyé un message concernant le gestionnaire de mails :

On a vu avec Pierre et on n'a trouvé que du Postfix avec DMARC et DKIM sur le serveur cohabit. Il nous a proposé de reprendre cette configuration, soit de voir avec Caddy. 
En cherchant, on a découvert Maddy (un serveur mail créé par la même équipe que Caddy). Qu'est-ce que vous préférez qu'on mette en place ?

Nous avons essayé de faire fonctionner maddy sur docker, mais sans succès.

Nous avons commencé la config avec cette vidéo tuto :
https://www.youtube.com/watch?v=uU0uXNHrPs4

Jour 37: Mardi 19 mai 2026

RDV prévu vendredi 22 mai pour mettre en place le nouveau serveur à la place de l'ancien.

Mise en place de Maddy sur le serveur toutatis (pas encore fini) :

wget -O maddy-0.9.4-x86_64-linux-musl.tar.zst https://github.com/foxcpp/maddy/releases/download/v0.9.4/maddy-0.9.4-x86_64-linux-musl.tar.zst
unzstd maddy-0.9.4-x86_64-linux-musl.tar.zst
tar xvf maddy-0.9.4-x86_64-linux-musl.tar
ls maddy-0.9.4-x86_64-linux-musl
sudo mkdir /etc/maddy
sudo cp maddy-0.9.4-x86_64-linux-musl/maddy.conf /etc/maddy
sudo cp maddy-0.9.4-x86_64-linux-musl/maddy /usr/bin
which maddy
sudo vi /etc/maddy/maddy.conf

...
$(hostname) = mail.192.168.23.3.nip.io
$(primary_domain) = 192.168.23.3.nip.io
$(local_domains) = $(primary_domain) cohabit.fr

tls file /var/lib/caddy/.local/share/caddy/certificates/local/mail.192.168.23.3.nip.io/mail.192.168.23.3.nip.io.crt /var/lib/caddy/.local/share/caddy/certificates/local/mail.192.168.23.3.nip.io/mail.192.168.23.3.nip.io.key
...

sudo vi /etc/caddy/Caddyfile

...
mail.192.168.23.3.nip.io {
    tls internal
    respond "Serveur Mail Cohabit" 200
}
...

sudo systemctl restart caddy

sudo apt install acl
sudo useradd -mrU -s /usr/sbin/nologin -c "maddy mail server" maddy
id maddy
sudo setfacl -R -m u:maddy:rX /var/lib/caddy/.local/share/caddy/certificates/local/mail.192.168.23.3.nip.io/mail.192.168.23.3.nip.io.crt /var/lib/caddy/.local/share/caddy/certificates/local/mail.192.168.23.3.nip.io/mail.192.168.23.3.nip.io.key

sudo getfacl /var/lib/caddy/.local/share/caddy/certificates/local/mail.192.168.23.3.nip.io/mail.192.168.23.3.nip.io.key

sudo chmod +x /usr/bin/maddy
sudo cp systemd/*.service /etc/systemd/system/
sudo systemctl daemon-reload

sudo setfacl -m u:maddy:x /var/lib/caddy
sudo setfacl -m u:maddy:x /var/lib/caddy/.local
sudo setfacl -m u:maddy:x /var/lib/caddy/.local/share
sudo setfacl -m u:maddy:x /var/lib/caddy/.local/share/caddy
sudo setfacl -m u:maddy:x /var/lib/caddy/.local/share/caddy/certificates
sudo setfacl -m u:maddy:x /var/lib/caddy/.local/share/caddy/certificates/local
sudo setfacl -R -m u:maddy:rX /var/lib/caddy/.local/share/caddy/certificates/local/mail.192.168.23.3.nip.io/
sudo setfacl -d -R -m u:maddy:rX /var/lib/caddy/.local/share/caddy/certificates/local/mail.192.168.23.3.nip.io/

sudo systemctl edit maddy

...
[Service]
ReadOnlyPaths=/var/lib/caddy/.local/share/caddy/certificates/
...
sudo systemctl stop postfix
sudo systemctl enable maddy
sudo systemctl start maddy
sudo systemctl status maddy

sudo maddy creds create projets@cohabit.fr

Jour 38: Mercredi 20 mai 2026

Nous avons demandé à pierre ceci :

Bonjour,
Sur le serveur Toutatis, est-ce qu'on met le SSH sur le port 55555 comme sur le serveur Cohabit ?

Est-ce que nous devons désactiver l’accès SSH par mot de passe ? Parce que si nous faisons cela, vous devrez d'abord mettre votre clé publique sur le serveur.

Et aussi pour vendredi, est-ce que c'est vous qui prenez le serveur pour l'emmener à Aquilenet ?

et il nous a répondu :

Oui port 55555
Pour le ssh, MDP interdit, et connexion en root interdite, recopiez mes clés depuis le serveur actuel
Oui c'est moi qui amène le serveur vendredi

Donc, nous avons copié tous les fichiers du dossier /home/pgp/.ssh/ et aussi de /home/alexander/.ssh/ pour les mettre sur le nouveau serveur.

Pour la config SSH, nous avons juste recopié la config de l'ancien serveur /etc/ssh/sshd_config vers le nouveau serveur.

Pour l'envoi de mail avec Maddy :
La commande mail que l'on utilisait avec postfix, ne fonctionnait pas pour maddy donc nous avons utilisé swaks :

sudo apt install swaks

swaks --to "yoangabriel33@gmail.com" \                                                     
      --from "projets@cohabit.fr" \
      --server 127.0.0.1 --port 587 --tls \
      --auth-user "projets@cohabit.fr" \
      --auth-password "MDP" 

et dans les logs, on peut voir comme avec postfix que le réseau bloque sur le port 25 donc normalement sur autre réseau ou le port 25 n'est pas bloqué, ça devrait marcher :

sudo journalctl -u maddy -f

mai 20 12:14:26 toutatis maddy[3595504]: submission: incoming message        {"msg_id":"92679cce","sender":"projets@cohabit.fr","src_host":"toutatis","src_ip":"127.0.0.1:34070","username":"projets@cohabit.fr"}
mai 20 12:14:26 toutatis maddy[3595504]: submission: RCPT ok        {"msg_id":"92679cce","rcpt":"yoangabriel33@gmail.com"}
mai 20 12:14:26 toutatis maddy[3595504]: submission: accepted        {"msg_id":"92679cce"}
mai 20 12:16:43 toutatis maddy[3595504]: cannot use MX        {"domain":"gmail.com","io_op":"dial","msg_id":"92679cce-6a0d8982","reason":"dial tcp 64.233.166.27:25: connect: connection timed out","remote_addr":"64.233.166.27:25","remote_server":"gmail-smtp-in.l.google.com.","smtp_code":450,"smtp_enchcode":"4.4.2","smtp_msg":"Network I/O error"}

Nous avons donc adapté le script incus-update.sh pour l'envoi du mail :

...
# Envoi du rapport de la session par mail avec swaks
MAIL_DEST="pierre.grange-praderas@u-bordeaux.fr" 
SUBJECT="[toutatis] Rapport mise à jour $(date '+%Y-%m-%d')" 

cat "$SESSION_LOG" | swaks --to "$MAIL_DEST" \
      --from "projets@cohabit.fr" \
      --server 127.0.0.1 --port 587 --tls \
      --auth-user "projets@cohabit.fr" \
      --auth-password "MDP" \
      --header "Subject: $SUBJECT" \
      --body - > /dev/null 2>&1

# Nettoyage du fichier temporaire
rm -f "$SESSION_LOG" 

Nous avons aussi mis Rickroll au démarrage du serveur dans /etc/default/grub :

...
GRUB_INIT_TUNE="65000 554 258 587 258 493 258 0 258 659 258 739 258 659 775 440 129 493 129 587 129 493 129 739 387 739 387 659 775 440 129 493 129 554 129 440 129 659 387 659 387 587 129 554 129 493 517 440 129 493 129 554 129 440 129 587 517 659 258 554 258 493 258 440 258 0 258 440 258 659 517 587 517 0 517 440 129 493 129 587 129 493 129 739 387 739 387 659 775 440 129 493 129 554 129 440 129 880 517 554 258 587 258 554 129 493 387 440 129 493 129 554 129 440 129 587 517 659 258 554 387 493 129 440 258 0 258 440 258 659 258 587 258 587 517" 

Jour 39: Jeudi 21 mai 2026

Installation de fail2ban sur le serveur :

sudo apt install fail2ban
sudo systemctl start fail2ban
sudo systemctl enable fail2ban
sudo systemctl status fail2ban

Voici la marche à suivre étape par étape pour mettre en place votre configuration DNS afin que Maddy puisse envoyer des e-mails propres (qui ne tomberont pas dans les spams) depuis `cohabit.fr`.

### Étape 1 : Récupérer la clé DKIM publique générée par Maddy

Maddy a généré ses propres clés cryptographiques. Vous devez afficher le contenu du fichier DNS qu'il a créé : [maddy](https://maddy.email/reference/modifiers/dkim/)

1. Dans votre terminal, tapez cette commande :
   ```bash
   sudo cat /var/lib/maddy/dkim_keys/cohabit.fr_default.dns
   ```
2. Cela va vous afficher un bloc de texte qui ressemble à ceci :
   `v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BA...`
   Copiez ce texte, vous allez en avoir besoin.

### Étape 2 : Mettre à jour votre zone DNS pour DKIM

Rendez-vous sur l'interface de votre hébergeur de nom de domaine (OVH, Gandi, etc.) où est géré `cohabit.fr`, puis dans la section "Zone DNS" :

1. Créez un nouvel enregistrement de type **TXT**. [all-inkl](https://all-inkl.com/fr/important/guides/changement-de-fournisseur/installation/dns/dkim-pour-envoi-via-serveurs-externes_444.html)
2. Dans le champ **Nom** (ou Sous-domaine), mettez : `default._domainkey` (donc `default._domainkey.cohabit.fr`).
3. Dans le champ **Valeur** (ou Cible/Texte), collez tout le texte que vous avez récupéré à l'étape 1 (`v=DKIM1; k=rsa; p=...`). [grafikart](https://grafikart.fr/tutoriels/email-dns-dkim-spf-551)
4. Enregistrez.

### Étape 3 : Vérifier et mettre à jour SPF

Dans la même interface de gestion de votre zone DNS :

1. Cherchez si vous avez déjà un enregistrement **TXT** dont la valeur commence par `v=spf1`. [grafikart](https://grafikart.fr/tutoriels/email-dns-dkim-spf-551)
2. Si vous n'en avez pas, créez-le. Si vous en avez un, modifiez-le pour vous assurer que l'adresse IP publique de votre serveur (l'IP de la machine "toutatis") y figure.
3. Voici l'enregistrement standard à créer (remplacez l'IP par la vôtre) :
   - **Type :** TXT
   - **Nom :** `@` (ou laissez vide selon l'hébergeur)
   - **Valeur :** `v=spf1 a mx ip4:VOTRE_IP_PUBLIQUE ~all` [grafikart](https://grafikart.fr/tutoriels/email-dns-dkim-spf-551)
   *(Exemple: `v=spf1 a mx ip4:192.168.23.3 ~all`)*

### Étape 4 : Configurer DMARC

Le DMARC s'appuie sur DKIM et SPF. C'est le dernier rempart qui prouve que vous êtes bien le propriétaire du domaine. [alsacreations](https://www.alsacreations.com/tuto/lire/1981-Utiliser-SPF-DKIM-DMARC-pour-vos-e-mails.html)

Toujours dans votre zone DNS, créez un nouvel enregistrement :
1. **Type :** TXT
2. **Nom :** `_dmarc` (donc `_dmarc.cohabit.fr`)
3. **Valeur :** `v=DMARC1; p=quarantine; rua=mailto:projets@cohabit.fr` [tutoriels.lws](https://tutoriels.lws.fr/e-mail/configurer-spf-dkim-dmarc)

*(Cette configuration indique : "Si quelqu'un essaie d'usurper mon identité et que SPF ou DKIM échoue, mettez le message en quarantaine et envoyez-moi un rapport à projets@cohabit.fr" ).* [tutoriels.lws](https://tutoriels.lws.fr/e-mail/configurer-spf-dkim-dmarc)

Une fois ces trois enregistrements sauvegardés, la propagation DNS peut prendre entre quelques minutes et quelques heures. Ensuite, vos e-mails automatiques issus du script seront parfaitement authentifiés !

Jour 40: Vendredi 22 mai 2026

Aujourd'hui, nous sommes allé à Aquilenet pour mettre en place le nouveau serveur.
Mais avant l'échange, il faut faire des dumps des bases de données des services qui en ont et aussi des copies des fichiers/dossiers.
Après cela, nous l'avons installé à la place de l'ancien serveur, nous avons du faire des modifications pour le redmine car dans l'url le /redmine/, ne fonctionné plus. Pour régler cela, nous avons du modifier la config apache.
Nous avons aussi changé les liens des services pour pointer le nom de domaine : cohabit.fr (nom_du_service.cohabit.fr).

Mais en fait de journée, Alexander nous a écrit les modifications/ajout qu'il reste à faire :

du coup en plus à faire en priorité: pare-feu réseau (iptables grand classique auquel on est habitué, ça peut en être un autre si il y a une raison derrière, vous pouvez vous inspirez de la conf de l'ancien serveur)
un petit motd personnalisé avec les services qui tournent pour chaque vm et l'endroit de configuration de ces services/stockage (et dépendance à d'autres vm/container si necessaire)
et voir pour le let's encrypt ou zeroSSL avec caddy du coup pour renouveler les certificats (dernière priorité mon vieux truc dégueulasse fonctionnera aussi sur cette infra mais bon, ça serait cool un truc plus propre

faudra voir pour mappé le port du forgero vers l'exterieur en 22222 aussi, git ça ne s'utilise pas pour la version web quand on fait des modifs ;)

Jour 41: Lundi 25 mai 2026

Férié

Jour 42: Mardi 26 mai 2026

Nous avons remarqué que redmine ne fonctionné plus, il fallait augmenter les ressources de la VM car quand il y a plusieurs personnes que l'utilise, les chargements étaient beaucoup trop longs :

sudo incus stop redmine
sudo incus config set redmine limits.memory 8192MiB
sudo incus config set redmine limits.cpu 4
sudo incus start redmine

Puis, nous avons commencé à faire ce que nous avait demandé Alenxander.
Donc, nous avons fait le motd :

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.

 _____                                       ______      _      _         _     
/  ___|                                      |  ___|    | |    | |       | |    
\ `--.   ___  _ __ __   __ ___  _   _  _ __  | |_  __ _ | |__  | |  __ _ | |__  
 `--. \ / _ \| '__|\ \ / // _ \| | | || '__| |  _|/ _` || '_ \ | | / _` || '_ \ 
/\__/ /|  __/| |    \ V /|  __/| |_| || |    | | | (_| || |_) || || (_| || |_) |
\____/  \___||_|     \_/  \___| \__,_||_|    \_|  \__,_||_.__/ |_| \__,_||_.__/ 

Serveur Toutatis
IP publique : 46.231.241.47

Scripts incus updates/snapshots : 
- /usr/local/bin/incus-snapshot.sh
- /usr/local/bin/incus-update.sh

Services de l'hôte :
- Caddy : reverse proxy, configuration dans /etc/caddy/Caddyfile
- Maddy : serveur mail, configuration dans /etc/maddy/maddy.conf, stockage dans /var/lib/maddy

Instances Incus :
- laravel (VM - 10.218.184.5)
  Service : Application Laravel
  Configuration : /etc/apache2/apache2.conf
  Stockage : /opt/laravel/laravel
  Dependances : PostgreSQL local

- redmine (VM - 10.218.184.3)
  Service : Redmine
  Configuration : /opt/redmine/redmine-5.1.12
  Stockage : /opt/redmine/redmine-5.1.12/files
  Dependances : PostgreSQL local

- git (container - 10.218.184.4)
  Service : Forgejo
  Configuration : /opt/forgejo/docker-compose.yml
  Stockage : /opt/forgejo/forgejo-data (monté sur /data dans le conteneur)
  Dependances : base de données PostgreSQL (conteneur forgejo-db-1 associé)

- vaultwarden (container - 10.218.184.6)
  Service : Vaultwarden
  Configuration : /opt/vaultwarden/docker-compose.yml
  Stockage : /opt/vaultwarden/vw-data (monté sur /data dans le conteneur)
  Dependances : base de données SQLite integrée dans le dossier de données

- nextcloud (VM - 10.218.184.2)
  Service : Nextcloud
  Configuration : /var/www/html/nextcloud/config/config.php
  Stockage : /var/www/html/nextcloud/
  Dependances : MariaDB local 

- zotero (container - 10.218.184.7)
  Service : Zotero
  Configuration : /opt/zotero/docker-compose.yml
  Stockage : /opt/zotero (monté sur /config)

- dokuwiki (VM - 10.218.184.8)
  Service : DokuWiki
  Configuration : /etc/dokuwiki/
  Stockage : /var/lib/dokuwiki/data
  Dependances : Aucune base de données

- twiki (container - 10.218.184.9)
  Service : TWiki
  Configuration : /etc/apache2/
  Stockage : /var/www/html/TWiki-6.1.0/
  Dependances : Aucune base de données

URLs :
- https://cohabit.fr
- https://projets.cohabit.fr
- https://git.cohabit.fr
- https://vaultwarden.cohabit.fr
- https://nextcloud.cohabit.fr
- https://zotero.cohabit.fr
- https://dokuwiki.cohabit.fr
- https://twiki.cohabit.fr

Nous avons aussi modifié le port et un problème d'url pour le git forgejo (de 2222 à 22222) dans /opt/forgejo/forgejo-data/gitea/conf/app :

...
[server]
...
ROOT_URL = https://git.cohabit.fr/
DISABLE_SSH = false
SSH_PORT = 22222
...

Nous avons aussi les regles iptables :

sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A INPUT -i lo -j ACCEPT
sudo iptables -A INPUT -i docker0 -j ACCEPT
sudo iptables -A INPUT -i br-+ -j ACCEPT
sudo iptables -A INPUT -i incusbr0 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 55555 -j ACCEPT
sudo iptables -A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT
sudo iptables -A INPUT -p tcp -m multiport --dports 25,465,587,143,993 -j ACCEPT
sudo iptables -A INPUT -p tcp -m multiport --dports 22222,3000,3001 -j ACCEPT
sudo iptables -P INPUT DROP
sudo iptables -P OUTPUT ACCEPT

Jour 43: Mercredi 27 mai 2026

Jour 44: Jeudi 28 mai 2026

Jour 45: Vendredi 29 mai 2026

Jour 46: Lundi 01 juin 2026

Partition Taille Point de montage
1 512 Mo /boot/efi (pour grub)
2 2 Go /boot
3 2 Go swap
4 20 Go /var
5 50 Go / (Racine et tout le reste)
6 ~940 Go Stockage VMs

Nous avons recréé le disque, car on avait oublié de faire une partition pour /var.

Paramètre Valeur à mettre
Nom efi
Utiliser comme Partition système EFI
Indicateur d'amorçage présent
Taille 512 Mo
Paramètre Valeur à mettre
Nom boot
Utiliser comme ext4 journalisé
Point de montage /boot
Options de montage defaults
Étiquette aucune
Blocs réservés 5%
Utilisation habituelle standard
Indicateur d'amorçage absent
Taille 2 Go
Paramètre Valeur à mettre
Nom swap
Utiliser comme espace d'échange (swap)
Indicateur d'amorçage absent
Taille 2 Go
Paramètre Valeur à mettre
Nom var
Utiliser comme ext4 journalisé
Point de montage /var
Options de montage défauts
Étiquette aucune
Blocs réservés 5%
Utilisation habituelle standard
Indicateur d'amorçage absent
Taille 20 Go
Paramètre Valeur à mettre
Nom root
Utiliser comme ext4 journalisé
Point de montage /
Options de montage défauts
Étiquette aucune
Blocs réservés 5%
Utilisation habituelle standard
Indicateur d'amorçage absent
Taille 50 Go
Paramètre Valeur à mettre
Nom stockage
Utiliser comme ne pas utiliser
Indicateur d'amorçage absent
Taille ~940 Go

Maddy :

sudo apt install acl
sudo useradd -mrU -s /usr/sbin/nologin -c "maddy mail server" maddy
id maddy
sudo setfacl -R -m u:maddy:rX /var/lib/caddy/.local/share/caddy/certificates/local/mail.cohabit.fr/mail.cohabit.fr.crt /var/lib/caddy/.local/share/caddy/certificates/local/mail.cohabit.fr/mail.cohabit.fr.key

sudo getfacl /var/lib/caddy/.local/share/caddy/certificates/local/mail.cohabit.fr/mail.cohabit.fr.key

sudo chmod +x /usr/bin/maddy
sudo cp systemd/*.service /etc/systemd/system/
sudo systemctl daemon-reload

sudo setfacl -m u:maddy:x /var/lib/caddy
sudo setfacl -m u:maddy:x /var/lib/caddy/.local
sudo setfacl -m u:maddy:x /var/lib/caddy/.local/share
sudo setfacl -m u:maddy:x /var/lib/caddy/.local/share/caddy
sudo setfacl -m u:maddy:x /var/lib/caddy/.local/share/caddy/certificates
sudo setfacl -m u:maddy:x /var/lib/caddy/.local/share/caddy/certificates/local
sudo setfacl -R -m u:maddy:rX /var/lib/caddy/.local/share/caddy/certificates/local/mail.cohabit.fr/
sudo setfacl -d -R -m u:maddy:rX /var/lib/caddy/.local/share/caddy/certificates/local/mail.cohabit.fr/

sudo systemctl edit maddy

...
[Service]
ReadOnlyPaths=/var/lib/caddy/.local/share/caddy/certificates/
...

sudo systemctl stop postfix
sudo systemctl disable postfix
sudo systemctl enable maddy
sudo systemctl start maddy
sudo systemctl status maddy

sudo maddy creds create projets@cohabit.fr

Iptables :

# 1. Maintenir les connexions déjà établies
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# 2. Autoriser l'interface locale (Loopback)
sudo iptables -A INPUT -i lo -j ACCEPT

# 3. Autoriser les interfaces virtuelles (Le symbole '+' sert de joker)
sudo iptables -A INPUT -i docker0 -j ACCEPT
sudo iptables -A INPUT -i br-+ -j ACCEPT
sudo iptables -A INPUT -i incusbr0 -j ACCEPT

# 4. Autoriser SSH
sudo iptables -A INPUT -p tcp --dport 55555 -j ACCEPT

# 5. Autoriser les ports Web, Mail et les services spécifiques via Docker
sudo iptables -A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT
sudo iptables -A INPUT -p tcp -m multiport --dports 25,465,587,143,993 -j ACCEPT
sudo iptables -A INPUT -p tcp -m multiport --dports 22222,3000,3001 -j ACCEPT
sudo iptables -A INPUT -p icmp -j ACCEPT

# 6. Appliquer
sudo iptables -P INPUT DROP
sudo iptables -P OUTPUT ACCEPT

# Appliquer meme après redémarrage
sudo apt install iptables-persistent
sudo netfilter-persistent save

# Vérifier
sudo iptables -L -v -n

Jour 47: Mardi 02 juin 2026

Partition Taille Point de montage
1 512 Mo /boot/efi (pour grub)
2 2 Go /boot
3 2 Go swap
4 20 Go /var
5 50 Go / (Racine et tout le reste)
6 ~940 Go Stockage VMs
Paramètre Valeur à mettre
Nom efi
Utiliser comme Partition système EFI
Indicateur d'amorçage présent
Taille 512 Mo
Paramètre Valeur à mettre
Nom boot
Utiliser comme ext4 journalisé
Point de montage /boot
Options de montage defaults
Étiquette aucune
Blocs réservés 5%
Utilisation habituelle standard
Indicateur d'amorçage absent
Taille 2 Go
Paramètre Valeur à mettre
Nom swap
Utiliser comme espace d'échange (swap)
Indicateur d'amorçage absent
Taille 2 Go
Paramètre Valeur à mettre
Nom var
Utiliser comme ext4 journalisé
Point de montage /var
Options de montage défauts
Étiquette aucune
Blocs réservés 5%
Utilisation habituelle standard
Indicateur d'amorçage absent
Taille 20 Go
Paramètre Valeur à mettre
Nom root
Utiliser comme ext4 journalisé
Point de montage /
Options de montage défauts
Étiquette aucune
Blocs réservés 5%
Utilisation habituelle standard
Indicateur d'amorçage absent
Taille 50 Go
Paramètre Valeur à mettre
Nom stockage
Utiliser comme ne pas utiliser
Indicateur d'amorçage absent
Taille ~940 Go

Après avoir partitionné correctement le disque, nous devons remettre la config du serveur :

# installation des services

apt install incus git curl wget zip caddy nano vim fail2ban swaks zsh sudo molly-guard mdadm gdisk rsync ncdu acl iptables -y

# RAID1

https://projets.cohabit.fr/projects/accueil/wiki/Nicolas_Schmauch_2#Jour-48-Mercredi-03-juin-2026

# user

# lister les users
cut -d: -f1 /etc/passwd

# en root
grep "user" /etc/passwd > passwd.user
grep "user" /etc/shadow > shadow.user
grep "user" /etc/group > group.user

# transférer le zip sur le nouveau serveur
zip -r user.zip *.user
scp/rsync ...

unzip user.zip
cat passwd.user >> /etc/passwd
cat shadow.user >> /etc/shadow
cat group.user >> /etc/group

# transférer le zip vers le nouveau serveur
zip -r home.zip /home/
scp/rsync ...

# vérifier si chaque user a un UID différent
grep user /etc/passwd

# pour changer
usermod -g 100X user
groupmod -g 100X user

# pour redonner les droits
chown -R user:user /home/user

# motd

# copier la config motd
cat /etc/motd

# ssh

# copier la config ssh
cat /etc/ssh/sshd_config
systemctl restart ssh

# grub

# copier la config grub
cat /etc/default/grub
update-grub

# Caddy

# copier le Caddyfile
cat /etc/caddy/Caddyfile
# sur le serveur qui a les configs
zip -r local.zip /var/lib/caddy/.local/share/caddy/certificates/local/
zip -r cert.zip /cert/fablab/live
scp/rsync ...
unzip ...
cp ...
chown -R caddy:caddy /var/lib/caddy
chmod 700 /var/lib/caddy/.local
chmod 700 /var/lib/caddy/.local/share
chmod -R 700 /var/lib/caddy/.local/share/caddy
systemctl enable caddy --now

# Maddy

wget -O maddy-0.9.5-x86_64-linux-musl.tar.zst https://github.com/foxcpp/maddy/releases/download/v0.9.5/maddy-0.9.5-x86_64-linux-musl.tar.zst
unzstd maddy-0.9.5-x86_64-linux-musl.tar.zst
tar xvf maddy-0.9.5-x86_64-linux-musl.tar
mkdir /etc/maddy
cp maddy-0.9.5-x86_64-linux-musl/maddy.conf /etc/maddy
cp maddy-0.9.5-x86_64-linux-musl/maddy /usr/local/bin
chmod +x /usr/local/bin/maddy
chown -R maddy:maddy /var/lib/maddy
chmod 700 /var/lib/maddy/*

which maddy

vi /etc/maddy/maddy.conf

...
$(hostname) = mail.cohabit.fr
$(primary_domain) = cohabit.fr
$(local_domains) = $(primary_domain)

tls file /var/lib/caddy/.local/share/caddy/certificates/local/mail.cohabit.fr/mail.cohabit.fr.crt /var/lib/caddy/.local/share/caddy/certificates/local/mail.cohabit.fr/mail.cohabit.fr.key
...

apt install acl
useradd -mrU -s /usr/sbin/nologin -c "maddy mail server" maddy
id maddy
setfacl -R -m u:maddy:rX /var/lib/caddy/.local/share/caddy/certificates/local/mail.cohabit.fr/mail.cohabit.fr.crt /var/lib/caddy/.local/share/caddy/certificates/local/mail.cohabit.fr/mail.cohabit.fr.key

getfacl /var/lib/caddy/.local/share/caddy/certificates/local/mail.cohabit.fr/mail.cohabit.fr.key

setfacl -m u:maddy:x /var/lib/caddy
setfacl -m u:maddy:x /var/lib/caddy/.local
setfacl -m u:maddy:x /var/lib/caddy/.local/share
setfacl -m u:maddy:x /var/lib/caddy/.local/share/caddy
setfacl -m u:maddy:x /var/lib/caddy/.local/share/caddy/certificates
setfacl -m u:maddy:x /var/lib/caddy/.local/share/caddy/certificates/local
setfacl -R -m u:maddy:rX /var/lib/caddy/.local/share/caddy/certificates/local/mail.cohabit.fr/
setfacl -d -R -m u:maddy:rX /var/lib/caddy/.local/share/caddy/certificates/local/mail.cohabit.fr/

chmod g+rx /var/lib/caddy /var/lib/caddy/.local /var/lib/caddy/.local/share /var/lib/caddy/.local/share/caddy /var/lib/caddy/.local/share/caddy/certificates /var/lib/caddy/.local/share/caddy/certificates/local /var/lib/caddy/.local/share/caddy/certificates/local/mail.cohabit.fr

chmod g+r /var/lib/caddy/.local/share/caddy/certificates/local/mail.cohabit.fr/*

cp maddy-0.9.5-x86_64-linux-musl/systemd/*.service /etc/systemd/system/
systemctl daemon-reload

systemctl edit maddy

...
[Service]
ReadOnlyPaths=/var/lib/caddy/.local/share/caddy/certificates/
...

systemctl enable maddy
systemctl start maddy
systemctl status maddy

maddy creds create projets@cohabit.fr
# mdp : Fablab

# Script incus + crontab

# recréer les scripts pour les snapshots et majs
cat /usr/local/bin/incus-snapshot.sh
cat /usr/local/bin/incus-update.sh

chmod +x /usr/local/bin/incus-update.sh
chmod +x /usr/local/bin/incus-snapshot.sh

# en root
crontab -e
0 0 * * 6 /usr/local/bin/incus-snapshot.sh
0 1 * * 6 /usr/local/bin/incus-update.sh

# incus et snapshot

# /var/lib/incus/storage-pools/default/virtual-machines-snapshots
# /var/lib/incus/storage-pools/default/containers-snapshots

# scp -r -P 55555 nicolas@projets.cohabit.fr:/var/lib/incus/storage-pools/default/virtual-machines-snapshots /home/nicolas/.

# init incus sur la partitiion de ~900 Go pour les VMs/CTs
mkfs.ext4 /dev/md5
mkdir -p /var/lib/incus/storage-pools/default

echo "/dev/md5 /var/lib/incus/storage-pools/default ext4 defaults 0 2" >> /etc/fstab

mount /dev/md5 /var/lib/incus/storage-pools/default
systemctl daemon-reload

apt install incus

incus admin init --auto --storage-backend=dir

incus storage show default
df -h /var/lib/incus/storage-pools/default
mount | grep md5
lsblk

incus network set incusbr0 ipv4.address=10.218.184.1/24
incus network set incusbr0 ipv4.nat=true
systemctl restart incus

# recreeation de VMs/CTs
incus launch images:debian/13 dokuwiki --vm

incus config device override dokuwiki etho
incus config device set dokuwiki eth0 ipv4.address=10.218.184.8
incus restart dokuwiki

incus launch images:debian/13 laravel --vm

incus config device override laravel eth0
incus config device set laravel eth0 ipv4.address=10.218.184.5
incus restart laravel

incus launch images:debian/13 nextcloud --vm

incus config device override nextcloud eth0
incus config device set nextcloud eth0 ipv4.address=10.218.184.2
incus restart nextcloud

incus launch images:debian/13 redmine --vm \
  -c limits.cpu=4 \
  -c limits.memory=8GiB \
  -d root,size=100GiB

incus config device override redmine eth0
incus config device set redmine eth0 ipv4.address=10.218.184.3
incus restart redmine

incus launch images:debian/13 git \
  -c limits.cpu=4 \
  -c limits.memory=8GiB \
  -d root,size=100GiB

incus config device override git eth0
incus config device set git eth0 ipv4.address=10.218.184.4
incus restart git

incus launch images:debian/13 twiki

incus config device override twiki eth0
incus config device set twiki eth0 ipv4.address=10.218.184.9
incus restart twiki

incus launch images:debian/13 vaultwarden

incus config device override vaultwarden eth0
incus config device set vaultwarden eth0 ipv4.address=10.218.184.6
incus restart vaultwarden

incus launch images:debian/13 zotero

incus config device override zotero eth0
incus config device set zotero eth0 ipv4.address=10.218.184.7
incus restart zotero

scp -r -P 55555 root@projets.cohabit.fr:/var/lib/incus/storage-pools/default/virtual-machines-snapshots /var/lib/incus/storage-pools/default/virtual-machines-snapshots/
rsync -Pa --rsh="ssh -p 55555" root@projets.cohabit.fr:/var/lib/incus/storage-pools/default/virtual-machines-snapshots /var/lib/incus/storage-pools/default/virtual-machines-snapshots/

scp -r -P 55555 root@projets.cohabit.fr:/var/lib/incus/storage-pools/default/containers-snapshots/ /var/lib/incus/storage-pools/default/containers-snapshots/
rsync -Pa --rsh="ssh -p 55555" root@projets.cohabit.fr:/var/lib/incus/storage-pools/default/containers-snapshots/ /var/lib/incus/storage-pools/default/containers-snapshots/

# Iptables

# Supprime toutes les règles existantes pour repartir à zéro
sudo iptables -F
sudo iptables -X

# Maintenir les connexions déjà établies
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# Autoriser l'interface locale (Loopback)
sudo iptables -A INPUT -i lo -j ACCEPT

# Autoriser les interfaces virtuelles de conteneurs (Docker, Incus)
sudo iptables -A INPUT -i docker0 -j ACCEPT
sudo iptables -A INPUT -i br-+ -j ACCEPT
sudo iptables -A INPUT -i incusbr0 -j ACCEPT

# Autoriser le ping
sudo iptables -A INPUT -p icmp -j ACCEPT

# Autoriser SSH vers la machine hôte
sudo iptables -A INPUT -p tcp --dport 55555 -j ACCEPT

# Web (Caddy se charge ensuite de router en interne vers Forgejo, Redmine, etc.)
sudo iptables -A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT

# Mail (Maddy)
sudo iptables -A INPUT -p tcp -m multiport --dports 25,465,587,143,993 -j ACCEPT

# Autoriser le port 22222 pour le ssh Git
sudo iptables -A INPUT -p tcp --dport 22222 -j ACCEPT

# On rejette ce qui n'a pas été autorisé dessus
sudo iptables -P INPUT DROP
sudo iptables -P OUTPUT ACCEPT

# Rend les règles persistantes au redémarrage
sudo apt install iptables-persistent
sudo netfilter-persistent save

# Vérifier
sudo iptables -L -v -n

Jour 48: Mercredi 03 juin 2026

Mise à jour de la doc pour redéployer le serveur :
https://projets.cohabit.fr/projects/accueil/wiki/Nicolas_Schmauch_2#Jour-47-Mardi-02-juin-2026


Documentation RAID 1

Vue d'ensemble

Array Partitions Point de montage Métadonnées
md1 sda2 + sdc2 `/boot` 0.90 (requis pour GRUB)
md2 sda3 + sdc3 swap 1.2
md3 sda4 + sdc4 `/var` 1.2
md4 sda5 + sdc5 `/` 1.2
md5 sda6 + sdc6 données 1.2

Prérequis

# Installer les outils nécessaires
apt install mdadm rsync

# Vérifier l'état des disques et réparer les disques
# Dans mon cas, c'est sdc le disque principal et sda le disque vide
lsblk

Copier la table de partitions de sdc vers sda

# Effacer sda
wipefs --all /dev/sda

# Cloner la table de partitions de sdc → sda
sfdisk -d /dev/sdc | sfdisk --force /dev/sda

# Vérifier que sda a les mêmes partitions que sdc
lsblk

Créer les arrays RAID

On crée les arrays avec uniquement `sda` en mettant `missing` pour `sdc`
Cela permet de ne pas toucher au système en cours de fonctionnement

# md1 = /boot → metadata 0.90 obligatoire pour que GRUB le reconnaisse au boot
mdadm --create /dev/md1 --level=1 --metadata=0.90 --raid-devices=2 missing /dev/sda2

# md2 = swap
mdadm --create /dev/md2 --level=1 --raid-devices=2 missing /dev/sda3

# md3 = /var
mdadm --create /dev/md3 --level=1 --raid-devices=2 missing /dev/sda4

# md4 = / (racine)
mdadm --create /dev/md4 --level=1 --raid-devices=2 missing /dev/sda5

# md5 = vms
mdadm --create /dev/md5 --level=1 --raid-devices=2 missing /dev/sda6

# Vérifier que les 5 arrays sont créés en mode dégradé [_U]
cat /proc/mdstat

Formater les arrays

mkfs.ext4 /dev/md1   # /boot
mkswap    /dev/md2   # swap
mkfs.ext4 /dev/md3   # /var
mkfs.ext4 /dev/md4   # /
mkfs.ext4 /dev/md5   # vms

Copier les données du système vers les arrays

mkdir -p /mnt/md1 /mnt/md3 /mnt/md4

mount /dev/md1 /mnt/md1
mount /dev/md3 /mnt/md3
mount /dev/md4 /mnt/md4

# Copie de /boot
rsync -axH /boot/ /mnt/md1/

# Copie de /var
rsync -axH /var/ /mnt/md3/

# Copie de / et on exclut les points de montage
rsync -axH \
  --exclude=/proc \
  --exclude=/sys \
  --exclude=/dev \
  --exclude=/run \
  --exclude=/mnt \
  --exclude=/boot \
  --exclude=/var \
  / /mnt/md4/

# Vérifier que la copie est complète
ls /mnt/md4/
ls /mnt/md1/

Préparer le chroot

# Créer les répertoires manquants
mkdir -p /mnt/md4/dev /mnt/md4/proc /mnt/md4/sys /mnt/md4/run
mkdir -p /mnt/md4/boot /mnt/md4/var

# Lier les fichiers
mount --bind /dev  /mnt/md4/dev
mount --bind /proc /mnt/md4/proc
mount --bind /sys  /mnt/md4/sys
mount --bind /run  /mnt/md4/run

# Monter /boot et /var dans le chroot
mount --bind /mnt/md1 /mnt/md4/boot
mount --bind /mnt/md3 /mnt/md4/var

# Monter la partition EFI actuelle (sdc1) dans le chroot
mount /dev/sdc1 /mnt/md4/boot/efi

# Entrer dans le chroot
chroot /mnt/md4

Dans le chroot : fstab + mdadm.conf + initramfs + GRUB

# Récupérer les UUID des arrays
blkid /dev/md1 /dev/md2 /dev/md3 /dev/md4 /dev/md5

# Éditer fstab : remplacer les UUID des anciennes partitions sdc* par ceux des md*
# Correspondances :
#   sdc2 (ancienne /boot)  → md1
#   sdc3 (ancien swap)     → md2
#   sdc4 (ancienne /var)   → md3
#   sdc5 (ancienne /)      → md4
#   sdc1 (EFI)             → UUID de sda1 (récupéré avec blkid /dev/sda1)
nano /etc/fstab

# Sauvegarder la config mdadm
mdadm --detail --scan > /etc/mdadm/mdadm.conf

# Régénérer l'initramfs pour inclure mdadm au démarrage
update-initramfs -u -k all

# Installer GRUB EFI sur sda
grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=debian --recheck /dev/sda

# Regénérer la config GRUB
update-grub

# Sortir du chroot
exit

Dupliquer la partition EFI sur sda1

# Formater sda1 en FAT32
mkfs.vfat -F32 /dev/sda1

# Copier le contenu EFI de sdc1 vers sda1
mkdir -p /mnt/efi_sda
mount /dev/sda1 /mnt/efi_sda
rsync -axH /mnt/md4/boot/efi/ /mnt/efi_sda/

# Vérifier
ls /mnt/efi_sda/EFI/

# Créer une entrée EFI pour sda dans le firmware UEFI
efibootmgr --create --disk /dev/sda --part 1 \
  --label "Debian RAID sda" \
  --loader /EFI/debian/shimx64.efi

Reboot sur sda

Dans le BIOS/UEFI : sélectionner "Debian RAID sda" en premier dans l'ordre de boot

reboot

Après le reboot : ajouter sdc dans les arrays

# Ajouter les partitions de sdc dans chaque array
mdadm --add /dev/md1 /dev/sdc2
mdadm --add /dev/md2 /dev/sdc3
mdadm --add /dev/md3 /dev/sdc4
mdadm --add /dev/md4 /dev/sdc5
mdadm --add /dev/md5 /dev/sdc6

# Suivre la synchronisation (Ctrl+C pour quitter)
watch cat /proc/mdstat

# Quand tout affiche [UU] : installer GRUB sur sdc aussi
grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=debian --recheck /dev/sdc

# Créer l'entrée EFI pour sdc
efibootmgr --create --disk /dev/sdc --part 1 \
  --label "Debian RAID sdc" \
  --loader /EFI/debian/shimx64.efi

# Dupliquer l'EFI sur sdc1
mkfs.vfat -F32 /dev/sdc1
mkdir -p /mnt/efi_sdc
mount /dev/sdc1 /mnt/efi_sdc
rsync -axH /boot/efi/ /mnt/efi_sdc/
umount /mnt/efi_sdc

Maintenance courante

Surveiller l'état du RAID

# État global — [UU] = sain, [_U] = dégradé
cat /proc/mdstat

# Détail d'un array spécifique
mdadm --detail /dev/md4

Récupérer après un disque débranché / reconnecté

Si un disque a été débranché puis rebranché, certains arrays peuvent se retrouver en mode
dégradé `[_U]`. Il suffit de réintégrer manuellement les partitions concernées :

# Vérifier quels arrays sont dégradés
cat /proc/mdstat

# Réintégrer les partitions manquantes (adapter selon les arrays en [_U])
mdadm --add /dev/md1 /dev/sdc2   # si md1 est dégradé
mdadm --add /dev/md3 /dev/sdc4   # si md3 est dégradé
mdadm --add /dev/md4 /dev/sdc5   # si md4 est dégradé
# etc.

# Suivre la resynchronisation
watch cat /proc/mdstat

Les arrays affichant `(auto-read-only) [UU]` sont normaux : ils repassent en
lecture/écriture automatiquement dès la première écriture. Aucune action requise.

En cas de panne définitive d'un disque

# 1. Repartitionner le disque neuf à l'identique
sfdisk -d /dev/sdc | sfdisk --force /dev/sda   # ex: sda est le disque neuf

# 2. Réintégrer dans chaque array
mdadm --add /dev/md1 /dev/sda2
mdadm --add /dev/md2 /dev/sda3
mdadm --add /dev/md3 /dev/sda4
mdadm --add /dev/md4 /dev/sda5
mdadm --add /dev/md5 /dev/sda6

# 3. Suivre la resynchronisation
watch cat /proc/mdstat

Jour 49: Jeudi 04 juin 2026

Mise à jour de la doc pour redéployer le serveur :
https://projets.cohabit.fr/projects/accueil/wiki/Nicolas_Schmauch_2#Jour-47-Mardi-02-juin-2026


Envoyer des mails sur vaultwarden

# modifier le docker-compose
vi /opt/vaultwarden/docker-compose.yml 

services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: always
    environment:
      - WEBSOCKET_ENABLED=true
      - ADMIN_TOKEN="Fablab" 
      - SMTP_HOST=10.218.184.1
      - SMTP_FROM=projets@cohabit.fr
      - SMTP_FROM_NAME=Vaultwarden
      - SMTP_PORT=587
      - SMTP_SECURITY=starttls
      - SMTP_USERNAME=projets@cohabit.fr
      - SMTP_PASSWORD=Fablab
      - SMTP_ACCEPT_INVALID_CERTS=true
      - SMTP_ACCEPT_INVALID_HOSTNAMES=true
    ports:
      - "80:80" 
    volumes:
      - ./vw-data:/data

Jour 50: Vendredi 05 juin 2026

Mise à jour de la doc pour redéployer le serveur :
https://projets.cohabit.fr/projects/accueil/wiki/Nicolas_Schmauch_2#Jour-47-Mardi-02-juin-2026


Il y avait un problème sur Redmine dans l'URL, car quand il n'y a pas de / à la fin de l'URL, ça ne fonctionne pas donc on a fait une redirection de https://projets.cohabit.fr/redmine vers https://projets.cohabit.fr/redmine/ en ajoutant redir /redmine /redmine/ dans /etc/caddy/Caddyfile :

...
projets.cohabit.fr {
    tls /cert/fablab/live/projets.cohabit.fr/fullchain.pem /cert/fablab/live/pr>
    redir /redmine /redmine/
    handle_path /redmine/* {
      reverse_proxy 10.218.184.3:80
    }
    reverse_proxy 10.218.184.3:80
}
...

Recrée les VM et les CT

incus launch images:debian/13 dokuwiki --vm

incus config device override dokuwiki etho
incus config device set dokuwiki eth0 ipv4.address=10.218.184.8
incus restart dokuwiki

incus launch images:debian/13 laravel --vm

incus config device override laravel eth0
incus config device set laravel eth0 ipv4.address=10.218.184.5
incus restart laravel

incus launch images:debian/13 nextcloud --vm

incus config device override nextcloud eth0
incus config device set nextcloud eth0 ipv4.address=10.218.184.2
incus restart nextcloud

incus launch images:debian/13 redmine --vm \
  -c limits.cpu=4 \
  -c limits.memory=8GiB \
  -d root,size=100GiB

incus config device override redmine eth0
incus config device set redmine eth0 ipv4.address=10.218.184.3
incus restart redmine

incus launch images:debian/13 git \
  -c limits.cpu=4 \
  -c limits.memory=8GiB \
  -d root,size=100GiB

incus config device override git eth0
incus config device set git eth0 ipv4.address=10.218.184.4
incus restart git

incus launch images:debian/13 twiki

incus config device override twiki eth0
incus config device set twiki eth0 ipv4.address=10.218.184.9
incus restart twiki

incus launch images:debian/13 vaultwarden

incus config device override vaultwarden eth0
incus config device set vaultwarden eth0 ipv4.address=10.218.184.6
incus restart vaultwarden

incus launch images:debian/13 zotero

incus config device override zotero eth0
incus config device set zotero eth0 ipv4.address=10.218.184.7
incus restart zotero

Envoyer des mails sur Redmine

vi /opt/redmine/redmine-5.1.12/config/configuration.yml

production:
  email_delivery:
    delivery_method: :smtp
    smtp_settings:
      address: 10.218.184.1
      port: 587
      domain: cohabit.fr
      authentication: :plain
      user_name: redmine@cohabit.fr
      password: Fablab
      enable_starttls_auto: true
      openssl_verify_mode: none

création adresse mail pour Redmine

maddy creds create redmine@cohabit.fr
# mdp : Fablab

Envoyer des mails sur git

vi /opt/forgejo/forgejo-data/gitea/conf/app.ini 

[mailer]
ENABLED                  = true
FROM                     = forgejo@cohabit.fr
PROTOCOL                 = smtp+starttls
SMTP_ADDR                = 10.218.184.1
SMTP_PORT                = 587
USER                     = forgejo@cohabit.fr
PASSWD                   = 'Fablab'
FORCE_TRUST_SERVER_CERT  = true

création adresse mail pour Git

maddy creds create git-forgejo@cohabit.fr
# mdp : Fablab

Envoyer des mails sur Laravel

vi /opt/laravel/laravel/.env

MAIL_MAILER=smtp
MAIL_HOST=10.218.184.1
MAIL_PORT=587
MAIL_USERNAME=projets@cohabit.fr
MAIL_PASSWORD=Fablab
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=projets@cohabit.fr
MAIL_FROM_NAME="${APP_NAME}" 
vi /opt/laravel/laravel/app/Providers/MailServiceProvider.php

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport;

class MailServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        $transport = $this->app->make('mailer')->getSymfonyTransport();
        if ($transport instanceof EsmtpTransport) {
            $stream = $transport->getStream();
            if (method_exists($stream, 'setStreamOptions')) {
                $stream->setStreamOptions([
                    'ssl' => [
                        'verify_peer'       => false,
                        'verify_peer_name'  => false,
                        'allow_self_signed' => true,
                    ],
                ]);
            }
        }
    }
}
vi /opt/laravel/laravel/config/app.php

'providers' => [
    ...
    App\Providers\MailServiceProvider::class,
],
cd /opt/laravel/laravel
php artisan config:clear
php artisan cache:clear

Il y avait un problème sur Redmine dans l'URL, car quand il n'y a pas de / à la fin de l'URL, ça ne fonctionne pas donc on a fait une redirection de https://projets.cohabit.fr/redmine vers https://projets.cohabit.fr/redmine/ en ajoutant redir /redmine /redmine/ dans /etc/caddy/Caddyfile :

...
projets.cohabit.fr {
    tls /cert/fablab/live/projets.cohabit.fr/fullchain.pem /cert/fablab/live/pr>
    redir /redmine /redmine/
    handle_path /redmine/* {
      reverse_proxy 10.218.184.3:80
    }
    reverse_proxy 10.218.184.3:80
}
...

Jour 51: Lundi 08 juin 2026

Mise à jour de la doc pour redéployer le serveur :
https://projets.cohabit.fr/projects/accueil/wiki/Nicolas_Schmauch_2#Jour-47-Mardi-02-juin-2026


Après avoir fait le transfère de la snapshot de git2 :

rsync -Pa --rsh="ssh -p 55555" root@projets.cohabit.fr:/var/lib/incus/storage-pools/default/containers-snapshots/git2/ /var/lib/incus/storage-pools/default/containers-snapshots/git2/

Mehdi le stagiaire peut travailler sur le problème de création de compte et l'erreur 500 avec de mauvais login.

Mais après le rsync, incus ne détecte pas la snapshot importé, donc nous avons créé une snapshot de git2 avec la commande :

incus snapshot create git2 test

Puis, nous avons supprimé le contenu de la snapshot :

rm -rf /var/lib/incus/storage-pools/default/containers-snapshots/git2/test/*

pour mettre dans celle-ci le contenu de la snapshot importé avec rsync :

mv /var/lib/incus/storage-pools/default/containers-snapshots/git2/hebdo-30-05-2026_00:00:01/*


rsync -Pa --rsh="ssh -p 55555" root@projets.cohabit.fr:/var/lib/incus/storage-pools/default/containers-snapshots/ /var/lib/incus/storage-pools/default/containers-snapshots/

# et

rsync -Pa --rsh="ssh -p 55555" root@projets.cohabit.fr:/var/lib/incus/storage-pools/default/virtual-machines-snapshots/ /var/lib/incus/storage-pools/default/virtual-machines-snapshots/

sur git2 :

ln -s /run/docker.sock /var/run/docker.sock
cd /opt/forgejo/
docker compose up -d


Messagerie sur Nextcloud :

# pour créer le mail de nextcloud
maddy creds create nextcloud@cohabit.fr
# mdp : Fablab

Se connecter avec un compte administrateur → Paramètres d’administrationParamètres de baseServeur de messagerie → remplir comme sur l’image

Mot de passe : Fablab

Jour 52: Mardi 09 juin 2026

Mise à jour du script incus-update.sh car les mails apparaissaient dans les spams :

...
SUBJECT="[toutatis] Rapport mise à jour du $(date '+%Y-%m-%d') (Automatique)" 

cat "$SESSION_LOG" | swaks --to "$MAIL_DEST" \
      --from "projets@cohabit.fr" \
      --server 127.0.0.1 --port 587 --tls \
      --auth-user "projets@cohabit.fr" \
      --auth-password "Fablab" \
      --ehlo "cohabit.fr" \
      --header "Subject: $SUBJECT" \
      --add-header "Date: $(LC_ALL=C date -R)" \
      --add-header "MIME-Version: 1.0" \
      --add-header "Content-Type: text/plain; charset=utf-8" \
      --add-header "Content-Transfer-Encoding: 8bit" \
      --add-header "Message-ID: <$(date +%s)-cron@cohabit.fr>" \
      --body - >> /var/log/swaks-cron.log 2>&1
...


Nous avons trouvé pourquoi nos mails n'arrivez pas dans la boite de réception des adresses Gmail ou dans les spams, car : https://mail-tester.com/test-muzcxg34n


modification de hostname et tls de /etc/maddy/maddy.conf

Jour 53: Mercredi 10 juin 2026

Les erreurs dans Git liées à des logins incorrects ou à la création d’un nouveau compte sont désormais résolues grâce à ce fichier app.ini correctement configuré (nous avons récupéré les informations manquantes de l’ancien fichier app.ini) :

APP_NAME = Forgejo Fablab Cohabit
RUN_MODE = prod
APP_SLOGAN = 
RUN_USER = git
WORK_PATH = /data/gitea

[repository]
ROOT = /data/gitea-repositories
DEFAULT_BRANCH = main
DISABLE_DOWNLOAD_SOURCE_ARCHIVES = true

[ui]
DEFAULT_THEME = forgejo-auto

[repository.local]
LOCAL_COPY_PATH = /data/gitea/tmp/local-repo

[repository.upload]
TEMP_PATH = /data/gitea/uploads

[server]
APP_DATA_PATH = /data/gitea
DOMAIN = git.cohabit.fr
SSH_DOMAIN = git.cohabit.fr
HTTP_PORT = 3000
ROOT_URL = https://git.cohabit.fr/
DISABLE_SSH = false
SSH_PORT = 22222
SSH_LISTEN_PORT = 22
LFS_START_SERVER = true
LFS_JWT_SECRET = NkZOEix2eI9OB8RRbMqYxDXKxSh47Kq8voKg1Yjlk5A
OFFLINE_MODE = true

[database]
PATH = /data/gitea/gitea.db
DB_TYPE = postgres
HOST = db:5432
NAME = gitea
USER = gitea
PASSWD = Zo0EiPha
LOG_SQL = false
SCHEMA = 
SSL_MODE = disable
CHARSET = utf8

[indexer]
ISSUE_INDEXER_PATH = /data/gitea/indexers/issues.bleve

[session]
PROVIDER_CONFIG = /data/gitea/sessions
PROVIDER = file

[picture]
DISABLE_GRAVATAR = false
ENABLE_FEDERATED_AVATAR = true
AVATAR_UPLOAD_PATH = /data/gitea/avatars
REPOSITORY_AVATAR_UPLOAD_PATH = /data/gitea/repo-avatars

[attachment]
PATH = /data/gitea/attachments

[log]
MODE = file
LEVEL = debug,error,info
logger.router.MODE = file
ROOT_PATH = /data/gitea/log

[security]
INSTALL_LOCK = true
SECRET_KEY = pkc3YAYqnq99eOKbduB9CYssdjJFgpWhDY5FpkU24XH4tCrsKTnwuOpeUYr3Kzpx
REVERSE_PROXY_LIMIT = 1
REVERSE_PROXY_TRUSTED_PROXIES = *
INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE2MjM3NDQ1NTJ9.f9CkwbleUsp1OSCAjEMDZtUtOnMNOGYp5-8QU_WRAnw
PASSWORD_HASH_ALGO = pbkdf2
DISABLE_GIT_HOOKS = false

[service]
DISABLE_REGISTRATION = true
REQUIRE_SIGNIN_VIEW = false
REGISTER_EMAIL_CONFIRM = false
ENABLE_NOTIFY_MAIL = false
ALLOW_ONLY_EXTERNAL_REGISTRATION = false
ENABLE_CAPTCHA = false
DEFAULT_KEEP_EMAIL_PRIVATE = false
DEFAULT_ALLOW_CREATE_ORGANIZATION = true
DEFAULT_ENABLE_TIMETRACKING = false
NO_REPLY_ADDRESS = 

[lfs]
PATH = /data/git/lfs

[mailer]
ENABLED                  = true
FROM                     = git-forgejo@cohabit.fr
PROTOCOL                 = smtp+starttls
SMTP_ADDR                = 10.218.184.1
SMTP_PORT                = 587
USER                     = git-forgejo@cohabit.fr
PASSWD                   = 'Fablab'
FORCE_TRUST_SERVER_CERT  = true

[openid]
ENABLE_OPENID_SIGNIN = false
ENABLE_OPENID_SIGNUP = false

[cron.update_checker]
ENABLED = true

[repository.pull-request]
DEFAULT_MERGE_STYLE = merge

[repository.signing]
DEFAULT_TRUST_MODEL = committer

[admin]
DISABLE_REGULAR_ORG_CREATION = false

[oauth2]
JWT_SECRET = ocZlSnBTG5alaFXd3jtc8Eoy-2iASKHRIq2H1NQQxUY

[actions]
ENABLED = true
DEFAULT_ACTIONS_URL = https://code.forgejo.org

Pour créer un utilisateur admin

docker exec -it -u git <nom_du_conteneur> forgejo admin user create --admin --username admintemp --password "MotDePasse123!" --email admintemp@example.com

Pour supprimer l'utilisateur admintemp :

docker exec -it -u git <nom_du_conteneur> forgejo admin user delete --username admintemp

Jour 54: Jeudi 11 juin 2026

Jour 55: Vendredi 12 juin 2026

Jour 56: Lundi 15 juin 2026

Jour 57: Mardi 16 juin 2026

Jour 58: Mercredi 17 juin 2026

Jour 59: Jeudi 18 juin 2026

Jour 60: Vendredi 19 juin 2026

Jour 61: Lundi 22 juin 2026

Jour 62: Mardi 23 juin 2026

Jour 63: Mercredi 24 juin 2026

Jour 64: Jeudi 25 juin 2026

Jour 65: Vendredi 26 juin 2026

Jour 66: Lundi 29 juin 2026

Jour 67: Mardi 30 juin 2026

Jour 68: Mercredi 01 juillet 2026

Jour 69: Jeudi 02 juillet 2026

Jour 70: Vendredi 03 juillet 2026

Jour 71: Lundi 06 juillet 2026

Jour 72: Mardi 07 juillet 2026

Jour 73: Mercredi 08 juillet 2026

Jour 74: Jeudi 09 juillet 2026

Jour 75: Vendredi 10 juillet 2026

Jour 76: Lundi 13 juillet 2026

Jour 77: Mardi 14 juillet 2026

Férié

Jour 78: Mercredi 15 juillet 2026

Jour 79: Jeudi 16 juillet 2026

Jour 80: Vendredi 17 juillet 2026

Jour 81: Lundi 20 juillet 2026

Jour 82: Mardi 21 juillet 2026

Jour 83: Mercredi 22 juillet 2026

Jour 84: Jeudi 23 juillet 2026

Jour 85: Vendredi 24 juillet 2026

Jour 86: Lundi 27 juillet 2026

Jour 87: Mardi 28 juillet 2026

Jour 88: Mercredi 29 juillet 2026

Jour 89: Jeudi 30 juillet 2026

Jour 90: Vendredi 31 juillet 2026