Skip to content

Releases: hf1985/husk

Husk 1.2

Choose a tag to compare

@hf1985 hf1985 released this 19 Sep 21:37

Bugfix. Switching the camera side over HTTP (/set?front=0|1) started an extra background check
loop each time instead of reusing the one already running, so repeated switches left several
one-second loops going at once. They are now collapsed into one.

Nothing else changed: no new permissions, no new endpoints, and the HTTP interface is the same
as in 1.1.

Husk 1.1 (versionCode 52)

Choose a tag to compare

@hf1985 hf1985 released this 18 Sep 22:10

Husk 1.1 (versionCode 52) lukker F-Droids review-rapport af 15-09-2026 og lægger den ene
Android-ændring PC-viewer'en skal bruge for at kunne blive et produkt.

Den indbyggede updater er fjernet

En app der henter og installerer sine egne opdateringer omgår butikkens signering og review, og
optages ikke i F-Droids hovedrepo. Updater og InstallReceiver er slettet, tilladelsen
REQUEST_INSTALL_PACKAGES er væk af manifestet, /update findes ikke længere, og feltet
lastUpdate er ude af både /flags og /info. A11y-motorens auto-tap af systemets
install-dialog er fjernet med resten.

Opdatér fra F-Droid, eller installér en ny APK med adb install -r. Appen tjekker, henter og
installerer aldrig noget af sig selv. Opgraderingen 1.0 → 1.1 er den sidste der kan ske i appen.

Frontkamera kan vælges over HTTP

/set?front=0|1 vælger kameraside, og /flags bærer det valgte i feltet front. Et ændret valg
genåbner capture-sessionen ved næste efterspørgsel, samme værdi er en no-op der ikke afbryder en
kørende stream, en ugyldig værdi giver 400 uden tilstandsændring, og findes den ønskede kameraside
ikke på enheden, svares 409 frem for et tavst fallback til den anden side.

Før 1.1 kunne forsidekameraet kun vælges med en Android-intent over adb. Nu kan en PC-klient gøre
det over netværket.

Andet

  • Den prebuilte husk-latest.apk er fjernet af kildetræet; APK'en ligger kun i udgivelsen.
  • Butiksteksten har nu en vært-for-vært-liste over hvad appen kan tale med, og hvad der udløser
    hver forbindelse.
  • /motions server-parameter blev læst med en test der altid var sand
    (dparam(...) != null, hvor dparam giver "" og aldrig null). Rettet til param(...).
  • /flagss camera-felt er dokumenteret som det det er: faktisk capture-aktivitet, ikke en
    diagnose. Kameraet er dovent og lukkes få sekunder efter sidste forbruger.

Verifikation

Bygget fra præcis dette commit på HFs_Dell 2026-09-19 og signeret med husets release-nøgle
(CN=xplat, O=xplat, C=DK, SHA-256 96195cfd540e75f8a34dfc08764438769d4bc3e5d7970a9527d97004d4f2c17d).

Prøvet live på en Samsung SM-A102U1 (Android 11) over Tailscale, både som opgradering fra 1.0
uden tab af konfiguration og som frisk installation: /set?front=1/flags.front sand →
/snapshot leverer et JPEG fra forsidekameraet, målt reproducerbart forskelligt fra bagkameraet.

SHA-256 (husk-v1.1.apk)  7682601edf8afd96781cc0c8b985d9bfc093368a114ad6cbb1a7777f93239f60

Husk 1.0

Choose a tag to compare

@hf1985 hf1985 released this 07 Sep 16:46

Husk 1.0 (versionCode 51).

The update button now names its source, in the app

F-Droid review !40810 asked that the built-in updater make clear, in the UI, that an update
taken through it comes directly from the developer rather than from F-Droid. It now says so
in two places: a permanent note under the button, and a confirmation dialog naming the source
before anything is downloaded.

The dialog is on the button only. GET /update is unchanged, so a headless phone on your own
private network can still be updated with nobody at the screen; that is what the endpoint is
for. Nothing checks for updates on its own: there is no background check and no scheduled
update, and the only two entry points are the button and that endpoint.

To be precise about what the updater fetches: it reads the version number from
xplat.co/husk/latest.json (falling back to this repository's copy) and downloads the APK
from the address that file names, today husk-latest.apk on the main branch of this
repository. That is not the release asset below, which is what F-Droid's Binaries:
reproducibility check uses.

Fixed: a carrier address was labelled as a Tailscale address

Net.tailscaleIp() returned the first 100.64.0.0/10 address on any interface. That block is
RFC 6598 CGNAT and mobile carriers hand it out to ordinary subscribers, so on mobile data the
carrier's address was displayed as "Tailscale IP" in /info and on the main screen. The
F-Droid tester measured this on a Galaxy S9 with Tailscale not installed at all.

An address now counts as Tailscale's only when the same NetworkInterface also carries an
address in fd7a:115c:a1e0::/48. Measured before release on an Android 12 device: the
Tailscale tun0 carries both the 100.x/32 address and the fd7a: one, so the label stays
correct on a real tailnet, while a carrier address (on rmnet0, no fd7a:) no longer
qualifies. This deliberately fails to the cautious side: a self-hosted Headscale using a
different ULA prefix will read as "no Tailscale". localIp() is unchanged; it already
excluded the CGNAT block.

Server responses are no longer Danish

The HTTP API is documented in English but returned some Danish error strings, and the two
built-in browser control pages (/control, /controlhw) had Danish buttons and status text.
Both are now English. The motion push and the two notification channel names were also
hardcoded Danish regardless of device language; they are string resources now, so English is
the default and Danish follows the device.

Corrected comments (no behaviour change)

ControlServer and Net claimed the server "binds only to loopback + the Tailscale IP, never
0.0.0.0". It has bound 0.0.0.0 since 0.9.x; protection is the source-IP allowlist plus an
optional token. Updater named the wrong GitHub host for the APK. Only the comments changed.

Not in this release: making the token mandatory and narrowing peerAllowed(). Turning the
token on by default breaks every existing deployment until it is re-paired, which is a fleet
migration rather than a patch.


husk-v1.0.apk is signed with the release key CN=xplat, SHA-256
96195cfd540e75f8a34dfc08764438769d4bc3e5d7970a9527d97004d4f2c17d, matching
AllowedAPKSigningKeys in the F-Droid recipe.

Husk 0.9.31

Choose a tag to compare

@hf1985 hf1985 released this 03 Sep 20:32

0.9.31 (versionCode 50) - a motion-alert fix, a new release signing key, and a reproducible build against F-Droid.

Fixed: motion alerts could stop silently

Calling /motion without a topic parameter cleared the saved ntfy topic and persisted the empty value, so push alerts stopped until the topic was set again. The guard tested dparam(query, "topic") != null, but dparam returns "" rather than null when the parameter is absent, so it was always true. Even a bare GET /motion wiped the topic.

(Corrected 2026-09-06: this release note previously said "No behaviour change in the app itself." That was wrong - the fix above is in v0.9.31 and not in v0.9.30.)

The signing key has changed

Husk is being submitted to F-Droid, which pins the signing certificate permanently through AllowedAPKSigningKeys. The old certificate was a reused debug keystore, so it had to be replaced before publication rather than after.

New certificate: RSA 4096, CN=xplat, O=xplat, C=DK, SHA-256 96195cfd540e75f8a34dfc08764438769d4bc3e5d7970a9527d97004d4f2c17d.

This release cannot be installed as an update. Android refuses an update whose signature differs from the installed one, so the in-app updater will fail with "app not installed". Uninstall the old version and install this one. Uninstalling clears the app's settings, so note down your token, ntfy topic and motion settings first.

Reproducible build

vcsInfo { include false } on the release build type. AGP 8.5.2 was writing META-INF/version-control-info.textproto into the APK, and its contents depend on whether the build ran inside a git checkout. That single file was the only difference between this project's release APK and a build made by F-Droid's builder; everything else was already bit-identical. The file is now gone entirely.

Husk 0.9.30

Choose a tag to compare

@hf1985 hf1985 released this 03 Sep 17:57

0.9.30 (versionCode 49) - ships the token gate for the streaming endpoints.

  • /stream, /screen and /screen.mp4 are now behind the same token gate as every other endpoint in dispatch(). Before this, a configured token could be bypassed on exactly the most sensitive surfaces (continuous live camera and screen). tokenOk() still returns true when no token is configured, so token-less devices are unaffected.

Signed with the canonical key (SHA-256 1b89a920...62af59).

Husk 0.9.29

Choose a tag to compare

@hf1985 hf1985 released this 12 Jul 21:34

0.9.29 (versionCode 48) - audit round 2 (via the full Note10 rig).

Decision log: docs/AUDIT-2026-07-12-runde2.md.

  • Fixed a HIGH regression a self-review of the 0.9.28 diff caught: acceptInstallConsent read a stale Rig.lastUpdate, so a repeat /update after a prior "latest" run bailed instantly and unattended self-update stalled. Fixed with a synchronous "checking" reset + a sawProgress gate.
  • 3 low-severity hardening fixes: /vibrate?ms= clamped to 10s; sensor floats finite-guarded (NaN/Infinity would break strict JSON parsers); InstallReceiver now surfaces install failures to /flags.
  • The rest of 0.9.28 verified correct; Fmp4Muxer math + Hardware confirmed clean.

Signed with the canonical key (SHA-256 1b89a920...62af59).

Husk 0.9.28

Choose a tag to compare

@hf1985 hf1985 released this 12 Jul 20:42

0.9.28 (versionCode 47) - security + correctness + performance audit.

Full audit (3 parallel review agents + manual verification) of all 18 sources. Decision log: docs/AUDIT-2026-07-12.md. No performance invariant weakened; several strengthened.

  • Security: CSRF + DNS-rebinding defense in ControlServer (Host/Origin/Sec-Fetch-Site); ntfy https-only (SSRF); Updater https-per-hop + redirect cap; request-line/header caps (DoS); nosniff.
  • Correctness: camera-permanent-death fix; H.264 main-thread ANR fix; PackageInstaller session-leak fix; Android-14-safe specialUse->camera FGS self-heal (unchanged on <=A13); accept-loop backoff; several guards.
  • Performance: removed 16fps motion-on-screen JPEG waste (strengthens the lazy invariant); reuse per-frame Bitmap/BAOS; narrow the a11y cache-widen to node-read ops only.

APK signed with the canonical key (SHA-256 1b89a920...62af59).

Husk 0.9.27

Choose a tag to compare

@hf1985 hf1985 released this 12 Jul 18:36

0.9.27 (versionCode 46)

In-app self-update now clears Google Play Protect's "App scan recommended" gate on fresh sideloads automatically: it expands "More details" and taps "Install without scanning" (anchored text matches so "Don't install app" / "Scan app" are never hit). This makes headless self-update on a fleet fully unattended. J4 self-heal (MY_PACKAGE_REPLACED) unchanged.

APK signed with the canonical key (SHA-256 1b89a920…62af59).

Husk 0.9.25

Choose a tag to compare

@hf1985 hf1985 released this 02 Jul 18:50

J4: selv-opdatering rejser 8090/ControlServer igen automatisk.

BootReceiver håndterer nu ACTION_MY_PACKAGE_REPLACED (på Androids FGS-start-undtagelsesliste, samme klasse som BOOT_COMPLETED; leveres kun til den netop-opdaterede app, uden <data>-URI). En in-app-opdatering dræber app-processen; på Android 12+ blokerede baggrunds-FGS-restriktionen genstarten fra InstallReceiver, så ControlServeren (8090) lå nede indtil et boot eller en manuel QUICKBOOT-nudge. Nu rejser 8090 sig selv efter enhver selv-opdatering – ingen reboot nødvendig.

  • versionCode 44, versionName 0.9.25
  • Signeret med den kanoniske nøgle (in-app-opdatering fra 0.9.24 accepteres)
  • Bemærk: gevinsten er fremadrettet – enheder skal først opdateres TIL 0.9.25; opdateringen FRA 0.9.24 bruger den gamle recovery én sidste gang.

Husk 0.9.24

Choose a tag to compare

@hf1985 hf1985 released this 02 Jul 12:20

Persistent delt token via Settings.Global husk_token (saettes med adb, overlever reboot, ingen UI-launch). versionCode 43.