feat(ansible): serveur dédié Satisfactory sur gpu-01 (#74)

* feat(ansible): serveur dédié Satisfactory sur gpu-01

- Nouveau rôle satisfactory_server : install steamcmd (login anonyme,
  app 1690800), service systemd avec update auto en ExecStartPre,
  MemoryMax=16G + Nice=5 (protège le llama-server), firewalld 7777 tcp+udp
- Retries steamcmd (« Missing configuration » rc=8 au premier run)
- Fcontext SELinux bin_t sur steamcmd/ et server/ (203/EXEC sinon)
- gateway : DNAT 192.168.1.200:7777 → gpu-01:7777 (TCP+UDP) + forward
- Doc admin/ops/satisfactory.md (accès, exploitation, pièges rencontrés)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(satisfactory): ouvrir le port 8888/tcp Reliable Messaging

Depuis la 1.1, le client ouvre un socket TCP :8888 en plus du jeu UDP
:7777. Bloqué par firewalld, le join réussissait puis le client était
déconnecté (~60 s) avec « connection to the host has been lost »
(log client : LogReliableMessaging: Failed to connect to server socket).

- firewalld gpu-01 : + 8888/tcp (variable satisfactory_reliable_port)
- gateway s01 : DNAT + forward étendus à 8888/tcp
- doc : tableau des 3 sockets + piège n°3

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(satisfactory): accès internet via IPv4 full-stack Free

Le remap de port Freebox (17777→7777) ne fonctionne pas : le serveur
annonce ses ports dans le protocole (externe = interne obligatoire).
Option IPv4 full-stack activée → redirections 1:1 7777 tcp+udp + 8888 tcp
vers funk-s01, DNAT existant inchangé.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
ALI YESILKAYA 2026-07-03 00:56:28 +02:00 committed by GitHub
parent aaa982f93c
commit 507f4822f0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 290 additions and 1 deletions

View file

@ -94,7 +94,7 @@ Les tags correspondent aux noms de rôles. Tous les rôles sont idempotents.
### Rôles — gpu-01
`hermes_user`, `common`, `rocm`, `nfs_client`, `llama_server`, `node_exporter`
`hermes_user`, `common`, `rocm`, `nfs_client`, `llama_server`, `node_exporter`, `satisfactory_server` (serveur dédié Satisfactory `:7777`, steamcmd + systemd)
> Chaque rôle a un `README.md` (variables, caveats, gaps IaC).
> `lm_studio` est présent dans `ansible/roles/` mais n'est plus utilisé dans aucun playbook (déprécié, voir son README).

71
admin/ops/satisfactory.md Normal file
View file

@ -0,0 +1,71 @@
# Serveur dédié Satisfactory (gpu-01)
Serveur de jeu **Satisfactory** hébergé sur **gpu-01** (192.168.10.20), à côté du
llama-server. Installé par le rôle Ansible **`satisfactory_server`** (steamcmd, login
anonyme Steam, app `1690800`) — service systemd `satisfactory-server`.
## Accès
| Depuis | Adresse |
|---|---|
| LAN domestique (192.168.1.x) | `192.168.10.20:7777` (route via s01) **ou** `192.168.1.200:7777` (DNAT) |
| Internet (amis) | `kaya.freeboxos.fr:7777` — redirections Freebox → funk-s01, le DNAT fait le reste |
> ⚠️ **Le remap de port (ex. 17777→7777) ne marche PAS** : le serveur annonce ses ports
> au client dans le protocole (API → port de jeu, jeu → reliable port). Externe = interne
> obligatoire ([wiki](https://satisfactory.wiki.gg/wiki/Dedicated_servers)). Free IPv4
> partagée ne permettant pas de rediriger < 16384, l'option **IPv4 full-stack** (gratuite,
> espace abonné Free) a été activée le 2026-07-03 (IP fixe `82.67.223.17`) pour pouvoir
> ouvrir 7777/8888 en 1:1. Redirections Freebox requises (destination **funk-s01**) :
> `7777/tcp`, `7777/udp`, `8888/tcp` — ports de début/fin/destination identiques.
Dans le jeu : **Server Manager → Add Server**. Nom du serveur et mot de passe admin se
définissent **en jeu à la première connexion** (pas de fichier de config).
## Réseau
Le serveur utilise **trois sockets** (Satisfactory ≥ 1.1) :
| Port | Proto | Rôle |
|---|---|---|
| 7777 | UDP | Jeu |
| 7777 | TCP | API HTTPS (Server Manager) |
| 8888 | TCP | **Reliable Messaging** — bloqué = déconnexion ~60 s après chaque join |
- **gpu-01 firewalld** : `7777/tcp+udp` + `8888/tcp` ouverts (gérés par le rôle).
Les ports `7778/udp` et `27015/udp` encore ouverts sont des restes pré-1.0, inutiles.
- **storage-01 nftables** (rôle `gateway`) : DNAT `192.168.1.200 → 192.168.10.20`
(7777 TCP+UDP, 8888 TCP) + règles forward associées. Redéploiement : `--tags gateway`.
## Exploitation
```bash
# état / logs (sur gpu-01)
systemctl status satisfactory-server
journalctl -u satisfactory-server -f
# mise à jour du jeu = simple restart (steamcmd +app_update en ExecStartPre)
systemctl restart satisfactory-server
```
- **Install** : `/srv/satisfactory/server` (~10 Go, disque OS), user système `satisfactory`
- **Sauvegardes** : `/srv/satisfactory/.config/Epic/FactoryGame/Saved/SaveGames/server/`
— ⚠️ **aucun backup automatisé** (gap connu, comme etcd/PostgreSQL)
- **Garde-fous ressources** (32 Go partagés avec llama-server) : `MemoryMax=16G` — une
usine late-game qui explose la RAM fait tuer **le jeu**, pas le LLM — et `Nice=5`
(priorité CPU au llama-server). À ajuster via host_vars si besoin.
## Pièges rencontrés à l'installation (2026-07-02)
1. **steamcmd « Missing configuration » (rc=8)** au tout premier `+app_update` — bug
connu steamcmd (app info pas encore en cache). Le rôle fait des retries.
2. **SELinux 203/EXEC** : `steamcmd.sh` extrait depuis /tmp gardait le contexte
`user_tmp_t`, le jeu héritait de `var_t` (/srv) — systemd (init_t) refuse d'exécuter
ces types. Le rôle pose un fcontext **`bin_t`** sur `steamcmd/` et `server/` +
`restorecon`. Symptôme : `Failed at step EXEC ... Permission denied`.
3. **Déconnexion ~60 s après chaque join** (« Your connection to the host has been
lost ») : le port **8888/tcp (Reliable Messaging)** n'était pas ouvert. Le join UDP
réussit, puis le client tente le socket TCP 8888, se fait rejeter par firewalld et
abandonne. Diagnostic dans le log **client**
(`.../FactoryGame/Saved/Logs/FactoryGame.log`) :
`LogReliableMessaging: Error: Failed to connect to server socket`.

View file

@ -8,3 +8,4 @@
- { role: nfs_client, tags: [nfs_client] }
- { role: llama_server, tags: [llama_server] }
- { role: node_exporter, tags: [node_exporter] }
- { role: satisfactory_server, tags: [satisfactory_server] }

View file

@ -67,6 +67,12 @@ table inet filter {
# Réponses WAN → cluster (connexions initiées depuis le cluster)
iif {{ wan_interface }} oif {{ lan_interface }} ct state established,related accept
# Satisfactory — trafic DNATé (192.168.1.200) → gpu-01, quelle que soit la source
# (couvre un éventuel port-forward internet depuis la box).
# 7777 = jeu/API ; 8888 = reliable messaging TCP (1.1) — bloqué = kick à ~60 s
ip daddr 192.168.10.20 tcp dport { 7777, 8888 } ct state new,established,related accept
ip daddr 192.168.10.20 udp dport 7777 ct state new,established,related accept
}
chain output {
@ -77,6 +83,11 @@ table inet filter {
table ip nat {
chain prerouting {
type nat hook prerouting priority -100;
# Satisfactory — DNAT WAN → serveur dédié sur gpu-01
# (joignable via 192.168.1.200 sans route vers 192.168.10.0/24)
iif {{ wan_interface }} tcp dport { 7777, 8888 } dnat to 192.168.10.20
iif {{ wan_interface }} udp dport 7777 dnat to 192.168.10.20:7777
}
chain postrouting {

View file

@ -0,0 +1,35 @@
# satisfactory_server
Serveur dédié **Satisfactory** sur gpu-01 — installé via **steamcmd** (login anonyme, app
Steam `1690800`), lancé par systemd (`satisfactory-server.service`).
## Variables (defaults)
| Variable | Défaut | Rôle |
|---|---|---|
| `satisfactory_user` | `satisfactory` | Utilisateur système du service |
| `satisfactory_home` | `/srv/satisfactory` | Home + racine d'install |
| `satisfactory_install_dir` | `/srv/satisfactory/server` | Fichiers du jeu (~10 Go) |
| `satisfactory_port` | `7777` | Jeu (UDP) + API HTTPS (TCP) |
| `satisfactory_reliable_port` | `8888` | Reliable Messaging (TCP, ≥ 1.1) — **bloqué = kick à ~60 s** |
| `satisfactory_update_on_start` | `true` | `steamcmd +app_update` en `ExecStartPre` à chaque (re)start |
| `satisfactory_memory_max` | `16G` | `MemoryMax` systemd — protège llama-server (32 Go partagés) |
| `satisfactory_nice` | `5` | Priorité CPU réduite vs llama-server |
## Caveats
- **Premier run long** : le task d'installation télécharge ~10 Go depuis Steam
(guardé par `creates:` — ne rejoue jamais). Prévoir 10-30 min selon le débit.
- **Mises à jour du jeu** : gérées par l'`ExecStartPre` du service (à chaque restart),
pas par Ansible. `systemctl restart satisfactory-server` = update + relance.
- **Sauvegardes** : `~satisfactory/.config/Epic/FactoryGame/Saved/SaveGames/server/`
**non sauvegardées ailleurs** (pas de backup automatisé, gap connu).
- **Config serveur** (nom, mot de passe admin) : se fait **en jeu** à la première
connexion (menu Server Manager), pas par fichier.
- **RAM** : usine late-game > 12 Go possible. `MemoryMax=16G` fait tuer le jeu par
l'OOM-killer plutôt que d'affamer le slot LLM (:1234) — augmenter si besoin via host_vars.
- **Réseau** : ton PC (192.168.1.x) joint gpu-01:7777 via storage-01 — la chaîne
`forward` du rôle `gateway` autorise déjà tout le trafic LAN domestique → LAN cluster.
Dans le jeu : ajouter le serveur avec l'adresse `192.168.10.20:7777`.
- Les ports firewalld `7778/udp` et `27015/udp` présents sur gpu-01 sont des restes
des anciens ports « Experimental » (avant la 1.0) — inutiles désormais.

View file

@ -0,0 +1,16 @@
---
satisfactory_user: satisfactory
satisfactory_home: /srv/satisfactory
satisfactory_steamcmd_dir: "{{ satisfactory_home }}/steamcmd"
satisfactory_install_dir: "{{ satisfactory_home }}/server"
satisfactory_app_id: 1690800 # Satisfactory Dedicated Server (Steam, login anonyme)
satisfactory_port: 7777 # jeu (UDP) + API HTTPS (TCP)
satisfactory_reliable_port: 8888 # socket TCP « Reliable Messaging » (depuis la 1.1) —
# bloqué = déconnexion ~60 s après chaque join
satisfactory_update_on_start: true # steamcmd +app_update à chaque (re)démarrage du service
# Garde-fous ressources : gpu-01 partage ses 32 Go avec llama-server (~10 Go résidents).
# Une usine late-game peut dépasser 12 Go — MemoryMax fait tuer le jeu par l'OOM-killer
# plutôt que d'affamer le slot LLM. Nice > 0 : priorité CPU au llama-server.
satisfactory_memory_max: 16G
satisfactory_nice: 5

View file

@ -0,0 +1,9 @@
---
- name: Reload systemd
ansible.builtin.systemd:
daemon_reload: true
- name: Restart satisfactory-server
ansible.builtin.systemd:
name: satisfactory-server
state: restarted

View file

@ -0,0 +1,123 @@
---
# --- Dépendances steamcmd (binaire 32 bits) ---
- name: Install steamcmd 32-bit dependencies
ansible.builtin.dnf:
name:
- glibc.i686
- libstdc++.i686
- policycoreutils-python-utils # semanage (contexte SELinux, cf. plus bas)
state: present
# --- Utilisateur système ---
- name: Create satisfactory system user
ansible.builtin.user:
name: "{{ satisfactory_user }}"
system: true
shell: /sbin/nologin
home: "{{ satisfactory_home }}"
create_home: true
state: present
- name: Create install directories
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: "{{ satisfactory_user }}"
group: "{{ satisfactory_user }}"
mode: '0750'
loop:
- "{{ satisfactory_steamcmd_dir }}"
- "{{ satisfactory_install_dir }}"
# --- steamcmd ---
- name: Download steamcmd
ansible.builtin.get_url:
url: "https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz"
dest: /tmp/steamcmd_linux.tar.gz
mode: '0644'
- name: Extract steamcmd
ansible.builtin.unarchive:
src: /tmp/steamcmd_linux.tar.gz
dest: "{{ satisfactory_steamcmd_dir }}"
remote_src: true
owner: "{{ satisfactory_user }}"
group: "{{ satisfactory_user }}"
creates: "{{ satisfactory_steamcmd_dir }}/steamcmd.sh"
# --- Installation initiale du jeu (~10 Go — long au premier run) ---
# Les mises à jour suivantes passent par l'ExecStartPre du service
# (satisfactory_update_on_start), pas par ce task.
# Retries : au tout premier run, steamcmd échoue souvent avec « Missing configuration »
# (rc=8) — l'app info n'est pas encore en cache. Le run suivant passe.
- name: Install Satisfactory dedicated server via steamcmd (first run only)
ansible.builtin.command:
cmd: >
{{ satisfactory_steamcmd_dir }}/steamcmd.sh
+force_install_dir {{ satisfactory_install_dir }}
+login anonymous
+app_update {{ satisfactory_app_id }} validate
+quit
creates: "{{ satisfactory_install_dir }}/FactoryServer.sh"
become: true
become_user: "{{ satisfactory_user }}"
environment:
HOME: "{{ satisfactory_home }}"
register: satisfactory_steamcmd_install
retries: 3
delay: 10
until: satisfactory_steamcmd_install.rc | default(0) == 0
# --- SELinux ---
# L'unarchive depuis /tmp laisse steamcmd.sh en user_tmp_t et le jeu hérite de var_t
# (/srv) : systemd (init_t) refuse d'exécuter ces types → 203/EXEC Permission denied.
# bin_t sur les deux arborescences exécutables règle le problème.
- name: Set bin_t SELinux context on executable trees
community.general.sefcontext:
target: "{{ satisfactory_home }}/(steamcmd|server)(/.*)?"
setype: bin_t
state: present
when: ansible_selinux.status | default('disabled') == 'enabled'
- name: Apply SELinux contexts
ansible.builtin.command:
cmd: restorecon -R {{ satisfactory_home }}
changed_when: false
when: ansible_selinux.status | default('disabled') == 'enabled'
# --- Service systemd ---
- name: Install satisfactory-server systemd unit
ansible.builtin.template:
src: satisfactory-server.service.j2
dest: /etc/systemd/system/satisfactory-server.service
mode: '0644'
notify:
- Reload systemd
- Restart satisfactory-server
- name: Enable satisfactory-server service
ansible.builtin.systemd:
name: satisfactory-server
enabled: true
daemon_reload: true
state: started
# --- Firewall ---
# 7777 : jeu (UDP) + API HTTPS (TCP). 8888 : reliable messaging (TCP, depuis la 1.1).
- name: Open game ports in firewall
ansible.posix.firewalld:
port: "{{ item }}"
permanent: true
state: enabled
immediate: true
loop:
- "{{ satisfactory_port }}/tcp"
- "{{ satisfactory_port }}/udp"
- "{{ satisfactory_reliable_port }}/tcp"

View file

@ -0,0 +1,23 @@
[Unit]
Description=Satisfactory dedicated server (steamcmd, app {{ satisfactory_app_id }})
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User={{ satisfactory_user }}
WorkingDirectory={{ satisfactory_install_dir }}
{% if satisfactory_update_on_start %}
# Mise à jour du jeu avant chaque démarrage (login anonyme) — ajoute ~1 min au boot.
# Désactivable via satisfactory_update_on_start: false (host_vars).
ExecStartPre={{ satisfactory_steamcmd_dir }}/steamcmd.sh +force_install_dir {{ satisfactory_install_dir }} +login anonymous +app_update {{ satisfactory_app_id }} +quit
{% endif %}
ExecStart={{ satisfactory_install_dir }}/FactoryServer.sh -Port={{ satisfactory_port }} -unattended
Restart=on-failure
RestartSec=15
Nice={{ satisfactory_nice }}
MemoryMax={{ satisfactory_memory_max }}
TimeoutStartSec=600
[Install]
WantedBy=multi-user.target