All notable changes to Isley are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
- Edit the
[Unreleased]section as part of each dev-branch commit that lands a user-visible change. One-liners are fine; the idea is "describe it while the context is fresh," not "write perfect prose." - When cutting a release, run
scripts/cut-release.sh X.Y.Zon arelease/vX.Y.Zbranch. The script renames[Unreleased]to[X.Y.Z] - YYYY-MM-DDand seeds a fresh empty[Unreleased]block. - The GitHub release workflow extracts the matching version's section from this file and uses it as the release body. Keep the headings exact.
- Historical releases prior to
0.1.42are catalogued on GitHub Releases; they predate this file and were generated from commit logs.
- EcoWitt
ch_ecsupport: WH52 soil sensor reporting soil moisture, soil temperature, and electrical conductivity (converted to mS/cm), registered per channel.
- Deleting a sensor that had hourly-rollup data failed with "Failed to delete
sensor".
DeleteSensorByIDnow purgessensor_data_hourlyandrolling_averagesrows alongsidesensor_datawithin a single transaction, so the foreign key onsensor_data_hourlyno longer blocks the delete (#206).
- CannaDB strain import. When enabled under Settings → "CannaDB Strain Import", the strain library gains an "Import from CannaDB" button that searches the public CannaDB database (cannadb.org) by name and one-click imports a chosen strain — name, breeder, indica/sativa lean, autoflower, descriptions, cycle time, source URL and parent lineage — into the local library. Records are keyed on the CannaDB AT-URI so re-importing updates in place instead of duplicating, and imported strains show a "View on CannaDB" link to the full record (THC/CBD, yield, terpenes, effects and other fields Isley doesn't store locally). The integration is read-only, unauthenticated, and honors CannaDB's rate limits with backoff. Disabled by default. An optional API-URL override is available for self-hosted CannaDB endpoints.
- Per-zone derived Vapor Pressure Deficit (VPD). When a zone is configured with
a leaf-temp offset (°F) plus temperature and humidity source sensors, Isley
computes canopy VPD each poll cycle (Tetens formula) and stores it as a
regular
derivedsensor reading, so it both displays on the plant page and graphs over time. The plant-page VPD chip is colored by the plant's current growth stage's ideal range — green (in range), blue + up-arrow (too low), red + down-arrow (too high). New per-stage ideal-range editor under Settings > Customization (seeded Seedling 0.4–0.8, Veg 0.8–1.2, Flower 1.2–1.5 kPa) and new per-zone VPD inputs in the zone edit modal. Adds migration 019. (PR #187, contributed by @evelinwa)
- CI/build dependency bumps:
actions/checkout4 → 6actions/setup-go5 → 6actions/github-script7 → 9docker/setup-buildx-action3 → 4crazy-max/ghaction-import-gpg6 → 7- Docker runtime base
alpine3.23 → 3.24
- The activity log's empty-state message now centers correctly, matching the
plants/strains/sensors empty states (the
.activities-emptyblock was missing the flex centering rules). (PR #185, contributed by @ababoude) - Completed the French (
fr) translations for the activity log / notebook / export strings (issue #148), which were still placeholderNEEDS_TRANSLATIONvalues. (PR #184, contributed by @ababoude)
- Bump golang.org/x/crypto to 0.53.0
- Bump golang.org/x/text to 0.38.0
- Bump golang.org/x/image to 0.42.0
- Bump modernc.org/sqlite to 1.52.0
- EcoWitt sensor support now covers
ch_aisle(WH31/WN31 multi-channel temperature & humidity) andcommon_listoutdoor weather-station sensors (WH32/WN32, WS80, WH65, WS90 — outdoor T&H, wind, rain, UV, barometric pressure, light). These keys appeared in the gateway's/get_livedata_inforesponse but were previously dropped on scan, so users running outdoor or greenhouse stations saw nothing register. Includes defensive handling for mixed hex/decimal sensor-ID formats across firmware versions and for embedded-unit / dash-placeholder values. (PR #176, contributed by @evelinwa) - The Plants view auto-switches to the Harvested tab on initial load when the Living tab is empty — handy after wrapping up a run and inventorying harvested plants only. (PR #170, contributed by @anon11215)
- The add/edit strain forms now surface the server's validation message in the failure toast instead of a generic "update failed" / "Failed to add strain", so the offending field is actionable.
- Removed an inconsistent
html.EscapeStringon thedescriptionfield returned by/strains/in-stockand/strains/out-of-stock. The list pages don't render the description, but the pre-escape would have seeded a double-escape spiral for any consumer that round-tripped the value back through a save. - GitHub Actions release pipeline bumps:
actions/upload-artifact4 → 7sigstore/cosign-installer3.5.0 → 4.1.1softprops/action-gh-release2 → 3docker/setup-qemu-action3 → 4docker/build-push-action6 → 7
- #146 (strain regression): v0.2.0's new strain URL validation rejected
pre-existing URLs stored without an
http(s)://scheme (common before validation existed). Because the edit modal sends the saved URL back verbatim, editing any field of such a strain — and adding a strain with a schemeless URL — failed with a genericupdate_error/Failed to add straintoast. Schemeless URLs are now normalized (https://prepended) on add/update and persisted clean; dangerous schemes (javascript:,ftp://, …) are still rejected. Thedescription/short_desclength caps were also relaxed so legacy long free text no longer blocks updates. UpdateStrainHandlernow refreshes the in-memory strain cache after a successful update — it regressed in the v0.2.0 ConfigStore refactor and the UI showed stale strain data until the next restart.- Follow-up to #146 (strain debugging): the dedicated edit page at
/strain/:id/editwas still showing a generic "Update failed" toast even after the previous round of validation work, becausestrain-edit.jswas missed whenstrain-add.jsand the (unused) edit modal were updated to parse the server's JSONerrorbody. The live editor now surfaces the actual validation message the same way.
- Bump golang Docker base to 1.26.4-alpine3.23
- Bump golang.org/x/crypto to 0.52.0
- Bump golang.org/x/text to 0.37.0
- Bump golang.org/x/image to 0.41.0
- Bump github.com/quic-go/quic-go to 0.59.1
- Bump modernc.org/sqlite to 1.50.1
- Fixed an ordering bug when uploading multiple plant images at once where descriptions and dates could be paired with the wrong image. (PR #157)
- The plant detail page now has prev/next arrows in the hero and responds to ArrowLeft / ArrowRight keyboard navigation, cycling within the plant's current top-level state (Living / Harvested / Dead). (PR #158)
- Activities can now be flagged as "watering" and/or "feeding". The built-in "Water" and "Feed" activities are migrated to set the matching flag, and the dashboard "days since last watering / feeding" calculations now use the flags rather than hardcoded names — so renaming or translating the built-in activities no longer breaks tracking. (PR #160)
- Activities can now be linked to metrics. When an activity type has linked metrics, the relevant measurement fields appear on the activity-entry form (single-plant, multi-plant, and edit modals), so values like fertilizer amount or pH can be captured alongside the activity rather than buried in the notes. Required vs. optional metrics are configurable per activity in Settings, and recorded measurements stay linked to the activity that produced them. (PR #161)
- Extensive Testing overhaul, dramatically improved test coverage
- New
ISLEY_SECURE_COOKIESdocumented in the README env-var table — set totruewhen fronting Isley with a TLS reverse proxy. dependabot.ymlnow also tracksdockerandgithub-actionsecosystems so base-image and action bumps surface as PRs instead of stale pins.- New
ISLEY_HSTS_MAX_AGE,ISLEY_HSTS_INCLUDE_SUBDOMAINS, andISLEY_HSTS_PRELOADenvironment variables documented in the README — opt-inStrict-Transport-Securityfor HTTPS-only deployments. strain_lineagenow travels through the SQLite→PostgreSQL bootstrap migration tool (model/sqlite_to_postgres.go), so users migrating an existing SQLite install with lineage data no longer silently lose it on the Postgres side.
- The activities page now matches the look and feel of the plants, strains, and sensors pages: in-page search, filter dropdowns, sortable column headers, and a result count, with no full-page reload between filter changes. CSV and XLSX export honor the active filters. Behind the scenes, the
/activities/listJSON endpoint now accepts a largerpage_sizeso the page can render the full activity log client-side. Shared CSS for the controls bar and table styling across the four list views was deduplicated. (PR #159) /healthnow pings the database with a 2-second timeout and returns 503 when the DB is unreachable, so the Dockerfile HEALTHCHECK no longer reports a container with an offline backend as healthy.- The shipped
docker-compose.{sqlite,postgres,migration}.ymlfiles now resolve the Isley image asdwot/isley:${ISLEY_VERSION:-latest}so deployments can pin a specific release without editing the compose file. ISLEY_DB_SSLMODEREADME description corrected to show the actualdisabledefault and the recommended values for TLS-enforcing Postgres hosts.- AC Infinity / EcoWitt scan handlers now surface upstream network and parsing failures as
502 Bad Gateway(with localized error keys) instead of silently returning200 OK. - The
/api/overlayendpoint now shares the ingest rate limiter (60 req/min/key) so an unauthenticated misconfiguration can no longer hammer it unbounded. - Bumped Linux release-binary Go toolchain in
.github/workflows/release.ymlfrom1.25.0to1.25.8, matching the test jobs. - GitLab CI
sign-devandpublish_dev_to_githubjobs upgraded fromalpine:3.20toalpine:3.23to match the Dockerfile runtime base. - All activity write paths (create, edit, delete, multi-plant create) now run inside database transactions. Partial failures no longer leave the activity log in a half-written state, and multi-plant creates pay one WAL fsync instead of N on SQLite.
- AC Infinity / EcoWitt scan handlers now route their sensor registration through the locked
findOrCreateSensorhelper, eliminating an unprotected SELECT-then-INSERT path that could create duplicate rows under concurrent scans.
- Removed the per-service
healthcheck:blocks from the shippeddocker-compose.*.ymlfiles for theisleyservice. Compose-level healthchecks fully override the DockerfileHEALTHCHECK, so the hardcodedlocalhost:8080probe ignoredISLEY_PORTeven after the v0.1.42 Dockerfile fix. The Dockerfile healthcheck (which honors${ISLEY_PORT:-8080}) is now the single source of truth. - Deleted the unused
jsonifytemplate helper (returned unescaped JSON; previous audits flagged it as a footgun) and the duplicateformatDateISOtemplate func —formatDatealready produced an identical ISO date. - Removed two redundant placeholder/args slice loops in
getPlantsByStatuswhose results were immediately discarded by a re-declaration on the next line. - Dropped the unused
ISLEY_MIGRATE_SQLITEline fromdocker-compose.migration.yml— the variable was never read by Go code (the migration scan usesISLEY_DB_FILE).
- Reopened #146: shipped Compose files were overriding the Dockerfile
healthcheck and pinning the probe to port 8080. Users who changed
ISLEY_PORTsaw the container marked unhealthy. Resolved by removing the redundant Compose healthcheck blocks; existing deployments may need todocker compose up -d --force-recreate(or edit their local compose file) to pick up the new behavior. - Sensor ingest endpoint no longer creates duplicate sensor rows under concurrent ingest of the same (source, device, type). Existing duplicates are merged on upgrade.
- Plant image uploads are now bounded with
http.MaxBytesReader(250 MB per request, 50 MB per file) and rejected with413 Request Entity Too Largewhen exceeded — previously, files above the in-memory threshold spilled to disk unbounded. LinkSensorsToPlantnow verifies every supplied sensor ID exists before persisting the JSON column, rejecting dangling references with400instead of silently storing them.- Strain create/update endpoints now length-validate
name,description,short_desc, andnew_breeder, and rejecturlvalues that are not http/https — closes a long-standing input-validation gap on the strain form. - Plant activity, measurement, and status edit endpoints now cap
noteatMaxNotesLength(5000) and reject malformeddatevalues via the newutils.ValidateDatehelper. - Rate-limit logs no longer record raw
X-API-KEYvalues; offending callers are now identified by a non-reversible 12-char SHA-256 prefix, preserving correlation while removing the credential from logs. app.NewEnginenow requiresSessionSecretto be at least 32 bytes (was: any non-empty length), preventing weak session keys.- Trusted-proxy list now includes the IPv6 unique-local block (
fc00::/7), so deployments that front Isley with an IPv6 reverse proxy see accuratec.ClientIP()values for rate limiting and audit logs. - CSP-nonce generation now aborts the request with
500 Internal Server Erroroncrypto/rand.Readfailure instead of serving a page with a predictable empty nonce. - Bumped bcrypt cost from 10 to 12 for password and API-key hashing. New env var
ISLEY_BCRYPT_COSTlets test suites lower the cost without weakening production defaults. - Bounded watcher and scan response bodies with
io.LimitReader(4 MiB cap) so a slow-drip server (notably user-supplied EcoWitt LAN addresses) cannot stream unbounded payloads inside the request timeout. - Capped the
sensor_data_hourlyrollup queries with the sameMaxRawDataRowslimit the raw-data branches already used (defensive only).
- Added #148 Activities list full view and export. Activity log is now a top level item with searching / filtering / export. Additionally the plant details page includes an expandable "Notebook" view with activity details displayed.
- Rebuilt the CI/CD pipeline around a CHANGELOG-driven release ceremony.
Release notes now come from the curated
CHANGELOG.mdsection matchingVERSIONrather than from parsed commit messages, which were structurally lossy after the GitLab → GitHub squash sync. - GitHub workflow adds OCI image labels (revision, source, version, created) so published Docker Hub images are traceable back to the commit that built them.
- GitLab → GitHub squash-sync commit message now embeds the originating GitLab short SHA for cross-registry traceability.
- Tightened
VERSIONdiscipline: release workflow rejects a push tomainif the tag already exists or the version string isn't strict SemVer.
- Fixed #146 Healthcheck didn't respect ISLEY_PORT
Bump golang to 1.25.9 for security updates Bump modernc.org/sqlite to 1.49.1 Bump golang.org/x/text to 0.36.0 Bump golang.org/x/image to 0.39.0 Bump golang.org/x/crypto to 0.50.0
Prior releases are recorded on
GitHub Releases. Beginning with
0.1.42 all user-visible changes are tracked here.