Phase 1 : socle & sécurité (WireGuard, Ansible, OpenTofu)
- Reconnaissance : le serveur héberge déjà 6 autres projets derrière un Caddy partagé (80/443) ; tripoli-edge sera un service interne (décision prise avec l'utilisateur), pas un second frontal. - WireGuard : canal admin Mac <-> serveur (10.100.0.0/24, port 58212/udp après avoir découvert que 51820 était filtré par le pare-feu Hetzner Cloud, distinct de ufw). - Ansible : rôle base (confirme fail2ban/unattended-upgrades/ufw déjà en place, sans toucher aux règles des autres projets) + rôle wireguard (clé privée générée sur la cible, jamais stockée dans ce dépôt). - OpenTofu : serveur Hetzner référencé en data source lecture seule (jamais recréé/détruit - serveur partagé et déjà en prod) ; DNS tripoli.cloud géré via le provider OVH. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
5dbd3b96f6
commit
2fa16083c3
17 changed files with 427 additions and 1 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -16,7 +16,6 @@ target/
|
||||||
*.tfstate.*
|
*.tfstate.*
|
||||||
*.tfvars
|
*.tfvars
|
||||||
!*.tfvars.example
|
!*.tfvars.example
|
||||||
.terraform.lock.hcl
|
|
||||||
|
|
||||||
# Secrets
|
# Secrets
|
||||||
*.pem
|
*.pem
|
||||||
|
|
|
||||||
41
README.md
Normal file
41
README.md
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
# Tripoli Cloud
|
||||||
|
|
||||||
|
Cloud personnel (photos, sauvegardes, partage de fichiers) construit pour
|
||||||
|
démontrer des compétences SRE proches de la stack de Clever Cloud (Rust,
|
||||||
|
netlink, WireGuard, CI/CD). **Un seul serveur, pas un cluster** — ce README
|
||||||
|
reste honnête sur ce qui est réellement en place vs prévu.
|
||||||
|
|
||||||
|
## Serveur
|
||||||
|
|
||||||
|
Hetzner CPX22 (2 vCPU, 3.7 Go RAM, 75 Go disque), Ubuntu 26.04, `65.109.142.188`
|
||||||
|
/ `tripoli.cloud`. **Ce serveur héberge déjà six autres projets en
|
||||||
|
production** (Forgejo, lecheminvert.tech, check-my-irve, iot-alarm-system,
|
||||||
|
voltvision, monitoring Prometheus/Grafana) derrière un Caddy partagé qui
|
||||||
|
tient les ports 80/443. Décision assumée : le futur `tripoli-edge` (Rust)
|
||||||
|
sera un service **interne**, derrière ce Caddy — pas un second frontal en
|
||||||
|
concurrence sur 80/443. Reprendre ces ports casserait les sites existants.
|
||||||
|
|
||||||
|
Pas de virtualisation imbriquée (KVM/QEMU) sur ce VPS Hetzner Cloud — non
|
||||||
|
tenté, non simulé.
|
||||||
|
|
||||||
|
## État réel
|
||||||
|
|
||||||
|
| Composant | État |
|
||||||
|
|---|---|
|
||||||
|
| Prototype frontend (`app/`) | Fait — React/TS/Vite/Tailwind, maquette |
|
||||||
|
| Reconnaissance serveur (Phase 0) | Fait |
|
||||||
|
| WireGuard (canal admin Mac ↔ serveur) | Fait — `10.100.0.0/24`, port 58212/udp (51820 filtré par le pare-feu Hetzner Cloud, voir `ansible/group_vars/all.yml`) |
|
||||||
|
| Pare-feu / fail2ban / unattended-upgrades | Déjà en place avant ce projet ; confirmés/complétés par Ansible |
|
||||||
|
| SSH public | Toujours ouvert (choix explicite, pas encore restreint au VPN) |
|
||||||
|
| Ansible (rôle de base) | Fait — `ansible/` |
|
||||||
|
| OpenTofu (serveur + DNS) | Code écrit — `tofu/` ; serveur en donnée en lecture seule (jamais recréé/détruit), DNS géré via le provider OVH. `plan`/`apply` pas encore exécutés (nécessitent des identifiants OVH/Hetzner non fournis) |
|
||||||
|
| `tripoli-edge` (Rust, Phase 2) | Pas commencé |
|
||||||
|
| `tripoli-api` (Rust, Phase 3) | Pas commencé |
|
||||||
|
| `tripoli-netctl` (Rust, Phase 4) | Pas commencé |
|
||||||
|
| Observabilité dédiée (Phase 5) | Pas commencé (une stack Prometheus/Grafana existe déjà sur le serveur, partagée avec les autres projets) |
|
||||||
|
| Bird/BGP (Phase 6, bonus) | Pas commencé |
|
||||||
|
|
||||||
|
## Dépôt
|
||||||
|
|
||||||
|
Code poussé sur `forge.adam.belkacemi.me/adam/tripoli.cloud` (même
|
||||||
|
instance Forgejo que les autres projets hébergés sur ce serveur).
|
||||||
39
ansible/README.md
Normal file
39
ansible/README.md
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
# Ansible — socle Tripoli Cloud
|
||||||
|
|
||||||
|
Un seul hôte (`tripoli_cloud`), pas d'inventaire dynamique : ce serait
|
||||||
|
simuler une flotte qui n'existe pas. Le serveur héberge aussi cinq autres
|
||||||
|
projets ; ce dépôt ne gère que ce qui concerne Tripoli Cloud (voir les
|
||||||
|
commentaires dans `roles/base/tasks/main.yml`).
|
||||||
|
|
||||||
|
## Lancer
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ansible-galaxy collection install -r requirements.yml # une fois
|
||||||
|
ansible-playbook site.yml --diff
|
||||||
|
```
|
||||||
|
|
||||||
|
`ansible_host` pointe pour l'instant sur l'IP publique (`inventory/hosts.yml`)
|
||||||
|
car le SSH public reste ouvert par choix explicite. Une fois le SSH public
|
||||||
|
fermé (`ssh_restrict_to_wireguard: true` dans `group_vars/all.yml`), changer
|
||||||
|
`ansible_host` pour `10.100.0.1` et lancer les playbooks depuis une machine
|
||||||
|
dont le tunnel WireGuard est monté — sinon Ansible ne pourra plus joindre le
|
||||||
|
serveur.
|
||||||
|
|
||||||
|
## Secrets
|
||||||
|
|
||||||
|
Aucun secret dans ce dépôt, volontairement :
|
||||||
|
|
||||||
|
- La clé privée WireGuard du serveur est générée **sur le serveur** par le
|
||||||
|
rôle `wireguard` (`wg genkey`, `creates:` la rend idempotente) et n'en sort
|
||||||
|
jamais. Pas de vault, pas de fichier chiffré à gérer.
|
||||||
|
- Les clés publiques des pairs (Mac admin, etc.) ne sont pas sensibles :
|
||||||
|
elles vivent en clair dans `group_vars/all.yml`.
|
||||||
|
|
||||||
|
## `ssh_restrict_to_wireguard`
|
||||||
|
|
||||||
|
À `false` (valeur actuelle) : le rôle ne touche pas à la règle `22/tcp`
|
||||||
|
existante (ouverte à "Anywhere", posée manuellement avant Ansible).
|
||||||
|
À `true` : le rôle retire cette règle et n'autorise plus le 22/tcp que
|
||||||
|
depuis `10.100.0.0/24`. Vérifier que le tunnel fonctionne avant de basculer
|
||||||
|
ce flag — sinon risque de perdre l'accès root normal (recours : console de
|
||||||
|
secours Hetzner, indépendante du réseau).
|
||||||
4
ansible/ansible.cfg
Normal file
4
ansible/ansible.cfg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
[defaults]
|
||||||
|
inventory = inventory/hosts.yml
|
||||||
|
host_key_checking = True
|
||||||
|
retry_files_enabled = False
|
||||||
18
ansible/group_vars/all.yml
Normal file
18
ansible/group_vars/all.yml
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
---
|
||||||
|
# WireGuard : canal d'administration, pas un VPN "route tout le trafic".
|
||||||
|
# Chaque pair n'a le droit de joindre que l'autre bout du tunnel (/32),
|
||||||
|
# jamais le reste du réseau du serveur.
|
||||||
|
wireguard_listen_port: 58212 # port non standard : 51820 (défaut) est filtré
|
||||||
|
# en amont par le pare-feu Hetzner Cloud sur ce projet ; voir README.
|
||||||
|
wireguard_server_address: "10.100.0.1/24"
|
||||||
|
wireguard_peers:
|
||||||
|
- name: mac-admin
|
||||||
|
public_key: "lG5XN+YazR4qgFH75kK41ouQGP00GBaygC1h85NrFmg="
|
||||||
|
allowed_ips: "10.100.0.2/32"
|
||||||
|
|
||||||
|
# Tant que false, le SSH public (22/tcp depuis Anywhere) reste ouvert.
|
||||||
|
# À ne passer à true qu'après avoir vérifié que le tunnel WireGuard
|
||||||
|
# fonctionne depuis la machine d'administration - sinon risque de perdre
|
||||||
|
# l'accès root normal (recours : console de secours Hetzner).
|
||||||
|
ssh_restrict_to_wireguard: false
|
||||||
|
wireguard_subnet: "10.100.0.0/24"
|
||||||
13
ansible/inventory/hosts.yml
Normal file
13
ansible/inventory/hosts.yml
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
---
|
||||||
|
# Un seul serveur : pas de groupe de production/inventaire dynamique, ce
|
||||||
|
# serait simuler une flotte qui n'existe pas.
|
||||||
|
all:
|
||||||
|
hosts:
|
||||||
|
tripoli_cloud:
|
||||||
|
# IP publique pour l'instant : l'accès SSH public reste ouvert
|
||||||
|
# (décision explicite, pas encore restreint au tunnel WireGuard).
|
||||||
|
# Une fois le SSH public fermé, remplacer par 10.100.0.1 (IP WireGuard)
|
||||||
|
# et lancer les playbooks depuis une machine dont le tunnel est monté.
|
||||||
|
ansible_host: 65.109.142.188
|
||||||
|
ansible_user: root
|
||||||
|
ansible_ssh_private_key_file: ~/.ssh/hetzner
|
||||||
3
ansible/requirements.yml
Normal file
3
ansible/requirements.yml
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
---
|
||||||
|
collections:
|
||||||
|
- name: community.general
|
||||||
53
ansible/roles/base/tasks/main.yml
Normal file
53
ansible/roles/base/tasks/main.yml
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
---
|
||||||
|
# Ce rôle ne fait que confirmer/maintenir un socle déjà en place sur ce
|
||||||
|
# serveur (partagé avec d'autres projets) : il n'active PAS ufw depuis
|
||||||
|
# zéro et ne touche à aucune règle existante en dehors de celles listées
|
||||||
|
# ici. Reproduire tout le pare-feu depuis ce rôle casserait les autres
|
||||||
|
# sites hébergés (Forgejo, iot-alarm, voltvision, etc.).
|
||||||
|
|
||||||
|
- name: S'assurer que fail2ban est installé et actif
|
||||||
|
ansible.builtin.apt:
|
||||||
|
name: fail2ban
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: S'assurer que fail2ban est démarré et activé au boot
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: fail2ban
|
||||||
|
state: started
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
- name: S'assurer que les mises à jour de sécurité automatiques sont actives
|
||||||
|
ansible.builtin.apt:
|
||||||
|
name: unattended-upgrades
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: S'assurer que ufw est actif (deny incoming par défaut)
|
||||||
|
community.general.ufw:
|
||||||
|
state: enabled
|
||||||
|
policy: deny
|
||||||
|
direction: incoming
|
||||||
|
|
||||||
|
- name: Autoriser le port WireGuard (règle spécifique à Tripoli Cloud)
|
||||||
|
community.general.ufw:
|
||||||
|
rule: allow
|
||||||
|
port: "{{ wireguard_listen_port }}"
|
||||||
|
proto: udp
|
||||||
|
comment: "tripoli-cloud : WireGuard admin"
|
||||||
|
|
||||||
|
- name: SSH restreint au tunnel WireGuard uniquement
|
||||||
|
when: ssh_restrict_to_wireguard
|
||||||
|
block:
|
||||||
|
- name: Retirer la règle SSH publique (Anywhere)
|
||||||
|
community.general.ufw:
|
||||||
|
rule: allow
|
||||||
|
port: "22"
|
||||||
|
proto: tcp
|
||||||
|
delete: true
|
||||||
|
|
||||||
|
- name: Autoriser SSH uniquement depuis le sous-réseau WireGuard
|
||||||
|
community.general.ufw:
|
||||||
|
rule: allow
|
||||||
|
port: "22"
|
||||||
|
proto: tcp
|
||||||
|
src: "{{ wireguard_subnet }}"
|
||||||
|
comment: "tripoli-cloud : SSH admin via WireGuard uniquement"
|
||||||
5
ansible/roles/wireguard/handlers/main.yml
Normal file
5
ansible/roles/wireguard/handlers/main.yml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
- name: redémarrer wireguard
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: wg-quick@wg0
|
||||||
|
state: restarted
|
||||||
61
ansible/roles/wireguard/tasks/main.yml
Normal file
61
ansible/roles/wireguard/tasks/main.yml
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
---
|
||||||
|
# La clé privée du serveur est générée SUR le serveur et n'en sort
|
||||||
|
# jamais : pas de vault, pas de secret dans ce dépôt. `creates:` rend la
|
||||||
|
# génération idempotente - un ré-exécution ne régénère pas une clé déjà
|
||||||
|
# en place.
|
||||||
|
|
||||||
|
- name: Installer le paquet wireguard
|
||||||
|
ansible.builtin.apt:
|
||||||
|
name: wireguard
|
||||||
|
state: present
|
||||||
|
update_cache: true
|
||||||
|
|
||||||
|
- name: Restreindre les permissions du dossier /etc/wireguard
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: /etc/wireguard
|
||||||
|
state: directory
|
||||||
|
mode: "0700"
|
||||||
|
|
||||||
|
- name: Générer la clé privée si absente
|
||||||
|
ansible.builtin.shell: umask 077 && wg genkey > /etc/wireguard/privatekey
|
||||||
|
args:
|
||||||
|
creates: /etc/wireguard/privatekey
|
||||||
|
|
||||||
|
- name: Restreindre les permissions de la clé privée
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: /etc/wireguard/privatekey
|
||||||
|
mode: "0600"
|
||||||
|
|
||||||
|
- name: Dériver la clé publique correspondante
|
||||||
|
ansible.builtin.shell: wg pubkey < /etc/wireguard/privatekey > /etc/wireguard/publickey
|
||||||
|
args:
|
||||||
|
creates: /etc/wireguard/publickey
|
||||||
|
|
||||||
|
- name: Lire la clé privée (jamais affichée dans les logs)
|
||||||
|
ansible.builtin.slurp:
|
||||||
|
path: /etc/wireguard/privatekey
|
||||||
|
register: wg_private_key_raw
|
||||||
|
no_log: true
|
||||||
|
|
||||||
|
- name: Déployer wg0.conf
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: wg0.conf.j2
|
||||||
|
dest: /etc/wireguard/wg0.conf
|
||||||
|
mode: "0600"
|
||||||
|
no_log: true
|
||||||
|
notify: redémarrer wireguard
|
||||||
|
|
||||||
|
- name: Activer et démarrer wg-quick@wg0
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: wg-quick@wg0
|
||||||
|
state: started
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
- name: Lire la clé publique du serveur (information non sensible)
|
||||||
|
ansible.builtin.slurp:
|
||||||
|
path: /etc/wireguard/publickey
|
||||||
|
register: wg_public_key_raw
|
||||||
|
|
||||||
|
- name: Afficher la clé publique du serveur
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: "Clé publique WireGuard du serveur : {{ (wg_public_key_raw.content | b64decode).strip() }}"
|
||||||
12
ansible/roles/wireguard/templates/wg0.conf.j2
Normal file
12
ansible/roles/wireguard/templates/wg0.conf.j2
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
[Interface]
|
||||||
|
Address = {{ wireguard_server_address }}
|
||||||
|
ListenPort = {{ wireguard_listen_port }}
|
||||||
|
PrivateKey = {{ (wg_private_key_raw.content | b64decode).strip() }}
|
||||||
|
|
||||||
|
{% for peer in wireguard_peers %}
|
||||||
|
[Peer]
|
||||||
|
# {{ peer.name }}
|
||||||
|
PublicKey = {{ peer.public_key }}
|
||||||
|
AllowedIPs = {{ peer.allowed_ips }}
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
7
ansible/site.yml
Normal file
7
ansible/site.yml
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
- name: Socle Tripoli Cloud
|
||||||
|
hosts: tripoli_cloud
|
||||||
|
become: true
|
||||||
|
roles:
|
||||||
|
- base
|
||||||
|
- wireguard
|
||||||
70
tofu/.terraform.lock.hcl
generated
Normal file
70
tofu/.terraform.lock.hcl
generated
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
# This file is maintained automatically by "tofu init".
|
||||||
|
# Manual edits may be lost in future updates.
|
||||||
|
|
||||||
|
provider "registry.opentofu.org/hetznercloud/hcloud" {
|
||||||
|
version = "1.69.0"
|
||||||
|
constraints = "~> 1.48"
|
||||||
|
hashes = [
|
||||||
|
"h1:/pWxd5Osefkg7cjjoocgUGsgV+ogMNR5Bj8NSUDLXUU=",
|
||||||
|
"h1:3LoUpRjqu3O/vSVTdHPQSMXvGSAA9g4O3emY8qhQPpw=",
|
||||||
|
"h1:3eLWWMWbF/q/UYAvhFbRfL6RwAs9cACPvi0N4W+vi14=",
|
||||||
|
"h1:H9WTbWGVkHW9oixJWfNOD9E6TdDVRwmVjFiHDqfe6hk=",
|
||||||
|
"h1:HGJANuVqrAGQiSN/m6DODhf05D7rSugUpriG2bPhBuM=",
|
||||||
|
"h1:MLxpeLlRL97wDLqL+WNpb7L9Vk+g8aUHLjXspFPR+tg=",
|
||||||
|
"h1:PWhmd5g+OPBPdwQoFkG+KFmppYSR6ujPof+zwCjfMPI=",
|
||||||
|
"h1:XuuF9gGyXrmeINI2/SdgfoaJntzP231hsM+MSbaQ5QM=",
|
||||||
|
"h1:e4gDEHjE7p660CTFwnkWS3ibVNV54nT7e1TliOJ0cqw=",
|
||||||
|
"h1:owcgdkfmWRbaGgiIl2HY5AFQdFENckbPcn91NJpr1r4=",
|
||||||
|
"h1:sRkgVfXIDHNIyGR/kOVjIa45/sxH2JzCHNzDQlKgdF4=",
|
||||||
|
"h1:xxSPOhL3DApttfBxP8GeH91tf1ywGw56NeNqpkD2P2k=",
|
||||||
|
"h1:z7i3QamSF7nU+9dj+5+g7JTRwtos+kAfkwQpZjbx+/Q=",
|
||||||
|
"zh:023c0580c46b48a1dce8aeb5832ec9896ce53ee019a2b296d854873f92aad7d9",
|
||||||
|
"zh:02fa14eebb1c48b52b9acb33fb987425513537b70a50dac5639aecfe62c4b66f",
|
||||||
|
"zh:0f534ee078e5f0f3c21e99cc020c3a9be565fceed4936d88b978f7b7d4b981fc",
|
||||||
|
"zh:42dcf6859d097179c30cbe346931be731548a51ef7c73d08cd4af69a044bd60f",
|
||||||
|
"zh:6d319ee6c9e64437634f3e682196dab24025ea91a7332b40972279e9c6e67f64",
|
||||||
|
"zh:71a1dc59a47ab1b4127696e8047b54bb4bc8f92b8994ecc13509eaeb7e13a729",
|
||||||
|
"zh:781310b68c74dede2e225096d7ada9a0b4b9252b6cafa4dc1ff064dd9ea4c817",
|
||||||
|
"zh:7c4e0411e6e10ca6bd49c8e699c7c61cd9750d32d46ee5472d01faa30fbd222f",
|
||||||
|
"zh:c4c70587a7632aea65d6b1bb0bbe8da2145a7acb6d16a7dd0e0dbbde6195c8ca",
|
||||||
|
"zh:caaab2e9bb7cf8bb478b143bc0156c40729d720d3e2c77cf021e515fbeca3a6a",
|
||||||
|
"zh:cbd1c2040111b595cfa5daa201166560b4988eb0227dd6be17e8ae72a5ff1df0",
|
||||||
|
"zh:eba595371b98a66c1bf64dfb9f9256e6dc365e990e7202c50b6977fb3d24f822",
|
||||||
|
"zh:fd477dca1d5b0bc7f41cce6b9f3e3250b58597cc178a70fa3136717343ffeede",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "registry.opentofu.org/ovh/ovh" {
|
||||||
|
version = "1.8.0"
|
||||||
|
constraints = "~> 1.1"
|
||||||
|
hashes = [
|
||||||
|
"h1:Cw+yHbVdi3SErNVKDfjkT7uJrkrz1iDoLKbtUYcD/vY=",
|
||||||
|
"h1:Emge4OtI8D3utNeWm9ZGp1dXJZGNM4XkfucpeECH/OM=",
|
||||||
|
"h1:EnUJhaLpRyNo9jyFbqNysVuB41aUHU2fPyDZviNyjQw=",
|
||||||
|
"h1:JiQ+u8Weik9cagC9zwQzgg50dSQCWIVSOcm6cBb/GLI=",
|
||||||
|
"h1:L70RPaCt9ynhgDA+ysLpg9XGUspDe/FV/i0bZwSDSQc=",
|
||||||
|
"h1:OY1R8TWQKdAa4/IlAFIhkz1mrjdCU4h7yjkdcaG3EVY=",
|
||||||
|
"h1:PxCb7XQsQSM+F7zL2uvQUU3c/GlHUX3achf8lZFE4GQ=",
|
||||||
|
"h1:SL4jA6hX+8gQmzogggo/T928Atu2uWxTSfwij5swnKg=",
|
||||||
|
"h1:UMNAYIM6tuCeexI0uNVvlY1BY38xELgDGR7mrl18gHk=",
|
||||||
|
"h1:gQAf5ZrxwGhPgEO8MYeExH05YNZaIwLGDPY7GUq89rs=",
|
||||||
|
"h1:giGZL9GqvPoLmwSSJei6xvM11NkPtRyYH4GvEH7PaRA=",
|
||||||
|
"h1:uforg5SSBOVHu0bHCVuKJhqv6TqI1q2oLiWid1WJDB0=",
|
||||||
|
"h1:yCfHzFrXA4RFEQUEpbfzWzeT0sa05yHz1U36I2EaYtQ=",
|
||||||
|
"h1:zq2m7JCCjXoIYoYJrSyD4Sn7D6bQErm05cbw28xRhdE=",
|
||||||
|
"zh:13de915a1de2f2d52daf3843be04b73b8c28102b4798b6519226ceba6de462a1",
|
||||||
|
"zh:229a6b42faaac23855d5b894a3d1116aa4bfe959870bf52820eba645dd84d240",
|
||||||
|
"zh:37c04f19df0462ae399d63da2a20efe16c8234302ef6ef7999f4ab9b17f82c84",
|
||||||
|
"zh:4145c35b7b06a3557c7d06694b4608356abb6c368b5ce94dea407b548c181213",
|
||||||
|
"zh:6848937828ec8f38fad2776abdfbcffc3cd353fbfb5885a2c1987775781e522b",
|
||||||
|
"zh:6d071a2ddcaddb318452b46782ae2f239acb1c83953248521858b59ff9651c98",
|
||||||
|
"zh:825ac2271e7f532d40e4633380fbe6ea7efca733b1e25ed01ed02e10bbb6e2bc",
|
||||||
|
"zh:9d24c56db0ced569615f84462d362a984d930cf21bbdbe69cf029cd89b9b179a",
|
||||||
|
"zh:a87773b85c2c732f8d03b65058cb595ec78a7fd5ad29ea61e69d4d3141675c40",
|
||||||
|
"zh:be454013f64a42f24cb1d7e14090dd03257fc387f0fff0e2992db77b47c125cf",
|
||||||
|
"zh:d4613672416be037a929e92d4c32e4d40eb5a7db4e2b67c689be6a00d91a7f46",
|
||||||
|
"zh:e435a038d9dc53bd3249c6f155311ea0bcbbba753e65236b2b5b630ae5917eb0",
|
||||||
|
"zh:ea0d3097edfbfd3e55140a04d570436fd62f2c18b7d4abbed3eb7d8a6781c875",
|
||||||
|
"zh:f88df12b81d7cbc320cf40dbca3cc45293cc74b8535362ed1a8360c487d744ee",
|
||||||
|
]
|
||||||
|
}
|
||||||
40
tofu/README.md
Normal file
40
tofu/README.md
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
# OpenTofu — serveur & DNS en code
|
||||||
|
|
||||||
|
## Ce que ça gère
|
||||||
|
|
||||||
|
- `data "hcloud_server"` : référence **en lecture seule** le serveur
|
||||||
|
Hetzner existant (`tripoli-cloud`). Volontairement pas une `resource` :
|
||||||
|
ce serveur héberge six autres projets en production, et le gérer comme
|
||||||
|
une ressource Tofu ferait courir le risque qu'un `apply` mal aligné le
|
||||||
|
recrée ou le détruise. Cette IaC documente le serveur, elle ne le pilote
|
||||||
|
pas.
|
||||||
|
- `ovh_domain_zone_record` : les enregistrements A de `tripoli.cloud` et
|
||||||
|
`www.tripoli.cloud`, pointés sur l'IP du serveur ci-dessus. Seule la zone
|
||||||
|
`tripoli.cloud` est touchée — les autres domaines du même compte OVH
|
||||||
|
(lecheminvert.tech, etc.) ne sont pas dans le champ de ce projet.
|
||||||
|
|
||||||
|
## Identifiants
|
||||||
|
|
||||||
|
Aucun token dans ce dépôt ni dans un `.tfvars`. Les providers lisent
|
||||||
|
directement l'environnement :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export HCLOUD_TOKEN=...
|
||||||
|
export OVH_ENDPOINT=ovh-eu
|
||||||
|
export OVH_APPLICATION_KEY=...
|
||||||
|
export OVH_APPLICATION_SECRET=...
|
||||||
|
export OVH_CONSUMER_KEY=...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Lancer
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tofu init
|
||||||
|
tofu plan
|
||||||
|
tofu apply
|
||||||
|
```
|
||||||
|
|
||||||
|
`tofu init` et `tofu validate` ont déjà été exécutés dans cette session
|
||||||
|
(sans toucher à l'infra réelle) pour vérifier la syntaxe. `plan`/`apply`
|
||||||
|
nécessitent les identifiants ci-dessus, qui n'ont pas été demandés ni
|
||||||
|
extraits automatiquement.
|
||||||
30
tofu/main.tf
Normal file
30
tofu/main.tf
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
# Le serveur existe déjà et héberge six autres projets en production
|
||||||
|
# (Forgejo, lecheminvert, check-my-irve, iot-alarm-system, voltvision,
|
||||||
|
# monitoring). Le déclarer en `resource "hcloud_server"` ferait courir
|
||||||
|
# le risque qu'un `tofu apply` mal aligné avec l'état réel décide de le
|
||||||
|
# recréer/détruire - inacceptable sur un serveur partagé et déjà en
|
||||||
|
# prod pour d'autres projets. On le référence en lecture seule via une
|
||||||
|
# data source : cette IaC documente le serveur, elle ne le pilote pas.
|
||||||
|
data "hcloud_server" "tripoli_cloud" {
|
||||||
|
name = "tripoli-cloud"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Le domaine tripoli.cloud est enregistré et sa zone DNS gérée chez OVH
|
||||||
|
# (registrar + DNS), pas chez Hetzner. Seule la zone tripoli.cloud est
|
||||||
|
# touchée ici - les autres domaines du même compte OVH (lecheminvert.tech,
|
||||||
|
# etc.) ne sont pas dans le champ de ce projet.
|
||||||
|
resource "ovh_domain_zone_record" "root" {
|
||||||
|
zone = "tripoli.cloud"
|
||||||
|
subdomain = ""
|
||||||
|
fieldtype = "A"
|
||||||
|
target = data.hcloud_server.tripoli_cloud.ipv4_address
|
||||||
|
ttl = 3600
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "ovh_domain_zone_record" "www" {
|
||||||
|
zone = "tripoli.cloud"
|
||||||
|
subdomain = "www"
|
||||||
|
fieldtype = "A"
|
||||||
|
target = data.hcloud_server.tripoli_cloud.ipv4_address
|
||||||
|
ttl = 3600
|
||||||
|
}
|
||||||
8
tofu/outputs.tf
Normal file
8
tofu/outputs.tf
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
output "server_ipv4" {
|
||||||
|
value = data.hcloud_server.tripoli_cloud.ipv4_address
|
||||||
|
description = "IP publique du serveur (source de vérité : Hetzner Cloud, pas un état local)"
|
||||||
|
}
|
||||||
|
|
||||||
|
output "dns_root_record" {
|
||||||
|
value = ovh_domain_zone_record.root.target
|
||||||
|
}
|
||||||
23
tofu/versions.tf
Normal file
23
tofu/versions.tf
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
terraform {
|
||||||
|
required_version = ">= 1.6.0"
|
||||||
|
required_providers {
|
||||||
|
hcloud = {
|
||||||
|
source = "hetznercloud/hcloud"
|
||||||
|
version = "~> 1.48"
|
||||||
|
}
|
||||||
|
ovh = {
|
||||||
|
source = "ovh/ovh"
|
||||||
|
version = "~> 1.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Pas de bloc "provider" avec un token en dur : hcloud lit HCLOUD_TOKEN et
|
||||||
|
# ovh lit OVH_ENDPOINT / OVH_APPLICATION_KEY / OVH_APPLICATION_SECRET /
|
||||||
|
# OVH_CONSUMER_KEY dans l'environnement. Rien à mettre dans ce dépôt ni
|
||||||
|
# dans un fichier .tfvars.
|
||||||
|
provider "hcloud" {}
|
||||||
|
|
||||||
|
provider "ovh" {
|
||||||
|
endpoint = "ovh-eu"
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue