Skip to content

← Firmware Downloads

Post-install setup · Reborn (Y2Linux) · Y2

Error reports: opt-in reports from players to the maintainer

Debian-based Linux with a fast clickwheel interface for the Innioasis Y2.

Instructions published by the project, shown here word for word.

Error reports: opt-in reports from players to the maintainer

Status 2026-10-19: **Beta 8.10 (Baseline 105) ships opt-in uploads (R7 done); before it Beta 8.8 shipped Save Report. Earlier status follows.** Beta 8.9 (built, not published) asked only where. R3 (Worker, fetch.py, deploy README, upload key) and R4 (uploader, periodic check) are done and tested on main, unreleased; the receiver is option 1 (Worker + R2, pulled home). Next: the owner deploys the Worker (tools/reports/README.md), then R6 with a test build.** See "R3 and R4 done" at the end. Owner: "an error logger/reporter for users that they can opt in. It should send me everything about the error and device, no personal information, over Wi-Fi HTTPS", then "go with the Cloudflare Worker … get as much information as possible … not only kernel level, also everything that happens in Reborn". Audit row ERROR-REPORTS in roadmap-gap-audit.

Decisions (owner, 2026-10-18)

TopicDecision
ReceiverCloudflare Worker + R2 at reports.schulzcode.dev (the domain is already on Cloudflare). The home server was checked and is well maintained, but it is home/Tailscale-only today, and opening Nginx Proxy Manager to the internet would expose every proxy host (none has an access list) and put the home IP into the firmware.
ContentScrubbed raw logs: everything the player records (kernel, platform, recovery/update, Reborn), hardware IDs and live state, with personal data replaced (below).
SendingAutomatic + manual: an error queues a report that uploads in the background over Wi-Fi; Settings has Send Report Now. Only after the user opted in.
LinkingRandom install ID, made on the player (never derived from a serial), shown in Settings and resettable.
AskingOne question after the update, then the switch in Settings (see Further decisions).

What a report contains

One gzip-compressed tar per report, report.json (index, identity, trigger, section sizes and scrub counts) plus one file per section. Budget 2 MiB compressed (8 MiB until the cost review of 2026-10-19; real reports ≈0.5 MB); a section that does not fit is cut from the oldest end and the cut is recorded.

Identity and trigger

Hardware

Kernel

Platform (Y2Linux services)

Reborn

What is never sent, and how

Two layers, both in one module (tools/platform/y2_platform/report.py, scrub()), applied to every byte of every section before it is written:

  1. Known secrets of this player, collected at report time and replaced wherever they appear: saved Wi-Fi

    network names and passphrases, the current BSSID, Bluetooth device names and addresses (paired and seen), the Last.fm user name and session key, the SSH key comment, the hostname, the player's own MAC and IP addresses. Exact-match is robust where a pattern is not (an SSID can be any text).

  2. Patterns: MAC addresses (keep the first three bytes, the maker; mask the rest), IPv4/IPv6 (keep the class:

    loopback, link-local, USB 10.42.0.x, private, public), serial-like fields (CID, serial, machine-id), e-mail addresses, URLs (keep the host, drop path and query), paths below /media (keep the extension: <media>/…/*.flac).

Replacements are stable within one report (a keyed hash: <ssid#3fa2c1>, 3C:22:FB:<mac#9b01e2>, with a random key per report that is never written), so a log still shows that the same network or device came back. report.json counts the replacements per class.

Not collected at all: library.db contents, scrobbles.jsonl, lastfm.json, Wi-Fi configuration, Bluetooth bond keys, SSH keys, /data/system/dbus/machine-id, entropy seed, artwork caches, any file below /media.

Tests: canary values (an SSID, a passphrase, a Bluetooth name, a Last.fm user, a MAC, an IP, a CID, a music path) are planted in every source of a synthetic player tree, a report is built, and none may appear in it. The same runs against a copy of real logs from the owner's player kept outside Git.

When a report is made

Only with Error Reports on. At most one report per trigger class per boot, at most five a day; the queue holds three (oldest dropped).

TriggerDetected by
Kernel crash on the previous bootpstore record at boot
Kernel warning / tainttaint flags or warning count rising
Unclean restart, watchdog resetprevious-boot-evidence.json (no orderly shutdown, reset cause)
Reborn crash or restart by its supervisora new panic-last.json, supervisor restart count
A required Reborn worker or subsystem failedReborn health
Update failed or rolled back, recovery entered/data/updates/state.json, recovery-last.json
Sleep failure (a failed resume, tasks that would not freeze, a driver error other than -EBUSY; not a sleep a wake event cancelled)/sys/power/suspend_stats
Radio failure (the connectivity chip reports an error)connectivity status error= (not wifi-failure.json: it is rewritten on every connection and names the network's answers)
Dark display (DSI register lag, no frame)kernel log lines of the display driver
Storage error (ext4 errors, eMMC I/O errors, read-only remount; not a pulled SD card)ext4 counters, kernel log
ManualSend Report Now and Save Report, no topic (since 8.10 and 8.9: every report holds everything; --topic stays optional on the command line)

A trigger collects at once (the logs are fresh) and queues the report; uploading waits for Wi-Fi and runs at low priority (the platform's NetworkTransfer workload class), never during an update and never below 15 % battery unless charging.

Transport

The Worker

Player settings and screens

Settings → About & Updates → Error Reports: the switch (off by default), What is sent (a short list and a link to the privacy note), the install ID with New ID, Send Report Now, and the last report's state (queued, sent with its ID, failed with the reason). Reborn talks to the platform through its existing client (client::Reply), like updates. State: /data/system/platform/reports.json (switch, install ID, counters), queue in /data/system/platform/reports/.

User documentation: docs/ERROR-REPORTS.md (what is sent, what never is, how to see and delete one's reports by install ID: a request on GitHub with the ID).

Steps

StepContentValidation
R1This plan, audit row—
R2report.py: sources, sections, scrubber, budget, triggers, queue (platform)Host tests with canaries, size bounds, every trigger, on a synthetic tree and on a private copy of real logs
R3Worker + fetch.pynode --test; owner deploys to reports.schulzcode.dev
R4Uploader and platform commands (`y2-platform report statussend
R5Reborn screens and client callsReborn tests, preview screenshots
R6Device test with a test build (owner's OK): manual report, a provoked Reborn crash, an update failure from the test bench, upload to the deployed Worker, owner downloadsDevice
R7Release, docs/ERROR-REPORTS.md, readmeRelease checks

Further decisions (owner, 2026-10-18: "yes to all")

R2 results (2026-10-18)

Test build 96 on the owner's player (2026-10-18)

1.0.0-beta.8.8, sequence 6950, Linux acec57c, Reborn ef9e805 (identity only); container suite 786 (the new tests run there too). Installed by stage + apply, ≈16 s, Acknowledged 6950. y2-platform report status → enabled: null; enable made an install ID and took the baseline (check then reported nothing, 0.7 s); send --topic other built and queued a manual report in 37 s, 409 KB, 163 sections, all 13 live commands OK; the leak search on it found none of the owner's secrets. Afterwards reports.json and the queue were removed (state back to "not asked") and /data/ota-96 deleted. Not released.

R5 screens, test builds 97–98 (2026-10-18)

Reborn 8ba33b3 (screens) and fbecb77 (question timing): About & Updates > Error Reports (switch, What Is Sent, Send Report Now with seven topics, install ID with New ID, last report state), the one-time question ("Help Improve Y2Linux?" No Thanks / Send Reports, both recorded), previews 33b–33g; 542 Reborn tests, strict Clippy. Test build 97 (sequence 6951): the question only came when a platform screen (Settings) opened, and the test's own scripted presses answered it (reset afterwards). Fixed in test build 98 (sequence 6952, Linux d9b19cb): one platform observation 20 s after start on any screen, and the question waits for a lit screen, no other dialog and 5 s without a button press. On the owner's player the question appeared after the restart without opening Settings; the owner answered Send Reports (21:47:25 UTC) and opened the page: install ID shown, nothing queued. Not published. Known flaky, unrelated: Reborn lastfm::tests::requests_carry_secrets_only_on_stdin_and_errors_classify failed once under the parallel workspace run, passes alone and on rerun.

R3 status and the open decision (2026-10-18, end of day)

Drafted, not tested, not deployed: tools/reports/worker/src/index.js (POST /v1/report only; upload key in an equal-time compare; schema, UUID install/report IDs, SHA-256 and trigger checked; ≤ 8 MiB read with a hard limit; gzip magic; body hash; KV limits 10 a day per install ID and 30 per salted daily address hash; duplicate report ID = 200; R2 key reports/YYYY-MM-DD/<install>/<report>.tar.gz with metadata; no read route; no IP stored) and tools/reports/worker/wrangler.toml (route reports.schulzcode.dev, R2 binding REPORTS, KV RATES, secrets UPLOAD_KEY and RATE_SALT).

Still to write in R3: node --test tests for the Worker (fake R2/KV), tools/reports/fetch.py (stdlib S3 SigV4 for R2: list and download new reports into out/reports/ or onto the home server; test with the AWS SigV4 example vector), tools/reports/README.md (deploy: bucket, KV namespace, secrets, custom domain, 180-day lifecycle rule), the build-wide upload key.

Owner question, open: "can this worker live on my server?" A Worker always runs on Cloudflare. Options given:

  1. Worker + R2, reports pulled to the home server by a script/n8n flow (recommended: only outgoing connections at

    home, no home IP in the firmware, no disk filling from fake uploads; the drafted code fits).

  2. The same receiver as a small container on the home server behind a Cloudflare Tunnel publishing only

    reports.schulzcode.dev (no port forward, NPM not involved; uploads land at home, the server must be online, one more internet-facing service).

  3. Port forward (advised against: every NPM proxy host would be exposed, home IP in the firmware).

The validation and limits are the same in 1 and 2; only the storage differs. Decide before finishing R3.

Save to storage and speed, Beta 8.8 (2026-10-19)

Owner: "publish the next beta with what we have currently and export to storage feature either internal or sd card, we will do the worker afterwards", then "40-50 seconds is too long".

R3 and R4 done (2026-10-19, main, unreleased)

Owner: "option 1, do all you can do also check the plan and code again with the changes we made".

Deployed and cost review (2026-10-19)

The owner deployed the Worker (R2 bucket y2linux-reports in EEUR, Standard storage, private: no r2.dev URL, no custom domain), set the secrets and made a read-only R2 token for fetch.py. Checked from the PC: with the key and a broken body 400 not_gzip, a wrong key 403, GET 405, other paths 404; the token lists the bucket and a DELETE is refused (403). Found missing: the 180-day lifecycle rule (only Cloudflare's default multipart rule).

Owner: "check if its moste secure not only the code but also rate limiting, so that i dont rack up cost in any case? i have the free tier". Workers and Durable Objects on the Free plan fail past their limits; R2 bills past 10 GB-month and 1 M class A operations. The upload key is public (in every image), so an attacker with fresh install IDs and addresses was bounded only by the KV free tier (1 000 writes a day ≈ 333 reports × 8 MiB ≈ 2.6 GB a day; KV counters are also not atomic and read stale for up to a minute, so concurrent uploads could overshoot), and 8 MiB risked the Free plan's 10 ms CPU per request for the hash. Changes:

No topic, live progress, test build 103 (2026-10-19)

Owner: "i also dont wnat to choose a topic for the automatic upload, it should just do it. also i want like the update player page to see the progress of creatign the report and uploading it / saving ti to dc or internal".

Test build 104 (1.0.0-beta.8.10, sequence 7300, Linux 7846476, Reborn cfac12b, 825 container tests): no topic, live progress, the narrower display trigger. Installed (Acknowledged 7300). Send Report Now: made 15:13:36, in R2 15:13:48 (≈12 s, 523 KB, 166 sections, topic: null); the progress record removed afterwards. Leak search on the player for both received reports: 0 of 203 private values. The first automatic report (display_failure, 14:55, from build 103) was the boot-time DSI read lag after the restart through the recovery (the driver worked around it, the panel lit): the trigger now needs Reborn's first frame to be missing; the lag itself after a recovery restart is a finding for the display work. Afterwards: Send Report Now goes first and, once it is stored, takes the waiting reports along regardless of their retry time (the build-103 report waited 15 min in backoff behind it); the progress follows the new report only.

R7: released in Beta 8.10 (2026-10-19)

Owner: "publish without it and answer the issue without it" (without a wall-charger test on build 105). Build 105 = baseline-105, sequence 7400, upload key included: players are asked once after updating. User guide section "Send reports over Wi-Fi". Still owed: Cloudflare WAF rate-limit rule and R2 billing alert (owner), the progress page checked by eye, the question on a player that never answered.

Published by the schulzcode/y2linux project [1] [3]