This commit is contained in:
1 parent
bed69945ce
commit
442b018f5d
17 files changed
+322
-25
No files matched your search
@@ -3,6 +3,11 @@ name: Terraform - deployer la VM Debian
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
vm:
|
||||
description: "Nom de la VM, tel que le fichier vms/<nom>.tfvars"
|
||||
type: string
|
||||
required: true
|
||||
default: centreon
|
||||
action:
|
||||
description: "plan pour vérifier, apply pour créer ou mettre à jour la VM"
|
||||
type: choice
|
||||
@@ -49,8 +54,18 @@ jobs:
|
||||
- name: Initialiser le backend S3
|
||||
run: terraform init
|
||||
|
||||
- name: Sélectionner l'espace de travail de la VM
|
||||
run: |
|
||||
set -euo pipefail
|
||||
test -f "vms/${{ inputs.vm }}.tfvars" || {
|
||||
echo "vms/${{ inputs.vm }}.tfvars n'existe pas. VM déclarées :"
|
||||
ls vms/*.tfvars | xargs -n1 basename
|
||||
exit 1
|
||||
}
|
||||
terraform workspace select -or-create "${{ inputs.vm }}"
|
||||
|
||||
- name: Calculer le plan
|
||||
run: terraform plan -var-file=lab.tfvars -lock-timeout=120s -out=tfplan
|
||||
run: terraform plan -var-file="vms/${{ inputs.vm }}.tfvars" -lock-timeout=120s -out=tfplan
|
||||
|
||||
- name: Résumé du plan
|
||||
run: terraform show -no-color tfplan | tail -n 40
|
||||
@@ -79,7 +94,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: mot-de-passe-root
|
||||
name: mot-de-passe-root-${{ inputs.vm }}
|
||||
path: mot-de-passe-root.txt
|
||||
retention-days: 1
|
||||
|
||||
@@ -92,6 +107,6 @@ jobs:
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: inventaire-ansible
|
||||
path: generated/inventory.yml
|
||||
name: inventaire-ansible-${{ inputs.vm }}
|
||||
path: generated/
|
||||
retention-days: 7
|
||||
@@ -3,6 +3,11 @@ name: Terraform - detruire la VM Debian
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
vm:
|
||||
description: "Nom de la VM à détruire, tel que le fichier vms/<nom>.tfvars"
|
||||
type: string
|
||||
required: true
|
||||
default: centreon
|
||||
confirmation:
|
||||
description: "Saisir DETRUIRE pour confirmer la suppression de la VM et de son disque"
|
||||
type: string
|
||||
@@ -52,5 +57,14 @@ jobs:
|
||||
- name: Initialiser le backend S3
|
||||
run: terraform init
|
||||
|
||||
- name: Sélectionner l'espace de travail de la VM
|
||||
run: |
|
||||
set -euo pipefail
|
||||
test -f "vms/${{ inputs.vm }}.tfvars" || {
|
||||
echo "vms/${{ inputs.vm }}.tfvars n'existe pas."
|
||||
exit 1
|
||||
}
|
||||
terraform workspace select "${{ inputs.vm }}"
|
||||
|
||||
- name: Détruire
|
||||
run: terraform destroy -var-file=lab.tfvars -lock-timeout=120s -auto-approve
|
||||
run: terraform destroy -var-file="vms/${{ inputs.vm }}.tfvars" -lock-timeout=120s -auto-approve
|
||||
@@ -0,0 +1,90 @@
|
||||
name: Terraform - migrer l etat vers les espaces de travail
|
||||
|
||||
# À exécuter UNE SEULE FOIS, après le passage aux espaces de travail.
|
||||
# La VM centreon existante vit dans l'espace « default » : ce workflow recopie
|
||||
# son état dans un espace nommé « centreon », sans rien détruire ni recréer.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
confirmation:
|
||||
description: "Saisir MIGRER pour confirmer la recopie de l etat"
|
||||
type: string
|
||||
required: true
|
||||
|
||||
concurrency:
|
||||
group: terraform-proxmox-debian-12
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
TF_VERSION: "1.9.8"
|
||||
TF_IN_AUTOMATION: "true"
|
||||
TF_INPUT: "false"
|
||||
OPENBAO_ROLE_ID: ${{ secrets.OPENBAO_ROLE_ID }}
|
||||
OPENBAO_SECRET_ID: ${{ secrets.OPENBAO_SECRET_ID }}
|
||||
|
||||
jobs:
|
||||
migrer:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Vérifier la confirmation
|
||||
run: |
|
||||
if [ "${{ inputs.confirmation }}" != "MIGRER" ]; then
|
||||
echo "Confirmation absente ou incorrecte : rien n'a été fait."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Récupérer le dépôt
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Installer les outils
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if ! command -v jq >/dev/null || ! command -v unzip >/dev/null; then
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq curl jq unzip
|
||||
fi
|
||||
curl -fsSL -o /tmp/terraform.zip \
|
||||
"https://releases.hashicorp.com/terraform/${TF_VERSION}/terraform_${TF_VERSION}_linux_amd64.zip"
|
||||
unzip -oq /tmp/terraform.zip -d /usr/local/bin
|
||||
terraform version
|
||||
|
||||
- name: Charger les secrets depuis OpenBao
|
||||
run: ./scripts/openbao-env.sh
|
||||
|
||||
- name: Initialiser le backend S3
|
||||
run: terraform init
|
||||
|
||||
- name: Recopier l'état de default vers centreon
|
||||
run: |
|
||||
set -euo pipefail
|
||||
terraform workspace select default
|
||||
terraform state pull > etat-default.json
|
||||
|
||||
ressources=$(jq '.resources | length' etat-default.json)
|
||||
echo "Ressources dans l'état default : $ressources"
|
||||
if [ "$ressources" -eq 0 ]; then
|
||||
echo "L'état default est vide, il n'y a rien à migrer."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
terraform workspace select -or-create centreon
|
||||
terraform state push -force etat-default.json
|
||||
terraform workspace select centreon
|
||||
terraform state list
|
||||
|
||||
- name: Vérifier qu'aucune modification n'est proposée
|
||||
run: |
|
||||
terraform workspace select centreon
|
||||
terraform plan -var-file=vms/centreon.tfvars -lock-timeout=120s -detailed-exitcode || {
|
||||
code=$?
|
||||
if [ "$code" -eq 2 ]; then
|
||||
echo "Le plan propose des modifications : à examiner avant tout apply."
|
||||
fi
|
||||
exit $code
|
||||
}
|
||||
|
||||
- name: Effacer la copie locale de l'état
|
||||
if: ${{ always() }}
|
||||
run: rm -f etat-default.json
|
||||
@@ -51,5 +51,13 @@ jobs:
|
||||
- name: Valider la configuration
|
||||
run: terraform validate
|
||||
|
||||
- name: Calculer le plan
|
||||
run: terraform plan -var-file=lab.tfvars -lock-timeout=120s
|
||||
- name: Calculer le plan de chaque VM déclarée
|
||||
run: |
|
||||
set -euo pipefail
|
||||
for fichier in vms/*.tfvars; do
|
||||
vm=$(basename "$fichier" .tfvars)
|
||||
echo "::group::$vm"
|
||||
terraform workspace select -or-create "$vm"
|
||||
terraform plan -var-file="$fichier" -lock-timeout=120s
|
||||
echo "::endgroup::"
|
||||
done
|
||||
+3
-3
@@ -11,6 +11,6 @@ backend.hcl
|
||||
tfplan
|
||||
|
||||
# Seules les cles publiques sont versionnees
|
||||
cles/*
|
||||
!cles/*.pub
|
||||
!cles/README.md
|
||||
key/*
|
||||
!key/*.pub
|
||||
!key/README.md
|
||||
@@ -85,7 +85,7 @@ Le fichier `.gitattributes` livré fige les fins de ligne en LF, pour la même r
|
||||
|
||||
### 4. Clé publique
|
||||
|
||||
`lab.tfvars` est versionné et contient la clé publique d'administration : y remplacer
|
||||
Chaque fichier `vms/<nom>.tfvars` est versionné et référence les clés publiques : vérifier
|
||||
le `ssh-ed25519 AAAA...` d'exemple avant le premier `apply`. Sans quoi la VM sera
|
||||
créée avec une clé inutilisable et il faudra la recréer — cloud-init ne rejoue rien.
|
||||
|
||||
|
||||
+5
-5
@@ -20,18 +20,18 @@ humain, ou la console Proxmox quand le réseau est cassé.
|
||||
|
||||
## Où vivent les clés
|
||||
|
||||
Un fichier `.pub` par identité dans `cles/`, référencé par `lab.tfvars`. Rien n'est
|
||||
Un fichier `.pub` par identité dans `key/`, référencé par les fichiers `vms/*.tfvars`. Rien n'est
|
||||
recopié dans le `.tfvars` lui-même.
|
||||
|
||||
```
|
||||
cles/
|
||||
key/
|
||||
├── hcornet.pub livré, c'est votre clé
|
||||
├── invite.pub à déposer pour activer le compte invite
|
||||
└── ansible.pub à déposer pour l'automatisation
|
||||
```
|
||||
|
||||
Un compte dont `ssh_key_files` est vide **n'est pas créé**. `invite` et `ansible` sont
|
||||
donc déclarés dans `lab.tfvars` mais inactifs : déposer leur `.pub` et compléter la
|
||||
donc déclarés dans `vms/centreon.tfvars` mais inactifs : déposer leur `.pub` et compléter la
|
||||
liste suffit à les activer au prochain déploiement.
|
||||
|
||||
Deux garde-fous avant création, faute de quoi une VM inaccessible serait produite et
|
||||
@@ -45,11 +45,11 @@ C'est ce second contrôle qui manquait : la version précédente vérifiait qu'u
|
||||
|
||||
### Votre clé donne aussi l'accès root
|
||||
|
||||
`root_ssh_key_files` contient `cles/hcornet.pub` : `ssh [email protected]` fonctionne
|
||||
`root_ssh_key_files` contient `key/hcornet.pub` : `ssh [email protected]` fonctionne
|
||||
directement, sans mot de passe. Votre clé privée est donc un identifiant root à part
|
||||
entière — ce qui rend sa phrase de passe et sa protection d'autant plus importantes.
|
||||
|
||||
Le jour où Ansible entre en jeu, ajouter `cles/ansible.pub` à cette même liste.
|
||||
Le jour où Ansible entre en jeu, ajouter `key/ansible.pub` à cette même liste.
|
||||
|
||||
## Le mot de passe root
|
||||
|
||||
|
||||
@@ -55,12 +55,19 @@ export TF_VAR_pve_ssh_password='...'
|
||||
Si le projet rejoint la CI Gitea, ces deux valeurs se lisent dans OpenBao comme pour
|
||||
`proxmox-create-vm`, en réutilisant `secret/proxmox`.
|
||||
|
||||
## Une VM par fichier
|
||||
|
||||
Ce dépôt gère plusieurs VM : un fichier `vms/<nom>.tfvars` et un espace de travail
|
||||
Terraform par machine, chacune avec son propre état. La version de Debian se choisit
|
||||
dans ce fichier. Tout est décrit dans **[VMS.md](VMS.md)**.
|
||||
|
||||
## Déclenchement habituel : la CI
|
||||
|
||||
Le déploiement passe par Gitea Actions (`.gitea/workflows/`), l'état vit sur le backend
|
||||
S3 et les secrets dans OpenBao. Tout est décrit dans **[CICD.md](CICD.md)**.
|
||||
|
||||
Les valeurs non sensibles sont versionnées dans `lab.tfvars` — dont la clé publique
|
||||
Les valeurs non sensibles sont versionnées dans `vms/<nom>.tfvars`, un fichier par VM
|
||||
(voir [VMS.md](VMS.md)) — dont la clé publique
|
||||
d'administration, à remplacer avant le premier déploiement.
|
||||
|
||||
## Exécution ponctuelle, sans installer Terraform
|
||||
@@ -79,7 +86,7 @@ docker run --rm -it -v "$PWD":/work -w /work \
|
||||
docker run --rm -it -v "$PWD":/work -w /work \
|
||||
-e TF_VAR_pve_api_token -e TF_VAR_pve_ssh_password \
|
||||
-e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY \
|
||||
hashicorp/terraform:latest apply -var-file=lab.tfvars
|
||||
hashicorp/terraform:latest apply -var-file=vms/centreon.tfvars
|
||||
```
|
||||
|
||||
Sous PowerShell, remplacer `"$PWD"` par `${PWD}` et passer les variables avec
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
# Créer plusieurs VM avec ce dépôt
|
||||
|
||||
Une VM = un fichier `vms/<nom>.tfvars` + un espace de travail Terraform du même nom.
|
||||
Chaque VM a donc **son propre état** : en détruire une ne touche pas aux autres, et un
|
||||
plan raté sur l'une n'a aucun effet sur l'autre.
|
||||
|
||||
```
|
||||
vms/
|
||||
├── centreon.tfvars VM 120, Debian 12, 10.0.5.20
|
||||
└── exemple-debian13.tfvars VM 121, Debian 13, 10.0.5.21
|
||||
```
|
||||
|
||||
Dans le bucket S3, les états se rangent sous `env:/<nom>/backend/…`, automatiquement.
|
||||
|
||||
## Ajouter une VM
|
||||
|
||||
Copier un fichier existant, le renommer, puis adapter. Trois valeurs doivent être
|
||||
uniques sur l'infrastructure, et **rien ne le vérifie** — deux VM avec le même `vm_id`
|
||||
provoquent un échec à la création, deux avec la même IP un conflit silencieux :
|
||||
|
||||
- `vm_id`
|
||||
- `vm_hostname`
|
||||
- `vm_ipv4_address`
|
||||
|
||||
Un `git push` déclenche le plan de **toutes** les VM déclarées : c'est le filet qui
|
||||
attrape une erreur de syntaxe ou un fichier de clé manquant avant tout déploiement.
|
||||
|
||||
Pour créer la VM, lancer `deploy` en saisissant son nom dans le champ `vm`. Le workflow
|
||||
refuse un nom sans fichier correspondant et liste les VM connues.
|
||||
|
||||
## Changer de version de Debian
|
||||
|
||||
Deux lignes, dans le fichier de la VM concernée :
|
||||
|
||||
```hcl
|
||||
debian_image_url = "https://cloud.debian.org/images/cloud/trixie/latest/debian-13-genericcloud-amd64.qcow2"
|
||||
debian_image_file_name = "debian-13-genericcloud-amd64.qcow2"
|
||||
```
|
||||
|
||||
Le reste du code n'en dépend pas : comptes, durcissement SSH, nftables, agent invité et
|
||||
mises à jour automatiques sont identiques d'une version à l'autre. Le nom du fichier
|
||||
doit garder une extension `.qcow2`, `.raw` ou `.vmdk`, seules acceptées par le contenu
|
||||
`import` de Proxmox.
|
||||
|
||||
Une limite à connaître : **Centreon 25.10 n'existe que pour Debian 12**. Le dépôt
|
||||
`ansible-centreon` refuse d'ailleurs de s'exécuter sur autre chose. Une VM Debian 13
|
||||
convient pour tout le reste.
|
||||
|
||||
## Migration de l'existant
|
||||
|
||||
La VM `centreon` a été créée avant ce découpage, son état vit donc dans l'espace
|
||||
`default`. Le workflow **migrer-espaces** le recopie une fois pour toutes dans l'espace
|
||||
`centreon`, sans rien détruire : confirmation `MIGRER`, puis il vérifie qu'aucune
|
||||
modification n'est proposée ensuite.
|
||||
|
||||
À lancer **avant** tout autre workflow. Sans cette migration, un `deploy` sur la VM
|
||||
`centreon` la verrait comme inexistante et tenterait de la recréer — avec un VMID déjà
|
||||
pris, donc un échec, mais autant l'éviter.
|
||||
|
||||
L'espace `default` conservera une copie de l'état après migration. Terraform interdit de
|
||||
le supprimer, et plus aucun workflow ne l'utilise : il devient inerte.
|
||||
|
||||
## Commandes hors CI
|
||||
|
||||
Pour un essai ponctuel, par conteneur :
|
||||
|
||||
```bash
|
||||
docker run --rm -it -v "$PWD":/work -w /work \
|
||||
-e TF_VAR_pve_api_token -e TF_VAR_pve_ssh_password \
|
||||
-e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY \
|
||||
hashicorp/terraform:latest workspace list
|
||||
```
|
||||
|
||||
`workspace list` montre les espaces existants, `workspace select <nom>` bascule, et
|
||||
chaque commande doit ensuite recevoir `-var-file=vms/<nom>.tfvars`. Se tromper de
|
||||
couple espace/fichier est la seule vraie façon de se faire mal ici : Terraform
|
||||
comparerait l'état d'une VM à la définition d'une autre et proposerait de tout recréer.
|
||||
C'est la raison pour laquelle les workflows sélectionnent toujours les deux ensemble, à
|
||||
partir du même nom.
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
locals {
|
||||
fqdn = "${var.vm_hostname}.${var.dns_domain}"
|
||||
|
||||
# Les clés sont lues dans les fichiers du répertoire cles/. Un fichier
|
||||
# Les clés sont lues dans les fichiers du répertoire key/. Un fichier
|
||||
# référencé mais absent fait échouer le plan, ce qui est le comportement
|
||||
# voulu : mieux vaut un plan rouge qu'une VM inaccessible.
|
||||
# Un compte sans aucun fichier de clé n'est pas créé.
|
||||
@@ -68,7 +68,7 @@ resource "proxmox_virtual_environment_file" "user_data" {
|
||||
for cle in local.toutes_les_cles :
|
||||
can(regex("^(ssh-ed25519|ssh-rsa|ecdsa-sha2-nistp[0-9]+|sk-ssh-ed25519) AAAA", cle))
|
||||
])
|
||||
error_message = "Un des fichiers de cles/ ne contient pas une clé publique valide."
|
||||
error_message = "Un des fichiers de key/ ne contient pas une clé publique valide."
|
||||
}
|
||||
|
||||
precondition {
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -102,7 +102,7 @@ resource "proxmox_virtual_environment_vm" "centreon" {
|
||||
}
|
||||
|
||||
resource "local_file" "ansible_inventory" {
|
||||
filename = var.ansible_inventory_path
|
||||
filename = coalesce(var.ansible_inventory_path, "generated/${var.vm_hostname}-inventory.yml")
|
||||
file_permission = "0644"
|
||||
|
||||
content = yamlencode({
|
||||
|
||||
+8
-4
@@ -196,7 +196,7 @@ variable "local_accounts" {
|
||||
est retirée du système. Pour administrer, on ouvre une session avec son
|
||||
compte puis on bascule par « su - » avec le mot de passe root.
|
||||
|
||||
ssh_key_files référence des fichiers du répertoire cles/, relatifs à la racine
|
||||
ssh_key_files référence des fichiers du répertoire key/, relatifs à la racine
|
||||
du dépôt. Un compte sans aucun fichier de clé n'est pas créé : c'est le moyen
|
||||
de déclarer un compte à l'avance sans l'activer.
|
||||
EOT
|
||||
@@ -257,7 +257,11 @@ variable "ssh_allowed_cidrs" {
|
||||
############################################
|
||||
|
||||
variable "ansible_inventory_path" {
|
||||
description = "Chemin du fichier d'inventaire généré à la fin de l'apply."
|
||||
type = string
|
||||
default = "generated/inventory.yml"
|
||||
description = <<-EOT
|
||||
Chemin du fichier d'inventaire généré à la fin de l'apply. Laissé à null,
|
||||
il vaut generated/<nom de la VM>-inventory.yml : chaque VM a le sien.
|
||||
EOT
|
||||
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
# Valeurs non sensibles, versionnées : c'est ce fichier que la CI passe en -var-file.
|
||||
# Les secrets (jeton d'API, mot de passe SSH du nœud) arrivent par TF_VAR_*, depuis OpenBao.
|
||||
|
||||
vm_id = 120
|
||||
vm_hostname = "centreon"
|
||||
vm_bridge = "vmbr3"
|
||||
|
||||
vm_ipv4_address = "10.0.5.20"
|
||||
vm_ipv4_gateway = "10.0.5.1"
|
||||
dns_servers = ["10.0.5.1"]
|
||||
dns_domain = "tips-of-mine.local"
|
||||
|
||||
vm_cores = 2
|
||||
vm_memory = 4096
|
||||
vm_disk_size = 40
|
||||
|
||||
ssh_allowed_cidrs = ["10.0.4.0/24", "10.0.5.0/24"]
|
||||
|
||||
# Comptes de la machine. Aucun n'a de privilège : sudo est absent du système.
|
||||
# Pour administrer, ouvrir une session avec son compte puis « su - ».
|
||||
# Les clés vivent dans key/, un fichier .pub par identité.
|
||||
# Une liste ssh_key_files vide signifie : compte déclaré mais non créé.
|
||||
local_accounts = [
|
||||
{
|
||||
name = "hcornet"
|
||||
gecos = "Hubert Cornet - administration"
|
||||
ssh_key_files = ["key/hcornet.pub"]
|
||||
},
|
||||
{
|
||||
name = "invite"
|
||||
gecos = "Compte de consultation - aucun privilege"
|
||||
ssh_key_files = []
|
||||
},
|
||||
{
|
||||
name = "ansible"
|
||||
gecos = "Compte de service - sans privilege"
|
||||
ssh_key_files = []
|
||||
},
|
||||
]
|
||||
|
||||
# Clés autorisées à ouvrir une session directement en root.
|
||||
# Y ajouter key/ansible.pub le jour où l'automatisation entre en jeu.
|
||||
root_ssh_key_files = ["key/hcornet.pub"]
|
||||
@@ -0,0 +1,37 @@
|
||||
# Exemple de seconde VM : Debian 13, pour un besoin quelconque.
|
||||
# Copier ce fichier sous le nom de la VM à créer, puis adapter.
|
||||
#
|
||||
# Trois valeurs doivent être uniques sur l'infrastructure, rien ne le vérifie
|
||||
# automatiquement : vm_id, vm_hostname et vm_ipv4_address.
|
||||
|
||||
vm_id = 121
|
||||
vm_hostname = "bac-a-sable"
|
||||
vm_bridge = "vmbr3"
|
||||
|
||||
vm_ipv4_address = "10.0.5.21"
|
||||
vm_ipv4_gateway = "10.0.5.1"
|
||||
dns_servers = ["10.0.5.1"]
|
||||
dns_domain = "tips-of-mine.local"
|
||||
|
||||
vm_cores = 2
|
||||
vm_memory = 2048
|
||||
vm_disk_size = 20
|
||||
|
||||
# C'est ici, et nulle part ailleurs, que se choisit la version de Debian.
|
||||
# Le reste du code n'en dépend pas : comptes, durcissement et pare-feu sont
|
||||
# identiques. Attention, Centreon 25.10 exige Debian 12 : cette VM-ci ne peut
|
||||
# pas accueillir le dépôt Ansible centreon.
|
||||
debian_image_url = "https://cloud.debian.org/images/cloud/trixie/latest/debian-13-genericcloud-amd64.qcow2"
|
||||
debian_image_file_name = "debian-13-genericcloud-amd64.qcow2"
|
||||
|
||||
ssh_allowed_cidrs = ["10.0.4.0/24", "10.0.5.0/24"]
|
||||
|
||||
local_accounts = [
|
||||
{
|
||||
name = "hcornet"
|
||||
gecos = "Hubert Cornet - administration"
|
||||
ssh_key_files = ["key/hcornet.pub"]
|
||||
},
|
||||
]
|
||||
|
||||
root_ssh_key_files = ["key/hcornet.pub"]
|
||||
Reference in new issue
Block a user