Commit Graph
377 Commits
Author SHA1 Message Date
Bastian de BylandClaude Opus 5.5 4220a2190d chore(gregtime): bump to 3.21.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 15:42:30 -04:00
Bastian de BylandClaude Opus 5.5 867b3038c8 chore(gregtime): bump to 3.20.3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 15:09:39 -04:00
Bastian de BylandClaude Opus 5.5 ecb85e96d0 chore(gregtime): bump to 3.20.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 14:42:43 -04:00
Bastian de BylandClaude Opus 5.5 135c26369f chore(gregtime): bump to 3.19.3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:27:07 -04:00
Bastian de BylandClaude Opus 5.5 bcebdc19cc chore(gregtime): bump to 3.19.2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 22:36:55 -04:00
Bastian de BylandClaude Opus 5.5 a38cb080da feat(hass): hook the living room desk lamp into the light schedule
switch.desk_lamp (EP10) is an on/off switch, so the area-targeted
light.* actions skip it. It now follows light.living_room explicitly:
on at sunset unless TV mode is on, on when the TV goes off in the
evening, and off with the living room at 23:30, the 01:00 sweep and
when the TV goes off late at night.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 16:21:09 -04:00
Bastian de BylandClaude Opus 5.5 3d03332aaa fix(hass): drop dead device-based automations, use entity ids
The five "Lights - 01/02/03/04/10" automations had no triggers left and
were superseded by the sunset/evening-ramp/lights-out/sweep automations;
nothing references them. "Driveway String Lights Off" and "Lights - 00 -
Morning" referenced device ids that no longer exist in the device
registry (they only worked because HA resolved the entity registry id),
so they now target switch.driveway_string_lights and
light.bathroom_hallway directly. Ids and aliases are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 15:58:01 -04:00
Bastian de BylandClaude Opus 5.5 f575f8f62d chore(hass): add one-off playbook to repoint tplink entries at IoT VLAN
The IoT WiFi moved from Default (192.168.1.x) to the IoT VLAN
(192.168.2.x) with fixed IPs reserved in UniFi. HA 2026.9.3's tplink
reconfigure flow ignores the entered host and reconnects to the stored
one, so this edits data.host in .storage/core.config_entries with HA
stopped, matching entries by MAC and refusing to write unless all 9
match. Backs up core.config_entries first and always restarts hass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 15:45:59 -04:00
Bastian de BylandClaude Opus 5.5 339d4b150c chore(gregtime): bump to 3.19.1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 17:46:04 -04:00
Bastian de BylandClaude Fable 5.1 0bd6c6e3fe chore(fulfillr): bump prod and dev image to 20260929.1624 (SCRUM-208 activity feed)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-29 12:40:08 -04:00
Bastian de BylandClaude Fable 5.1 4759ee5909 chore(fulfillr): bump prod and dev image to 20260929.1553 (SCRUM-207, SCRUM-208)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-29 12:04:00 -04:00
Bastian de BylandClaude Fable 5.1 a55e7b7c3d chore(fulfillr): bump prod image to 20260929.1412 (nav badges, SCRUM-206)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-29 10:23:53 -04:00
Bastian de BylandClaude Fable 5.1 554d3acb6e chore(fulfillr-dev): bump image to 20260929.1412 (nav badges, SCRUM-206)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-29 10:22:05 -04:00
Bastian de BylandClaude Fable 5.1 69d2c2de92 chore(fulfillr): bump prod and dev image to 20260929.0203 (project invoicing, SCRUM-205)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-29 09:39:39 -04:00
Bastian de BylandClaude Fable 5.1 72c1006b3e chore(fulfillr-dev): bump image to 20260929.0015 (project invoicing, SCRUM-202)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 20:23:29 -04:00
Bastian de BylandClaude Opus 5.5 1f399cee91 chore(vault): rotate gitea registry token to write:package
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 19:53:46 -04:00
Bastian de BylandClaude Opus 5.5 0cc4c1460e feat(debyltech-cloud): limit customers to Files, Activity and signing
Nothing was group-restricted, so customers saw Dashboard, Photos, Office
and the rest, plus Nextcloud's first-run and promo apps.

- Disable for everyone: firstrunwizard, recommendations, related_resources,
  weather_status, survey_client, support, app_api, contactsinteraction,
  photos. A refused disable now fails the play (occ exits 0 on "can't be
  disabled").
- Restrict dashboard and office to staff. defaultapp=dashboard,files so
  staff land on the dashboard and customers fall through to Files.
- libresign groups_request_sign pinned to staff and asserted in verify.
  LibreSign itself is deliberately NOT group-restricted: that also blocks
  anonymous requests and would break public signing links.
- profile.enabled=false; lookup_server="" (lookup_server_connector
  can't be disabled).
- README: what customers can open.

Checked as a probe customer: apps=files,activity,libresign,text,viewer,
lands in Files, can't request signatures; staff land on Dashboard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 19:48:39 -04:00
Bastian de BylandClaude Opus 5.5 c4fe506860 feat(debyltech-cloud): lock customers to read-only, no discovery
Customers are admin-created accounts in per-customer groups. They should
read what staff share with them and nothing else. Checked against a test
customer in the UI, and by running the sharee and contacts-menu search
services as that user.

- shareapi_exclude_groups=allow, list [admin]: only staff can share. Exclude
  mode ("yes") only disables sharing for users whose groups are ALL
  excluded, so it never catches a customer in their own group.
- User/group autocomplete off. By default a customer typing "bas" found the
  owner's account. Staff share by exact group name; LibreSign signers are
  found by email.
- New shares default to View only (shareapi_default_permissions=1).
- files default_quota 0 B, so customers get no personal storage. Staff in
  cloud_debyltech_staff_users are exempted (skipped if not yet created).
- No "Leon Green" sample contact in new address books.
- The verify script fails the deploy if any isolation setting drifts.
- README: staff and customer onboarding checklist.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 19:10:47 -04:00
Bastian de BylandClaude Opus 5.5 be50798096 fix(ci-images): static build matrix for Gitea
CI Images / Plan (push) Successful in 29s
CI Images / Build ci (push) Failing after 1m8s
CI Images / Build espidf (push) Failing after 1m16s
CI Images / Build platformio (push) Failing after 1m30s
Gitea expands a job's matrix when the run is created, before plan has any
outputs, so fromJSON(needs.plan.outputs.matrix) collapsed to a single empty
"Build ${{ matrix.key }}" job and nothing was ever built. The matrix is now
the fixed list of image keys; plan emits every image's spec with a build flag
and each matrix job looks its own entry up, no-opping when it wasn't picked.

Also document that both registry tokens need write:package -- the vault token
was read-only, so the gitea_ci_build_local bootstrap failed its push.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 19:00:35 -04:00
Bastian de BylandClaude Opus 5.5 8701ada7e2 feat(debyltech-cloud): plain login background, empty homes for new accounts
- theming background -> backgroundColor, dropping the stock image for the
  navy theming colour.
- skeletondirectory/templatedirectory set to "" so customer accounts start
  empty instead of getting Nextcloud's sample Manual, intro video and
  Templates folder.
- The system-config compare now tells an unset key apart from an empty
  value. Both print nothing, so an intentionally empty setting was
  silently skipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 18:38:52 -04:00
Bastian de BylandClaude Opus 5.5 9ce9610965 fix(debyltech-cloud): external signers, proxy trust, light-only mail
- Enable LibreSign's email identify method (click-to-sign, no account
  creation), remove the stamp background and collect signer metadata.
  On Skudak these were only ever set in the admin UI. Without the email
  method a fresh instance answers "No signers." for any outside address.
  The verify script now asserts it.
- Store add_footer=true explicitly. The code already defaults to it, but
  the 14.2 admin page shows unset as unchecked.
- trusted_proxies = the container's own address, read per deploy. Behind
  rootless port forwarding every request arrives from it, so without this
  X-Forwarded-For was ignored and every client shared one IP for
  brute-force throttling.
- maintenance_window_start and default_phone_region, which clears the setup
  warnings.
- Mail declares color-scheme "light only" so Apple Mail's dark mode doesn't
  repaint the white ground and bury the black wordmark (asserted in verify).
- Idempotency: redis image fully qualified (docker.io/library/...), the
  debyltechmail copy owned by the mapped www-data uid, and theming
  compared before setting.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 18:08:41 -04:00
Bastian de BylandClaude Opus 5.5 fa5bbf8e54 feat(debyltech-cloud): add cloud.debyltech.com Nextcloud
A de Byl Technologies LLC Nextcloud cloned from the Skudak instance:
LibreSign signing for people without an account, registration off
(admin-created accounts only), no Group Folders. DNS is a terraform-managed
ALIAS to fulfillr.debyltech.com.

- containers/debyltech/cloud.yml: nextcloud/mariadb/redis on port 8091.
  It installs unattended on the first deploy, sends mail through SES as
  noreply@debyltech.com, and re-asserts the Skudak LibreSign settings.
- files/debyltechmail: skudakmail rebranded, with a new black-and-white
  wordmark and white web-UI logos.
- LibreSign is pinned to 14.2.2 from the GitHub release (sha256-checked)
  rather than `occ app:install`. The app store served a same-day 14.2.3
  whose tarball has no binary-signature metadata. 14.2.x also doesn't
  create its own download dirs, so they're pre-created.
- The backup runs nightly at 04:15 to TrueNAS /mnt/glacier/debyltechcloud and
  reaches personal iDrive via the "iDrive E2 Backup" task; the TrueNAS side
  excludes /debyltechcloud/_backup/config/**.
- Fix the libresign:configure:check gate in both instances: '\berror\b'
  becomes a backspace in Jinja and never matched, so a check reporting three
  errors passed clean. Now '\\berror\\b'.
- vault: cloud_debyltech_* secrets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 17:04:04 -04:00
bastian 0eca63d4b7 Merge pull request 'fix(gitea-actions): serve CI images from the Gitea registry' (#12) from feat/ci-images-registry into master
CI Images / Plan (push) Failing after 1s
CI Images / Build ${{ matrix.key }} (push) Skipped
2026-09-28 12:19:57 -04:00
Bastian de BylandClaude Opus 5.5 73c552303b docs(claude): work directly on master, no branches or PRs
CI Images / Plan (pull_request) Failing after 2s
CI Images / Build ${{ matrix.key }} (pull_request) Skipped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 11:53:14 -04:00
Bastian de BylandClaude Opus 5.5 5d6aa187cc chore(vault): update secrets
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 11:51:45 -04:00
Bastian de BylandClaude Opus 5.5 1852af5fc9 chore: bump gregtime to 3.19.0, rsvp to 1.0.7
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 11:51:45 -04:00
Bastian de BylandClaude Opus 5.5 f674f61b8d fix(hass): don't fire on-off automations when a device reconnects
CI Images / Plan (pull_request) Failing after 2s
CI Images / Build ${{ matrix.key }} (pull_request) Skipped
The Bedroom Light HS200 dropped off Wi-Fi for 5 s at 03:01 and came back
reporting "on"; the Bedroom On device trigger treated unavailable -> on as
someone flipping the switch and lit the bedroom Hue lamps at 100%.

Bedroom On/Off and TV On/Off now use state triggers with not_from
unavailable/unknown, so reconnects and HA restarts no longer count as a
flip. The driveway's switch-reconnect catch-up is dropped for the same
reason (it would undo a manual off); the HA-restart catch-up stays.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 11:32:24 -04:00
Bastian de BylandClaude Opus 5.5 fd985e014c fix(hass): driveway lights ignore TV mode, ramped evening dimming, bump to 2026.9.3
CI Images / Plan (pull_request) Failing after 2s
CI Images / Build ${{ matrix.key }} (pull_request) Skipped
The sunset automation required TV mode off, so an afternoon of TV skipped
the driveway string lights entirely (Sep 22 and 23) - not an outage. The
driveway now has its own sunset -1h automation, with catch-up on restart
or switch reconnect before 23:00.

Evening brightness lives in one script (evening_lights_apply) that blends
between the old step levels; a 5-minute ramp from 20:30 eases lights that
are on and leaves alone any a person has changed by hand. The Dining Hall
no longer bumps to 50% at 21:30.

TV off after 23:30 now only turns off the living room glow instead of
bringing the whole house back to full brightness, and TV on/off leave the
lights alone in daylight. Lights-out moves to 23:30, and a 01:00 sweep
catches anything switched back on at the wall.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 12:42:33 -04:00
Bastian de BylandClaude Fable 5.1 15a8ec693e chore(gitea): bump git.skudak.com to 1.27.3
CI Images / Plan (pull_request) Successful in 37s
CI Images / Build ${{ matrix.key }} (pull_request) Failing after 58s
Same security fixes as git.debyl.io, deployed and verified after it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 11:10:07 -04:00
Bastian de BylandClaude Fable 5.1 64850995ec chore(gitea): bump git.debyl.io to 1.27.3, pin the two instances separately
1.26.1 -> 1.27.3 picks up the security fixes in 1.27.0 through 1.27.3.
The image was one shared variable, so the two instances could only move
together; split it into gitea_debyl_image / gitea_skudak_image and tag
the debyl tasks gitea-debyl so each can be upgraded and verified on its
own. Skudak stays on 1.26.1 in this commit.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 11:06:31 -04:00
Bastian de BylandClaude Opus 5 ba0936bc8d chore(gregtime): bump to 3.18.4
The daily quote keeps a ledger of what it has posted and re-rolls ZenQuotes
until it finds something the channel has not read, with the header framing and
the offline fallback pool drawn against that same ledger.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 10:30:07 -04:00
Bastian de BylandClaude Opus 5 d0e76bd6cf feat(gitea-actions): build the CI job images in Gitea CI
The runner's job images were built by ansible into localhost/ only, so the
nightly CI prune deleted them and every idle stretch ended with CI failing in
under a second on `docker pull localhost/gitea-ci:latest` until someone re-ran
the role and waited out a rebuild. The previous commit moved them to the Gitea
registry; this moves the *build* off the deploy path entirely.

- .gitea/workflows/ci-images.yml builds files/Containerfile.* and pushes to
  git.debyl.io/gitbot/. Per-image change detection, so an ESP-IDF pin bump does
  not rebuild the other two; weekly schedule for base-image updates; a
  workflow_dispatch selector. PRs build under a throwaway :pr-<n> tag and drop
  it -- the build lands in the live runner's store, and act_runner will not
  re-pull a tag it already has, so a PR using the real tag would hand every
  later job on this host an unmerged image.
- The Containerfiles stop being ansible templates: their version vars are now
  --build-arg, read by the workflow out of the same defaults/main.yml the role
  interpolates, so CI and ansible build the same bytes from one set of pins.
- LABEL io.debyl.ci-base moves into each Containerfile so neither builder can
  forget the prune exemption; the workflow re-checks it before pushing.
- roles/gitea-actions pulls instead of building. gitea_ci_build_local=true
  restores the local build+push for seeding a cold registry or when CI is
  down -- the workflow that builds gitea-ci runs in gitea-ci.
- Lint .gitea/ alongside ansible/, and document the flow in the role README.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 11:10:43 -04:00
Bastian de Byl a52209ff6a Merge branch 'master' into feat/ci-images-registry 2026-09-21 11:02:55 -04:00
Bastian de BylandClaude Opus 5 56d74660b8 chore(rsvp): bump to 1.0.5
Nights are checkboxes again: a household ticks every night that works, which
also says which nights do not. At least one tick (or "Any of these works")
is required.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 23:42:58 -04:00
bastian 306e43e700 Merge pull request 'SCRUM-196: fulfillr 20260915.0011 (GA4 users over the exact window)' (#13) from scrum-196/fulfillr-users-window into master 2026-09-14 20:34:12 -04:00
Bastian de BylandClaude Opus 5 355a0739ff SCRUM-196: fulfillr 20260915.0011 (GA4 users over the exact window)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 20:26:02 -04:00
Bastian de Byl f7f4d903a0 Merge remote-tracking branch 'origin/master' into feat/ci-images-registry 2026-09-14 19:57:33 -04:00
bastian f0e2fae900 Merge pull request 'SCRUM-196: GA4 analytics config for fulfillr Traffic & funnel tab' (#11) from scrum-196/fulfillr-ga4-analytics into master 2026-09-14 17:59:21 -04:00
Bastian de BylandClaude Opus 5 75e257077e SCRUM-196: fulfillr 20260914.2149 (embedded tzdata for GA4)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 17:55:15 -04:00
Bastian de BylandClaude Opus 5 80edc84586 SCRUM-196: fulfillr 20260914.2103 (funnel + GA4 traffic endpoints)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 17:10:08 -04:00
Bastian de BylandClaude Opus 5 42e6f5271d fix(gitea-actions): serve CI images from the Gitea registry
The CI job images only existed under localhost/ in the gitea-runner store,
and the nightly CI prune deletes any image older than 48h that no container
holds. After every idle stretch CI failed in 0-1s pulling
localhost/gitea-ci:latest until the role was re-run and the images rebuilt.

- Build under git.debyl.io/gitbot/..., push after every run, and pull from the
  registry instead of rebuilding when the Containerfile is unchanged.
- Log gitea-runner in via ~/.docker/config.json, which both act_runner (job
  image pulls) and podman read.
- Label the base images io.debyl.ci-base and skip that label in the CI prune;
  its `until` counts from build time, so a re-pulled image would otherwise be
  deleted again the next night.

Workflows pinning `container: image: localhost/gitea-ci-*` must move to the
registry paths.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 16:40:57 -04:00
Bastian de BylandClaude Opus 5 e174e259eb SCRUM-196: Read GA4 key from fulfillr_ga4_credentials_json
Match the vault variable name holding the service-account key file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 16:21:04 -04:00
Bastian de BylandClaude Opus 5 e681a46b78 SCRUM-196: GA4 analytics config for fulfillr Traffic & funnel tab
Render an analytics block (property 353859448 + service-account key) into the
fulfillr dev and prod configs once fulfillr_ga4_credentials is in the vault.
Without the vault var the block is omitted and the portal reports GA as not
connected. Remember to restart the container after deploy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 14:02:38 -04:00
Bastian de BylandClaude Opus 5 d9ab55f05d chore(rsvp): bump to 1.0.4
Compacts the admin invite table so it fits without clipping: short token link
with Copy/Msg, status as an emoji, Qty, allergens/notes behind popovers, and
relative update times.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 10:16:39 -04:00
Bastian de BylandClaude Opus 5 1c5d3b020a chore(rsvp): bump to 1.0.3
Fixes the admin invite table clipping its Edit/Revoke column, and adds
default-headcount people estimates to the invited/awaiting summary.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 23:36:08 -04:00
Bastian de BylandClaude Opus 5 1b9fccd779 chore(rsvp): bump to 1.0.2
Adds the anonymized "Who's coming" card for guests who have answered, and a
message-template copy button on the admin invite table.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 23:20:57 -04:00
Bastian de BylandClaude Opus 5 f59ade748b feat(rsvp): deploy rsvp.debyl.io, the invite-only party RSVP app
A single Go binary with SQLite, built and loaded as localhost/rsvpd:<VERSION>
by make deploy-remote in ~/src/rsvp-debylio. Both of its listeners are
published on 127.0.0.1 only: Caddy proxies the public one to everyone and the
admin one (/admin, no login) only to caddy_local_networks. A Caddyfile mistake
alone cannot expose admin, and neither can a port mistake alone.

Guests' invite links are the credential and they sit in the URL path, which
shapes the vhost:

- It does not import common_headers. That snippet sets Referrer-Policy
  same-origin, which would replace the app's no-referrer and let a token leak
  in a Referer header.
- Its access log rewrites request>uri to /i/REDACTED and drops the Location
  response header, since every POST 303s back to /i/<token>.
- Caddy's error logger is separate from the site's and wrote the raw URI to
  caddy.log when the upstream was down. The global log now excludes
  http.log.error.rsvp and a filtered rsvp-errors logger takes it instead.
  Verified with zero token occurrences in both logs, locally and live.

The data directory is owned directly by the host uid of the container's uid
10001 (subuid + 10000). Setting it to the podman user and chowning back each run
flipped ownership on every deploy and briefly locked the app out of its
database.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 23:14:46 -04:00
Bastian de BylandClaude Opus 5 a9f51b77e1 fix(podman): pull a new image before removing the running container
podman-check deleted the old container as soon as the pinned image differed,
and the create task pulled afterwards. A tag that did not exist, or a registry
that was down, therefore left the service with no container at all. The pull
now happens first, so that failure stops the play with the old container still
running. localhost/ images are built and loaded by hand and are never pulled.

The pull is skipped when the container does not exist yet: there is nothing to
protect, and containers[0] is not there to compare against. Without that guard
the first deploy of any new service failed on the conditional -- rsvp was the
first to hit it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 23:14:46 -04:00
Bastian de BylandClaude Opus 5 73e50bb19d chore(gregtime): bump to 3.17.3
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 23:14:45 -04:00
Bastian de BylandClaude Opus 5 6cd4d56de1 feat(labelprint): 4x6 label print proxy on a Raspberry Pi
A Pi 3B+ (stickah.local) shares a Phomemo PM246 to the LAN as a plain CUPS
queue, so any machine can print 4x6 labels -- fulfillr-site's shipping labels
in particular -- without installing the vendor driver, which is x86-64 only.
The role builds the TSPL CUPS driver from source instead.

It is Debian, not Fedora, so it lives in its own inventory and playbook
(make deploy-labelprint / check-labelprint) and the home.debyl.io roles can
never run against it. make bootfs renders its cloud-init first-boot files onto
a freshly imaged SD card from the same templates the role uses. The Wi-Fi
credentials for the home and rescue networks are in the vault.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 23:14:45 -04:00