Compare commits

...

13 Commits

Author SHA1 Message Date
Martin Tahiraj
6cfb9df309 feat(ci): add docker tool configuration to Jenkins pipeline 2026-04-08 15:10:52 +02:00
Martin Tahiraj
fdd336fe6e feat(ci): improve pipelineEvent with dynamic title, context and results
- context: 'ha-addons_build'
- title: 'Build degli addon HA: <addon1, addon2>: Completato/Fallito'
- results: mappa addon  {status, available, file} derivata da buildMap

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-02 15:48:37 +02:00
Martin Tahiraj
3e21e423bf fix(jenkins): update webhook token in Generic Trigger configuration 2026-04-02 15:38:09 +02:00
Martin Tahiraj
4e096de6c9 fix(ci): use correct registry credential ID 'REGISTRY'
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-02 14:52:04 +02:00
Martin Tahiraj
c8dbd399ce refactor(ci): extract inline scripts to ci/scripts/
- ci/scripts/read_meta.py     legge version|build_from da config.yaml/json
- ci/scripts/update_repo.py   upserta repository.json
- ci/scripts/lint_addon.sh    hadolint + yaml/json + shellcheck
- ci/scripts/git_push_repo.sh  commit e push di repository.json

Rimuove writeFile /tmp/ dal Jenkinsfile

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-02 14:46:17 +02:00
Martin Tahiraj
88fe792ca0 fix(jenkins): use GIT_TOKEN as credential ID for Gitea
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-02 14:41:54 +02:00
Martin Tahiraj
b6dc2aefe4 refactor(jenkins): remove redundant Checkout stage
Declarative Pipeline with agent any already runs 'Declarative: Checkout SCM'
automatically before user stages  the explicit stage('Checkout') was cloning
the repo a second time for no reason. Remove it entirely; capture GIT_COMMIT_SHA
and GIT_PREV_SHA at the top of stage('Detect') using the already-present workspace.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-02 14:39:39 +02:00
Martin Tahiraj
18f8d38796 fix(jenkins): move credentials out of environment block to avoid pipeline abort
credentials() in the pipeline-level environment{} block causes an immediate
abort before any stage runs if the credential ID does not exist in Jenkins.
The node is released, post{} runs without a node context, and sh steps fail
with 'Required context class hudson.FilePath is missing'.

Fix: remove REGISTRY_CREDS and GITEA_CREDS from environment{}, replace with
withCredentials() inside the stages that actually need them (Build & Push,
Publish). Wrap post{cleanup} sh calls in try/catch as a safety net.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-02 14:34:48 +02:00
Martin Tahiraj
b9b35f7b41 fix(jenkins): update trigger method to use pollSCM and provide upgrade recommendations for Generic Webhook Trigger 2026-04-02 14:07:53 +02:00
Martin Tahiraj
c62240d0de fix(jenkins): update default GITEA_USER and improve variable naming in notification logic 2026-04-02 13:01:10 +02:00
Martin Tahiraj
90793f500b feat(jenkins): add CI/CD pipeline for HA add-ons with linting, building, and publishing 2026-04-02 12:05:55 +02:00
Martin Tahiraj
33d6e9eb9d Merge branch 'main' of https://git.mt-home.uk/martin/HomeAssistantAddons 2026-04-02 11:59:13 +02:00
Martin Tahiraj
0cf4e15de4 fix readmes 2026-04-02 11:58:49 +02:00
11 changed files with 659 additions and 12 deletions

View File

@@ -43,11 +43,6 @@ _OpenVAS is a full-featured vulnerability scanner. Its capabilities include unau
[Official Repo](https://github.com/greenbone/openvas-scanner)
## Sponsoring ❤️
If you like this add-on and would like to support my work and future projects, you can buy me a coffee. ☕
Sponsoring available on Paypal (https://paypal.me/martemme).
[aarch64-shield]: https://img.shields.io/badge/aarch64-yes-green.svg
[amd64-shield]: https://img.shields.io/badge/amd64-yes-green.svg
[armv7-shield]: https://img.shields.io/badge/armv7-yes-green.svg

466
ci/Jenkinsfile vendored Normal file
View File

@@ -0,0 +1,466 @@
@Library('shared-lib') _
/* ============================================================================
* HA ADD-ONS CI/CD PIPELINE
* Repo : HomeAssistantAddOns
* File : ci/Jenkinsfile
* Autore: Martin
*
* Descrizione
* -----------
* Pipeline che su ogni push su main rileva quali addon sono cambiati,
* esegue lint, builda l'immagine Docker (amd64) e la pusha sul registry
* privato. Al termine aggiorna repository.json con i metadati aggiornati
* e committa il file con [skip ci] per evitare loop infiniti.
*
* Flusso
* ------
* 1. Checkout
* 2. Detect — git diff → sotto-directory addon modificate
* 3. Lint — hadolint + yaml/json validate + shellcheck (parallel)
* 4. Build — docker build --platform linux/amd64 + push (parallel)
* 5. Publish — aggiorna repository.json e committa su main
* 6. Summary — tabella risultati + notifica ntfy
*
* Trigger
* -------
* Generic Webhook Trigger su push → refs/heads/main
* Token : homeassistant-addons
* Webhook : https://pipelines.mt-home.uk/generic-webhook-trigger/invoke?token=homeassistant-addons
*
* Credenziali Jenkins richieste
* ------------------------------
* GIT_TOKEN Username/Password — token Gitea (checkout + push)
* REGISTRY Username/Password — Docker registry
*
* ============================================================================
*/
pipeline {
agent any
/* -------------------------------------------------------------------------
* PARAMETRI
* Valori infrastrutturali come parametri: portabilità garantita,
* nessuna informazione sensibile nel Jenkinsfile.
* ----------------------------------------------------------------------- */
parameters {
string(
name: 'GITEA_USER',
defaultValue: 'martin',
description: 'Username Gitea del repository HomeAssistantAddOns'
)
string(
name: 'NTFY_TOPIC',
defaultValue: 'jenkins-addons',
description: 'Topic ntfy per notifiche build (es: jenkins-addons)'
)
string(
name: 'NTFY_URL',
defaultValue: 'https://ntfy.mt-home.uk',
description: 'URL base ntfy — lasciare vuoto per disabilitare le notifiche'
)
booleanParam(
name: 'BUILD_ALL',
defaultValue: false,
description: 'Forza build di TUTTI gli addon (ignora il diff)'
)
}
/* -------------------------------------------------------------------------
* TRIGGER
*
* ATTUALE: pollSCM — Jenkins interroga il repo ogni 2 minuti e parte
* se ci sono nuovi commit su main. Nessun plugin aggiuntivo richiesto.
*
* UPGRADE CONSIGLIATO → Generic Webhook Trigger (risposta immediata):
* 1. Manage Jenkins → Plugin Manager → "Generic Webhook Trigger" → Install
* 2. Sostituire il blocco triggers con:
*/
triggers {
GenericTrigger(
genericVariables: [
[key: 'GITEA_REF', value: '$.ref'],
[key: 'GITEA_BEFORE', value: '$.before'],
[key: 'GITEA_AFTER', value: '$.after'],
],
causeString: 'Push Gitea su $GITEA_REF',
token: '1993dec12_ha-addons',
printContributedVariables: true,
regexpFilterText: '$GITEA_REF',
regexpFilterExpression: 'refs/heads/main'
)
}
/*
* 3. Creare webhook in Gitea → Settings → Webhooks:
* URL: https://pipelines.mt-home.uk/generic-webhook-trigger/invoke?token=1993dec12_ha-addons
triggers {
pollSCM('H/2 * * * *')
}
* ----------------------------------------------------------------------- */
environment {
REGISTRY = 'registry.mt-home.uk'
GITEA_BASE_URL = 'https://git.mt-home.uk'
// Credenziali NON vincolate qui — usare withCredentials() dentro gli stage.
// Vincolare credentials() a livello di pipeline causa un abort immediato
// se la credenziale non esiste, prima ancora che giri qualsiasi stage.
}
tools {
dockerTool 'docker'
}
options {
disableConcurrentBuilds()
timestamps()
timeout(time: 60, unit: 'MINUTES')
ansiColor('xterm')
}
// =========================================================================
stages {
/* ---------------------------------------------------------------------
* STAGE 1 — Detect changed addons
*
* Il checkout è già fatto da "Declarative: Checkout SCM" automatico.
* Un addon valido è una sotto-directory della root che contiene
* config.yaml oppure config.json (standard HA Supervisor).
* ------------------------------------------------------------------- */
stage('Detect') {
steps {
script {
// SHA del commit corrente e del precedente (già nel workspace)
env.GIT_COMMIT_SHA = sh(script: 'git rev-parse HEAD', returnStdout: true).trim()
env.GIT_PREV_SHA = sh(script: 'git rev-parse HEAD~1', returnStdout: true).trim()
echo "[INFO] HEAD : ${env.GIT_COMMIT_SHA}"
echo "[INFO] HEAD~1 : ${env.GIT_PREV_SHA}"
// Tutti gli addon presenti nel workspace (indipendentemente da cosa è cambiato)
def allAddons = sh(
script: '''
find . -maxdepth 2 \\( -name 'config.yaml' -o -name 'config.json' \\) \
| sed 's|^./||' \
| xargs -I{} dirname {} \
| grep -v '^\\.\\?$' \
| grep -v '/' \
| sort -u
''',
returnStdout: true
).trim().split('\n').findAll { it?.trim() } as List
echo "[INFO] Addon disponibili nel repo: ${allAddons}"
List<String> toProcess
if (params.BUILD_ALL) {
toProcess = allAddons
echo "[INFO] BUILD_ALL=true — selezionati tutti gli addon: ${toProcess}"
} else {
// SHA prima/dopo del push (da webhook o da git)
def beforeSha = env.GITEA_BEFORE?.trim() ?: env.GIT_PREV_SHA
def afterSha = env.GITEA_AFTER?.trim() ?: env.GIT_COMMIT_SHA
// Diff: file modificati tra i due commit
def changedFiles = sh(
script: "git diff --name-only ${beforeSha} ${afterSha} 2>/dev/null || git diff --name-only HEAD~1 HEAD",
returnStdout: true
).trim().split('\n').findAll { it?.trim() } as List
echo "[INFO] File modificati: ${changedFiles}"
// Estrai la top-level directory e filtra solo addon validi
toProcess = changedFiles
.collect { it.split('/')[0] }
.unique()
.findAll { dir -> allAddons.contains(dir) }
if (toProcess.isEmpty()) {
echo "[INFO] Nessun addon modificato in questo push — skip build."
} else {
echo "[INFO] Addon da buildare: ${toProcess}"
}
}
// Serializza come stringa CSV per i prossimi stage
env.ADDONS_TO_BUILD = toProcess.join(',')
}
}
}
/* ---------------------------------------------------------------------
* STAGE 3 — Lint (parallel)
*
* Eseguito in parallelo per ogni addon rilevato. Un fallimento
* di lint marca lo stage UNSTABLE ma non interrompe gli altri addon.
*
* Checks:
* - hadolint → Dockerfile best practices
* - yaml/json → config.yaml o config.json valido
* - shellcheck → tutti gli *.sh nella directory
* ------------------------------------------------------------------- */
stage('Lint') {
when { expression { env.ADDONS_TO_BUILD?.trim() } }
steps {
script {
def addons = env.ADDONS_TO_BUILD.split(',').findAll { it?.trim() } as List
def lintOk = [] as List
def lintWarn = [] as List
def lintJobs = addons.collectEntries { addon ->
[(addon): {
stage("lint ${addon}") {
try {
sh "bash ci/scripts/lint_addon.sh ${addon}"
echo "[OK] ${addon}: tutti i check superati"
lintOk.add(addon)
} catch (err) {
echo "[WARN] ${addon}: lint con avvisi — ${err.message}"
lintWarn.add(addon)
unstable("Lint warning in ${addon}")
}
}
}]
}
parallel lintJobs
// Serializza risultati lint per il summary
def lintResultsMap = [:]
lintOk.each { lintResultsMap[it] = 'OK' }
lintWarn.each { lintResultsMap[it] = 'WARN' }
env.LINT_RESULTS = lintResultsMap.collect { k, v -> "${k}:${v}" }.join(',')
}
}
}
/* ---------------------------------------------------------------------
* STAGE 4 — Build & Push (parallel)
*
* Per ogni addon:
* 1. Legge la versione da config.yaml / config.json
* 2. Legge build_from.amd64 e lo passa come --build-arg BUILD_FROM
* (pattern standard dei Dockerfile HA con ARG BUILD_FROM)
* 3. docker build --platform linux/amd64
* 4. Push di :{version} e :latest
*
* Un fallimento su un addon non blocca gli altri.
* ------------------------------------------------------------------- */
stage('Build & Push') {
when { expression { env.ADDONS_TO_BUILD?.trim() } }
steps {
script {
def addons = env.ADDONS_TO_BUILD.split(',').findAll { it?.trim() } as List
// Login al registry dentro withCredentials — non fallisce il
// pipeline se la credenziale non esiste ancora (gestisce errore)
withCredentials([usernamePassword(
credentialsId: 'REGISTRY',
usernameVariable: 'REGISTRY_USR',
passwordVariable: 'REGISTRY_PSW'
)]) {
sh 'echo "$REGISTRY_PSW" | docker login ${REGISTRY} -u "$REGISTRY_USR" --password-stdin'
}
def buildResults = [:] // addon → [status, version]
def buildJobs = addons.collectEntries { addon ->
[(addon): {
stage("build ${addon}") {
try {
def meta = sh(script: "python3 ci/scripts/read_meta.py ${addon}", returnStdout: true).trim()
def parts = meta.split('\\|')
def version = parts[0]
def buildFrom = parts.size() > 1 ? parts[1] : ''
def imageBase = "${env.REGISTRY}/hassio-addons/${addon}"
def buildArgLine = buildFrom ? "--build-arg BUILD_FROM=${buildFrom}" : ''
def labelSource = "${env.GITEA_BASE_URL}/${params.GITEA_USER}/HomeAssistantAddOns"
echo "[INFO] ${addon}: versione=${version}, BUILD_FROM=${buildFrom ?: '(none)'}"
sh """
docker build \\
--platform linux/amd64 \\
${buildArgLine} \\
--label "org.opencontainers.image.revision=${env.GIT_COMMIT_SHA}" \\
--label "org.opencontainers.image.source=${labelSource}" \\
--label "org.opencontainers.image.version=${version}" \\
-t ${imageBase}:${version} \\
-t ${imageBase}:latest \\
${addon}/
"""
sh "docker push ${imageBase}:${version}"
sh "docker push ${imageBase}:latest"
echo "[OK] ${addon}:${version} pushato su ${imageBase}"
buildResults[addon] = [status: 'OK', version: version]
} catch (err) {
echo "[FAIL] Build di '${addon}' fallita: ${err.message}"
buildResults[addon] = [status: 'FAILED', version: '-']
unstable("Build fallita: ${addon}")
}
}
}]
}
parallel buildJobs
// Serializza per i prossimi stage
env.BUILD_RESULTS = buildResults.collect { k, v ->
"${k}:${v.status}:${v.version}"
}.join(',')
}
}
}
/* ---------------------------------------------------------------------
* STAGE 5 — Publish (aggiorna repository.json)
*
* Eseguito solo se almeno un addon è stato buildato con successo.
* Lo script Python:
* - Legge/crea repository.json
* - Upserta ogni addon buildato con successo (match su slug)
* - Scrive il file aggiornato
* Il commit usa [skip ci] nel messaggio per evitare loop webhook.
* ------------------------------------------------------------------- */
stage('Publish') {
when {
expression {
env.BUILD_RESULTS?.split(',')?.any { it?.contains(':OK:') }
}
}
steps {
script {
def successAddons = env.BUILD_RESULTS.split(',')
.findAll { it?.contains(':OK:') }
.collect { it.split(':')[0] }
echo "[INFO] Aggiorno repository.json per: ${successAddons}"
sh "python3 ci/scripts/update_repo.py \"${params.GITEA_USER}\" ${successAddons.join(' ')}"
sh 'git diff repository.json || true'
withCredentials([usernamePassword(
credentialsId: 'GIT_TOKEN',
usernameVariable: 'GITEA_PUSH_USR',
passwordVariable: 'GITEA_PUSH_PSW'
)]) {
sh "bash ci/scripts/git_push_repo.sh \"${params.GITEA_USER}\" \"${env.GITEA_BASE_URL}\""
}
}
}
}
} // end stages
// =========================================================================
/* -------------------------------------------------------------------------
* POST ACTIONS
* ----------------------------------------------------------------------- */
post {
always {
script {
// Ricostruisce le mappe risultato dalla serializzazione CSV
def lintMap = env.LINT_RESULTS
? env.LINT_RESULTS.split(',').collectEntries {
def p = it.split(':'); [(p[0]): p.size() > 1 ? p[1] : '-']
} : [:]
def buildMap = env.BUILD_RESULTS
? env.BUILD_RESULTS.split(',').collectEntries {
def p = it.split(':')
[(p[0]): [status: p.size() > 1 ? p[1] : '-', version: p.size() > 2 ? p[2] : '-']]
} : [:]
// Tabella riepilogativa
def addons = env.ADDONS_TO_BUILD?.trim() ? env.ADDONS_TO_BUILD.split(',') as List : []
def header = String.format('%-26s %-6s %-8s %-12s', 'Addon', 'Lint', 'Build', 'Version')
def divider = '─' * 56
def rows = addons.collect { addon ->
String.format('%-26s %-6s %-8s %-12s',
addon,
lintMap[addon] ?: '─',
buildMap[addon]?.status ?: '─',
buildMap[addon]?.version ?: '─'
)
}
echo "\n${divider}\n${header}\n${divider}\n${rows.join('\n')}\n${divider}"
if (addons.isEmpty()) {
echo '[INFO] Nessun addon processato in questo build.'
}
// Notifica ntfy
def anyFailed = buildMap.any { k, v -> v.status == 'FAILED' }
def summary = addons.isEmpty()
? "Build #${env.BUILD_NUMBER}: nessun addon da buildare"
: anyFailed
? "Build #${env.BUILD_NUMBER}: uno o più addon falliti"
: "Build #${env.BUILD_NUMBER}: ${buildMap.size()} addon completati ✓"
if (params.NTFY_URL?.trim()) {
try {
sh """
curl -sf \\
-H "Title: HA Add-ons CI" \\
-H "Priority: ${anyFailed ? 'high' : 'default'}" \\
-H "Tags: ${anyFailed ? 'warning' : 'white_check_mark'}" \\
-d "${summary}" \\
"${params.NTFY_URL}/${params.NTFY_TOPIC}" || true
"""
} catch (e) {
echo "[WARN] ntfy non raggiungibile: ${e.message}"
}
}
// Notifica via shared-lib pipelineEvent → Redis Stream
try {
def finalStatus = currentBuild.result ?: 'SUCCESS'
def addonNames = addons.isEmpty() ? 'nessun addon' : addons.join(', ')
def outcomeWord = (finalStatus == 'SUCCESS') ? 'Completato' : 'Fallito'
def evResults = buildMap.collectEntries { addon, info ->
def isOk = (info.status == 'OK')
[(addon): [
status: isOk ? 'done' : 'failed',
available: isOk,
file: isOk ? "${env.REGISTRY}/hassio-addons/${addon}:${info.version}" : null,
]]
}
pipelineEvent(
title: "Build degli addon HA: ${addonNames}: ${outcomeWord}",
context: 'ha-addons_build',
status: finalStatus,
results: evResults,
)
} catch (e) {
echo "[WARN] pipelineEvent fallito: ${e.message}"
}
}
}
success { echo '[SUCCESS] ✓ Pipeline completata con successo.' }
unstable { echo '[WARN] ⚠ Uno o più step con avvisi — verificare i log.' }
failure { echo '[ERROR] ✗ Pipeline fallita.' }
cleanup {
script {
try {
sh 'docker logout ${REGISTRY} 2>/dev/null || true'
} catch (e) {
echo "[WARN] cleanup: ${e.message}"
}
}
}
}
}

View File

@@ -0,0 +1,36 @@
#!/usr/bin/env bash
# git_push_repo.sh <gitea_user> <gitea_base_url>
#
# Committa repository.json (se modificato) e lo pusha su main.
# Il messaggio include [skip ci] per evitare loop webhook.
#
# Variabili d'ambiente richieste:
# GITEA_PUSH_PSW — password/token Gitea (iniettata da withCredentials)
set -euo pipefail
GITEA_USER=${1:?Uso: git_push_repo.sh <gitea_user> <gitea_base_url>}
GITEA_BASE_URL=${2:?Uso: git_push_repo.sh <gitea_user> <gitea_base_url>}
GITEA_PSW=${GITEA_PUSH_PSW:?GITEA_PUSH_PSW non impostata}
git config user.email "jenkins@pipelines.mt-home.uk"
git config user.name "Jenkins CI"
git add repository.json
if git diff --staged --quiet; then
echo "[INFO] Nessuna modifica a repository.json — niente da committare"
exit 0
fi
git commit -m "chore: update repository.json [skip ci]"
# Rimuove schema (https://) per costruire l'URL con credenziali
GITEA_HOST=${GITEA_BASE_URL#https://}
GITEA_HOST=${GITEA_HOST#http://}
git push \
"https://oauth2:${GITEA_PSW}@${GITEA_HOST}/${GITEA_USER}/HomeAssistantAddons.git" \
HEAD:main
echo "[OK] repository.json pushato su main"

38
ci/scripts/lint_addon.sh Normal file
View File

@@ -0,0 +1,38 @@
#!/usr/bin/env bash
# lint_addon.sh <addon_dir>
#
# Esegue tre check su un addon HA:
# 1. hadolint — Dockerfile best practices
# 2. yaml/json — config.yaml o config.json valido
# 3. shellcheck — tutti gli *.sh nella directory
#
# Esce con codice 0 se tutti i check passano, 1 altrimenti.
set -euo pipefail
ADDON=${1:?Uso: lint_addon.sh <addon_dir>}
echo "[INFO] === Lint: ${ADDON} ==="
# ── 1. hadolint ────────────────────────────────────────────────────────────────
echo "[INFO] hadolint: ${ADDON}/Dockerfile"
docker run --rm -i hadolint/hadolint < "${ADDON}/Dockerfile"
# ── 2. Config valida ───────────────────────────────────────────────────────────
if [ -f "${ADDON}/config.yaml" ]; then
python3 -c "import yaml,sys; yaml.safe_load(open(sys.argv[1]))" "${ADDON}/config.yaml"
echo "[OK] ${ADDON}/config.yaml valido"
elif [ -f "${ADDON}/config.json" ]; then
python3 -c "import json,sys; json.load(open(sys.argv[1]))" "${ADDON}/config.json"
echo "[OK] ${ADDON}/config.json valido"
else
echo "[WARN] Nessuna config trovata in ${ADDON}/"
fi
# ── 3. shellcheck ──────────────────────────────────────────────────────────────
find "${ADDON}/" -name '*.sh' | while read -r f; do
echo "[INFO] shellcheck: ${f}"
docker run --rm -v "$PWD:/mnt" koalaman/shellcheck:stable "/mnt/${f}"
done
echo "[OK] ${ADDON}: lint completato"

32
ci/scripts/read_meta.py Normal file
View File

@@ -0,0 +1,32 @@
#!/usr/bin/env python3
"""
Legge version e build_from.amd64 dalla config di un addon HA.
Uso: python3 read_meta.py <addon_dir>
Output su stdout: version|build_from_amd64
"""
import json
import os
import sys
try:
import yaml
HAS_YAML = True
except ImportError:
HAS_YAML = False
addon = sys.argv[1]
for filename in ('config.yaml', 'config.json'):
path = os.path.join(addon, filename)
if not os.path.exists(path):
continue
with open(path) as f:
cfg = yaml.safe_load(f) if (filename.endswith('.yaml') and HAS_YAML) else json.load(f)
version = str(cfg.get('version', 'latest'))
build_from = (cfg.get('build_from') or {}).get('amd64', '')
print(f'{version}|{build_from}')
sys.exit(0)
# Nessuna config trovata
print('latest|')

87
ci/scripts/update_repo.py Normal file
View File

@@ -0,0 +1,87 @@
#!/usr/bin/env python3
"""
Aggiorna repository.json con i metadati degli addon buildati con successo.
Upserta ogni addon (match su slug). Il file viene creato se non esiste.
Uso: python3 update_repo.py <gitea_user> <addon1> [addon2 ...]
Variabili d'ambiente richieste:
REGISTRY es. registry.mt-home.uk
GITEA_BASE_URL es. https://git.mt-home.uk
"""
import json
import os
import sys
try:
import yaml
HAS_YAML = True
except ImportError:
HAS_YAML = False
REGISTRY = os.environ['REGISTRY']
GITEA_BASE_URL = os.environ['GITEA_BASE_URL']
GITEA_USER = sys.argv[1]
addons_arg = sys.argv[2:]
REPO_FILE = 'repository.json'
skeleton = {
'name': f'HA Add-ons by {GITEA_USER}',
'url': f'{GITEA_BASE_URL}/{GITEA_USER}/HomeAssistantAddons',
'maintainer': GITEA_USER,
'addons': [],
}
repo = skeleton.copy()
if os.path.exists(REPO_FILE):
with open(REPO_FILE) as f:
repo = json.load(f)
repo.setdefault('addons', [])
def load_cfg(addon_dir):
for name, use_yaml in [('config.yaml', True), ('config.json', False)]:
p = os.path.join(addon_dir, name)
if not os.path.exists(p):
continue
with open(p) as f:
return yaml.safe_load(f) if (use_yaml and HAS_YAML) else json.load(f)
return {}
changed = False
for addon in addons_arg:
cfg = load_cfg(addon)
if not cfg:
print(f'[WARN] Nessuna config trovata per {addon}, skip', flush=True)
continue
slug = cfg.get('slug', addon)
version = str(cfg.get('version', 'latest'))
entry = {
'slug': slug,
'name': cfg.get('name', addon),
'description': cfg.get('description', ''),
'version': version,
'url': cfg.get('url', ''),
'arch': cfg.get('arch', ['amd64']),
'image': f'{REGISTRY}/hassio-addons/{slug}:{version}',
}
idx = next((i for i, a in enumerate(repo['addons']) if a.get('slug') == slug), None)
if idx is not None:
old_ver = repo['addons'][idx].get('version', '?')
repo['addons'][idx] = entry
print(f'[UPDATE] {slug}: {old_ver}{version}', flush=True)
else:
repo['addons'].append(entry)
print(f'[ADD] {slug} v{version}', flush=True)
changed = True
if changed:
with open(REPO_FILE, 'w') as f:
json.dump(repo, f, indent=2, ensure_ascii=False)
f.write('\n')
print('[OK] repository.json aggiornato', flush=True)
else:
print('[INFO] Nessuna modifica a repository.json', flush=True)

View File

@@ -104,7 +104,6 @@ based on the following:
- `PATCH`: Backwards-compatible bugfixes and package updates.
---
Made with ❤️ for automation and resilience.
[semver]: http://semver.org/spec/v2.0.0.html
[aarch64-shield]: https://img.shields.io/badge/aarch64-yes-green.svg

View File

@@ -52,8 +52,6 @@ gvm/
---
Made with ❤️ for automation and resilience.
[semver]: http://semver.org/spec/v2.0.0.html
[aarch64-shield]: https://img.shields.io/badge/aarch64-yes-green.svg
[amd64-shield]: https://img.shields.io/badge/amd64-yes-green.svg

View File

@@ -75,7 +75,6 @@ based on the following:
- `PATCH`: Backwards-compatible bugfixes and package updates.
---
Made with ❤️ for automation and resilience.
[semver]: http://semver.org/spec/v2.0.0.html
[aarch64-shield]: https://img.shields.io/badge/aarch64-yes-green.svg

View File

@@ -87,7 +87,6 @@ based on the following:
- `PATCH`: Backwards-compatible bugfixes and package updates.
---
Made with ❤️ for automation and resilience.
[semver]: http://semver.org/spec/v2.0.0.html
[aarch64-shield]: https://img.shields.io/badge/aarch64-yes-green.svg

View File

@@ -76,8 +76,6 @@ sonarqube/
---
Made with ❤️ for automation and resilience.
[semver]: http://semver.org/spec/v2.0.0.html
[aarch64-shield]: https://img.shields.io/badge/aarch64-yes-green.svg
[amd64-shield]: https://img.shields.io/badge/amd64-yes-green.svg