Sistema di build automatizzato per firmware OpenWrt per i nodi della rete mesh Ninux.
- Struttura del repo
- Configurazione rapida
- Installazione Jenkins su Debian Trixie
- Configurazione Jenkins
- Configurazione del build
- Gestione password con ansible-vault
- Aggiungere device e organizzazioni
- Uso da riga di comando
- Performance e ottimizzazioni
- OpenWISP Firmware Upgrader
- GitHub Release
- Struttura dei firmware prodotti
- Troubleshooting
ansible-ninux-openwrt/
│
├── ninux.yml <- CONFIGURAZIONE PRINCIPALE (modifica qui)
├── ninux.yml.example <- Template per nuove installazioni
│
├── config/
│ ├── base.config <- Pacchetti comuni a tutti i target
│ ├── chilli.ext <- Estensione Captive Portal
│ ├── zerotier.ext <- Estensione ZeroTier VPN
│ ├── wireguard.ext <- Estensione WireGuard VPN
│ └── organizations/
│ └── <org>/
│ └── <device>.config <- Config per device
│
├── setup/
│ └── install-jenkins.sh <- Script autoinstall (Debian Trixie)
│
├── inventory/
│ ├── hosts.yml <- Build host (localhost)
│ └── group_vars/build_hosts.yml
│
├── playbooks/
│ ├── build_all.yml <- Build tutti i device (varianti parallele)
│ ├── build_firmware.yml <- Build singolo device
│ ├── build_matrix.yml <- Build matrice personalizzata
│ ├── build_parallel.yml <- Build parallela tra device
│ ├── cleanup.yml <- Pulizia manuale disco
│ └── _build_device_variants.yml <- Helper interno
│
├── roles/ninux_build_openwrt/
│ ├── defaults/main.yml <- Default variabili ruolo
│ └── tasks/
│ ├── main.yml
│ ├── deps.yml <- apt install dipendenze
│ ├── prepare.yml <- Directory, ccache, tmpfs
│ ├── clone_ninux.yml <- Copia config/ sul build host
│ ├── clone_openwrt.yml <- Clone/aggiorna OpenWrt
│ ├── rootfiles.yml <- Overlay filesystem + openwisp-config
│ ├── feeds.yml <- feeds.conf + update + install
│ ├── dotconfig.yml <- Assembla .config
│ ├── build.yml <- make download + make -jN
│ ├── artifacts.yml <- Copia firmware output/ e NFS
│ └── openwisp_upload.yml <- Upload OpenWISP (opzionale)
│
├── Jenkinsfile
├── ansible.cfg
└── .gitignore
Il file principale da modificare è ninux.yml — contiene tutte le variabili di build,
la configurazione openwisp-config per organizzazione e le password cifrate inline con ansible-vault encrypt_string.
# 1. Clona il repo
git clone https://github.com/mikysal78/ansible-ninux-openwrt.git
cd ansible-ninux-openwrt
# 2. Crea ninux.yml dal template
cp ninux.yml.example ninux.yml
# 3. Modifica org, versione OpenWrt e configurazione openwisp
nano ninux.yml
# 4. Genera le password cifrate (shared_secret, credenziali OpenWISP)
# Vedi sezione "Gestione password con ansible-vault"
# 5. Verifica i device disponibili
ls config/organizations/default/
# 6. Lancia la build
ansible-playbook playbooks/build_all.yml \
--vault-password-file /var/lib/jenkins/.vault_pass| Risorsa | Minimo | Consigliato |
|---|---|---|
| CPU | 4 core | 12 core |
| RAM | 8 GB | 24 GB |
| Disco | 80 GB | 200 GB |
Nota Proxmox LXC: il container deve avere
nesting=1abilitato per permettere il mount di tmpfs. In/etc/pve/lxc/<CTID>.conf:features: nesting=1Dopo:
pct restart <CTID>
git clone https://github.com/mikysal78/ansible-ninux-openwrt.git
cd ansible-ninux-openwrt
sudo ./setup/install-jenkins.sh
# Con vault password per le password openwisp
sudo ./setup/install-jenkins.sh --vault-pass "mia-password-vault"
# Solo dipendenze, Jenkins già installato
sudo ./setup/install-jenkins.sh --skip-jenkinsLo script installa e configura automaticamente:
- Java 21 (OpenJDK)
- Ansible (via pipx, metodo consigliato su Trixie)
- Jenkins LTS con chiave GPG 2026
- Tutte le dipendenze build OpenWrt
- ccache persistente in
/var/cache/openwrt-ccache - tmpfs in
/mnt/openwrt-tmpfs(8G, montato al boot via fstab) - Permessi sudo per l'utente
jenkins
apt-get update
apt-get install -y fontconfig openjdk-21-jre
java -versionapt-get install -y pipx
pipx install --include-deps ansible
pipx ensurepath
source ~/.bashrc
ansible --versionmkdir -p /etc/apt/keyrings
curl -fsSL https://pkg.jenkins.io/debian-stable/jenkins.io-2026.key \
-o /etc/apt/keyrings/jenkins-keyring.asc
echo "deb [signed-by=/etc/apt/keyrings/jenkins-keyring.asc] \
https://pkg.jenkins.io/debian-stable binary/" \
> /etc/apt/sources.list.d/jenkins.list
apt-get update
apt-get install -y jenkins
systemctl enable --now jenkins
# Password iniziale
cat /var/lib/jenkins/secrets/initialAdminPasswordapt-get install -y \
build-essential ccache time git subversion g++ bash make \
libssl-dev patch libncurses-dev zlib1g-dev gawk flex gettext \
wget unzip xz-utils python3 python3-distutils-extra \
rsync curl libsnmp-dev liblzma-dev libpam0g-dev cpiomkdir -p /var/cache/openwrt-ccache
chown jenkins:jenkins /var/cache/openwrt-ccachemkdir -p /mnt/openwrt-tmpfs
echo "tmpfs /mnt/openwrt-tmpfs tmpfs defaults,size=8G,mode=0777 0 0" >> /etc/fstab
mount /mnt/openwrt-tmpfscat > /etc/sudoers.d/jenkins-openwrt << 'SUDOEOF'
jenkins ALL=(ALL) NOPASSWD: /bin/mount
jenkins ALL=(ALL) NOPASSWD: /bin/umount
jenkins ALL=(ALL) NOPASSWD: /usr/bin/apt-get
jenkins ALL=(ALL) NOPASSWD: /usr/bin/apt
SUDOEOF
chmod 440 /etc/sudoers.d/jenkins-openwrt- Apri
http://<IP-SERVER>:8080 - Inserisci la password iniziale:
cat /var/lib/jenkins/secrets/initialAdminPassword - Scegli "Install suggested plugins"
- Crea l'utente amministratore
Vai in Manage Jenkins → Plugins → Available plugins:
| Plugin | Note |
|---|---|
| Ansible | Integrazione Ansible |
| Timestamper | Timestamp nei log di build |
| Build Timeout | Timeout per build lunghe |
| Workspace Cleanup | Pulizia workspace post-build |
Pipeline e Git sono già inclusi nei plugin suggeriti.
Manage Jenkins → Tools → Ansible installations:
- Name:
ansible - Install automatically: no
- Path to ansible executables directory:
/root/.local/bin
- New Item → nome
NinuxOpenwrt→ tipo Pipeline → OK - Tab General:
- Spunta "Do not allow concurrent builds"
- Build Timeout: 240 minuti
- Tab Pipeline:
- Definition: Pipeline script from SCM
- SCM: Git
- Repository URL:
https://github.com/mikysal78/ansible-ninux-openwrt.git - Branch Specifier:
*/main - Script Path:
Jenkinsfile
- Save → Build with Parameters per il primo lancio
| Parametro | Default | Descrizione |
|---|---|---|
OPENWRT_ORG |
default |
Organizzazione Ninux |
OPENWRT_VERSION |
v25.12.4 |
Tag OpenWrt |
VPN_VARIANTS |
ALL |
ALL / NO / ZeroTier / WireGuard / DualVPN |
CAPTIVE_PORTAL_VARIANTS |
false | Compila anche varianti con CP |
SKIP_DEPS |
false | Salta apt install (dopo il primo run) |
TMPFS_ENABLED |
true | RAM disk per tmp/ (+30% velocità) |
TMPFS_SIZE |
8G |
Dimensione tmpfs |
CCACHE_DIR |
/var/cache/openwrt-ccache |
ccache persistente |
CCACHE_SIZE |
20G |
Dimensione massima ccache |
OPENWISP_UPLOAD |
false | Upload su OpenWISP Firmware Upgrader |
OPENWISP_TRIGGER_UPGRADE |
false | Avvia batch upgrade OpenWISP |
OPENWISP_URL |
`` | URL istanza OpenWISP Firmware Upgrader |
# Sul server Jenkins — necessario per decifrare le password in ninux.yml
echo "mia-password-vault" > /var/lib/jenkins/.vault_pass
chmod 600 /var/lib/jenkins/.vault_pass
chown jenkins:jenkins /var/lib/jenkins/.vault_passTutto in ninux.yml nella root del repo. Per una nuova installazione:
cp ninux.yml.example ninux.yml
nano ninux.ymlSezioni principali:
# Versione e org
openwrt_version: "v25.12.4"
openwrt_org: "default"
# Varianti da compilare
openwrt_vpn_variants: [NO, ZeroTier, WireGuard, DualVPN]
openwrt_cp_variants: false
# openwisp-config per org (shared_secret cifrata con encrypt_string)
openwisp_orgs:
default:
controller_url: "https://controller.nnxx.ninux.org"
management_interface: "owz12345"
shared_secret: !vault |
$ANSIBLE_VAULT;1.1;AES256
<stringa cifrata>
# Performance
openwrt_ccache_dir: "/var/cache/openwrt-ccache"
openwrt_tmpfs_enabled: true
openwrt_tmpfs_size: "8G"I password (shared_secret openwisp, credenziali Firmware Upgrader) sono cifrati
inline in ninux.yml con ansible-vault encrypt_string. Non esiste un vault
file separato — tutto sta in un file solo, i valori sensibili sono illeggibili
senza la vault password.
# Sul server Jenkins (una volta sola)
echo "la-tua-password-vault" > /var/lib/jenkins/.vault_pass
chmod 600 /var/lib/jenkins/.vault_pass
chown jenkins:jenkins /var/lib/jenkins/.vault_passansible-vault encrypt_string \
--vault-password-file /var/lib/jenkins/.vault_pass \
'VALORE_DA_CIFRARE' --name 'NOME_VARIABILE'L'output va incollato direttamente in ninux.yml.
ansible-vault encrypt_string \
--vault-password-file /var/lib/jenkins/.vault_pass \
'SecretRomaXyz' --name 'shared_secret'Output da incollare in ninux.yml:
openwisp_orgs:
roma:
controller_url: "https://controller.nnxx.ninux.org"
management_interface: "owzABCDE"
shared_secret: !vault |
$ANSIBLE_VAULT;1.1;AES256
66386439653236336462626566653337...ansible-vault encrypt_string \
--vault-password-file /var/lib/jenkins/.vault_pass \
'admin' --name 'openwisp_username'
ansible-vault encrypt_string \
--vault-password-file /var/lib/jenkins/.vault_pass \
'MyPassword123' --name 'openwisp_password'ansible -i inventory/hosts.yml localhost \
-m debug -a "var=openwisp_orgs.default.shared_secret" \
-e @ninux.yml \
--vault-password-file /var/lib/jenkins/.vault_pass# 1. Genera il .config con OpenWrt menuconfig
cd /path/to/openwrt-src
make menuconfig # seleziona target e salva
cp .config /repo/config/organizations/default/nome_device.config
# 2. Commita
git add config/organizations/default/nome_device.config
git commit -m "feat: aggiungi device nome_device"Il nome del file senza .config è il valore di openwrt_target.
L'autodiscovery lo includerà automaticamente nella prossima build.
# 1. Crea directory config device
mkdir -p config/organizations/roma
# 2. Aggiungi i .config dei device
cp config/organizations/default/*.config config/organizations/roma/
# 3. Aggiungi la sezione in ninux.yml
nano ninux.yml
# openwisp_orgs:
# roma:
# controller_url: "https://controller.nnxx.ninux.org"
# management_interface: "owzABCDE"
# shared_secret: !vault | ...
# 4. Genera la shared_secret cifrata
ansible-vault encrypt_string \
--vault-password-file /var/lib/jenkins/.vault_pass \
'SecretRomaXyz' --name 'shared_secret'
# 5. Build per la nuova org
ansible-playbook playbooks/build_all.yml \
-e openwrt_org=roma \
--vault-password-file /var/lib/jenkins/.vault_passSe un'org non è definita in
openwisp_orgso manca lashared_secret, la build continua normalmente ma salta la generazione di/etc/config/openwisp.
# Tutti i device, tutte le varianti VPN
ansible-playbook playbooks/build_all.yml \
--vault-password-file /var/lib/jenkins/.vault_pass
# Con Captive Portal (2x build per device)
ansible-playbook playbooks/build_all.yml \
-e openwrt_cp_variants=true \
--vault-password-file /var/lib/jenkins/.vault_pass
# Solo alcune varianti VPN
ansible-playbook playbooks/build_all.yml \
-e '{"openwrt_vpn_variants": ["NO", "ZeroTier"]}' \
--vault-password-file /var/lib/jenkins/.vault_pass
# Singolo device, tutte le varianti
ansible-playbook playbooks/build_firmware.yml \
-e openwrt_target=glinet_gl-mt300n-v2 \
--vault-password-file /var/lib/jenkins/.vault_pass
# Solo installazione dipendenze
ansible-playbook playbooks/build_all.yml --tags deps \
--vault-password-file /var/lib/jenkins/.vault_pass
# Solo build (dipendenze già installate)
ansible-playbook playbooks/build_all.yml --skip-tags deps \
--vault-password-file /var/lib/jenkins/.vault_pass
# Pulizia disco
ansible-playbook playbooks/cleanup.yml # solo temporanei
ansible-playbook playbooks/cleanup.yml -e cleanup_full=true # tutto
ansible-playbook playbooks/cleanup.yml -e cleanup_output=true # solo output/Device 1
├── VPN=NO ─┐
├── VPN=ZeroTier ├─ parallelo (async, condividono toolchain)
├── VPN=WireGuard │
└── VPN=DualVPN ─┘
→ pulizia staging_dir/build_dir
Device 2
└── (idem)
...
Post: pulizia totale + smonta tmpfs + stats ccache
Le varianti dello stesso device condividono la toolchain già compilata e ricompilano solo i pacchetti che differiscono (pochi MB), quindi il parallelo è efficiente senza moltiplicare RAM/disco.
| Ottimizzazione | Guadagno |
|---|---|
make -j14 (nproc+2) |
baseline |
| ccache (dalla 2a build) | -70% tempo |
tmpfs per tmp/ |
-30% I/O |
| 4 varianti in parallelo | -60% per device |
# Host Proxmox
echo "features: nesting=1" >> /etc/pve/lxc/<CTID>.conf
pct restart <CTID>In ninux.yml:
openwisp_upload_enabled: true
openwisp_url: "https://controller.nnxx.ninux.org"
openwisp_org_slug: "default"
openwisp_org_id: !vault |
$ANSIBLE_VAULT;1.1;AES256
<UUID cifrato>
openwisp_trigger_upgrade: false # true = avvia upgrade automatico
openwisp_orgs:
default:
controller_url: "https://controller.nnxx.ninux.org"
management_interface: "owzXXXXX"
shared_secret: !vault |
$ANSIBLE_VAULT;1.1;AES256
<stringa cifrata>
api_token: !vault |
$ANSIBLE_VAULT;1.1;AES256
<token API cifrato>Build → artifacts.yml → openwisp_upload.yml
1. Token Bearer da api_token (nessun login, evita rate limiting)
2. Risolvi UUID organizzazione da ninux.yml
3. Cerca/crea Category (org + device target)
4. Crea Build (versione-target-VPN-CP)
5. Carica immagine sysupgrade (type = nome file senza prefisso openwrt-)
6. (opzionale) Batch upgrade
Dopo ogni build è possibile pubblicare i firmware come release GitHub, rendendoli scaricabili direttamente dalla pagina Releases del repository.
1. Personal Access Token (PAT) su GitHub
Vai su https://github.com/settings/tokens → Generate new token (fine-grained):
| Campo | Valore |
|---|---|
| Repository access | solo ansible-ninux-openwrt |
| Contents | Read and write |
| Metadata | Read (obbligatorio) |
2. Credenziale Jenkins
Vai su Manage Jenkins → Credentials → System → Global → Add Credentials:
| Campo | Valore |
|---|---|
| Kind | Secret text |
| Secret | il token GitHub |
| ID | github-release-token |
github_release_enabled: true
github_repo: "mikysal78/ansible-ninux-openwrt"
github_prerelease: true # false per release ufficiali
github_release_include_sha256: trueOgni release viene creata con tag <versione>-<org>-build<N>, es. v25.12.4-default-build42.
Gli asset vengono caricati con nome che riflette il percorso:
Standard_VPN-NO_x86_64_openwrt-x86-64-generic-squashfs-combined-efi.img.gz
Standard_VPN-ZeroTier_x86_64_openwrt-x86-64-generic-squashfs-combined-efi.img.gz
CaptivePortal_VPN-WireGuard_glinet_gl-mt300n-v2_openwrt-...-squashfs-sysupgrade.bin
...
Spunta il parametro GITHUB_RELEASE al lancio del job,
oppure imposta github_release_enabled: true in ninux.yml per abilitarlo sempre.
output/
└── v25.12.4/
└── default/
├── Standard/
│ ├── VPN-NO/glinet_gl-mt300n-v2/
│ ├── VPN-ZeroTier/glinet_gl-mt300n-v2/
│ ├── VPN-WireGuard/glinet_gl-mt300n-v2/
│ └── VPN-DualVPN/glinet_gl-mt300n-v2/
└── CaptivePortal/
└── VPN-*/...
Assicurati di usare i playbook da playbooks/ — caricano ninux.yml
tramite vars_files. Non richiamare il ruolo direttamente senza caricare le variabili.
Verifica che l'org sia definita in openwisp_orgs in ninux.yml con tutti e tre
i campi (controller_url, management_interface, shared_secret). Se shared_secret
manca o non è decifrabile, la build continua senza generare il file e logga un avviso.
Il --vault-password-file non corrisponde alla password usata durante encrypt_string.
Verifica che /var/lib/jenkins/.vault_pass contenga la password corretta.
I task non usano owner sulle directory NFS. Se persiste, verifica che
il server NFS esporti con no_root_squash o adatta i permessi lato server.
Manage Jenkins → Tools → Git installations:
- Name:
Default - Path:
git
In Manage Jenkins → Configure System imposta Build Timeout a 240+ minuti.
CCACHE_DIR=/var/cache/openwrt-ccache ccache --show-statsHit rate sotto 50% dopo la seconda build: controlla che CCACHE_DIR
sia lo stesso tra i job e che nesting=1 sia attivo (per tmpfs).
Abilita nesting=1 nella config Proxmox del container (vedi sezione Performance).
GPL-3.0