Repository navigation
Conversation
chore(submodule): bump frontend to 748ce1e
chore(submodule): bump backend to 7f5aa0d and frontend to 1eb9e1d (NEH-71)
NEH-230: drag-to-pan and 1:1 in the review views, filename labels below the page (frontend #91).
chore(submodule): bump frontend to b5364aa (NEH-230)
chore(submodule): bump backend to 8ccc3b5 and frontend to bad7c51 (NEH-222)
NEH-162: the email is optional on account creation. NEH-166: the dashboard's camera test takes a real still, returned inline and never stored.
chore(submodule): bump backend to f3f42f8 and frontend to c70cde1
NEH-228: the capture key is learned from the pedal rather than configured.
chore(submodule): bump frontend to cbd3ff1
The page walked an operator all the way through preparing cards and connecting both bodies without ever saying that the capture itself has never been run against these cameras. The backend captures over the wire and neither it nor pychdk v0.1.2 falls back to the card, so if the A2500's alpha CHDK port refuses remote capture the whole capture fails - and the page as written would have had them find that out mid-session.
The page said a shared parity makes the appliance lay every body out in bus order, so a healthy odd-shooting body could sit at index 0. That was true of the backend as written and it was the bug: the healthy body carried no refusal and its odd pages were filed as even ones. The backend now keeps a claimed parity on its own index and moves only the cameras actually sharing one, which is what this now describes.
Both were mine. A capture that fails evicts the camera, so saying it still enumerates and previews reassures an operator about the one thing that failure takes away - and would have them read a vanished camera as an unrelated fault. And only an assigned parity holds an index: a camera with no parity yet can be pushed to another index when a third appears, so 'never moved by one' was the inference and not the guarantee.
…parity The sentence I wrote to replace one overclaim was itself unconditional: a camera with EVEN on its card does move when a second EVEN arrives ahead of it on the bus, which is what the rest of the same paragraph goes on to describe. The opening now carries the condition the paragraph was already explaining.
feat(setup): CHDK camera provisioning and the operator guide (NEH-231)
Brings in the CHDK backend for NEH-231: enumeration over PTP/USB, live view decoding, remote capture straight down the wire, and the page-parity layout. 471 tests from a 306 baseline. Acceptance is the bench - no part of this has been run against a camera.
Backend picks up the pychdk v0.1.3 pin (#99) on top of NEH-255. Frontend advances ten commits to its dev tip, which the superproject has not tracked since cbd3ff1.
chore(submodule): bump backend to cb578e5 and frontend to 9178252
rollback-update.sh returned the git checkout and the database and then said "The appliance is back on the previous version". That was false. update.sh had already rebuilt the frontend image from the new code and resolved the pixi environment against the new lock, and rollback rebuilds neither, so the running system was a hybrid: old code, new environment, new frontend image. Rebuilding either one needs the registries, and rollback is exactly when a remote unit is already in trouble. So this does not attempt the repair; it stops claiming it happened. The summary now reports per component - database, code, pixi environment, frontend image - whether each corresponds to the version now checked out, and names the command that would fix the ones that do not. Getting that right needed two facts rollback could not previously know, both recorded by update.sh: The digest of the pixi lock the update installed against. Reported as its own state rather than as an environment verdict, because start.sh runs `pixi run start`, which installs when required, so by the time the summary prints, the restart may already have reinstalled against the restored lock. The script says it does not check which. The image of the running frontend container, recorded beside last-deployed-SHA on a SUCCESSFUL update. Not the tag, which moves before anything stops, and not "whatever was serving before this attempt": a failed update leaves the new image serving while last-deployed-SHA still names the old commit, so a retry would otherwise record an image that never belonged to the commit being restored. Every path that cannot establish correspondence says UNKNOWN and still names the remedy, rather than reading as benign. That covers a missing or unreadable record, one already pruned by the retention policy, a record truncated mid-write (a short image id compared against a full one would read as a definite mismatch), a dump chosen from outside the backup directory, a symlink standing in for either the record or the dump, and a code rollback that was skipped because the commit was unavailable. Records are tied to their attempt rather than to a commit. last-deployed-SHA only advances on success, so several aborted attempts share one previously-deployed value; a reference written in the same step as the dump identifies which attempt the dump belongs to.
On the first boot of a freshly provisioned Pi 5 the Ctrl+Alt+F2 terminal escape was dead. Everything install-kiosk-service.sh provisions was correct, but thd had opened only 6 input devices and not the keyboard interface carrying the F-keys: its input node appeared after thd enumerated. `systemctl restart triggerhappy` brought it to 9 and the escape worked. An escape hatch that needs someone to SSH in and restart a daemon is not an escape hatch on a sealed appliance. thd opens /dev/input/event* once, at start, via --deviceglob. A node appearing later reaches it only through the packaged udev rule, so this adds a sync helper run from our own rule (61-, after the packaged 60-, carrying its guard against thd's own virtual device) and once from ExecStartPost, which covers a node that appears between thd's enumeration and its socket binding. The helper only ever adds. An earlier version removed the existing registration first so that a second add could not duplicate it, which inverted the risk: th-cmd opens the device anew under --passfd and can fail, and a successful remove followed by a failed add leaves the keyboard unmonitored while thd keeps running - a state the unmodified script would never have produced, reached on every service start, since ExecStartPost sweeps every node. Duplicates are prevented instead by skipping a device thd already holds, and tolerated when that cannot be established: two descriptors on one device fire chvt twice, which is the same as once, while a skipped add costs the only local way back into the machine. A non-idempotent trigger added later would need this revisited. Identity is treated as unknown rather than guessed. systemd supplies MAINPID for ExecStartPost; otherwise a single thd process is taken to be the service's, and both none and several mean unknown, because a diagnostic thd may hold the keyboard and answering from the wrong process would skip the add the service needed. Both th-cmd calls are bounded: its sendmsg has no deadline, so a full queue would otherwise hold a udev worker, and the leading '-' on ExecStartPost ignores the exit status without exempting the unit from its start timeout. The udev path queues the work through systemd-run so it never runs in the worker at all, and that enqueue is bounded too, along with the logger call that reports its failure - logger writes to a blocking socket, so a stalled journal would wedge a worker just as surely as a stalled manager. A node missed either way is picked up by the sweep on the next thd start. Verification is on hardware: there is no /dev/input, th-cmd or thd in development. The bench procedure is in the release notes for 0.1.0-beta.
Report truthfully what a rollback restores, and hand late keyboards to triggerhappy (NEH-218, NEH-184)
The backend's offsite backup path shells out to rclone, and the appliance cannot get it from apt: Debian Bookworm packages 1.60, which fails B2 authentication for application keys created after mid-2024, and fails in a way indistinguishable from a wrong key. A unit running it would report every backup as a credentials problem. So provisioning installs a pinned official static build under /usr/local/bin, verifying its SHA-256 before installing anything, and asserts the installed version afterwards. The floor is 1.70. Offline-safe by design: if the rclone already present is new enough it touches no network at all, which is what makes it safe to run on every provision and every update. Nothing is installed on a checksum mismatch, an unreachable download, an unknown architecture or a failed unpack, and the binary is installed via a temporary name and moved into place so the appliance never has a torn binary at the final path. The per-unit B2 remote is written as a credential, not as configuration: mode 0600, owned by the service user, at /var/lib/dtk/rclone.conf, outside the repository so it cannot be committed by accident. It is written with a heredoc rather than `rclone config create` so the application key never appears in the process table, and key material supplied in a file is parsed rather than sourced, so an operator's stray line is not executed as root. An existing remote is left completely alone; only the mode and owner, which drift on a cloned or hand-edited card, are re-asserted. A unit with no key material gets no remote and no DTK_OFFSITE_REMOTE, which leaves the backend's whole path a deliberate no-op rather than a half-configured remote failing every thirty minutes. --advisory changes only the exit status, never the diagnosis. setup.sh calls it plainly, because a unit that cannot end up with a working rclone must not be handed to an archive as provisioned. update.sh calls it with the flag, because a merely-offline field unit must not lose an otherwise fine update over a backup binary it could not fetch.
setup.sh runs once, at first provisioning, so the update path is the only way an appliance already in the field reaches a release whose backend uploads through rclone. Without this, such a unit would run a backup path with no rclone to run it. This is the same reasoning step 4b already applies to CHDK USB access, and it sits beside it. --advisory, unlike setup.sh's plain call. At provisioning a unit that cannot end up with a working rclone must not be handed to an archive as provisioned. Here the cost inverts: a merely-offline unit updating to a release whose dependency manifests did not move would otherwise abort an otherwise fine update over a feature that stays disabled until it has a key. Failed updates on remote units are expensive - that is why NEH-214, NEH-215, NEH-216 and NEH-218 exist - and this would have invented a new way to fail one. The operator sees the full diagnosis either way. Placed after step 2's internet honesty check, so an operator has already been warned and confirmed, and before the stack is stopped.
Rounds 2 to 6 of the builder work, committed so it does not live only in a working tree. Review round 4 left six blocking findings, M to R, which are the next work on this branch.
Seven findings from the fourth review round, each with a dry run against the previous file. A migration could disable a working backup. The writer always emitted DTK_OFFSITE_REMOTE, empty included, and offsite.env wins over .env per key — so the first run on a unit configured by an earlier version turned its backup off and called it the intended state. The key is now written only when a remote is actually recorded. Absence does not override .env, and the run reports what .env still names and whether a credential stands behind it. Nothing claims to have disabled anything it did not. A read failure was treated as absence, and absence is what authorises writing a credential — so an offsite.env this script could not read was replaced, destroying the recorded remote. grep's status is now read explicitly: 1 is absent, anything above it aborts. A strict KEY=value match hid a later `export KEY=value`, which python-dotenv would have taken. Every declaration of a key is examined, and a non-strict one anywhere makes the file malformed. Two runs could both find no credential and the second would replace the first. The credential is published with ln, which refuses rather than replaces, and the whole run holds an exclusive flock. The wait is bounded at 120 seconds: update.sh calls this synchronously before the pre-migration backup and passes --advisory so provisioning can never cost an update, and an unbounded wait would have hung the update by a route that flag cannot reach. Field classification piped printf into grep -q, so under pipefail a large but valid config read as unusable once the output passed the pipe buffer and printf took SIGPIPE. One awk pass over a here-string now consumes the whole value, and a check that could not run reports undetermined rather than making a claim about the credential. Both invocations that read the config pass --ask-password=false. An encrypted config would otherwise have waited for a password behind suppressed stderr, in the lsf case after both files were written. bash -n clean; shellcheck 0.11.0 reports nothing at --severity=style, and --enable=all trips only the five optional codes the file already tripped.
…record The repair's undo removed the record before it rewrote HOST_IP, so a power cut or a failed rewrite in between left HOST_IP=captua.local on a unit called digitool, with nothing left to repair it. HOST_IP now goes back first, and the undo waits for the next start if it cannot.
…ing the rename A restore after a failed update, or a rollback, brings back a start-kiosk.sh without the lock cleanup while the unit keeps the name captua, so the next Chromium found a lock naming digitool and refused the profile. The rename now points the running kiosk's SingletonLock at the new name with the same pid (and an undo points it back), which any Chromium of this host takes over once that pid has exited. The kiosk user is read from the tty1 autologin.
…wait for a timeout
…ss events at boot The dispatcher hook restarted the alias on up, down, dhcp4-change, dhcp6-change and connectivity-change, several of them within one second at boot. systemd hit the start limit and left the unit failed. The unit now has no start limit, and the hook restarts only on up, dhcp4-change and dhcp6-change. The install step resets a failed alias unit so one stuck in the start limit recovers on the next update.
… down When the interface the alias published on goes away while another stays up, NetworkManager emits down and no up for the survivor, so the alias kept advertising the dead address. down restarts it again; connectivity-change still does not.
…(NEH-294)
The scripts that read a DTK_ setting from the environment now read
CAPTUA_<X> first and DTK_<X> second, as the backend does. When both are
set to different values CAPTUA_<X> wins and one warning names the two
variables, never their values.
The rule lives in scripts/captua-env.sh, sourced by setup.sh, update.sh,
provision-rclone.sh and check-stack-health.sh. rollback-update.sh keeps
its own copy of the function, since its kept copy under /var/lib/dtk runs
without a scripts/ directory; a test checks the two stay identical. The
helpers install-kiosk-service.sh writes to /usr/local/sbin are installed
alone, so they use the nested ${CAPTUA_X:-${DTK_X:-default}} form.
Everything the scripts write keeps its DTK_ name: .env, offsite.env, the
systemd units, directories, compose names and the Postgres names are
unchanged, so a rollback to pre-0.1.0 code still reads them.
… volume to review, and address the operator as tú (NEH-100) The card now follows the interface 0.1.0 ships: every role shuts down and restarts the unit from the user menu, which the sidebar's user row opens on Resumen and Proyectos and the avatar opens on the capture and volume screens. The Pi's power button is the first fallback, the console the last. New sections: sending a finished volume to review and taking it back, and reconnecting the cameras from Resumen or the capture screen. The console restart says no message appears and to wait about 30 seconds. Every label quoted is the interface's own, the card speaks to the operator as tú like the interface, and guillemets are gone.
feat: reach units at captua.local, keep digitool.local as an alias, and call the API on the page's own origin (NEH-293, NEH-296)
chore(submodule): bump frontend to 10f5bc8
feat(scripts): read CAPTUA_ settings, falling back to the DTK_ names (NEH-294)
docs(operator-card): the operator card for 0.1.0 (NEH-100)
/api/ already had access_log off, but nginx's error log prints the request line in full whenever the backend is down or slow, and the image, thumbnail, preview, file and export-download URLs carry the session token in the query string. I give those paths a nested location that logs only crit and leave every other API path at error level.
fix(nginx): keep session tokens out of nginx's error log (NEH-301)
- backend 8c757d9 Merge pull request #150 from UCSB-AMPLab/backend/neh-301-log-redaction - backend 6794bfe Merge pull request #149 from UCSB-AMPLab/backend/neh-300-serial-fallback - backend 56cf4d6 Merge pull request #148 from UCSB-AMPLab/backend/setup-script-paths - backend efdc097 Merge pull request #147 from UCSB-AMPLab/backend/captua-names - backend 91b1067 Merge pull request #146 from UCSB-AMPLab/backend/rotation-multi-file - backend 368f400 Merge pull request #145 from UCSB-AMPLab/backend/comment-rules - backend b2f33c9 Merge pull request #144 from UCSB-AMPLab/backend/dockerfile-comment-english - frontend 116ef22 Merge pull request #142 from UCSB-AMPLab/frontend/neh-300-rotation-seed
chore(submodule): bump backend to 8c757d9 and frontend to 116ef22
Brings in the AMPL name in the Credits page's logo text (frontend #143).
chore(submodule): bump frontend to 31d0b7b
chore(submodule): bump frontend to 9cf6eea, the credits page in two columns
…check for version headers
…cripts by their shebang
…e 0.1.0-beta version headers
chore(release): prepare 0.1.0-beta
There was a problem hiding this comment.
Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.
An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Merges dev into main for Captua 0.1.0-beta. Pins backend f0c2dd9 and frontend 2436a69, now on both submodules' main (backend #69, frontend #147). Release notes: CHANGELOG.md, section 0.1.0-beta.