Commit Graph

233 Commits

Author SHA1 Message Date
Bastian de Byl 16145fb6bd only email genuine backup failures
A spurious invocation carries no action for a reader, so mailing it just
trains them to skip past the subject line -- which defeats the point of the
alert. Send mail only when the unit actually reports failure; spurious
triggers still leave their journald record, so they stay greppable and can
still feed a Graylog rule.

Verified both paths: a spurious trigger leaves /var/log/msmtp.log untouched
and logs alert_mail=skipped reason=spurious, while a genuinely failed unit
still composes mail with the FAILED subject.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 20:34:45 -04:00
Bastian de Byl 6e99794d0f restore full dump flags; stop spurious alerts claiming FAILED
mariadb-upgrade --force has now been run against both instances and repaired
the system tables, so --routines and --events no longer abort the dump.
Restore them for completeness. Both verified: rc=0 with a clean completion
trailer, and both Nextcloud instances report installed with unchanged table
counts afterwards.

The upgrade still exits non-zero on these containers because it cannot create
the `sys` schema: /var/lib/mysql is owned by daemon rather than mysql, so
mysqld may not create top-level databases. `sys` is diagnostic only and
unused by Nextcloud, but the same permission would block creating any new
database, so it is recorded in the template comment.

The alert handler claimed FAILED in its subject line regardless of what the
unit actually reported, so starting it by hand mailed out a failure notice
for a run that succeeded. Derive the subject and log line from the real
Result, and emit status=spurious rather than status=failed so such triggers
cannot match a Graylog alert rule keyed on genuine failures.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 20:04:32 -04:00
Bastian de Byl 0ab423ca55 harden nextcloud backups: db dumps, alerting, drift fix
The data-only rsync left no way to restore a working instance: mysql/ and
config/ were never backed up, so a recovery would have files but no shares,
users or metadata. Dump the database before syncing files (a DB older than
the files is repairable with occ files:scan; a newer one references blobs
that never made it into the backup) and ship config/ alongside it.

Capture the --chmod=Du=rwx,Dgo=rx flag that had been hand-added to the
deployed skudak-cloud script. It was outside git, so every deploy silently
reverted it. It now lives in backup_rsync_extra_args.

Add OnFailure= alerting. The units failed silently before, which is how an
iDrive sync failure sat unnoticed since May. msmtp rather than the esmtp
already installed: the OpenSRS relay is port 465 (implicit TLS) and libesmtp
only speaks STARTTLS.

Exclude nextcloud.log* from the sync and cap log_rotate_size. skudak-cloud
was running at loglevel 0 and had written a 64 GB log that was being rsynced
and pushed to S3; set it to 2 to match the home instance.

Stagger the timers (04:00 / 04:30) so both finish before the 05:00 TrueNAS
snapshot task, and bound TimeoutStartSec so a wedged rsync cannot leave the
unit activating forever and skip every subsequent trigger.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 16:03:18 -04:00
Bastian de Byl 1e1d53ecd8 feat(gitea-actions): install jq in the ESP-IDF CI image
esp32-stm32-vcu's scripts/release.sh uses jq to rewrite the protocol
manifest before publishing it to S3. The image did not have it, so the
release aborted at:

    ./scripts/release.sh: line 61: jq: command not found

The failure mode is nastier than a red job. jq is only reached *after*
the firmware .bin and version.json have already been uploaded, so
clients were served the new build while the git tag, the Gitea release
and the protocol manifest were never written — the repo still showed
the previous release as latest while production served a newer one.

Only jq was missing; aws and sha256sum are already present (verified in
the rebuilt image: jq-1.7, aws-ok, sha256sum-ok).

This surfaced when the skudak firmware jobs were moved into this image
(they previously ran on the default gitea-ci image, which has jq).
2026-07-25 12:01:16 -04:00
Bastian de Byl 7abec05cc2 SCRUM-148/SCRUM-149: Bump fulfillr + fulfillr-dev images to 20260723.2044
Per-product sales metrics + storefront image proxy endpoints.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 16:52:24 -04:00
Bastian de Byl 8703875825 SCRUM-140: Bump fulfillr images to 20260717.1955 (import status ratchet)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 21:36:43 -04:00
Bastian de Byl b380e33c92 SCRUM-140: Bump fulfillr images to 20260717.0202; snipcart_api_key via EXTRA_VARS
The import-only Snipcart key renders empty on normal deploys (import
endpoint 503s); it was injected via EXTRA_VARS just for the historical
refund re-import and is now blanked again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 22:43:13 -04:00
Bastian de Byl 6737b4621d SCRUM-140: Bump fulfillr + fulfillr-dev images to 20260716.2108
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 17:19:42 -04:00
Bastian de Byl 3f0a78da39 SCRUM-129: Bump fulfillr + fulfillr-dev images to 20260714.2120
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 17:30:32 -04:00
Bastian de Byl 51beb906ba gitea-actions: pre-bake esptoolpy + mkspiffs in the PlatformIO CI image
Both are pulled on demand by pio run/buildfs; without them baked in, every
ephemeral job container re-downloads them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 17:01:59 -04:00
Bastian de Byl 9dfa812a80 gitea-actions: add PlatformIO CI image for Arduino-framework ESP32 builds
New opt-in job image localhost/gitea-ci-platformio:7.0.1 (pattern matches the
ESP-IDF image): PlatformIO core 6.1.19 with the espressif32@7.0.1 platform,
xtensa toolchain and Arduino framework pre-baked via a seed project so
ephemeral job containers download nothing. First consumer is
Skudak/esp32-web-interface.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 16:59:57 -04:00
Bastian de Byl 5588e81fb0 SCRUM-126: Bump fulfillr + fulfillr-dev images to 20260714.2006
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 16:29:39 -04:00
Bastian de Byl 66d2502d31 SCRUM-114: Bump fulfillr + fulfillr-dev images to 20260712.1826
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 15:07:54 -04:00
Bastian de Byl c4c0067756 SCRUM-108: Bump prod fulfillr image to 20260708.0147
Ship-email delivery visibility + resend-tracking endpoint.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 21:55:12 -04:00
Bastian de Byl 6f158bc2fb SCRUM-108: Bump fulfillr-dev image to 20260708.0147
Ship-email delivery visibility + resend-tracking endpoint.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 21:52:43 -04:00
Bastian de Byl a78cf91be9 SCRUM-105: Bump prod fulfillr image to 20260706.1823
Promotes the ASCII-safe address transliteration fix (go-fulfillr#24) to prod
after dev verification.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 14:36:44 -04:00
Bastian de Byl 1cf264bbdf SCRUM-105: Bump fulfillr-dev image to 20260706.1823
Deploys the ASCII-safe address transliteration fix (go-fulfillr#24) to the
staging back-office. Prod image tag unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 14:29:13 -04:00
Bastian de Byl a696d8c47b SCRUM-102: Bump prod fulfillr image to 20260703.1535
Deploys the go-store banner-columns build to the prod back-office so the Coupons
admin round-trips the global-sale banner fields on prod. Applied via
`make deploy TAGS=fulfillr`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 13:31:25 -04:00
Bastian de Byl f9e9adc507 SCRUM-102: Bump fulfillr-dev image to 20260703.1535
Deploys the go-store banner-columns bump (coupon banner_label/banner_text) to
the staging back-office so the Coupons admin round-trips the global-sale fields.
Applied via `make deploy TAGS=fulfillr-dev`. Prod tag unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 21:41:41 -04:00
Bastian de Byl 704c1a8311 immich: upgrade to v3.0.0 + migrate DB to VectorChord
v3.0.0 dropped pgvecto.rs support, which crash-looped immich-server
("No vector extension found") and left photos.debyl.io on a white page.
Swap db_image to Immich's bundled postgres image (VectorChord 0.4.3 +
pgvecto.rs 0.2.0) so v3 reindexes the existing embeddings into
VectorChord in place; bump server/ml from v2.7.5 to v3.0.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 17:21:14 -04:00
Bastian de Byl b8f68acd0e SCRUM-98: Bump fulfillr (prod) image to 20260629.2000
Stripe relink endpoints (stripe-candidates / link-stripe) + ch_ refund support
now live on prod back-office.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 23:05:11 -04:00
Bastian de Byl 4f6e3f709e SCRUM-98: Bump fulfillr-dev image to 20260629.2000
Stripe relink endpoints (stripe-candidates / link-stripe) + ch_ refund support.
Prod (fulfillr) left on 20260628.1930 pending prod cutover.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 16:10:39 -04:00
Bastian de Byl 5d0d15f414 SCRUM-97: Healthcheck + restart-on-unhealthy for fulfillr containers
After a power cycle a transient HMAC Secrets Manager blip leaves
go-fulfillr's gated routes unregistered (404) with the process still up,
so nothing restarts it. Add a podman healthcheck probing the new
dependency-free /api/v1/health/startup (503 until those routes register)
with healthcheck_failure_action: restart, so podman restarts the
container in place and the next boot self-heals.

- fulfillr.yml + fulfillr-dev.yml: healthcheck via busybox wget (ships in
  the alpine image), interval 30s / timeout 5s / retries 3 /
  start_period 30s (covers the ~14s HMAC retry backoff), failure_action
  restart. Existing restart_policy on-failure:3 kept (process-exit case).
- main.yml: bump fulfillr + fulfillr-dev image to 20260628.1930 (the
  build carrying the /health/startup probe).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 15:35:22 -04:00
Bastian de Byl 285ef2ad01 gitea-actions: add python3-yaml + python3-jinja2 to the ESP-IDF CI image
The esp32-stm32-vcu firmware build generates common-yaml headers with
`python3 generate.py`, which needs pyyaml + jinja2. The runner's base Python is
PEP 668 externally-managed (pip install fails) and the IDF venv isn't on PATH in
the docker-exec step shell, so install both as distro packages. Lets firmware
jobs run a plain `python3 generate.py` with no pip and no IDF sourcing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 16:03:36 -04:00
Bastian de Byl 2df697f5f6 SCRUM-51: Bump fulfillr image to 20260614.1925 (dev + prod)
Adds email_sent/email_failed timeline events for ticket label emails.
Deployed to fulfillr-dev and fulfillr (prod) on home.debyl.io.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 15:31:47 -04:00
Bastian de Byl b4dec16cad SCRUM-50: Bump fulfillr image to 20260614.1518 (dev + prod)
Ticket refund + replacement-shipment endpoints and guarded transitions.
Deployed to fulfillr-dev and fulfillr (prod) on home.debyl.io.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 11:25:14 -04:00
Bastian de Byl 16053e1cbb fulfillr: drop Snipcart key, add outreach/recovery schedule config, bump image
- remove snipcart_api_key from dev/production config (Snipcart decommissioned
  post-migration)
- add review-outreach and cart-recovery schedule_name/schedule_group blocks
  (dev + prod) for the EventBridge-driven outreach and cart-recovery jobs
- bump fulfillr image 20260607.0217 -> 20260613.0117

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 10:19:56 -04:00
Bastian de Byl a30ff9b165 gitea-actions: add ARM/Python CI deps and SSH bind-mount for submodule clones
- Containerfile.ci: add python3-yaml + python3-jinja2 and the
  gcc-arm-none-eabi / binutils / libnewlib toolchain for embedded builds
- bind-mount the runner's SSH key + known_hosts read-only into each job
  container at /root/.ssh so submodule clones over
  ssh://git@git.skudak.com:2222 succeed; staged as a dedicated
  container_file_t-labelled ci-ssh copy (tasks/user.yml) and allowlisted
  via valid_volumes (config.yaml.j2)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 10:19:45 -04:00
Bastian de Byl 7d4a398bba Drop self-hosted AI (Ollama + SearXNG); gregtime switches to xAI Grok
The Ollama role and SearXNG container backed FISTO AI responses in the
greg-time Discord bot. greg-time 3.9.6 drops both (plus the Gemini path)
in favor of a single xAI Grok backend, so:

- remove the ollama role and its wiring in deploy_home.yml
- remove the searxng container task, template, and searxng_path default
- gregtime: swap OLLAMA_*/SEARXNG_URL/GEMINI_API_KEY env for XAI_API_KEY,
  bump image 3.6.5 -> 3.9.6
- vault: add xai_api_key, drop gemini_api_key

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 10:19:45 -04:00
Bastian de Byl 87cf953364 SCRUM-45: Revert Caddy /webhooks/easypost carve-out
The EasyPost tracker webhook moved to debyltech-api (publicly reachable Lambda);
the fulfillr host is LAN-restricted and no longer hosts it, so the carve-out is
no longer needed. Removes the handle blocks for prod and dev.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 21:13:56 -04:00
Bastian de Byl c896f69ff9 SCRUM-45: Caddy carve-out for the EasyPost return webhook
The Fulfillr host is IP-restricted, so EasyPost's servers can't reach it. Add a
narrow `handle /webhooks/easypost` before the IP restriction (handle blocks are
mutually exclusive, first match wins) for prod (:9054) and dev (:9055) so the
HMAC-verified tracker webhook is reachable while the rest of the host stays locked.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 20:29:44 -04:00
Bastian de Byl 3b9c46a11b fulfillr prod: bump to 20260606.2328 (immutable note write handler) 2026-06-06 19:34:10 -04:00
Bastian de Byl 7c58a2a358 fulfillr prod: bump to 20260606.2231 (immutable notes + go-store v0.2.1) 2026-06-06 19:24:14 -04:00
Bastian de Byl 2ce6c531ee fulfillr prod: bump to 20260606.1840 (go-store v0.2.1 order INSERT fix) 2026-06-06 18:29:36 -04:00
Bastian de Byl 2335b4980d fulfillr(prod): wire prod Turso store + live Stripe (fulfillr_prod_* vars) + image 20260606.1735 2026-06-06 17:28:00 -04:00
Bastian de Byl da98a2c5dc fulfillr(prod): add download_base_url=https://api.debyltech.com to production.json.j2 (cutover prep) 2026-06-06 16:55:55 -04:00
Bastian de Byl 5d1db841f0 fulfillr-dev: bump to 20260606.1735 (no double shipped-email) 2026-06-06 14:40:08 -04:00
Bastian de Byl 1f16749935 fulfillr-dev: bump to 20260606.1727 (importer fixes + tickets/custom-shipment on Turso) 2026-06-06 13:37:52 -04:00
Bastian de Byl fcde86153c fulfillr-dev: bump to 20260606.1639 (refund + internal notes) 2026-06-06 12:42:58 -04:00
Bastian de Byl e149d860d5 gitea-ci: add zip to the CI image
Lambda packaging steps in some workflows shell out to `zip`; the image
only had `unzip`. Add `zip` alongside it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 11:46:23 -04:00
Bastian de Byl bafc32226c fulfillr-dev: bump to 20260606.1523 (resend downloads + new-products-inactive seed) 2026-06-06 11:33:22 -04:00
Bastian de Byl 935de1fcfe fulfillr-dev: download_base_url for resend-download links 2026-06-06 11:32:42 -04:00
Bastian de Byl a024078a55 fulfillr-dev: bump image to 20260606.1425 (digital file upload + download-admin + tickets payment refresh) 2026-06-06 10:29:49 -04:00
Bastian de Byl 35213d81c3 fulfillr-dev: point aws.bucket at debyltech.digital.dev (digital file uploads) 2026-06-06 08:42:33 -04:00
Bastian de Byl e82ace6de3 fulfillr-dev: staging back-office container + Turso store prep
Add a second go-fulfillr container (fulfillr-dev) wired to the staging
Turso store + EasyPost/Stripe test keys via dev.json, served at
fulfillr-dev.debyltech.com (Caddy -> :9055), LAN-restricted like prod.

- fulfillr-dev.yml + dev.json.j2: the staging container, volumes, config
- defaults: fulfillr_dev_* vars; prod store URL stubbed off until cutover
- Caddyfile + caddy.yml: fulfillr-dev site block and static mount
- awsddns.yml: Route53 DDNS for the fulfillr-dev hostname
- production.json.j2: add store_database_url/store_auth, rename stripe key
  var to fulfillr_stripe_api_key
- vault.yml: dev + store/stripe secrets

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 00:23:07 -04:00
Bastian de Byl 2640d09cb5 gitea-actions: run CI jobs in rootless-podman containers
Switch the act_runners from :host execution to docker:// images backed by
a rootless podman socket under the gitea-runner user, so each job runs in
its own ephemeral container with per-job Go caches. This eliminates the
cross-repo GOMODCACHE/go-build poisoning that forced the debyl runner to
capacity:1.

- deps.yml: enable the rootless --user podman.socket, ensure subuid/subgid,
  register gitea_runner_uid; drop the rootful system socket override,
  podman-docker and host golang
- images.yml + Containerfile.ci/.espidf: build localhost/gitea-ci and
  localhost/gitea-ci-espidf into the runner's rootless image store
- config.yaml.j2: docker:// labels (per-runner overridable), docker_host
  -> rootless socket, force_pull false
- act_runner.service.j2: XDG_RUNTIME_DIR + DOCKER_HOST -> user socket
- defaults: uniform capacity:4 (drop the debyl capacity:1 workaround);
  esp_idf_version now tags the espressif/idf-based image
- main.yml: import images.yml, drop the host esp-idf install (firmware jobs
  use the espressif/idf job container instead)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 00:16:54 -04:00
Bastian de Byl 72ecc63e17 fulfillr-dev: bump image to 20260606.0357 (inventory editor, logs page, branded shipped email, U5 trim) 2026-06-06 00:10:39 -04:00
Bastian de Byl 2df5b7fc03 Deploy fulfillr 20260603.0222 and wire tickets_table
Bump fulfillr image to the build with the tickets feature, and add the
tickets_table to the fulfillr production.json config (new debyltech-tickets-prod
DynamoDB table) so the /api/v1/tickets routes register.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 22:32:52 -04:00
Bastian de Byl 5e189289e7 fulfillr: deploy Stripe payment requests (key + image 20260530.2348)
- add stripe_api_key to fulfillr production.json template
- add restricted Stripe key to ansible vault (encrypted)
- bump fulfillr image to the CI build containing the Stripe endpoints

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 20:58:40 -04:00
Bastian de Byl 1bc1a7f619 chore: bump fulfillr container to 20260527.2345
Records the back-in-stock notify-route fix image now running in prod.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 20:08:19 -04:00