Compare commits

..

12 commits

Author SHA1 Message Date
0fcdc878a9 chore: release 26.12-alpha
Some checks failed
Release / release (push) Successful in 28s
CI / shellcheck (push) Failing after 10m40s
CI / validate (push) Failing after 10m51s
2026-04-28 14:03:55 +02:00
1c14446434 feat(home-assistant): add smart-home hub to the catalog (closes #1)
All checks were successful
CI / validate (push) Successful in 17s
CI / shellcheck (push) Successful in 50s
Bridge-mode networking with explicit 8123:8123 port mapping. Image is
homeassistant/home-assistant:stable — upstream's recommended production
tag, same image as the Docker Hub link in #1.

One Docker-managed volume (`furtka_home-assistant_config`). No
manifest.settings — onboarding (admin user, home location, units) is
the standard Home Assistant in-browser flow on first visit to :8123,
same shape as Jellyfin and Uptime Kuma.

Cloud integrations (Hue Cloud, Tado, Sonos via account) work today.
Local-discovery integrations that need host networking, Bluetooth, or
Zigbee/Z-Wave dongle passthrough are explicitly deferred — listed as
a follow-up once the manifest schema grows a network_mode knob.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 12:17:11 +02:00
b1dadbc952 chore: release 26.11-alpha
All checks were successful
CI / validate (push) Successful in 5s
CI / shellcheck (push) Successful in 13s
Release / release (push) Successful in 5s
Catalog 26.11-alpha rolls up the Jellyfin compose default-substitution
fix (already on main as 2a31a79) into a tagged release so the
validator CI goes green on every post-Jellyfin commit.

No app additions or schema changes — core 26.11-alpha (auth) is the
substantive release this cycle, catalog just clears its CI red X.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 13:01:55 +02:00
57593d98a7 chore: retrigger CI
All checks were successful
CI / validate (push) Successful in 15s
CI / shellcheck (push) Successful in 36s
Forgejo Actions did not pick up the push of 2a31a79 (fix(jellyfin)
default-substitution) — no ci.yml task registered 10+ min after the
push landed. Empty commit forces a fresh webhook trigger so the
validator fix can go green. Same Forgejo hiccup also dropped
ci.yml for core 577c246 earlier.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 12:26:15 +02:00
2a31a7927c fix(jellyfin): use compose default-substitution so validator passes
Some checks are pending
CI / validate (push) Waiting to run
CI / shellcheck (push) Waiting to run
Without a .env in the jellyfin app dir, validate-catalog.py's
\`docker compose config\` step substituted \${MEDIA_PATH} → empty →
\`:/media:ro\` which compose rejects ("empty section between colons"),
failing CI on every post-Jellyfin commit. Local runs skipped the
check because the validator gates it on \`which docker\` and my dev
box has none.

Fix: use the default-substitution form \${MEDIA_PATH:-/nonexistent}.
Empty/unset MEDIA_PATH now expands to /nonexistent — valid volume
spec, CI green. Real install flow (form fill → .env with real path)
substitutes the user value as before. /nonexistent is obviously
wrong, so if a broken install path ever reaches \`docker compose up\`
the mount fails loudly rather than silently mounting something
random.

Doesn't affect the already-published 26.10-alpha catalog tarball —
that one works end-to-end for the Web UI install flow. This fix is
purely to un-red CI on subsequent pushes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 12:18:57 +02:00
1dac7b810a chore: release 26.10-alpha
Some checks failed
CI / validate (push) Failing after 11s
CI / shellcheck (push) Successful in 22s
Release / release (push) Successful in 6s
Catalog 26.10-alpha adds Jellyfin (first app to use the core-26.10
path-type setting for host-bind-mounted media libraries) and bumps
the vendored manifest schema to match core. Requires boxes to be on
core 26.10-alpha or newer — the 26.9 manifest parser rejects the
path type.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 11:48:33 +02:00
cf623d46bc docs(apps): document the new path setting type
Some checks failed
CI / validate (push) Failing after 11s
CI / shellcheck (push) Successful in 17s
Mirror of the core repo's apps/README.md update. Keeps the two copies
in lockstep since app authors may land on either one.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 11:43:16 +02:00
fcf2f22a47 feat(jellyfin): add media-server app using new path-type setting
Some checks are pending
CI / validate (push) Waiting to run
CI / shellcheck (push) Waiting to run
First app to use the core-26.10-alpha `path` setting type. User picks
their existing media folder (MEDIA_PATH=/mnt/media) in the install
form; core installer validates the path server-side; compose mounts
it read-only at /media. Docker-managed volumes hold /config and
/cache; admin account is created from the first browser visit to
:8096. No HW transcoding yet — that's a later schema extension.

Also bumps the vendored manifest schema in scripts/vendor/ to match
core 26.10-alpha — catches up both the missing `open_url` field (gap
since 26.6-alpha) and the new `path` setting type. No changes needed
to validate-catalog.py itself.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 11:40:29 +02:00
a2e47c2c0c chore: release 26.9-alpha
All checks were successful
CI / validate (push) Successful in 11s
CI / shellcheck (push) Successful in 20s
Release / release (push) Successful in 13s
Catalog 26.9-alpha cuts the IT-Tools addition from b2b4793 into a
tagged release so boxes pick it up via on-box sync.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 11:18:14 +02:00
b2b47938e6 feat(it-tools): add browser-toolbox app to the catalog
All checks were successful
CI / validate (push) Successful in 7s
CI / shellcheck (push) Successful in 32s
Client-side utility collection (password/UUID/QR generators, hashes,
Base64/JWT, JSON/YAML formatter, regex tester, cron parser, etc.) from
corentinth/it-tools. No state, no volumes, no settings — runs fully in
the browser and serves on port 8080. Good smoke-test target for the
catalog-sync path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 11:16:59 +02:00
436ce11f52 feat(catalog): 26.8-alpha — add open_url to fileshare + uptime-kuma
All checks were successful
CI / validate (push) Successful in 29s
CI / shellcheck (push) Successful in 56s
Release / release (push) Successful in 25s
Both apps grow an `open_url` manifest field so the core UI can render
an Open button that deep-links into the app. fileshare uses smb:// to
trigger the OS file manager; uptime-kuma uses http://{host}:3001/ for
the browser. No image or compose changes.

- fileshare 0.1.1 → 0.1.2
- uptime-kuma 1.0.0 → 1.0.1
2026-04-20 17:47:19 +02:00
f377a5a387 feat(uptime-kuma): add status-monitor app to the catalog
All checks were successful
CI / validate (push) Successful in 20s
CI / shellcheck (push) Successful in 31s
Release / release (push) Successful in 37s
louislam/uptime-kuma:1 with one data volume and port 3001. Admin
account bootstrapped from the first browser visit to :3001 — no
Furtka settings form needed. Catalog release 26.7-alpha.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 15:34:28 +02:00
17 changed files with 342 additions and 4 deletions

View file

@ -6,6 +6,86 @@ Versioning: CalVer (`YY.N`) — same scheme as the core Furtka repo.
## [Unreleased]
## [26.12-alpha] - 2026-04-28
### Added
- **Home Assistant** (v1.0.0, image `homeassistant/home-assistant:stable`,
daniel/furtka-apps#1). Smart-home hub for lights, sensors, and locally
controlled devices. Bridge-mode networking with explicit `8123:8123`
port mapping for v1 — Cloud integrations (Hue Cloud, Tado, Sonos via
account) work, mDNS/Bluetooth/Zigbee-stick discovery deferred until a
manifest `network_mode` knob lands. One Docker volume (`config`),
no manifest settings — onboarding (admin user, home location, units)
happens in the browser on first visit to `:8123`.
## [26.11-alpha] - 2026-04-21
### Fixed
- **Jellyfin compose default-substitution.** Without an `.env` in the
jellyfin app dir the CI validator's `docker compose config` step
substituted an empty `${MEDIA_PATH}` into `:/media:ro` — which
compose rejects as `empty section between colons`. Changed the
spec to `${MEDIA_PATH:-/nonexistent}:/media:ro` so the CI syntax
check always sees a valid volume even with no env file. Real
install flow (form fill → .env with user path) is unchanged; a
broken install that reaches `docker compose up` with no
MEDIA_PATH now fails loudly on a nonexistent bind target instead
of silently mounting something random.
## [26.10-alpha] - 2026-04-21
### Added
- **Jellyfin** (v1.0.0, image `jellyfin/jellyfin:latest`). Media
server for movies, shows, music on the LAN. First app to use the new
`path`-type setting (core ≥ 26.10-alpha): the user picks their
existing media folder in the install form (`MEDIA_PATH=/mnt/media`),
the installer validates the path server-side, and docker-compose
mounts it read-only at `/media`. Docker-managed volumes handle
`/config` and `/cache`. Admin account bootstraps from the first
browser visit to port 8096. Ports: 8096. No HW transcoding yet —
that's a later schema extension.
### Changed
- Vendored manifest schema (`scripts/vendor/furtka_manifest.py`)
caught up to core 26.10-alpha: adds the `open_url` field (missing
since 26.6-alpha cut) and the new `path` setting type. `validate-
catalog.py` now accepts path-type settings without changes.
## [26.9-alpha] - 2026-04-21
### Added
- **IT-Tools** (v1.0.0, image `corentinth/it-tools:latest`). A browser
toolbox: password/UUID/QR generators, hash and HMAC, Base64 / URL /
JWT decoders, JSON/YAML/SQL formatters, regex tester, cron parser,
subnet calculator, and the usual long tail. Runs fully client-side —
no state, no volumes, no settings. Serves on port 8080.
## [26.8-alpha] - 2026-04-21
### Changed
- **fileshare** bumped to v0.1.2: adds `open_url` (`smb://{host}/files`)
so the core UI can render an Open button that deep-links to the share.
- **Uptime Kuma** bumped to v1.0.1: adds `open_url`
(`http://{host}:3001/`) for the same UI Open button. No image or
compose changes.
## [26.7-alpha] - 2026-04-20
### Added
- **Uptime Kuma** (v1.0.0, image `louislam/uptime-kuma:1`). Self-hosted
status monitor — pings hosts, checks HTTPS endpoints, watches Docker
containers, and screams on Telegram/webhook when something breaks.
No manifest settings: the admin account is created on the first
browser visit to port 3001. Persists history + config via one
namespaced volume `furtka_uptime-kuma_data`.
## [26.6-alpha] - 2026-04-20
### Added
@ -20,5 +100,11 @@ 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.6-alpha...HEAD
[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
[26.9-alpha]: https://forgejo.sourcegate.online/daniel/furtka-apps/releases/tag/26.9-alpha
[26.8-alpha]: https://forgejo.sourcegate.online/daniel/furtka-apps/releases/tag/26.8-alpha
[26.7-alpha]: https://forgejo.sourcegate.online/daniel/furtka-apps/releases/tag/26.7-alpha
[26.6-alpha]: https://forgejo.sourcegate.online/daniel/furtka-apps/releases/tag/26.6-alpha

View file

@ -47,10 +47,42 @@ Rules enforced by `furtka/manifest.py`:
- `volumes` — short names, strings. Namespaced to `furtka_<app>_<short>` at runtime.
- `ports` — integers. Informational only; compose owns the actual port binding.
- `settings[].name` — must match `^[A-Z_][A-Z0-9_]*$`. This name becomes both the env-var key and the form-field ID.
- `settings[].type` — one of `text`, `password`, `number`.
- `settings[].type` — one of `text`, `password`, `number`, `path`.
- `settings[].required` — if true, the install refuses when the value is empty.
- `settings[].default` — optional string. Used to pre-fill the form and the bootstrapped `.env`.
### Path-type settings (host bind mounts)
Use `"type": "path"` when the app should point at an existing folder on the host — media libraries, document archives, photo backups. The value is written to `.env` like any other setting, and compose consumes it via `${VAR}` substitution as a bind mount.
```json
{
"name": "MEDIA_PATH",
"label": "Medienordner",
"description": "Absoluter Pfad zu deinem Medien-Ordner, z.B. /mnt/media.",
"type": "path",
"required": true
}
```
```yaml
services:
app:
volumes:
- ${MEDIA_PATH}:/media:ro
```
The installer (`install_from` and `update_env`) refuses values that:
- aren't absolute (must start with `/`),
- don't exist on the host,
- aren't directories,
- resolve (after `Path.resolve()`) into a system-path deny-list: `/`, `/etc`, `/root`, `/boot`, `/proc`, `/sys`, `/dev`, `/bin`, `/sbin`, `/usr/bin`, `/usr/sbin`, `/var/lib/furtka`.
Traversal like `/mnt/../etc` is caught too — the deny-list check runs on the resolved path.
Path settings sit alongside manifest-declared volumes. Use `manifest.volumes` for internal state the app owns (databases, caches, config), and path settings for user data the container should mount and — usually — read without owning. Mounting read-only (`:ro`) is a good default for data the app only consumes.
## `docker-compose.yaml`
- File extension is `.yaml`. The compose runner hardcodes this — `.yml` will not be found.

View file

@ -1,12 +1,13 @@
{
"name": "fileshare",
"display_name": "Network Files",
"version": "0.1.1",
"version": "0.1.2",
"description": "SMB share for Mac, Windows, Linux and Android devices on the LAN.",
"description_long": "Alle Geräte im WLAN sehen einen gemeinsamen Ordner. Funktioniert mit Windows, Mac, Linux und Android. Verbinden zu smb://furtka.local — Anmeldung mit dem hier gesetzten Benutzernamen und Passwort.",
"volumes": ["files"],
"ports": [445, 139],
"icon": "icon.svg",
"open_url": "smb://{host}/files",
"settings": [
{
"name": "SMB_USER",

View file

@ -0,0 +1,38 @@
# Furtka Home Assistant — smart-home hub.
#
# Bridge networking + explicit `8123:8123` port mapping. Upstream's own
# docs lean toward `network_mode: host` because that's what mDNS-based
# discovery, Bluetooth, HomeKit, and Zigbee/Z-Wave dongles need. We're
# deliberately starting in bridge mode for v1: it keeps the catalog's
# network model consistent (every other app is bridged), and Cloud-only
# integrations (Hue Cloud, Tado, Sonos via account, etc.) work fine.
# Host networking + USB passthrough is a follow-up once the manifest
# schema grows a `network_mode` knob.
#
# Image pin: `homeassistant/home-assistant:stable` is upstream's
# recommended tag for production — release-train, not bleeding edge.
# Same image as the Docker Hub link Robert pointed at in the new-app
# request (#1).
#
# No HEALTHCHECK override needed: the upstream image's healthcheck is
# tame, and a temporarily-yellow status during the first-boot DB
# migrations is expected behaviour.
#
# No manifest.settings — Home Assistant's onboarding (admin user, home
# location, units) happens entirely in the browser on first visit, just
# like Jellyfin and Uptime Kuma. Nothing for the install form to ask.
services:
homeassistant:
image: homeassistant/home-assistant:stable
restart: unless-stopped
ports:
- "8123:8123"
environment:
- TZ=Europe/Berlin
volumes:
- furtka_home-assistant_config:/config
volumes:
furtka_home-assistant_config:
external: true

View file

@ -0,0 +1,6 @@
<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="M10 30 L32 10 L54 30 V52 H10 Z"/>
<circle cx="32" cy="36" r="5"/>
<path d="M32 10 V4 M22 22 L18 18 M42 22 L46 18"/>
<path d="M22 44 H42"/>
</svg>

After

Width:  |  Height:  |  Size: 341 B

View file

@ -0,0 +1,11 @@
{
"name": "home-assistant",
"display_name": "Home Assistant",
"version": "1.0.0",
"description": "Smart-home hub for lights, sensors, and locally controlled devices.",
"description_long": "Steuere Lampen, Heizung, Sensoren und Smart-Home-Geräte von einem zentralen Dashboard aus. Beim ersten Aufruf im Browser unter http://furtka.local:8123 wird das Admin-Konto angelegt und die Wohnung eingerichtet. Zigbee-/Z-Wave-USB-Sticks und automatische Geräteerkennung über mDNS/Bluetooth sind in dieser ersten Version noch nicht verdrahtet — die App läuft im Bridge-Modus auf Port 8123. Cloud-Integrationen (Hue Cloud, Tado, Sonos via Account) funktionieren ohne Einschränkung.",
"volumes": ["config"],
"ports": [8123],
"icon": "icon.svg",
"open_url": "http://{host}:8123/"
}

View file

@ -0,0 +1,20 @@
# Furtka IT-Tools — browser-side utility toolbox.
#
# IT-Tools is a purely static single-page app: the container serves
# prebuilt HTML/JS and does all work client-side. No state, no volumes,
# no env knobs — that's why this app has no manifest.settings and no
# .env.example.
#
# TODO(image-pin): `:latest` is shaky for production — pin to a digest
# (`corentinth/it-tools@sha256:...`) or a stable tag once we've verified
# one against the upstream registry. For the MVP run we accept the drift
# risk to keep the install reproducible against whatever the upstream
# image happens to be on test day; revisit before any non-developer
# touches this.
services:
it-tools:
image: corentinth/it-tools:latest
restart: unless-stopped
ports:
- "8080:80"

5
apps/it-tools/icon.svg Normal file
View file

@ -0,0 +1,5 @@
<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">
<circle cx="32" cy="32" r="26" opacity="0.28"/>
<path d="M42 14 L50 22 L44 28 L38 22 Z M40 26 L22 44 L18 48 L14 44 L18 40 L36 22"/>
<path d="M16 50 L20 46"/>
</svg>

After

Width:  |  Height:  |  Size: 349 B

View file

@ -0,0 +1,11 @@
{
"name": "it-tools",
"display_name": "IT-Tools",
"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],
"icon": "icon.svg",
"open_url": "http://{host}:8080/"
}

View file

@ -0,0 +1 @@
MEDIA_PATH=

View file

@ -0,0 +1,47 @@
# Furtka Jellyfin — media server.
#
# Two Docker-managed volumes (config, cache) for app state + one
# user-supplied host path (MEDIA_PATH) mounted read-only for the media
# library. Admin account bootstraps from the first browser visit to
# :8096 — that's why this app has no manifest.settings for admin creds.
#
# MEDIA_PATH is a `path`-type setting (furtka/manifest.py ≥ 26.10-alpha
# schema). The install form asks for it, the installer validates that
# the directory exists and isn't a system path, and docker-compose
# substitutes the value below at `docker compose up` time.
#
# The `${MEDIA_PATH:-/nonexistent}` default-substitution keeps
# `validate-catalog.py` (which runs `docker compose config` without any
# .env) from failing on the empty-string case: an empty MEDIA_PATH
# would expand to `:/media:ro` which compose rejects as "empty section
# between colons". /nonexistent is an obviously-wrong fallback so if it
# ever actually reaches `docker compose up` (which requires a broken
# install flow), the mount fails loudly instead of silently mounting
# something random.
#
# TODO(image-pin): `:latest` is shaky for production — pin to a digest
# (`jellyfin/jellyfin@sha256:...`) or a stable tag once we've verified
# one against the upstream registry. MVP drift risk accepted.
#
# No HW transcoding yet — /dev/dri passthrough is a separate, later
# schema extension. 1080p software transcode + Direct Play over LAN
# are fine for the Medion-Haswell target.
services:
jellyfin:
image: jellyfin/jellyfin:latest
restart: unless-stopped
ports:
- "8096:8096"
environment:
- TZ=Europe/Berlin
volumes:
- furtka_jellyfin_config:/config
- furtka_jellyfin_cache:/cache
- ${MEDIA_PATH:-/nonexistent}:/media:ro
volumes:
furtka_jellyfin_config:
external: true
furtka_jellyfin_cache:
external: true

5
apps/jellyfin/icon.svg Normal file
View file

@ -0,0 +1,5 @@
<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">
<circle cx="32" cy="32" r="26" opacity="0.28"/>
<path d="M12 18 H52 V46 H12 Z M12 26 H52 M18 22 L18 22 M24 22 L24 22 M30 22 L30 22"/>
<path d="M28 32 L28 40 L38 36 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 360 B

View file

@ -0,0 +1,20 @@
{
"name": "jellyfin",
"display_name": "Jellyfin",
"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],
"icon": "icon.svg",
"open_url": "http://{host}:8096/",
"settings": [
{
"name": "MEDIA_PATH",
"label": "Medienordner",
"description": "Absoluter Pfad zu deinem Filme-/Serien-Ordner auf dieser Maschine, z.B. /mnt/media. Wird nur gelesen.",
"type": "path",
"required": true
}
]
}

View file

@ -0,0 +1,29 @@
# Furtka Uptime Kuma — self-hosted status monitor.
#
# The volume `furtka_uptime-kuma_data` is created by the Furtka reconciler
# from the manifest's "volumes" list before this compose file is brought
# up; it's declared `external: true` here so docker compose doesn't try
# to manage its lifecycle.
#
# Image pin: `louislam/uptime-kuma:1` tracks the 1.x major and rolls new
# patch/minor releases in place. Good enough for now — revisit to a
# digest once the Furtka app-update UI reports a diff so users can see
# what moved.
#
# No admin bootstrap via env vars: Kuma creates its admin account from
# the first browser visit to :3001. That's a deliberate Kuma design
# choice, and it's why this app has no manifest.settings — nothing for
# the Furtka install form to ask.
services:
kuma:
image: louislam/uptime-kuma:1
restart: unless-stopped
ports:
- "3001:3001"
volumes:
- furtka_uptime-kuma_data:/app/data
volumes:
furtka_uptime-kuma_data:
external: true

View file

@ -0,0 +1,4 @@
<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">
<circle cx="32" cy="32" r="26" opacity="0.28"/>
<path d="M8 32 H20 L24 22 L30 46 L36 16 L42 42 L46 32 H56"/>
</svg>

After

Width:  |  Height:  |  Size: 298 B

View file

@ -0,0 +1,11 @@
{
"name": "uptime-kuma",
"display_name": "Uptime Kuma",
"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],
"icon": "icon.svg",
"open_url": "http://{host}:3001/"
}

View file

@ -13,7 +13,7 @@ REQUIRED_FIELDS = (
"icon",
)
VALID_SETTING_TYPES = frozenset({"text", "password", "number"})
VALID_SETTING_TYPES = frozenset({"text", "password", "number", "path"})
SETTING_NAME_RE = re.compile(r"^[A-Z_][A-Z0-9_]*$")
@ -42,6 +42,12 @@ class Manifest:
icon: str
description_long: str = ""
settings: tuple[Setting, ...] = field(default_factory=tuple)
# Optional "Open" link for the landing page + installed-app row.
# `{host}` is substituted with the current browser hostname at render
# time so the URL follows whatever the user typed to reach Furtka —
# furtka.local, a raw IP, a future reverse-proxy hostname. Apps with
# no frontend (CLI-only, background workers) leave this empty.
open_url: str = ""
def volume_name(self, short: str) -> str:
# Namespace volume names so two apps can each declare e.g. "data"
@ -127,6 +133,10 @@ def load_manifest(path: Path, expected_name: str | None = None) -> Manifest:
settings = _parse_settings(raw.get("settings"), path)
open_url_raw = raw.get("open_url", "")
if not isinstance(open_url_raw, str):
raise ManifestError(f"{path}: open_url must be a string if set")
return Manifest(
name=name,
display_name=str(raw["display_name"]),
@ -137,4 +147,5 @@ def load_manifest(path: Path, expected_name: str | None = None) -> Manifest:
icon=str(raw["icon"]),
description_long=str(raw.get("description_long", "")),
settings=settings,
open_url=open_url_raw,
)