Compare commits
No commits in common. "main" and "feat/mqtt-dependency-pair" have entirely different histories.
main
...
feat/mqtt-
9 changed files with 14 additions and 165 deletions
28
CHANGELOG.md
28
CHANGELOG.md
|
|
@ -6,30 +6,6 @@ Versioning: CalVer (`YY.N`) — same scheme as the core Furtka repo.
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
## [26.14-alpha] - 2026-10-23
|
||||
|
||||
### Added
|
||||
|
||||
- **Vaultwarden** (v1.0.0, image `vaultwarden/server:1.37.4`, multi-arch).
|
||||
Bitwarden-compatible password manager on port 8222, one Docker volume
|
||||
(`data`). Install form asks for `SIGNUPS_ALLOWED` (default `true`; turn it
|
||||
off after creating your accounts). The web vault and clients need HTTPS, so
|
||||
use it through Furtka Gateway or the box's local HTTPS. `/admin` stays
|
||||
disabled (no `ADMIN_TOKEN`). Declares `internet.viable` on 8222.
|
||||
- **Gateway-ready apps:** `it-tools` (1.1.0, port 8080), `uptime-kuma`
|
||||
(1.1.0, 3001) and `jellyfin` (1.1.0, 8096) declare
|
||||
`"internet": {"viable": true, "port": …}`, so core 26.21-alpha can expose
|
||||
them through Furtka Gateway (Apps → ⋯ → Expose to Internet). All three have
|
||||
their own login or hold no data. Not exposable on purpose: fileshare (SMB),
|
||||
mosquitto, zigbee2mqtt, and home-assistant (needs `trusted_proxies` first).
|
||||
|
||||
### Changed
|
||||
|
||||
- `scripts/vendor/furtka_manifest.py` synced with core 26.21-alpha (adds the
|
||||
`internet` block).
|
||||
|
||||
## [26.13-alpha] - 2026-08-23
|
||||
|
||||
### Added
|
||||
|
||||
- **Mosquitto** (v1.0.0, image `eclipse-mosquitto:2.0`). MQTT broker, the
|
||||
|
|
@ -164,9 +140,7 @@ Versioning: CalVer (`YY.N`) — same scheme as the core Furtka repo.
|
|||
the vendored `furtka.manifest.load_manifest` + cross-checks compose
|
||||
volume references).
|
||||
|
||||
[Unreleased]: https://forgejo.sourcegate.online/daniel/furtka-apps/compare/26.14-alpha...HEAD
|
||||
[26.14-alpha]: https://forgejo.sourcegate.online/daniel/furtka-apps/releases/tag/26.14-alpha
|
||||
[26.13-alpha]: https://forgejo.sourcegate.online/daniel/furtka-apps/releases/tag/26.13-alpha
|
||||
[Unreleased]: https://forgejo.sourcegate.online/daniel/furtka-apps/compare/26.12-alpha...HEAD
|
||||
[26.12-alpha]: https://forgejo.sourcegate.online/daniel/furtka-apps/releases/tag/26.12-alpha
|
||||
[26.11-alpha]: https://forgejo.sourcegate.online/daniel/furtka-apps/releases/tag/26.11-alpha
|
||||
[26.10-alpha]: https://forgejo.sourcegate.online/daniel/furtka-apps/releases/tag/26.10-alpha
|
||||
|
|
|
|||
|
|
@ -1,17 +1,11 @@
|
|||
{
|
||||
"name": "it-tools",
|
||||
"display_name": "IT-Tools",
|
||||
"version": "1.1.0",
|
||||
"version": "1.0.0",
|
||||
"description": "Collection of small developer and admin utilities in one web UI.",
|
||||
"description_long": "Werkzeugkasten für den Browser: Passwörter generieren, Hashes berechnen, JSON/YAML formatieren, Base64 en- und dekodieren, JWTs lesen, Regex testen, UUIDs oder QR-Codes erzeugen und vieles mehr. Läuft komplett im Browser — es werden keine Daten an externe Dienste gesendet.",
|
||||
"volumes": [],
|
||||
"ports": [
|
||||
8080
|
||||
],
|
||||
"ports": [8080],
|
||||
"icon": "icon.svg",
|
||||
"open_url": "http://{host}:8080/",
|
||||
"internet": {
|
||||
"viable": true,
|
||||
"port": 8080
|
||||
}
|
||||
"open_url": "http://{host}:8080/"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,11 @@
|
|||
{
|
||||
"name": "jellyfin",
|
||||
"display_name": "Jellyfin",
|
||||
"version": "1.1.0",
|
||||
"version": "1.0.0",
|
||||
"description": "Media server for movies, shows, and music on the LAN.",
|
||||
"description_long": "Ein eigener Streaming-Server für deine Filme, Serien und Musik. Apps für Smart-TVs, Handy und Web-Browser verfügbar. Admin-Konto wird beim ersten Besuch im Browser angelegt. Medien werden nur gelesen, nicht verändert.",
|
||||
"volumes": [
|
||||
"config",
|
||||
"cache"
|
||||
],
|
||||
"ports": [
|
||||
8096
|
||||
],
|
||||
"volumes": ["config", "cache"],
|
||||
"ports": [8096],
|
||||
"icon": "icon.svg",
|
||||
"open_url": "http://{host}:8096/",
|
||||
"settings": [
|
||||
|
|
@ -21,9 +16,5 @@
|
|||
"type": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"internet": {
|
||||
"viable": true,
|
||||
"port": 8096
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,11 @@
|
|||
{
|
||||
"name": "uptime-kuma",
|
||||
"display_name": "Uptime Kuma",
|
||||
"version": "1.1.0",
|
||||
"version": "1.0.1",
|
||||
"description": "Self-hosted status monitor for services, ports, and devices on the LAN.",
|
||||
"description_long": "Überwacht andere Geräte und Dienste im Netzwerk und schickt dir Bescheid, wenn etwas ausfällt. Nach der Installation den Admin-Account direkt im Browser unter http://furtka.local:3001 anlegen, dann Monitore (Ping, Webseite, Port, Docker-Container) hinzufügen.",
|
||||
"volumes": [
|
||||
"data"
|
||||
],
|
||||
"ports": [
|
||||
3001
|
||||
],
|
||||
"volumes": ["data"],
|
||||
"ports": [3001],
|
||||
"icon": "icon.svg",
|
||||
"open_url": "http://{host}:3001/",
|
||||
"internet": {
|
||||
"viable": true,
|
||||
"port": 3001
|
||||
}
|
||||
"open_url": "http://{host}:3001/"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
SIGNUPS_ALLOWED=true
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
# Furtka Vaultwarden — Bitwarden-compatible password manager.
|
||||
#
|
||||
# One Docker-managed volume (data) holds the SQLite vault, attachments and
|
||||
# the RSA keys. The volume `furtka_vaultwarden_data` is created by the Furtka
|
||||
# reconciler from the manifest's "volumes" list before compose comes up;
|
||||
# `external: true` keeps compose from managing its lifecycle.
|
||||
#
|
||||
# HTTPS: the Bitwarden web vault and clients need a secure context, so plain
|
||||
# http://<host>:8222 only shows a warning page. Reach it through Furtka
|
||||
# Gateway (manifest `internet.viable`) or the box's local HTTPS.
|
||||
#
|
||||
# No ADMIN_TOKEN: the /admin panel stays disabled. Account creation is
|
||||
# controlled by SIGNUPS_ALLOWED from the install form; the user turns it off
|
||||
# after creating their accounts.
|
||||
#
|
||||
# Image pin: 1.37.4 (multi-arch, x86_64 + arm64). Bump deliberately.
|
||||
|
||||
services:
|
||||
vaultwarden:
|
||||
image: vaultwarden/server:1.37.4
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8222:80"
|
||||
environment:
|
||||
- TZ=Europe/Berlin
|
||||
- SIGNUPS_ALLOWED=${SIGNUPS_ALLOWED:-true}
|
||||
volumes:
|
||||
- furtka_vaultwarden_data:/data
|
||||
|
||||
volumes:
|
||||
furtka_vaultwarden_data:
|
||||
external: true
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M32 6 L54 14 V30 C54 44 44 54 32 58 C20 54 10 44 10 30 V14 Z" opacity="0.28"/>
|
||||
<rect x="22" y="28" width="20" height="16" rx="3"/>
|
||||
<path d="M26 28 V23 a6 6 0 0 1 12 0 V28"/>
|
||||
<path d="M32 34 V38"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 399 B |
|
|
@ -1,22 +0,0 @@
|
|||
{
|
||||
"name": "vaultwarden",
|
||||
"display_name": "Vaultwarden",
|
||||
"version": "1.0.0",
|
||||
"description": "Password manager compatible with the Bitwarden apps and browser extensions.",
|
||||
"description_long": "Dein eigener Passwortmanager, kompatibel mit den offiziellen Bitwarden-Apps für Handy, Browser und PC. Wichtig: Der Web-Tresor und die Apps brauchen HTTPS. Am einfachsten über Furtka Gateway (Apps → ⋯ → Expose to Internet) oder mit eingeschaltetem lokalem HTTPS. Erstes Konto im Browser anlegen, danach in den Einstellungen \"Neue Konten erlauben\" auf false stellen, damit sich niemand Fremdes registriert.",
|
||||
"volumes": ["data"],
|
||||
"ports": [8222],
|
||||
"icon": "icon.svg",
|
||||
"open_url": "http://{host}:8222/",
|
||||
"internet": { "viable": true, "port": 8222 },
|
||||
"settings": [
|
||||
{
|
||||
"name": "SIGNUPS_ALLOWED",
|
||||
"label": "Neue Konten erlauben",
|
||||
"description": "true, solange du dein Konto (und das deiner Familie) anlegst. Danach auf false setzen, besonders wenn Vaultwarden über das Gateway aus dem Internet erreichbar ist.",
|
||||
"type": "text",
|
||||
"required": true,
|
||||
"default": "true"
|
||||
}
|
||||
]
|
||||
}
|
||||
45
scripts/vendor/furtka_manifest.py
vendored
45
scripts/vendor/furtka_manifest.py
vendored
|
|
@ -32,21 +32,6 @@ class Setting:
|
|||
default: str | None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Internet:
|
||||
# Whether this app is safe to expose to the internet through a
|
||||
# furtka-gateway at all. Only ever a catalog author's self-declaration —
|
||||
# the gateway independently re-validates the port it's given at publish
|
||||
# time regardless of what this says (see furtka-gateway's routes.py).
|
||||
viable: bool
|
||||
# Which declared `ports` entry is the exposable web UI. Required when
|
||||
# viable is True — an app declaring several ports (e.g. a DNS service
|
||||
# exposing both its resolver port and an admin UI port) must say which
|
||||
# one is actually meant to be reachable from the internet; there is no
|
||||
# way to infer that from `ports` alone.
|
||||
port: int | None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Requirement:
|
||||
app: str # name of the required app — must resolve in installed/catalog/bundled
|
||||
|
|
@ -77,7 +62,6 @@ class Manifest:
|
|||
# no frontend (CLI-only, background workers) leave this empty.
|
||||
open_url: str = ""
|
||||
requires: tuple[Requirement, ...] = field(default_factory=tuple)
|
||||
internet: Internet = field(default_factory=lambda: Internet(viable=False, port=None))
|
||||
|
||||
def volume_name(self, short: str) -> str:
|
||||
# Namespace volume names so two apps can each declare e.g. "data"
|
||||
|
|
@ -171,27 +155,6 @@ def _parse_requires(raw: object, manifest_path: Path, self_name: str) -> tuple[R
|
|||
return tuple(out)
|
||||
|
||||
|
||||
def _parse_internet(raw: object, manifest_path: Path, ports: list[int]) -> Internet:
|
||||
if raw is None:
|
||||
return Internet(viable=False, port=None)
|
||||
if not isinstance(raw, dict):
|
||||
raise ManifestError(f"{manifest_path}: internet must be an object")
|
||||
|
||||
viable = bool(raw.get("viable", False))
|
||||
port = raw.get("port")
|
||||
|
||||
if not viable:
|
||||
return Internet(viable=False, port=None)
|
||||
|
||||
if not isinstance(port, int) or isinstance(port, bool):
|
||||
raise ManifestError(f"{manifest_path}: internet.port must be an integer when viable")
|
||||
if port not in ports:
|
||||
raise ManifestError(
|
||||
f"{manifest_path}: internet.port {port} must be one of the declared ports {ports}"
|
||||
)
|
||||
return Internet(viable=True, port=port)
|
||||
|
||||
|
||||
def load_manifest(path: Path, expected_name: str | None = None) -> Manifest:
|
||||
"""Parse and validate a manifest.json.
|
||||
|
||||
|
|
@ -212,10 +175,8 @@ def load_manifest(path: Path, expected_name: str | None = None) -> Manifest:
|
|||
raise ManifestError(f"{path}: missing required fields: {', '.join(missing)}")
|
||||
|
||||
name = raw["name"]
|
||||
if not isinstance(name, str) or not APP_NAME_RE.fullmatch(name):
|
||||
raise ManifestError(
|
||||
f"{path}: name must be lowercase letters, digits, '-' or '_' (got {name!r})"
|
||||
)
|
||||
if not isinstance(name, str) or not name:
|
||||
raise ManifestError(f"{path}: name must be a non-empty string")
|
||||
if expected_name is not None and name != expected_name:
|
||||
raise ManifestError(f"{path}: name {name!r} must equal {expected_name!r}")
|
||||
|
||||
|
|
@ -229,7 +190,6 @@ def load_manifest(path: Path, expected_name: str | None = None) -> Manifest:
|
|||
|
||||
settings = _parse_settings(raw.get("settings"), path)
|
||||
requires = _parse_requires(raw.get("requires"), path, name)
|
||||
internet = _parse_internet(raw.get("internet"), path, ports)
|
||||
|
||||
open_url_raw = raw.get("open_url", "")
|
||||
if not isinstance(open_url_raw, str):
|
||||
|
|
@ -247,5 +207,4 @@ def load_manifest(path: Path, expected_name: str | None = None) -> Manifest:
|
|||
settings=settings,
|
||||
open_url=open_url_raw,
|
||||
requires=requires,
|
||||
internet=internet,
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue