Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf9981c6eb |
No files matched your search
@@ -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
@@ -7,7 +7,7 @@ Zentrale Verwaltung mehrerer [OPNsense](https://opnsense.org)-Firewalls: Regeln,
|
||||
|
||||

|
||||
|
||||
> **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
|
||||
|
||||
@@ -7,7 +7,7 @@ Central management for multiple [OPNsense](https://opnsense.org) firewalls: rule
|
||||
|
||||

|
||||
|
||||
> **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
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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."
|
||||
},
|
||||
|
||||
@@ -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,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";
|
||||
Reference in new issue
Block a user