diff --git a/CHANGELOG.md b/CHANGELOG.md index ae24e6b..96b03a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,31 @@ This project uses calendar versioning: `YY.N-stage` (e.g. `26.0-alpha` = 2026, r ## [Unreleased] +## [26.9-alpha] - 2026-04-21 + +### Fixed + +- Landing-page app tiles with an `open_url` now open in a new tab + (`target="_blank" rel="noopener"`), matching the Open button + behaviour on `/apps`. Without this, clicking "Uptime Kuma" on the + home screen replaced Furtka itself with the Kuma admin page. + Internal links (the `Manage →` fallback for apps without an + `open_url`) still open in the same tab. +- `scripts/publish-release.sh` no longer fails the whole release when + the ISO upload hits a Forgejo proxy 504. The core tarball + sha256 + + release.json (which running boxes need for self-update) are uploaded + first and the ISO is attempted last as a best-effort; a 504 now logs + a warning and exits 0 so the release page still publishes. Surfaced + by the 26.8-alpha cut: the tarball landed but the ~1 GB ISO upload + timed out at the Forgejo reverse proxy. + +### Changed + +- `furtka app list --json` now mirrors `/api/apps` field-for-field — + previously the CLI emitted a slim projection missing + `description_long`, `open_url`, and `settings`. Anyone piping the + CLI output into jq for automation was seeing an incomplete view. + ## [26.8-alpha] - 2026-04-20 ### Added @@ -130,7 +155,8 @@ First tagged snapshot. Pre-alpha — the installer does not yet boot, but the de - **Containers:** Docker + Compose - **License:** AGPL-3.0 -[Unreleased]: https://forgejo.sourcegate.online/daniel/furtka/compare/26.8-alpha...HEAD +[Unreleased]: https://forgejo.sourcegate.online/daniel/furtka/compare/26.9-alpha...HEAD +[26.9-alpha]: https://forgejo.sourcegate.online/daniel/furtka/releases/tag/26.9-alpha [26.8-alpha]: https://forgejo.sourcegate.online/daniel/furtka/releases/tag/26.8-alpha [26.6-alpha]: https://forgejo.sourcegate.online/daniel/furtka/releases/tag/26.6-alpha [26.5-alpha]: https://forgejo.sourcegate.online/daniel/furtka/releases/tag/26.5-alpha diff --git a/assets/www/index.html b/assets/www/index.html index 4f1513f..6bf3476 100644 --- a/assets/www/index.html +++ b/assets/www/index.html @@ -100,9 +100,9 @@ // frontend fall back to /apps for management. if (app.open_url) { const host = HOSTNAME || location.hostname; - return { href: app.open_url.replace('{host}', host), label: 'Open' }; + return { href: app.open_url.replace('{host}', host), label: 'Open', external: true }; } - return { href: '/apps', label: 'Manage →' }; + return { href: '/apps', label: 'Manage →', external: false }; } async function renderApps() { @@ -119,8 +119,9 @@ } target.innerHTML = apps.map(a => { const icon = a.icon_svg || FALLBACK_ICON; - const { href, label } = primaryAction(a); - return ` + const { href, label, external } = primaryAction(a); + const tgt = external ? ' target="_blank" rel="noopener"' : ''; + return `
${icon}
${esc(a.display_name || a.name)} ${esc(label)} diff --git a/furtka/cli.py b/furtka/cli.py index eb4452c..d4af59a 100644 --- a/furtka/cli.py +++ b/furtka/cli.py @@ -21,9 +21,22 @@ def _cmd_app_list(args: argparse.Namespace) -> int: "display_name": r.manifest.display_name, "version": r.manifest.version, "description": r.manifest.description, + "description_long": r.manifest.description_long, "volumes": list(r.manifest.volumes), "ports": list(r.manifest.ports), "icon": r.manifest.icon, + "open_url": r.manifest.open_url, + "settings": [ + { + "name": s.name, + "label": s.label, + "description": s.description, + "type": s.type, + "required": s.required, + "default": s.default, + } + for s in r.manifest.settings + ], } if r.manifest else None, diff --git a/scripts/publish-release.sh b/scripts/publish-release.sh index e5674a3..b784294 100755 --- a/scripts/publish-release.sh +++ b/scripts/publish-release.sh @@ -103,9 +103,16 @@ upload_asset "$RELEASE_JSON" # exists. Release workflows that want this build the ISO via iso/build.sh # and move the output here before calling publish-release. Local runs # that skip the ISO step still publish the core release successfully. +# +# Soft-fail: the ISO is ~1 GB and Forgejo's reverse proxy has returned +# 504 on the upload even when the write eventually succeeds. The core +# tarball (which boxes need for self-update) is already uploaded above, +# so don't let an ISO transport hiccup fail the whole release. ISO="$DIST_DIR/furtka-$VERSION.iso" if [ -f "$ISO" ]; then - upload_asset "$ISO" + if ! upload_asset "$ISO"; then + echo "warning: ISO upload failed — release published without ISO asset" >&2 + fi fi echo "Release $VERSION published: https://$HOST/$REPO/releases/tag/$VERSION" diff --git a/tests/test_cli.py b/tests/test_cli.py index 24f8c2a..f6137b5 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -32,9 +32,21 @@ def test_app_list_json_with_one_app(tmp_path, monkeypatch, capsys): "display_name": "Network Files", "version": "0.1.0", "description": "SMB", + "description_long": "Long description here.", "volumes": ["files"], "ports": [445], "icon": "icon.svg", + "open_url": "smb://{host}/files", + "settings": [ + { + "name": "SMB_USER", + "label": "User", + "description": "SMB user", + "type": "text", + "default": "furtka", + "required": True, + } + ], } ) ) @@ -43,7 +55,14 @@ def test_app_list_json_with_one_app(tmp_path, monkeypatch, capsys): data = json.loads(capsys.readouterr().out) assert len(data) == 1 assert data[0]["ok"] is True - assert data[0]["manifest"]["name"] == "fileshare" + m = data[0]["manifest"] + assert m["name"] == "fileshare" + assert m["description_long"] == "Long description here." + assert m["open_url"] == "smb://{host}/files" + assert len(m["settings"]) == 1 + assert m["settings"][0]["name"] == "SMB_USER" + assert m["settings"][0]["required"] is True + assert m["settings"][0]["default"] == "furtka" def test_reconcile_dry_run_empty(tmp_path, monkeypatch, capsys):