first commit
This commit is contained in:
277
ARCHITECTURE.md
Normal file
277
ARCHITECTURE.md
Normal file
@@ -0,0 +1,277 @@
|
||||
# Architecture NETbian
|
||||
|
||||
Ce document decrit l'architecture actuelle de NETbian et le pipeline d'execution du provisioning.
|
||||
|
||||
## Vue d'ensemble
|
||||
|
||||
NETbian repose sur une architecture **profiles + roles**.
|
||||
|
||||
```text
|
||||
profile -> liste ordonnee de roles
|
||||
role -> implementation technique autonome
|
||||
```
|
||||
|
||||
Un profil ne contient que de la composition. Toute la logique technique est portee par les roles et par les fonctions partagees de `lib.sh`.
|
||||
|
||||
## Arborescence
|
||||
|
||||
```text
|
||||
netbian/
|
||||
├── run.sh
|
||||
├── roles.sh
|
||||
├── lib.sh
|
||||
├── profiles/
|
||||
│ ├── cli.sh
|
||||
│ ├── desktop.sh
|
||||
│ ├── devel.sh
|
||||
│ └── server.sh
|
||||
├── roles/
|
||||
│ ├── base/
|
||||
│ ├── codium/
|
||||
│ ├── desktop/
|
||||
│ ├── devel/
|
||||
│ ├── docker/
|
||||
│ ├── firewall/
|
||||
│ ├── server/
|
||||
│ └── zram/
|
||||
└── config/
|
||||
```
|
||||
|
||||
## Profils
|
||||
|
||||
Chaque profil est un script shell qui declare `ROLE_ORDER`.
|
||||
|
||||
Exemple avec `profiles/devel.sh` :
|
||||
|
||||
```bash
|
||||
ROLE_ORDER=(base desktop firewall zram docker codium devel)
|
||||
```
|
||||
|
||||
Profils fournis :
|
||||
|
||||
- `cli` -> `base zram`
|
||||
- `server` -> `base firewall server zram docker`
|
||||
- `desktop` -> `base desktop firewall zram`
|
||||
- `devel` -> `base desktop firewall zram docker codium devel`
|
||||
|
||||
## Contrat d'un role
|
||||
|
||||
Un role est un repertoire sous `roles/<nom>/`.
|
||||
|
||||
Fichiers reconnus par le moteur :
|
||||
|
||||
- `repo.sh`
|
||||
- `packages.list`
|
||||
- `install.sh`
|
||||
- `config.sh`
|
||||
- `run.sh`
|
||||
- `l10n.packages`
|
||||
- `rules.<suffix>.list`
|
||||
|
||||
Tout autre fichier est rejete par `validate_role()` pour eviter les derives silencieuses.
|
||||
|
||||
## Pipeline global
|
||||
|
||||
### 1. `run.sh`
|
||||
|
||||
`run.sh` est le point d'entree. Il :
|
||||
|
||||
1. initialise l'environnement (`PROJECT_DIR`, `ROLE_DIR`, `PROFILE_DIR`)
|
||||
2. charge `lib.sh` et active `set -Eeuo pipefail`
|
||||
3. parse les options CLI
|
||||
4. peut lister les profils et roles disponibles
|
||||
5. verifie les prerequis (`root`, Debian 13, reseau HTTP)
|
||||
6. valide puis lit / met a jour le fichier de configuration
|
||||
7. exporte `CONFIG_FILE`
|
||||
8. execute `roles.sh`
|
||||
|
||||
### 2. `roles.sh`
|
||||
|
||||
`roles.sh` :
|
||||
|
||||
1. charge `lib.sh`
|
||||
2. valide `CONFIG_FILE`
|
||||
3. lit `CONFIG_FILE`
|
||||
4. recupere `profile`
|
||||
5. source `profiles/<profile>.sh`
|
||||
6. valide la definition du profil et les roles references
|
||||
7. execute `run_role` pour chaque role dans l'ordre
|
||||
8. imprime un resume d'execution en sortie
|
||||
|
||||
## Resolution de configuration
|
||||
|
||||
Ordre de priorite :
|
||||
|
||||
1. options CLI
|
||||
2. variables d'environnement `NETBIAN_*`
|
||||
3. fichier de configuration cible
|
||||
|
||||
Variables gerees :
|
||||
|
||||
- `profile`
|
||||
- `lang`
|
||||
- `CONFIG_FILE` (transportee par l'environnement entre `run.sh` et `roles.sh`)
|
||||
|
||||
`validate_config_file()` refuse les lignes invalides et controle les formats de `profile` et `lang` avant sourcing.
|
||||
|
||||
## Pipeline d'un role
|
||||
|
||||
Le moteur execute les etapes suivantes dans cet ordre :
|
||||
|
||||
1. `repo.sh`
|
||||
2. `packages.list`
|
||||
3. `install.sh`
|
||||
4. `config.sh`
|
||||
5. `run.sh`
|
||||
|
||||
Toutes les etapes sont optionnelles.
|
||||
|
||||
Implementation logique de `run_role()` :
|
||||
|
||||
```text
|
||||
validate_role
|
||||
run_role_script repo.sh
|
||||
load_role_packages
|
||||
append_localized_packages
|
||||
run_role_packages
|
||||
run_role_script install.sh
|
||||
run_role_script config.sh
|
||||
run_role_script run.sh
|
||||
```
|
||||
|
||||
Les erreurs sont propagees explicitement avec `|| return 1` pour eviter de dependre uniquement du comportement implicite de `set -e`.
|
||||
|
||||
## Gestion des paquets
|
||||
|
||||
- `load_role_packages()` charge `roles/<role>/packages.list`
|
||||
- `append_localized_packages()` enrichit la liste avec les paquets localises definis dans `roles/base/l10n.packages`
|
||||
- la langue utilisee provient de la configuration active (`lang`)
|
||||
- `run_role_packages()` appelle `ensure_packages_installed()`
|
||||
|
||||
Si un role n'a aucun paquet a installer, le moteur enregistre un `[SKIP]` sur `role/packages.list`.
|
||||
|
||||
## Configuration et copie de fichiers
|
||||
|
||||
`copy_config()` copie les fichiers depuis `config/` vers leur destination finale :
|
||||
|
||||
- propriete `root:root` pour les fichiers systeme
|
||||
- propriete utilisateur si la destination est sous `/home/<user>/`
|
||||
- ecriture idempotente si le contenu est identique
|
||||
|
||||
`write_if_changed()` et `write_text_file_if_changed()` centralisent les ecritures atomiques simples.
|
||||
|
||||
## Firewall declaratif
|
||||
|
||||
Le role `firewall` utilise un systeme declaratif base sur des listes de regles :
|
||||
|
||||
```text
|
||||
roles/firewall/
|
||||
├── rules.common.list
|
||||
├── rules.desktop.list
|
||||
├── rules.devel.list
|
||||
└── rules.server.list
|
||||
```
|
||||
|
||||
Ordre d'application :
|
||||
|
||||
1. initialisation UFW
|
||||
2. regles communes
|
||||
3. regles specifiques au profil
|
||||
|
||||
Chaque ligne correspond a une regle passee a `ufw allow`.
|
||||
|
||||
Exemple :
|
||||
|
||||
```text
|
||||
# rules.common.list
|
||||
ssh
|
||||
|
||||
# rules.server.list
|
||||
http
|
||||
https
|
||||
imap
|
||||
imaps
|
||||
smtp
|
||||
submissions
|
||||
```
|
||||
|
||||
Le modele est volontairement unique : les ouvertures UFW sont centralisees dans les fichiers `rules.*.list`, pas dans les autres roles.
|
||||
|
||||
## Services systeme
|
||||
|
||||
`restart_service_if_present()` permet de redemarrer un service uniquement s'il existe. Cela evite de rendre certains roles fragiles sur des machines ou le service n'est pas installe.
|
||||
|
||||
Le role `server` s'appuie sur ce mecanisme apres ecriture de la configuration SSH.
|
||||
|
||||
## Logs et resume d'execution
|
||||
|
||||
Le moteur expose les helpers suivants :
|
||||
|
||||
- `log_run`
|
||||
- `log_ok`
|
||||
- `log_skip`
|
||||
- `log_info`
|
||||
- `log_warn`
|
||||
|
||||
Tags utilises par l'orchestrateur :
|
||||
|
||||
- `[RUN]`
|
||||
- `[OK]`
|
||||
- `[SKIP]`
|
||||
- `[INFO]`
|
||||
- `[WARN]`
|
||||
|
||||
Des tableaux runtime suivent les etapes et roles executes, ignores ou en echec :
|
||||
|
||||
- `EXECUTED_STEPS`
|
||||
- `SKIPPED_STEPS`
|
||||
- `FAILED_STEPS`
|
||||
- `EXECUTED_ROLES`
|
||||
|
||||
Le resume est imprime par `print_execution_summary()` via un `trap EXIT` dans `roles.sh`.
|
||||
|
||||
## Decouverte et validation
|
||||
|
||||
Lister les profils :
|
||||
|
||||
```bash
|
||||
./run.sh --list-profiles
|
||||
```
|
||||
|
||||
Lister les roles :
|
||||
|
||||
```bash
|
||||
./run.sh --list-roles
|
||||
```
|
||||
|
||||
Validation minimale recommandee :
|
||||
|
||||
```bash
|
||||
bash -n run.sh roles.sh lib.sh roles/*/*.sh profiles/*.sh
|
||||
./run.sh --list-profiles
|
||||
./run.sh --list-roles
|
||||
```
|
||||
|
||||
Validation recommandee avant production :
|
||||
|
||||
1. machine Debian 13 fraiche
|
||||
2. test de chaque profil
|
||||
3. verification des services systemd critiques (`ssh`, `docker`, `zramswap`)
|
||||
4. verification UFW apres run
|
||||
5. rerun complet pour confirmer l'idempotence
|
||||
|
||||
## Principes de conception
|
||||
|
||||
- **Idempotence** : rejouer un role ne doit pas deteriorer l'etat.
|
||||
- **Composition simple** : les profils ne font qu'ordonner des roles.
|
||||
- **Validation stricte** : les roles, manifests et fichiers de configuration sont verifies avant execution.
|
||||
- **Ecritures limitees** : seules les differences utiles sont ecrites.
|
||||
- **Previsibilite** : les comportements critiques sont centralises dans `lib.sh`.
|
||||
- **Source de verite unique** : un seul mecanisme par sujet critique (ex. firewall).
|
||||
|
||||
|
||||
## Resume d execution
|
||||
|
||||
- `Steps executed` compte les etapes ayant effectue une action.
|
||||
- `Steps skipped` compte les etapes entierement conformes ou sautees par l'orchestrateur.
|
||||
- `Skip events` compte tous les messages `[SKIP]` emis pendant l'execution.
|
||||
221
README.md
Normal file
221
README.md
Normal file
@@ -0,0 +1,221 @@
|
||||
# NETbian
|
||||
|
||||
NETbian est un outil de provisioning pour **Debian 13** base sur une architecture **profiles + roles**.
|
||||
|
||||
Chaque **profil** compose une suite de **roles fonctionnels**. Un role regroupe au meme endroit :
|
||||
|
||||
- ses paquets
|
||||
- sa configuration APT eventuelle
|
||||
- ses actions d'installation
|
||||
- sa configuration systeme
|
||||
- ses actions finales eventuelles
|
||||
|
||||
Le projet est pense pour rester **idempotent** : un second passage doit converger vers l'etat attendu au lieu de casser la machine.
|
||||
|
||||
## Profils disponibles
|
||||
|
||||
- `cli`
|
||||
- `server`
|
||||
- `desktop`
|
||||
- `devel`
|
||||
|
||||
Les profils sont definis dans `profiles/` et peuvent etre listes avec :
|
||||
|
||||
```bash
|
||||
./run.sh --list-profiles
|
||||
```
|
||||
|
||||
## Roles disponibles
|
||||
|
||||
Le depot contient actuellement les roles suivants :
|
||||
|
||||
- `base`
|
||||
- `codium`
|
||||
- `desktop`
|
||||
- `devel`
|
||||
- `docker`
|
||||
- `firewall`
|
||||
- `server`
|
||||
- `zram`
|
||||
|
||||
Ils peuvent etre listes avec :
|
||||
|
||||
```bash
|
||||
./run.sh --list-roles
|
||||
```
|
||||
|
||||
## Structure du depot
|
||||
|
||||
```text
|
||||
netbian/
|
||||
├── run.sh
|
||||
├── roles.sh
|
||||
├── lib.sh
|
||||
├── profiles/
|
||||
│ ├── cli.sh
|
||||
│ ├── desktop.sh
|
||||
│ ├── devel.sh
|
||||
│ └── server.sh
|
||||
├── roles/
|
||||
│ ├── base/
|
||||
│ ├── codium/
|
||||
│ ├── desktop/
|
||||
│ ├── devel/
|
||||
│ ├── docker/
|
||||
│ ├── firewall/
|
||||
│ ├── server/
|
||||
│ └── zram/
|
||||
└── config/
|
||||
```
|
||||
|
||||
## Convention d'un role
|
||||
|
||||
Un role peut contenir les fichiers suivants :
|
||||
|
||||
- `packages.list` : paquets du role
|
||||
- `repo.sh` : ajout de depots / cles APT
|
||||
- `install.sh` : installation specifique
|
||||
- `config.sh` : configuration systeme
|
||||
- `run.sh` : actions finales du role
|
||||
- `l10n.packages` : mappings de paquets localises
|
||||
- `rules.<suffix>.list` : regles declaratives UFW pour le role firewall
|
||||
|
||||
Tous ces fichiers sont **optionnels**. Le moteur execute uniquement ceux qui existent.
|
||||
|
||||
## Profils fournis
|
||||
|
||||
### `cli`
|
||||
|
||||
Provisioning minimal en ligne de commande :
|
||||
|
||||
```bash
|
||||
ROLE_ORDER=(base zram)
|
||||
```
|
||||
|
||||
### `server`
|
||||
|
||||
Machine orientee services :
|
||||
|
||||
```bash
|
||||
ROLE_ORDER=(base firewall server zram docker)
|
||||
```
|
||||
|
||||
### `desktop`
|
||||
|
||||
Poste utilisateur graphique :
|
||||
|
||||
```bash
|
||||
ROLE_ORDER=(base desktop firewall zram)
|
||||
```
|
||||
|
||||
### `devel`
|
||||
|
||||
Poste de developpement :
|
||||
|
||||
```bash
|
||||
ROLE_ORDER=(base desktop firewall zram docker codium devel)
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
git clone https://git.netig.net/netig/netbian.git
|
||||
cd netbian
|
||||
sudo ./run.sh --profile devel
|
||||
```
|
||||
|
||||
## Utilisation
|
||||
|
||||
```text
|
||||
Usage: run.sh [options]
|
||||
Options:
|
||||
-p, --profile <server|desktop|devel|cli> Profile to install (required)
|
||||
-l, --lang <code> Language code if translations are needed (e.g. fr, es, de)
|
||||
--config <path> Config file path (default: /etc/netbian.conf)
|
||||
--list-profiles List available profiles and exit
|
||||
--list-roles List available roles and exit
|
||||
-h, --help Show this help
|
||||
```
|
||||
|
||||
Exemples :
|
||||
|
||||
```bash
|
||||
sudo ./run.sh --profile server
|
||||
sudo ./run.sh --profile devel --lang fr
|
||||
sudo ./run.sh --config /root/netbian.conf --profile desktop
|
||||
NETBIAN_PROFILE=server sudo -E ./run.sh
|
||||
./run.sh --list-profiles
|
||||
./run.sh --list-roles
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
`run.sh` lit et met a jour un fichier de configuration shell simple, par defaut :
|
||||
|
||||
```text
|
||||
/etc/netbian.conf
|
||||
```
|
||||
|
||||
Variables gerees actuellement :
|
||||
|
||||
```bash
|
||||
profile=devel
|
||||
lang=fr
|
||||
```
|
||||
|
||||
Priorite de resolution :
|
||||
|
||||
1. options CLI (`--profile`, `--lang`, `--config`)
|
||||
2. variables d'environnement (`NETBIAN_PROFILE`, `NETBIAN_LANG`, `NETBIAN_CONFIG_FILE`)
|
||||
3. fichier de configuration cible
|
||||
4. absence de valeur -> erreur si `profile` reste non defini
|
||||
|
||||
Le meme chemin de configuration est ensuite reutilise par `roles.sh`.
|
||||
|
||||
## Logs d'execution
|
||||
|
||||
NETbian utilise des logs simples pour le moteur d'orchestration :
|
||||
|
||||
- `[RUN]` : etape lancee
|
||||
- `[OK]` : etape executee avec succes
|
||||
- `[SKIP]` : etat deja conforme ou etape vide
|
||||
- `[INFO]` : information de progression
|
||||
- `[WARN]` : situation non bloquante
|
||||
|
||||
Les scripts de roles peuvent aussi ecrire leurs propres messages metier, mais la recommandation est d'utiliser les helpers centralises pour garder des traces homogenes.
|
||||
|
||||
## Choix d'architecture pour un niveau infra mature
|
||||
|
||||
- **Firewall centralise** : seules les listes `roles/firewall/rules.*.list` definissent les ouvertures UFW.
|
||||
- **Validation stricte** : les roles, manifests et le fichier de configuration sont verifies avant execution.
|
||||
- **Resume de run** : `roles.sh` imprime un recapitulatif des roles, etapes executees, sautees et en echec.
|
||||
- **Precedence explicite** : la resolution de la configuration est documentee et stable.
|
||||
|
||||
## Particularites importantes
|
||||
|
||||
- `run.sh` exige les droits `root`.
|
||||
- Le projet verifie qu'il tourne sur **Debian 13**.
|
||||
- Un test reseau HTTP est effectue avant provisioning en utilisant `curl` puis `wget`.
|
||||
- Le role `firewall` applique des regles `ufw` a partir de fichiers declaratifs.
|
||||
- Le role `server` ecrit une configuration SSH dediee puis tente de recharger `ssh` / `sshd` si le service existe.
|
||||
- Le role `codium` installe VSCodium, ses extensions et la configuration utilisateur si un utilisateur cible est detecte.
|
||||
- Les paquets localises eventuels sont ajoutes a partir de la variable `lang` de la configuration active.
|
||||
|
||||
## Validation rapide
|
||||
|
||||
Avant livraison, au minimum :
|
||||
|
||||
```bash
|
||||
bash -n run.sh roles.sh lib.sh roles/*/*.sh profiles/*.sh
|
||||
./run.sh --list-profiles
|
||||
./run.sh --list-roles
|
||||
```
|
||||
|
||||
Pour une validation production complete, il reste recommande de tester le provisioning sur une **Debian 13 fraiche** pour verifier le comportement reel des roles et des services.
|
||||
|
||||
|
||||
## Resume d execution
|
||||
|
||||
- `Steps executed` compte les etapes ayant effectue une action.
|
||||
- `Steps skipped` compte les etapes entierement conformes ou sautees par l'orchestrateur.
|
||||
- `Skip events` compte tous les messages `[SKIP]` emis pendant l'execution.
|
||||
10
config/codium/settings.json
Normal file
10
config/codium/settings.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
// Largeur maximale recommandée (règle d'édition)
|
||||
"editor.rulers": [
|
||||
100
|
||||
],
|
||||
// Chemin vers l'exécutable PHP système (utilisé pour la validation)
|
||||
"php.validate.executablePath": "/usr/bin/php",
|
||||
// Chemin relatif vers le binaire php-cs-fixer du projet
|
||||
"php-cs-fixer.executablePath": "${workspaceFolder}/vendor/bin/php-cs-fixer",
|
||||
}
|
||||
86
config/firefox/policies.json
Normal file
86
config/firefox/policies.json
Normal file
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"policies": {
|
||||
"OverrideFirstRunPage": "",
|
||||
"NoDefaultBookmarks": true,
|
||||
"DisableTelemetry": true,
|
||||
"DisableFirefoxAccounts": true,
|
||||
"DisablePocket": true,
|
||||
"FirefoxHome": {
|
||||
"Search": false,
|
||||
"TopSites": false,
|
||||
"SponsoredTopSites": false,
|
||||
"Highlights": false,
|
||||
"Pocket": false,
|
||||
"Stories": false,
|
||||
"SponsoredPocket": false,
|
||||
"SponsoredStories": false,
|
||||
"Snippets": false
|
||||
},
|
||||
"Homepage": {
|
||||
"StartPage": "none"
|
||||
},
|
||||
"NewTabPage": false,
|
||||
"SearchEngines": {
|
||||
"Remove": [
|
||||
"Bing",
|
||||
"eBay",
|
||||
"Google",
|
||||
"Perplexity",
|
||||
"Qwant"
|
||||
],
|
||||
"Default": "DuckDuckGo"
|
||||
},
|
||||
"UserMessaging": {
|
||||
"ExtensionRecommendations": false,
|
||||
"FeatureRecommendations": false,
|
||||
"UrlbarInterventions": false,
|
||||
"SkipOnboarding": false,
|
||||
"MoreFromMozilla": false,
|
||||
"FirefoxLabs": false
|
||||
},
|
||||
"GenerativeAI": {
|
||||
"Enabled": false,
|
||||
"Chatbot": false,
|
||||
"LinkPreviews": false,
|
||||
"TabGroups": false
|
||||
},
|
||||
"SearchSuggestEnabled": false,
|
||||
"FirefoxSuggest": {
|
||||
"WebSuggestions": false,
|
||||
"SponsoredSuggestions": false,
|
||||
"ImproveSuggest": false
|
||||
},
|
||||
"EnableTrackingProtection": {
|
||||
"Value": true,
|
||||
"Cryptomining": true,
|
||||
"Fingerprinting": true,
|
||||
"EmailTracking": true,
|
||||
"SuspectedFingerprinting": true,
|
||||
"Category": "strict",
|
||||
"BaselineExceptions": false,
|
||||
"ConvenienceExceptions": true
|
||||
},
|
||||
"AutofillAddressEnabled": false,
|
||||
"AutofillCreditCardEnabled": false,
|
||||
"Preferences": {
|
||||
"privacy.globalprivacycontrol.enabled": true,
|
||||
"network.cookie.cookieBehavior": 1,
|
||||
"signon.rememberSignons": false,
|
||||
"browser.formfill.enable": false,
|
||||
"browser.search.suggest.enabled": false,
|
||||
"browser.urlbar.suggest.searches": false,
|
||||
"browser.urlbar.suggest.history": false,
|
||||
"browser.urlbar.suggest.bookmark": false,
|
||||
"browser.urlbar.suggest.openpage": false,
|
||||
"browser.urlbar.suggest.topsites": false,
|
||||
"browser.urlbar.suggest.quicksuggest": false,
|
||||
"browser.urlbar.suggest.pocket": false,
|
||||
"browser.urlbar.suggest.engines": false
|
||||
},
|
||||
"Extensions": {
|
||||
"Install": [
|
||||
"https://addons.mozilla.org/firefox/downloads/latest/ublock-origin/latest.xpi"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
739
lib.sh
Executable file
739
lib.sh
Executable file
@@ -0,0 +1,739 @@
|
||||
#!/usr/bin/env bash
|
||||
# Bibliotheque centrale
|
||||
|
||||
if [[ "${NETBIAN_LIB_SOURCED:-}" == "1" ]]; then
|
||||
return 0 2>/dev/null || exit 0
|
||||
fi
|
||||
NETBIAN_LIB_SOURCED=1
|
||||
readonly NETBIAN_LIB_SOURCED
|
||||
|
||||
###############################################################################
|
||||
### Helpers generaux
|
||||
#
|
||||
|
||||
enable_strict_mode() {
|
||||
set -Eeuo pipefail
|
||||
}
|
||||
|
||||
err() { echo "$@" >&2; }
|
||||
errln() { printf '%s\n' "$*" >&2; }
|
||||
fatal() {
|
||||
err "$@"
|
||||
exit 2
|
||||
}
|
||||
|
||||
exists_cmd() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
safe_tempfile() {
|
||||
mktemp --tmpdir netbian.tmp.XXXXXX 2>/dev/null || mktemp /tmp/netbian.tmp.XXXXXX
|
||||
}
|
||||
|
||||
require_debian_version() {
|
||||
local requested="${1:-}"
|
||||
[[ -n "$requested" ]] || fatal "Missing required Debian version argument."
|
||||
[[ -r /etc/os-release ]] || fatal "/etc/os-release not found or not readable."
|
||||
|
||||
. /etc/os-release
|
||||
[[ "${ID:-}" == "debian" ]] || fatal "Not Debian (ID=${ID:-})."
|
||||
|
||||
case "${VERSION_ID:-}" in
|
||||
"$requested" | "$requested".*) return 0 ;;
|
||||
*) fatal "You are using Debian ${VERSION_ID:-}, Debian ${requested} is required." ;;
|
||||
esac
|
||||
}
|
||||
|
||||
require_valid_name() {
|
||||
local kind="$1" value="$2"
|
||||
[[ -n "$value" ]] || fatal "Missing ${kind}."
|
||||
[[ "$value" =~ ^[a-z0-9][a-z0-9_-]*$ ]] || fatal "Invalid ${kind}: ${value}"
|
||||
}
|
||||
|
||||
validate_config_file() {
|
||||
local cfg="$1" line lineno=0 key value
|
||||
[[ -f "$cfg" ]] || return 0
|
||||
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
lineno=$((lineno + 1))
|
||||
[[ -z "$line" ]] && continue
|
||||
[[ "$line" =~ ^[[:space:]]*# ]] && continue
|
||||
if [[ ! "$line" =~ ^[A-Za-z_][A-Za-z0-9_]*= ]]; then
|
||||
fatal "Invalid config line ${cfg}:${lineno}: ${line}"
|
||||
fi
|
||||
key="${line%%=*}"
|
||||
value="${line#*=}"
|
||||
case "$key" in
|
||||
profile) require_valid_name "profile" "$value" ;;
|
||||
lang)
|
||||
[[ -z "$value" || "$value" =~ ^[a-z]{2}([-_][a-z]{2})?$ ]] || fatal "Invalid language code in ${cfg}:${lineno}: ${value}"
|
||||
;;
|
||||
esac
|
||||
done <"$cfg"
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
### Logging and execution summary
|
||||
#
|
||||
|
||||
log_run() { printf '[RUN] %s\n' "$*"; }
|
||||
log_ok() { printf '[OK] %s\n' "$*"; }
|
||||
log_skip() {
|
||||
NETBIAN_SKIP_EVENTS=$(( ${NETBIAN_SKIP_EVENTS:-0} + 1 ))
|
||||
printf '[SKIP] %s\n' "$*"
|
||||
}
|
||||
log_warn() { printf '[WARN] %s\n' "$*"; }
|
||||
log_info() { printf '[INFO] %s\n' "$*"; }
|
||||
|
||||
# Runtime flags
|
||||
NETBIAN_DRY_RUN="${NETBIAN_DRY_RUN:-0}"
|
||||
NETBIAN_DEBUG="${NETBIAN_DEBUG:-0}"
|
||||
|
||||
# Execution summary
|
||||
if [[ -z "${NETBIAN_SUMMARY_INITIALIZED:-}" ]]; then
|
||||
declare -ag EXECUTED_STEPS=()
|
||||
declare -ag SKIPPED_STEPS=()
|
||||
declare -ag EXECUTED_ROLES=()
|
||||
declare -ag FAILED_STEPS=()
|
||||
NETBIAN_SKIP_EVENTS=0
|
||||
NETBIAN_SUMMARY_INITIALIZED=1
|
||||
fi
|
||||
|
||||
debug_enabled() {
|
||||
[[ "${NETBIAN_DEBUG:-0}" == "1" ]]
|
||||
}
|
||||
|
||||
dry_run_enabled() {
|
||||
[[ "${NETBIAN_DRY_RUN:-0}" == "1" ]]
|
||||
}
|
||||
|
||||
record_executed_step() {
|
||||
EXECUTED_STEPS+=("$1")
|
||||
}
|
||||
|
||||
record_skipped_step() {
|
||||
SKIPPED_STEPS+=("$1")
|
||||
}
|
||||
|
||||
record_failed_step() {
|
||||
FAILED_STEPS+=("$1")
|
||||
}
|
||||
|
||||
record_executed_role() {
|
||||
EXECUTED_ROLES+=("$1")
|
||||
}
|
||||
|
||||
print_execution_summary() {
|
||||
local item
|
||||
echo
|
||||
echo "==== Execution summary ===="
|
||||
printf 'Roles executed : %d\n' "${#EXECUTED_ROLES[@]}"
|
||||
printf 'Steps executed : %d\n' "${#EXECUTED_STEPS[@]}"
|
||||
printf 'Steps skipped : %d\n' "${#SKIPPED_STEPS[@]}"
|
||||
printf 'Skip events : %d\n' "${NETBIAN_SKIP_EVENTS:-0}"
|
||||
printf 'Steps failed : %d\n' "${#FAILED_STEPS[@]}"
|
||||
|
||||
if ((${#EXECUTED_ROLES[@]} > 0)); then
|
||||
echo "Executed roles:"
|
||||
for item in "${EXECUTED_ROLES[@]}"; do
|
||||
printf ' - %s\n' "$item"
|
||||
done
|
||||
fi
|
||||
|
||||
if ((${#SKIPPED_STEPS[@]} > 0)); then
|
||||
echo "Skipped steps:"
|
||||
for item in "${SKIPPED_STEPS[@]}"; do
|
||||
printf ' - %s\n' "$item"
|
||||
done
|
||||
fi
|
||||
|
||||
if ((${#FAILED_STEPS[@]} > 0)); then
|
||||
echo "Failed steps:"
|
||||
for item in "${FAILED_STEPS[@]}"; do
|
||||
printf ' - %s\n' "$item"
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
### Gestion de fichiers
|
||||
#
|
||||
|
||||
write_if_changed() {
|
||||
local src="$1" dst="$2"
|
||||
|
||||
if [[ -f "$dst" ]] && cmp -s "$src" "$dst"; then
|
||||
rm -f "$src"
|
||||
return 1
|
||||
fi
|
||||
|
||||
mv "$src" "$dst"
|
||||
chmod 0644 "$dst"
|
||||
return 0
|
||||
}
|
||||
|
||||
write_text_file_if_changed() {
|
||||
local content="$1" dst="$2" mode="${3:-0644}" tmp
|
||||
install -d -m0755 "$(dirname "$dst")"
|
||||
tmp="$(safe_tempfile)" || tmp="/tmp/netbian.write.$$"
|
||||
printf '%s' "$content" >"$tmp"
|
||||
if write_if_changed "$tmp" "$dst"; then
|
||||
chmod "$mode" "$dst"
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
copy_config() {
|
||||
local rel="$1" dest_dir="$2" src file dest user rootname
|
||||
|
||||
[[ -n "$rel" && -n "$dest_dir" ]] || {
|
||||
echo "Usage: copy_config <relpath> <dest_dir>" >&2
|
||||
return 2
|
||||
}
|
||||
|
||||
src="$PROJECT_DIR/config/$rel"
|
||||
[[ -f "$src" ]] || {
|
||||
log_warn "$src not found - skipping"
|
||||
return 0
|
||||
}
|
||||
|
||||
file="$(basename "$rel")"
|
||||
dest="$(realpath -m "$dest_dir")/$file"
|
||||
install -d "$(dirname "$dest")"
|
||||
|
||||
rootname="${rel%%/*}"
|
||||
rootname="$(tr '[:lower:]' '[:upper:]' <<<"${rootname:0:1}")${rootname:1}"
|
||||
if [[ ! -f "$dest" ]] || ! cmp -s "$src" "$dest"; then
|
||||
log_info "${rootname} configuration copied"
|
||||
cp -a "$src" "$dest"
|
||||
else
|
||||
log_skip "${rootname} already configured"
|
||||
fi
|
||||
|
||||
if [[ "$dest" =~ ^/home/([^/]+)/ ]]; then
|
||||
user="${BASH_REMATCH[1]}"
|
||||
if id -u "$user" >/dev/null 2>&1; then
|
||||
chown "$user:$user" "$dest"
|
||||
chmod 0664 "$dest"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
chown root:root "$dest"
|
||||
chmod 0644 "$dest"
|
||||
return 0
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
### Paquets / APT
|
||||
#
|
||||
|
||||
ensure_packages_installed() {
|
||||
local to_install=() p prev_deb
|
||||
for p in "$@"; do
|
||||
if ! dpkg-query -W -f='${Status}' "$p" 2>/dev/null | grep -q '^install ok installed$'; then
|
||||
to_install+=("$p")
|
||||
fi
|
||||
done
|
||||
|
||||
if ((${#to_install[@]} == 0)); then
|
||||
log_skip "packages already installed"
|
||||
return 3
|
||||
fi
|
||||
|
||||
prev_deb="${DEBIAN_FRONTEND:-}"
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
if ! apt-get install -y "${to_install[@]}"; then
|
||||
if [[ -n "$prev_deb" ]]; then
|
||||
export DEBIAN_FRONTEND="$prev_deb"
|
||||
else
|
||||
unset DEBIAN_FRONTEND
|
||||
fi
|
||||
echo "Failed to install packages: ${to_install[*]}" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ -n "$prev_deb" ]]; then
|
||||
export DEBIAN_FRONTEND="$prev_deb"
|
||||
else
|
||||
unset DEBIAN_FRONTEND
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
add_apt_key_from_url() {
|
||||
local key_url="$1" keyring_path="$2" tmpkey tmpkeyring
|
||||
install -d -m0755 "$(dirname "$keyring_path")"
|
||||
tmpkey="$(safe_tempfile)" || tmpkey="/tmp/key.$$"
|
||||
tmpkeyring="$(safe_tempfile)" || tmpkeyring="/tmp/keyring.$$"
|
||||
|
||||
(
|
||||
set -e
|
||||
if exists_cmd curl; then
|
||||
curl -fsSL "$key_url" -o "$tmpkey"
|
||||
elif exists_cmd wget; then
|
||||
wget -qO "$tmpkey" "$key_url"
|
||||
else
|
||||
echo "curl or wget is required to download the key, but none are installed." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exists_cmd gpg || {
|
||||
echo "gpg is required to dearmor the key, but is not installed." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
gpg --batch --yes --dearmor -o "$tmpkeyring" "$tmpkey"
|
||||
mv -f "$tmpkeyring" "$keyring_path"
|
||||
chown root:root "$keyring_path" || true
|
||||
chmod a+r "$keyring_path"
|
||||
rm -f "$tmpkey"
|
||||
)
|
||||
}
|
||||
|
||||
add_apt_source_file() {
|
||||
local content="$1" dest="$2" tmpfile
|
||||
install -d -m0755 "$(dirname "$dest")"
|
||||
tmpfile="$(safe_tempfile)" || tmpfile="/tmp/src.$$"
|
||||
printf '%s\n' "$content" >"$tmpfile"
|
||||
|
||||
if [[ -f "$dest" ]] && cmp -s "$tmpfile" "$dest"; then
|
||||
rm -f "$tmpfile"
|
||||
return 0
|
||||
fi
|
||||
|
||||
mv -f "$tmpfile" "$dest"
|
||||
chmod 0644 "$dest"
|
||||
return 0
|
||||
}
|
||||
|
||||
add_apt_sources_file() {
|
||||
add_apt_source_file "$1" "$2"
|
||||
}
|
||||
|
||||
install_apt_repo() {
|
||||
local key_url="$1" keyring_path="$2" sources_content="$3" sources_path="$4"
|
||||
shift 4
|
||||
local pkgs=("$@") all_installed=true p key_added=false src_changed=false
|
||||
|
||||
for p in "${pkgs[@]}"; do
|
||||
if ! dpkg-query -W -f='${Status}' "$p" 2>/dev/null | grep -q 'installed'; then
|
||||
all_installed=false
|
||||
break
|
||||
fi
|
||||
done
|
||||
$all_installed && return 0
|
||||
|
||||
if [[ ! -f "$keyring_path" ]]; then
|
||||
if add_apt_key_from_url "$key_url" "$keyring_path"; then
|
||||
key_added=true
|
||||
else
|
||||
log_warn "failed to add key from $key_url"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ ! -f "$sources_path" ]] || ! printf '%s\n' "$sources_content" | cmp -s - "$sources_path"; then
|
||||
src_changed=true
|
||||
add_apt_sources_file "$sources_content" "$sources_path"
|
||||
fi
|
||||
|
||||
if $key_added || $src_changed; then
|
||||
apt-get update
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
### Systeme
|
||||
#
|
||||
|
||||
ufw_initialize() {
|
||||
ufw --force reset >/dev/null || true
|
||||
ufw default deny incoming
|
||||
ufw default allow outgoing
|
||||
ufw --force enable
|
||||
}
|
||||
|
||||
apply_ufw_rules_file() {
|
||||
local rules_file="$1" rule
|
||||
[[ -f "$rules_file" ]] || return 0
|
||||
|
||||
while IFS= read -r rule || [[ -n "$rule" ]]; do
|
||||
rule="${rule%%#*}"
|
||||
rule="$(echo "$rule" | xargs)"
|
||||
[[ -z "$rule" ]] && continue
|
||||
log_info "Allowing firewall rule: $rule"
|
||||
ufw allow "$rule"
|
||||
done <"$rules_file"
|
||||
return 0
|
||||
}
|
||||
|
||||
remove_primary_network_section() {
|
||||
local if_file="/etc/network/interfaces" header='# The primary network interface' tmp
|
||||
if [[ -f "$if_file" ]] && grep -q "^${header}" "$if_file" && sed --version >/dev/null 2>&1; then
|
||||
tmp="$(mktemp --tmpdir netif.XXXXXX)" || tmp="/tmp/netif.$$"
|
||||
sed "/${header}/Q" "$if_file" >"$tmp" || true
|
||||
mv -f "$tmp" "$if_file"
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_grub_cmdline() {
|
||||
local f="${1:-/etc/default/grub}" from="${2:-quiet}" to="${3:-quiet loglevel=3 nowatchdog}"
|
||||
local pat="^GRUB_CMDLINE_LINUX_DEFAULT=\"${from}\"$"
|
||||
local repl="GRUB_CMDLINE_LINUX_DEFAULT=\"${to}\""
|
||||
|
||||
[[ -r "$f" ]] || {
|
||||
echo "File not found or not readable: $f" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
if grep -qE "$pat" "$f"; then
|
||||
sed -i "s|${pat}|${repl}|" "$f"
|
||||
command -v update-grub >/dev/null 2>&1 && update-grub || true
|
||||
log_ok "GRUB configuration updated"
|
||||
else
|
||||
log_skip "GRUB command line already compliant"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
service_exists() {
|
||||
local service="$1"
|
||||
systemctl list-unit-files --type=service --no-legend 2>/dev/null | awk '{print $1}' | grep -Fxq "${service}.service"
|
||||
}
|
||||
|
||||
restart_service_if_present() {
|
||||
local service="$1"
|
||||
if service_exists "$service"; then
|
||||
log_info "Restarting ${service}"
|
||||
systemctl restart "$service" || true
|
||||
else
|
||||
log_skip "service ${service} absent"
|
||||
fi
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
### Utilisateur / roles
|
||||
#
|
||||
|
||||
get_target_user() {
|
||||
local user=""
|
||||
|
||||
if [[ -n "${SUDO_USER:-}" && "${SUDO_USER}" != "root" ]]; then
|
||||
printf '%s\n' "$SUDO_USER"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -n "${PKEXEC_UID:-}" ]] && user="$(id -nu "$PKEXEC_UID" 2>/dev/null || true)" && [[ -n "$user" && "$user" != "root" ]]; then
|
||||
printf '%s\n' "$user"
|
||||
return 0
|
||||
fi
|
||||
|
||||
user="$(logname 2>/dev/null || true)"
|
||||
if [[ -n "$user" && "$user" != "root" ]]; then
|
||||
printf '%s\n' "$user"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
install_code_extensions() {
|
||||
local code_bin="$1"
|
||||
shift
|
||||
[[ -n "$code_bin" && "$#" -gt 0 ]] || return 0
|
||||
|
||||
if ! command -v "$code_bin" >/dev/null 2>&1 && [[ -x "/usr/bin/$code_bin" ]]; then
|
||||
code_bin="/usr/bin/$code_bin"
|
||||
elif ! command -v "$code_bin" >/dev/null 2>&1; then
|
||||
echo "Code binary $code_bin not found; skipping extensions install." >&2
|
||||
return 0
|
||||
fi
|
||||
|
||||
local run_as ext installed_list
|
||||
run_as="$(get_target_user 2>/dev/null || true)"
|
||||
|
||||
log_info "Synchronizing ${code_bin##*/} extensions"
|
||||
|
||||
if [[ -n "$run_as" ]]; then
|
||||
installed_list="$(sudo -u "$run_as" env PATH="${PATH:-/usr/bin:/bin}" "$code_bin" --list-extensions 2>/dev/null || true)"
|
||||
else
|
||||
installed_list="$("$code_bin" --list-extensions 2>/dev/null || true)"
|
||||
fi
|
||||
|
||||
for ext in "$@"; do
|
||||
if grep -Fqx "$ext" <<<"$installed_list"; then
|
||||
log_skip "extension '$ext' already installed"
|
||||
continue
|
||||
fi
|
||||
|
||||
log_info "Installing extension '$ext'"
|
||||
if [[ -n "$run_as" ]]; then
|
||||
sudo -u "$run_as" env PATH="${PATH:-/usr/bin:/bin}" "$code_bin" --install-extension "$ext" --force || true
|
||||
else
|
||||
"$code_bin" --install-extension "$ext" --force || true
|
||||
fi
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
configure_php_no_jit() {
|
||||
local php_version="${1:-$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;' 2>/dev/null || true)}"
|
||||
local mod_name="no-jit" mods_dir mod_file sapi
|
||||
local config_content='; Disable JIT because it conflicts with Xdebug
|
||||
opcache.jit=0
|
||||
opcache.jit_buffer_size=0
|
||||
'
|
||||
|
||||
[[ -n "$php_version" ]] || {
|
||||
echo "Unable to detect PHP version." >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
log_info "Using PHP version: ${php_version}"
|
||||
[[ -d "/etc/php/${php_version}" ]] || {
|
||||
echo "PHP config directory not found: /etc/php/${php_version}" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
mods_dir="/etc/php/${php_version}/mods-available"
|
||||
mod_file="${mods_dir}/${mod_name}.ini"
|
||||
install -d "$mods_dir"
|
||||
write_text_file_if_changed "$config_content" "$mod_file" >/dev/null || true
|
||||
|
||||
if exists_cmd phpenmod; then
|
||||
phpenmod -v "$php_version" "$mod_name"
|
||||
else
|
||||
echo "phpenmod not found; skipping module enablement." >&2
|
||||
fi
|
||||
|
||||
if exists_cmd phpquery; then
|
||||
echo
|
||||
echo "Module status by SAPI:"
|
||||
for sapi in cli apache2 fpm; do
|
||||
if phpquery -v "$php_version" -s "$sapi" -m "$mod_name" >/dev/null 2>&1; then
|
||||
echo " - ${sapi}: enabled"
|
||||
elif [[ -d "/etc/php/${php_version}/${sapi}" ]]; then
|
||||
echo " - ${sapi}: not enabled"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
restart_service_if_present apache2
|
||||
restart_service_if_present "php${php_version}-fpm"
|
||||
|
||||
echo
|
||||
echo "Verification:"
|
||||
php -i | grep -E '^opcache.jit =>|^opcache.jit_buffer_size =>|^Loaded Configuration File|^Scan this dir for additional .ini files' || true
|
||||
return 0
|
||||
}
|
||||
|
||||
load_role_packages() {
|
||||
local role="$1"
|
||||
local packages_file="$ROLE_DIR/$role/packages.list"
|
||||
ROLE_PACKAGES=()
|
||||
|
||||
if [[ -f "$packages_file" ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "$packages_file"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
append_localized_packages() {
|
||||
local -n target_ref="$1"
|
||||
local lang_override="${lang:-}" l10n_fragment="$ROLE_DIR/base/l10n.packages"
|
||||
local mapping base prefix cand
|
||||
|
||||
[[ -n "$lang_override" && -f "$l10n_fragment" ]] || return 0
|
||||
|
||||
L10N_MAP_PKGS=()
|
||||
source "$l10n_fragment"
|
||||
apt-get update >/dev/null 2>&1 || true
|
||||
|
||||
for mapping in "${L10N_MAP_PKGS[@]:-}"; do
|
||||
base="${mapping%%::*}"
|
||||
prefix="${mapping##*::}"
|
||||
cand="${prefix}-${lang_override}"
|
||||
|
||||
if apt-cache show "$cand" >/dev/null 2>&1 || {
|
||||
dpkg-query -W -f='${Status}' "$base" 2>/dev/null | grep -q 'installed' &&
|
||||
! dpkg-query -W -f='${Status}' "$cand" 2>/dev/null | grep -q 'installed'
|
||||
}; then
|
||||
case " ${target_ref[*]} " in
|
||||
*" $cand "*) : ;;
|
||||
*) target_ref+=("$cand") ;;
|
||||
esac
|
||||
fi
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
verify_role_manifest() {
|
||||
local role="$1"
|
||||
local manifest="$ROLE_DIR/$role/packages.list"
|
||||
local line trimmed
|
||||
|
||||
[[ -f "$manifest" ]] || return 0
|
||||
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
trimmed="${line#"${line%%[![:space:]]*}"}"
|
||||
[[ -z "$trimmed" ]] && continue
|
||||
[[ "$trimmed" =~ ^# ]] && continue
|
||||
if [[ "$trimmed" =~ [[:space:]] ]]; then
|
||||
fatal "Invalid package entry in $manifest: $trimmed"
|
||||
fi
|
||||
done <"$manifest"
|
||||
return 0
|
||||
}
|
||||
|
||||
validate_role() {
|
||||
local role="$1"
|
||||
local role_path="$ROLE_DIR/$role"
|
||||
local allowed='^(repo\.sh|install\.sh|config\.sh|run\.sh|packages\.list|l10n\.packages|rules\.[A-Za-z0-9_-]+\.list)$'
|
||||
local item base
|
||||
|
||||
require_valid_name "role" "$role"
|
||||
[[ -d "$role_path" ]] || fatal "Role not found: $role"
|
||||
|
||||
shopt -s nullglob
|
||||
for item in "$role_path"/*; do
|
||||
base="$(basename "$item")"
|
||||
[[ "$base" =~ $allowed ]] || fatal "Unexpected file in role $role: $base"
|
||||
done
|
||||
shopt -u nullglob
|
||||
return 0
|
||||
}
|
||||
|
||||
run_role_script() {
|
||||
local role="$1"
|
||||
local step="$2"
|
||||
local script="$ROLE_DIR/$role/$step"
|
||||
local step_name="$role/$step"
|
||||
|
||||
if [[ ! -f "$script" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_run "$step_name"
|
||||
if dry_run_enabled; then
|
||||
log_ok "$step_name (dry-run)"
|
||||
record_skipped_step "$step_name (dry-run)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if bash "$script"; then
|
||||
log_ok "$step_name"
|
||||
record_executed_step "$step_name"
|
||||
else
|
||||
record_failed_step "$step_name"
|
||||
fatal "Step failed: $step_name"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
run_role_packages() {
|
||||
local role="$1"
|
||||
local -n pkgs_ref="$2"
|
||||
local step_name="$role/packages.list"
|
||||
|
||||
if ((${#pkgs_ref[@]} == 0)); then
|
||||
log_skip "$step_name empty"
|
||||
record_skipped_step "$step_name"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_run "$step_name"
|
||||
if dry_run_enabled; then
|
||||
printf '[DRY] Would ensure packages: %s\n' "${pkgs_ref[*]}"
|
||||
log_ok "$step_name (dry-run)"
|
||||
record_skipped_step "$step_name (dry-run)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local rc=0
|
||||
if ensure_packages_installed "${pkgs_ref[@]}"; then
|
||||
rc=0
|
||||
else
|
||||
rc=$?
|
||||
fi
|
||||
|
||||
case "$rc" in
|
||||
0)
|
||||
log_ok "$step_name"
|
||||
record_executed_step "$step_name"
|
||||
;;
|
||||
3)
|
||||
log_ok "$step_name (already compliant)"
|
||||
record_skipped_step "$step_name"
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
return 0
|
||||
}
|
||||
|
||||
run_role() {
|
||||
local role="$1"
|
||||
local -a role_packages=()
|
||||
|
||||
validate_role "$role"
|
||||
echo
|
||||
echo "==== Role: $role ===="
|
||||
|
||||
run_role_script "$role" repo.sh || return 1
|
||||
load_role_packages "$role" || return 1
|
||||
role_packages=("${ROLE_PACKAGES[@]:-}")
|
||||
append_localized_packages role_packages || return 1
|
||||
run_role_packages "$role" role_packages || return 1
|
||||
run_role_script "$role" install.sh || return 1
|
||||
run_role_script "$role" config.sh || return 1
|
||||
run_role_script "$role" run.sh || return 1
|
||||
|
||||
record_executed_role "$role"
|
||||
return 0
|
||||
}
|
||||
|
||||
list_available_profiles() {
|
||||
local p
|
||||
shopt -s nullglob
|
||||
for p in "$PROFILE_DIR"/*.sh; do
|
||||
basename "${p%.sh}"
|
||||
done
|
||||
shopt -u nullglob
|
||||
return 0
|
||||
}
|
||||
|
||||
list_available_roles() {
|
||||
local r
|
||||
shopt -s nullglob
|
||||
for r in "$ROLE_DIR"/*; do
|
||||
[[ -d "$r" ]] || continue
|
||||
basename "$r"
|
||||
done
|
||||
shopt -u nullglob
|
||||
return 0
|
||||
}
|
||||
|
||||
validate_profile_definition() {
|
||||
local profile_file="$1"
|
||||
[[ -f "$profile_file" ]] || fatal "Profile definition not found: $profile_file"
|
||||
|
||||
declare -a ROLE_ORDER=()
|
||||
# shellcheck disable=SC1090
|
||||
source "$profile_file"
|
||||
((${#ROLE_ORDER[@]} > 0)) || fatal "No roles declared in $profile_file"
|
||||
return 0
|
||||
}
|
||||
|
||||
validate_all_roles() {
|
||||
local role
|
||||
for role in "$@"; do
|
||||
validate_role "$role"
|
||||
verify_role_manifest "$role"
|
||||
done
|
||||
return 0
|
||||
}
|
||||
2
profiles/cli.sh
Executable file
2
profiles/cli.sh
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env bash
|
||||
ROLE_ORDER=(base zram)
|
||||
2
profiles/desktop.sh
Executable file
2
profiles/desktop.sh
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env bash
|
||||
ROLE_ORDER=(base desktop firewall zram)
|
||||
2
profiles/devel.sh
Executable file
2
profiles/devel.sh
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env bash
|
||||
ROLE_ORDER=(base desktop firewall zram docker codium devel)
|
||||
2
profiles/server.sh
Executable file
2
profiles/server.sh
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env bash
|
||||
ROLE_ORDER=(base firewall server zram docker)
|
||||
38
roles.sh
Executable file
38
roles.sh
Executable file
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
# Orchestrateur de roles
|
||||
source "$PROJECT_DIR/lib.sh"
|
||||
enable_strict_mode
|
||||
|
||||
CONFIG_FILE="${CONFIG_FILE:-/etc/netbian.conf}"
|
||||
[[ -f "$CONFIG_FILE" ]] || fatal "Config file $CONFIG_FILE not found. Create it via run.sh."
|
||||
validate_config_file "$CONFIG_FILE"
|
||||
source "$CONFIG_FILE"
|
||||
[[ -n "${profile:-}" ]] || fatal "The 'profile' variable is not set in $CONFIG_FILE."
|
||||
export profile
|
||||
[[ -n "${lang:-}" ]] && export lang
|
||||
|
||||
PROFILE_FILE="$PROFILE_DIR/${profile}.sh"
|
||||
[[ -f "$PROFILE_FILE" ]] || fatal "Profile definition not found: $PROFILE_FILE"
|
||||
|
||||
trap 'status=$?; print_execution_summary; exit $status' EXIT
|
||||
|
||||
declare -a ROLE_ORDER=()
|
||||
source "$PROFILE_FILE"
|
||||
|
||||
((${#ROLE_ORDER[@]} > 0)) || fatal "No roles declared in $PROFILE_FILE"
|
||||
|
||||
validate_profile_definition "$PROFILE_FILE"
|
||||
validate_all_roles "${ROLE_ORDER[@]}"
|
||||
|
||||
for role in "${ROLE_ORDER[@]}"; do
|
||||
run_role "$role"
|
||||
done
|
||||
|
||||
cat <<'EOM'
|
||||
____ _
|
||||
/ ___| _ _ ___ ___ ___ ___ ___ | |
|
||||
\___ \| | | |/ __/ __/ _ \/ __/ __| | |
|
||||
___) | |_| | (_| (_| __/\__ \__ \ |_|
|
||||
|____/ \__,_|\___\___\___||___/___/ (_)
|
||||
|
||||
EOM
|
||||
4
roles/base/l10n.packages
Normal file
4
roles/base/l10n.packages
Normal file
@@ -0,0 +1,4 @@
|
||||
L10N_MAP_PKGS=(
|
||||
"firefox-esr::firefox-esr-l10n"
|
||||
"libreoffice::libreoffice-l10n"
|
||||
)
|
||||
11
roles/base/packages.list
Normal file
11
roles/base/packages.list
Normal file
@@ -0,0 +1,11 @@
|
||||
ROLE_PACKAGES=(
|
||||
"ca-certificates"
|
||||
"curl"
|
||||
"git"
|
||||
"gnupg"
|
||||
"htop"
|
||||
"rsync"
|
||||
"tree"
|
||||
"ufw"
|
||||
"wget"
|
||||
)
|
||||
47
roles/base/repo.sh
Executable file
47
roles/base/repo.sh
Executable file
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env bash
|
||||
# Configure les dépôts Debian de base
|
||||
source "$PROJECT_DIR/lib.sh"
|
||||
enable_strict_mode
|
||||
|
||||
cat <<'EOM'
|
||||
|
||||
=> Base APT configuration
|
||||
|
||||
EOM
|
||||
|
||||
KEY_URL="https://ftp-master.debian.org/keys/archive-key-12.asc"
|
||||
KEYRING="/usr/share/keyrings/debian-archive-keyring.pgp"
|
||||
SOURCES="/etc/apt/sources.list.d/debian.sources"
|
||||
|
||||
read -r -d '' CONTENT <<EOM || true
|
||||
Types: deb
|
||||
URIs: https://deb.debian.org/debian
|
||||
Suites: trixie trixie-updates
|
||||
Components: main non-free-firmware contrib
|
||||
Signed-By: $KEYRING
|
||||
|
||||
Types: deb
|
||||
URIs: https://security.debian.org/debian-security
|
||||
Suites: trixie-security
|
||||
Components: main non-free-firmware contrib
|
||||
Signed-By: $KEYRING
|
||||
EOM
|
||||
|
||||
[[ -f /etc/apt/sources.list ]] && rm -f /etc/apt/sources.list && echo "Old /etc/apt/sources.list removed."
|
||||
|
||||
if [[ ! -f "$KEYRING" ]]; then
|
||||
if ! add_apt_key_from_url "$KEY_URL" "$KEYRING"; then
|
||||
echo "Warning: failed to add key from $KEY_URL" >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ ! -f "$SOURCES" ]] || ! printf '%s\n' "$CONTENT" | cmp -s - "$SOURCES"; then
|
||||
add_apt_sources_file "$CONTENT" "$SOURCES" || {
|
||||
echo "Failed to write $SOURCES" >&2
|
||||
exit 1
|
||||
}
|
||||
apt-get update
|
||||
echo "File $SOURCES written"
|
||||
else
|
||||
echo "File $SOURCES unchanged"
|
||||
fi
|
||||
22
roles/codium/config.sh
Executable file
22
roles/codium/config.sh
Executable file
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
source "$PROJECT_DIR/lib.sh"
|
||||
enable_strict_mode
|
||||
|
||||
cat <<'EOM'
|
||||
|
||||
=> Codium configuration
|
||||
|
||||
EOM
|
||||
|
||||
install_code_extensions codium \
|
||||
junstyle.php-cs-fixer \
|
||||
mkhl.shfmt \
|
||||
sibiraj-s.vscode-scss-formatter \
|
||||
asispts.vscode-symfony-twig || true
|
||||
|
||||
TARGET_USER="$(get_target_user 2>/dev/null || true)"
|
||||
if [[ -n "$TARGET_USER" ]]; then
|
||||
copy_config "codium/settings.json" "/home/${TARGET_USER}/.config/VSCodium/User"
|
||||
else
|
||||
echo 'No regular target user detected for VSCodium settings; skipping user settings copy.' >&2
|
||||
fi
|
||||
3
roles/codium/packages.list
Normal file
3
roles/codium/packages.list
Normal file
@@ -0,0 +1,3 @@
|
||||
ROLE_PACKAGES=(
|
||||
"codium"
|
||||
)
|
||||
28
roles/codium/repo.sh
Executable file
28
roles/codium/repo.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
source "$PROJECT_DIR/lib.sh"
|
||||
enable_strict_mode
|
||||
|
||||
cat <<'EOM'
|
||||
|
||||
=> VSCodium
|
||||
|
||||
EOM
|
||||
|
||||
KEYRING_DIR="/etc/apt/keyrings"
|
||||
KEYRING="$KEYRING_DIR/vscodium-archive-keyring.gpg"
|
||||
KEY_URL="https://gitlab.com/paulcarroty/vscodium-deb-rpm-repo/-/raw/master/pub.gpg"
|
||||
SRC_FILE="/etc/apt/sources.list.d/vscodium.sources"
|
||||
VSCODIUM_URI="https://download.vscodium.com/debs"
|
||||
ARCH_CUR=$(dpkg --print-architecture 2>/dev/null || true)
|
||||
ARCH_CUR=${ARCH_CUR:-amd64}
|
||||
|
||||
read -r -d '' VSCODIUM_SOURCES_CONTENT <<EOM || true
|
||||
Types: deb
|
||||
URIs: $VSCODIUM_URI
|
||||
Suites: vscodium
|
||||
Components: main
|
||||
Architectures: $ARCH_CUR
|
||||
Signed-By: $KEYRING
|
||||
EOM
|
||||
|
||||
install_apt_repo "$KEY_URL" "$KEYRING" "$VSCODIUM_SOURCES_CONTENT" "$SRC_FILE" codium
|
||||
14
roles/desktop/config.sh
Executable file
14
roles/desktop/config.sh
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
# Configuration du rôle desktop
|
||||
source "$PROJECT_DIR/lib.sh"
|
||||
enable_strict_mode
|
||||
|
||||
cat <<'EOM'
|
||||
|
||||
=> Desktop configuration
|
||||
|
||||
EOM
|
||||
|
||||
ensure_grub_cmdline
|
||||
remove_primary_network_section
|
||||
copy_config "firefox/policies.json" "/etc/firefox/policies"
|
||||
14
roles/desktop/packages.list
Normal file
14
roles/desktop/packages.list
Normal file
@@ -0,0 +1,14 @@
|
||||
ROLE_PACKAGES=(
|
||||
"gimp"
|
||||
"gnome-core"
|
||||
"gnome-music"
|
||||
"gnome-shell-extension-caffeine"
|
||||
"gnome-tweaks"
|
||||
"gufw"
|
||||
"libreoffice"
|
||||
"keepassxc-minimal"
|
||||
"papirus-icon-theme"
|
||||
"qbittorrent"
|
||||
"firefox-esr"
|
||||
"torbrowser-launcher"
|
||||
)
|
||||
13
roles/devel/config.sh
Executable file
13
roles/devel/config.sh
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
source "$PROJECT_DIR/lib.sh"
|
||||
enable_strict_mode
|
||||
|
||||
cat <<'EOM'
|
||||
|
||||
=> Developer environment
|
||||
|
||||
EOM
|
||||
|
||||
configure_php_no_jit
|
||||
log_ok "PHP developer configuration applied"
|
||||
echo 'Developer profile ready.'
|
||||
9
roles/devel/packages.list
Normal file
9
roles/devel/packages.list
Normal file
@@ -0,0 +1,9 @@
|
||||
ROLE_PACKAGES=(
|
||||
"php-cli"
|
||||
"composer"
|
||||
"sqlite3"
|
||||
"php-sqlite3"
|
||||
"npm"
|
||||
"shfmt"
|
||||
"php-xdebug"
|
||||
)
|
||||
7
roles/docker/packages.list
Normal file
7
roles/docker/packages.list
Normal file
@@ -0,0 +1,7 @@
|
||||
ROLE_PACKAGES=(
|
||||
"docker-ce"
|
||||
"docker-ce-cli"
|
||||
"containerd.io"
|
||||
"docker-buildx-plugin"
|
||||
"docker-compose-plugin"
|
||||
)
|
||||
29
roles/docker/repo.sh
Executable file
29
roles/docker/repo.sh
Executable file
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
source "$PROJECT_DIR/lib.sh"
|
||||
enable_strict_mode
|
||||
|
||||
cat <<'EOM'
|
||||
|
||||
=> Docker
|
||||
|
||||
EOM
|
||||
|
||||
KEYRING_DIR="/etc/apt/keyrings"
|
||||
KEYRING="$KEYRING_DIR/docker.gpg"
|
||||
KEY_URL="https://download.docker.com/linux/debian/gpg"
|
||||
SRC_FILE="/etc/apt/sources.list.d/docker.sources"
|
||||
CODENAME=$(source /etc/os-release && echo "$VERSION_CODENAME")
|
||||
DOCKER_URI="https://download.docker.com/linux/debian"
|
||||
ARCH_CUR=$(dpkg --print-architecture 2>/dev/null || true)
|
||||
ARCH_CUR=${ARCH_CUR:-amd64}
|
||||
|
||||
read -r -d '' DOCKER_SOURCES_CONTENT <<EOM || true
|
||||
Types: deb
|
||||
URIs: $DOCKER_URI
|
||||
Suites: $CODENAME
|
||||
Components: stable
|
||||
Architectures: $ARCH_CUR
|
||||
Signed-By: $KEYRING
|
||||
EOM
|
||||
|
||||
install_apt_repo "$KEY_URL" "$KEYRING" "$DOCKER_SOURCES_CONTENT" "$SRC_FILE" docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||
21
roles/firewall/config.sh
Executable file
21
roles/firewall/config.sh
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env bash
|
||||
# Declarative firewall configuration with common + profile-specific rules
|
||||
source "$PROJECT_DIR/lib.sh"
|
||||
enable_strict_mode
|
||||
|
||||
cat <<'EOM'
|
||||
|
||||
=> Firewall configuration
|
||||
|
||||
EOM
|
||||
|
||||
ufw_initialize
|
||||
|
||||
COMMON_RULES_FILE="$ROLE_DIR/firewall/rules.common.list"
|
||||
PROFILE_RULES_FILE="$ROLE_DIR/firewall/rules.${profile:-}.list"
|
||||
|
||||
apply_ufw_rules_file "$COMMON_RULES_FILE"
|
||||
apply_ufw_rules_file "$PROFILE_RULES_FILE"
|
||||
|
||||
ufw reload
|
||||
log_ok "Firewall rules applied"
|
||||
1
roles/firewall/rules.common.list
Normal file
1
roles/firewall/rules.common.list
Normal file
@@ -0,0 +1 @@
|
||||
# Common firewall rules
|
||||
1
roles/firewall/rules.desktop.list
Normal file
1
roles/firewall/rules.desktop.list
Normal file
@@ -0,0 +1 @@
|
||||
# Desktop-specific firewall rules
|
||||
2
roles/firewall/rules.devel.list
Normal file
2
roles/firewall/rules.devel.list
Normal file
@@ -0,0 +1,2 @@
|
||||
# Development-specific firewall rules
|
||||
# 3000/tcp
|
||||
8
roles/firewall/rules.server.list
Normal file
8
roles/firewall/rules.server.list
Normal file
@@ -0,0 +1,8 @@
|
||||
# Server-specific firewall rules
|
||||
ssh
|
||||
http
|
||||
https
|
||||
imap
|
||||
imaps
|
||||
smtp
|
||||
submissions
|
||||
28
roles/server/config.sh
Executable file
28
roles/server/config.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# Configuration du rôle server
|
||||
source "$PROJECT_DIR/lib.sh"
|
||||
enable_strict_mode
|
||||
|
||||
cat <<'EOM'
|
||||
|
||||
=> Server configuration
|
||||
|
||||
EOM
|
||||
|
||||
ensure_grub_cmdline
|
||||
|
||||
SSH_DIR="/etc/ssh/sshd_config.d"
|
||||
mkdir -p "$SSH_DIR"
|
||||
SSH_CONF="${SSH_DIR}/custom.conf"
|
||||
|
||||
read -r -d '' SSH_CONF_CONTENT <<'EOM' || true
|
||||
# SSH keys only
|
||||
PasswordAuthentication no
|
||||
PubkeyAuthentication yes
|
||||
PermitEmptyPasswords no
|
||||
EOM
|
||||
|
||||
if write_text_file_if_changed "$SSH_CONF_CONTENT" "$SSH_CONF" >/dev/null; then
|
||||
restart_service_if_present ssh
|
||||
restart_service_if_present sshd
|
||||
fi
|
||||
3
roles/server/packages.list
Normal file
3
roles/server/packages.list
Normal file
@@ -0,0 +1,3 @@
|
||||
ROLE_PACKAGES=(
|
||||
# add server-specific packages here, e.g. "nginx" "postgresql" "fail2ban"
|
||||
)
|
||||
30
roles/zram/config.sh
Executable file
30
roles/zram/config.sh
Executable file
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env bash
|
||||
source "${PROJECT_DIR:?}/lib.sh"
|
||||
enable_strict_mode
|
||||
|
||||
cat <<'EOM'
|
||||
|
||||
=> ZRAM
|
||||
|
||||
EOM
|
||||
|
||||
ZFILE="/etc/default/zramswap"
|
||||
|
||||
if [[ ! -f "$ZFILE" ]]; then
|
||||
write_text_file_if_changed $'# Configuration minimale pour zramswap\nALGO=zstd\nPERCENT=50\n' "$ZFILE" >/dev/null || true
|
||||
fi
|
||||
|
||||
if grep -q '^ALGO=lz4' "$ZFILE" 2>/dev/null; then
|
||||
sed -i 's/^ALGO=lz4/ALGO=zstd/' "$ZFILE"
|
||||
elif ! grep -q '^ALGO=' "$ZFILE" 2>/dev/null; then
|
||||
echo 'ALGO=zstd' >>"$ZFILE"
|
||||
fi
|
||||
|
||||
if ! grep -q '^PERCENT=' "$ZFILE" 2>/dev/null; then
|
||||
echo 'PERCENT=50' >>"$ZFILE"
|
||||
fi
|
||||
|
||||
restart_service_if_present zramswap
|
||||
if ! systemctl is-active --quiet zramswap.service; then
|
||||
echo 'Warning: zramswap.service not active' >&2
|
||||
fi
|
||||
3
roles/zram/packages.list
Normal file
3
roles/zram/packages.list
Normal file
@@ -0,0 +1,3 @@
|
||||
ROLE_PACKAGES=(
|
||||
"zram-tools"
|
||||
)
|
||||
177
run.sh
Executable file
177
run.sh
Executable file
@@ -0,0 +1,177 @@
|
||||
#!/usr/bin/env bash
|
||||
# Point d'entree pour le provisioning NETbian
|
||||
|
||||
export PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
export ROLE_DIR="$PROJECT_DIR/roles"
|
||||
export PROFILE_DIR="$PROJECT_DIR/profiles"
|
||||
|
||||
source "$PROJECT_DIR/lib.sh"
|
||||
enable_strict_mode
|
||||
|
||||
PROG="$(basename "$0")"
|
||||
usage() {
|
||||
local exit_code="${1:-1}"
|
||||
cat <<EOM >&2
|
||||
Usage: $PROG [options]
|
||||
Options:
|
||||
-p, --profile <server|desktop|devel|cli> Profile to install (required)
|
||||
-l, --lang <code> Language code if translations are needed (e.g. fr, es, de)
|
||||
--config <path> Config file path (default: /etc/netbian.conf)
|
||||
--list-profiles List available profiles and exit
|
||||
--list-roles List available roles and exit
|
||||
-h, --help Show this help
|
||||
EOM
|
||||
exit "$exit_code"
|
||||
}
|
||||
|
||||
PROFILE_OVERRIDE="${NETBIAN_PROFILE:-}"
|
||||
LANG_OVERRIDE="${NETBIAN_LANG:-}"
|
||||
CONFIG_FILE="${NETBIAN_CONFIG_FILE:-/etc/netbian.conf}"
|
||||
ARGS_PROVIDED=false
|
||||
LIST_PROFILES=false
|
||||
LIST_ROLES=false
|
||||
|
||||
if [[ $# -gt 0 ]]; then
|
||||
ARGS_PROVIDED=true
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-p | --profile)
|
||||
shift
|
||||
[[ $# -gt 0 && -n "${1:-}" ]] || {
|
||||
err "Option --profile requires a value."
|
||||
usage
|
||||
}
|
||||
PROFILE_OVERRIDE="$1"
|
||||
shift
|
||||
;;
|
||||
-l | --lang)
|
||||
shift
|
||||
[[ $# -gt 0 && -n "${1:-}" ]] || {
|
||||
err "Option --lang requires a value."
|
||||
usage
|
||||
}
|
||||
LANG_OVERRIDE="$1"
|
||||
shift
|
||||
;;
|
||||
--config)
|
||||
shift
|
||||
[[ $# -gt 0 && -n "${1:-}" ]] || {
|
||||
err "Option --config requires a path."
|
||||
usage
|
||||
}
|
||||
CONFIG_FILE="$1"
|
||||
shift
|
||||
;;
|
||||
--list-profiles)
|
||||
LIST_PROFILES=true
|
||||
shift
|
||||
;;
|
||||
--list-roles)
|
||||
LIST_ROLES=true
|
||||
shift
|
||||
;;
|
||||
-h | --help)
|
||||
usage 0
|
||||
;;
|
||||
-*)
|
||||
err "Unknown option: $1"
|
||||
usage
|
||||
;;
|
||||
*)
|
||||
err "Positional arguments are not supported."
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
|
||||
if $LIST_PROFILES; then
|
||||
list_available_profiles | sort
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if $LIST_ROLES; then
|
||||
list_available_roles | sort
|
||||
exit 0
|
||||
fi
|
||||
|
||||
[[ "$(id -u)" -eq 0 ]] || fatal "Please run this script as root."
|
||||
require_debian_version "13"
|
||||
if exists_cmd curl; then
|
||||
curl -fsSI --connect-timeout 10 https://deb.debian.org/ >/dev/null || fatal "Network unavailable (HTTP test failed with curl)."
|
||||
elif exists_cmd wget; then
|
||||
wget -q --timeout=10 --spider https://deb.debian.org/ >/dev/null 2>&1 || fatal "Network unavailable (HTTP test failed with wget)."
|
||||
else
|
||||
fatal "curl or wget is required for the network availability check."
|
||||
fi
|
||||
|
||||
cat <<'EOM'
|
||||
_ _ _____ _____ _ _
|
||||
| \ | | ____|_ _| |__ (_) __ _ _ __
|
||||
| \| | _| | | | '_ \| |/ _` | '_ \
|
||||
| |\ | |___ | | | |_) | | (_| | | | |
|
||||
|_| \_|_____| |_| |_.__/|_|\__,_|_| |_|
|
||||
|
||||
EOM
|
||||
|
||||
declare -A CFG
|
||||
CONFIG_EXISTED=false
|
||||
if [[ -f "$CONFIG_FILE" ]]; then
|
||||
CONFIG_EXISTED=true
|
||||
validate_config_file "$CONFIG_FILE"
|
||||
source "$CONFIG_FILE"
|
||||
[[ -n "${profile:-}" ]] && CFG[profile]="$profile"
|
||||
[[ -n "${lang:-}" ]] && CFG[lang]="$lang"
|
||||
fi
|
||||
|
||||
if [[ -n "$PROFILE_OVERRIDE" ]]; then
|
||||
PROFILE_OVERRIDE="$(tr '[:upper:]' '[:lower:]' <<<"$PROFILE_OVERRIDE")"
|
||||
case "$PROFILE_OVERRIDE" in
|
||||
server | desktop | devel | cli) CFG[profile]="$PROFILE_OVERRIDE" ;;
|
||||
*)
|
||||
err "Invalid profile"
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
if [[ -n "$LANG_OVERRIDE" ]]; then
|
||||
CFG[lang]="$(tr '[:upper:]' '[:lower:]' <<<"$LANG_OVERRIDE" | tr '_' '-')"
|
||||
fi
|
||||
|
||||
[[ -n "${CFG[profile]:-}" ]] || {
|
||||
err "Profile not provided. Use --profile or NETBIAN_PROFILE."
|
||||
usage
|
||||
}
|
||||
|
||||
TMP_IN="$(mktemp --tmpdir netbian.conf.in.XXXXXX)" || TMP_IN="/tmp/netbian.conf.in.$$"
|
||||
TMP_OUT="$(mktemp --tmpdir netbian.conf.out.XXXXXX)" || TMP_OUT="/tmp/netbian.conf.out.$$"
|
||||
trap 'rm -f "$TMP_IN" "$TMP_OUT"' EXIT
|
||||
|
||||
if [[ -f "$CONFIG_FILE" ]]; then
|
||||
grep -v -E '^(profile|lang)=' "$CONFIG_FILE" >"$TMP_IN" || true
|
||||
else
|
||||
: >"$TMP_IN"
|
||||
fi
|
||||
|
||||
{
|
||||
cat "$TMP_IN"
|
||||
printf 'profile=%s\n' "${CFG[profile]}"
|
||||
[[ -n "${CFG[lang]:-}" ]] && printf 'lang=%s\n' "${CFG[lang]}"
|
||||
} >"$TMP_OUT"
|
||||
|
||||
if write_if_changed "$TMP_OUT" "$CONFIG_FILE"; then
|
||||
echo "Configuration written to $CONFIG_FILE:"
|
||||
grep -E '^(profile|lang)=' "$CONFIG_FILE" || true
|
||||
elif $CONFIG_EXISTED; then
|
||||
if $ARGS_PROVIDED; then
|
||||
echo "No changes; configuration remains in $CONFIG_FILE:"
|
||||
else
|
||||
echo "Configuration read from $CONFIG_FILE:"
|
||||
fi
|
||||
grep -E '^(profile|lang)=' "$CONFIG_FILE" || true
|
||||
else
|
||||
echo "No configuration file present and no changes were made."
|
||||
fi
|
||||
|
||||
export CONFIG_FILE
|
||||
exec "$PROJECT_DIR/roles.sh"
|
||||
Reference in New Issue
Block a user