Compare commits

...
1 Commits
Author SHA1 Message Date
Julian Boldt cf9981c6eb Release v1.32.1
Release / verify (push) Waiting to run
Release / test (push) Blocked by required conditions
CI / backend (push) Blocked by required conditions
CI / frontend (push) Blocked by required conditions
CI / checks (push) Blocked by required conditions
- **Korrektur: Rollouts erkannten die Zielversion bei anderen Release-Typen falsch.** OPNexus las die neue Version nur am Kernpaket `opnsense`. Bei der
  Business-Edition (`opnsense-business`) und beim Wechsel des Release-Typs (z. B. auf `opnsense-devel`, dann als neues Paket angeboten) fiel es auf
  `product_latest` zurück; das Gate hielt den Rollout dann fälschlich mit „Version passt nicht“ an. Live gefunden und belegt (Rollout mit Cluster in einer Welle).
- **Plan-Hinweis zu Alias-Namen ehrlich formuliert:** „kein zentraler Alias in OPNexus“ statt „kein Alias dieses Namens“ (von Hand auf der Firewall
  angelegte Aliase sind in Ordnung; der Planer fragt bewusst keine Firewall).
2026-10-23 12:40:49 +02:00
11 changed files with 52 additions and 17 deletions

No files matched your search

+8
View File
@@ -31,6 +31,14 @@ den Einstellungen.
Nächste Codenamen (der Reihe nach, wenn die jeweils nächste MINOR-Version
ansteht): Saker …
## [1.32.1] "Merlin" — 2026-10-23
- **Korrektur: Rollouts erkannten die Zielversion bei anderen Release-Typen falsch.** OPNexus las die neue Version nur am Kernpaket `opnsense`. Bei der
Business-Edition (`opnsense-business`) und beim Wechsel des Release-Typs (z. B. auf `opnsense-devel`, dann als neues Paket angeboten) fiel es auf
`product_latest` zurück; das Gate hielt den Rollout dann fälschlich mit „Version passt nicht“ an. Live gefunden und belegt (Rollout mit Cluster in einer Welle).
- **Plan-Hinweis zu Alias-Namen ehrlich formuliert:** „kein zentraler Alias in OPNexus“ statt „kein Alias dieses Namens“ (von Hand auf der Firewall
angelegte Aliase sind in Ordnung; der Planer fragt bewusst keine Firewall).
## [1.32.0] "Merlin" — 2026-10-23
- **Konfiguration als Code:** Unter **System › Konfiguration als Code** (und per `opnexus export` / `opnexus apply`) lassen sich Gerätegruppen, Hierarchiegruppen,
+2 -2
View File
@@ -7,7 +7,7 @@ Zentrale Verwaltung mehrerer [OPNsense](https://opnsense.org)-Firewalls: Regeln,
![Zentrale Firewall-Regeln](docs/screenshots/readme-rules-de.webp)
> **Stand:** `1.32.0` ist die aktuelle stabile Version. Entwickelt und getestet wurde gegen echte OPNsense-Instanzen; eine frische Installation aus dem Release-Bundle sowie Updates und Rollbacks sind durchgängig geprüft. Ecken und Kanten bitte melden.
> **Stand:** `1.32.1` ist die aktuelle stabile Version. Entwickelt und getestet wurde gegen echte OPNsense-Instanzen; eine frische Installation aus dem Release-Bundle sowie Updates und Rollbacks sind durchgängig geprüft. Ecken und Kanten bitte melden.
## Funktionen
@@ -66,7 +66,7 @@ Für den Zugriff von anderen Rechnern und den Produktivbetrieb mit TLS (mitgelie
```bash
OPNEXUS_IMAGE_REPO=git.opnexus.dev/opnexus
OPNEXUS_VERSION=1.32.0
OPNEXUS_VERSION=1.32.1
```
```bash
+2 -2
View File
@@ -7,7 +7,7 @@ Central management for multiple [OPNsense](https://opnsense.org) firewalls: rule
![Central firewall rules](docs/screenshots/readme-rules-en.webp)
> **Status:** `1.32.0` is the current stable release. It has been developed and tested against real OPNsense instances; a clean install from the release bundle, updates and rollbacks have been verified end to end. Please report rough edges.
> **Status:** `1.32.1` is the current stable release. It has been developed and tested against real OPNsense instances; a clean install from the release bundle, updates and rollbacks have been verified end to end. Please report rough edges.
## What it does
@@ -68,7 +68,7 @@ For access from other machines and for production use with TLS (bundled Caddy pr
```bash
OPNEXUS_IMAGE_REPO=git.opnexus.dev/opnexus
OPNEXUS_VERSION=1.32.0
OPNEXUS_VERSION=1.32.1
```
```bash
+1 -1
View File
@@ -74,7 +74,7 @@ REASONS_DE = {
WARNINGS_DE = {
"redeploy_needed": "{count} geänderte Objekte sind ausgerollt: danach über die Vorschau neu ausrollen",
"targets_without_link": "{count} neue Objekte haben Ziele, aber keine Verknüpfung zu Objekten auf den Firewalls (Duplikate beim Ausrollen möglich)",
"unknown_alias_reference": "{value}: kein Alias dieses Namens",
"unknown_alias_reference": "{value}: kein zentraler Alias in OPNexus (auf der Firewall von Hand angelegt? dann in Ordnung)",
"variable_removed_in_use": "Variable {name} von {host} wird entfernt, ein Objekt nutzt sie noch",
"target_host_disabled": "Ziel-Host {host} ist deaktiviert",
}
+16 -7
View File
@@ -409,21 +409,30 @@ async def _handle_resuming(db: AsyncSession, run: RolloutRun) -> Decision | None
return apply
# The core package of each OPNsense release type (community, development, business edition). Switching the type
# installs another core package: live 2026-10-23, community -> devel offered `opnsense-devel` 27.1.a_483 as a NEW
# package while product_latest still said 26.7.7 (the rollout expected 26.7.7 and stopped at the gate).
CORE_PACKAGES = ("opnsense", "opnsense-devel", "opnsense-business")
def target_of(status: dict, action: str) -> str | None:
"""The version a firmware action will install, from core/firmware/status (read after a fresh check).
Live finding (test-fw-3, 26.7.1_1, 2026-10-21): ``product.product_latest`` said 26.7.2 while the
``opnsense`` core package was offered as 26.7.7 — and the package is what gets installed. So the
core package's new version comes first; product_latest is only a fallback."""
core package's new version comes first (whichever release type it is, see CORE_PACKAGES, also when it
arrives as a new package); product_latest is only a fallback."""
if action == "major_update":
return status.get("upgrade_major_version") or None
packages = status.get("all_packages") if isinstance(status.get("all_packages"), dict) else {}
core = packages.get("opnsense") if isinstance(packages.get("opnsense"), dict) else None
if core and core.get("new") and core.get("new") != "N/A":
return str(core["new"])
for row in status.get("upgrade_packages") or []:
if isinstance(row, dict) and row.get("name") == "opnsense" and row.get("new_version"):
return str(row["new_version"])
for name in CORE_PACKAGES:
core = packages.get(name) if isinstance(packages.get(name), dict) else None
if core and core.get("new") and core.get("new") != "N/A":
return str(core["new"])
for key, field in (("upgrade_packages", "new_version"), ("new_packages", "version")):
for row in status.get(key) or []:
if isinstance(row, dict) and row.get("name") in CORE_PACKAGES and row.get(field):
return str(row[field])
product = status.get("product") if isinstance(status.get("product"), dict) else {}
return product.get("product_latest") or status.get("product_latest") or None
+1 -1
View File
@@ -6,5 +6,5 @@ convention — but both are bumped together whenever a release ships).
Versioning scheme: see CHANGELOG.md ("Versionierung").
"""
APP_VERSION = "1.32.0"
APP_VERSION = "1.32.1"
APP_CODENAME = "Merlin"
+13
View File
@@ -621,6 +621,19 @@ def test_target_version_comes_from_the_core_package_not_product_latest():
assert engine.target_of({"upgrade_major_version": "27.1"}, "major_update") == "27.1"
def test_target_version_of_other_release_types_and_a_switched_core_package():
# Shape seen live on test-fw-1 (2026-10-23) after switching the release type community -> development.
switch = {"status": "update", "product": {"product_id": "opnsense", "product_version": "26.7.7", "product_latest": "26.7.7"},
"all_packages": {"opnsense-devel": {"new": "27.1.a_483", "reason": "new", "name": "opnsense-devel", "old": "N/A"}},
"upgrade_packages": [], "new_packages": [{"name": "opnsense-devel", "repository": "OPNsense", "version": "27.1.a_483"}]}
assert engine.target_of(switch, "update") == "27.1.a_483"
assert engine.target_of({**switch, "all_packages": {}}, "update") == "27.1.a_483" # from new_packages
business = {"status": "update", "product": {"product_latest": "26.10"},
"all_packages": {"opnsense-business": {"old": "26.10", "new": "26.10.1", "reason": "upgrade"}}}
assert engine.target_of(business, "update") == "26.10.1"
assert engine.target_of({"all_packages": {"unrelated": {"new": "9"}}, "product": {"product_latest": "26.7.7"}}, "update") == "26.7.7"
# ── 1.29 (S6): window exceptions in the fingerprint, stable account id ────────
async def _activate(client, plan):
+6 -1
View File
@@ -950,6 +950,11 @@ solche von Hand. Deshalb gilt:
wartet.
- Nach dem Start Version prüfen und die Wartung abschließen. Ein Rollout macht **nie**
von selbst einen Rückweg; bei einem fehlgeschlagenen Host hält er an, und du entscheidest.
- Das verlassene Boot-Environment (der Stand nach dem Update) bleibt erhalten, damit du auch
wieder vorwärts kannst. Brauchst du es nicht mehr, lösche es unter Host-System › Snapshots.
Die Firewall läuft danach aus dem Boot-Environment mit dem Namen des Rückweg-Punkts.
- Im Cluster gilt wie beim Neustart: nur auf dem BACKUP. Für den MASTER zuerst die
CARP-Übergabe ausführen.
### Update Center
@@ -1520,7 +1525,7 @@ in der Datei darfst du eigene vergeben (`allow-dns-lan`), der Import speichert s
**Umbenennen** gilt als Entfernen und Neuanlegen, ein ausgerolltes Objekt blockiert
dann mit Prune. Die Reihenfolge der Hierarchie-Regeln ist die Reihenfolge in der Datei.
**Hinweise im Plan:** ein Alias-Name, den kein Alias hat; eine Variable, die entfernt
**Hinweise im Plan:** ein Wert, der wie ein Alias aussieht, aber kein zentraler Alias in OPNexus ist (auf der Firewall von Hand angelegte Aliase sind in Ordnung; OPNexus fragt beim Planen keine Firewall); eine Variable, die entfernt
wird, obwohl ein Objekt sie nutzt (das Ausrollen auf diesen Host scheitert dann sicher);
ein deaktivierter Ziel-Host; neue Objekte mit Zielen ohne Verknüpfung zu bereits auf den
Firewalls vorhandenen Objekten. Letzteres ist wichtig bei einem Import in eine **neue
+1 -1
View File
@@ -4189,7 +4189,7 @@
"warn": {
"redeploy_needed": "{count} geänderte Objekte sind ausgerollt: nach dem Übernehmen über die Vorschau neu ausrollen.",
"targets_without_link": "{count} neue Objekte haben Ziele, sind aber nicht mit Objekten auf den Firewalls verknüpft: Ausrollen kann Duplikate anlegen. Besser vorher die Verknüpfung nutzen oder aus einer Vollsicherung wiederherstellen.",
"unknown_alias_reference": "„{value}“ sieht wie ein Alias aus, ein solcher Alias existiert aber nicht.",
"unknown_alias_reference": "„{value}“ sieht wie ein Alias aus, ist aber kein zentraler Alias in OPNexus. Existiert er auf den Ziel-Firewalls (von Hand angelegt), ist das in Ordnung; sonst schlägt das Ausrollen fehl.",
"variable_removed_in_use": "Variable {name} von {host} wird entfernt, ein Objekt benutzt sie noch (Ausrollen auf diesen Host schlägt dann fehl).",
"target_host_disabled": "Ziel-Host {host} ist deaktiviert."
},
+1 -1
View File
@@ -4189,7 +4189,7 @@
"warn": {
"redeploy_needed": "{count} changed objects are deployed: redeploy them through the preview after applying.",
"targets_without_link": "{count} new objects have targets but are not linked to objects on the firewalls: deploying may create duplicates. Link them first or restore from a full backup.",
"unknown_alias_reference": "\"{value}\" looks like an alias, but no such alias exists.",
"unknown_alias_reference": "\"{value}\" looks like an alias but is not a central alias in OPNexus. If it exists on the target firewalls (created by hand), that is fine; otherwise deploying fails.",
"variable_removed_in_use": "Variable {name} of {host} is removed while an object still uses it (deploying to that host will then fail).",
"target_host_disabled": "Target host {host} is disabled."
},
+1 -1
View File
@@ -1,6 +1,6 @@
// Manually bumped whenever a notable change ships — mirrors the dated
// entries already kept in the project README/CHANGELOG.md, no build
// tooling involved. Versioning scheme: see CHANGELOG.md ("Versionierung").
export const APP_VERSION = "1.32.0";
export const APP_VERSION = "1.32.1";
export const APP_CODENAME = "Merlin";
export const APP_MAINTAINER = "Julian Boldt";