155 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
97e7b9d319 feat: add VerifySignature + webhook decode/signature tests
Co-authored-by: coolaj86 <122831+coolaj86@users.noreply.github.com>
2026-03-02 08:37:57 +00:00
copilot-swe-agent[bot]
60097fd661 refactor: rename ping CSV columns to match doc key names (battery_charging, connection_status, etc.)
Co-authored-by: coolaj86 <122831+coolaj86@users.noreply.github.com>
2026-03-02 08:28:37 +00:00
copilot-swe-agent[bot]
9b79800b07 refactor: remove Challenge() method; use w.Header().Set("WWW-Authenticate", ...) directly
Co-authored-by: coolaj86 <122831+coolaj86@users.noreply.github.com>
2026-03-02 08:10:17 +00:00
copilot-swe-agent[bot]
0b05b30129 refactor: merge CSV/JSON handler pairs into one handler per event type + serveCSVOrJSON helper
Co-authored-by: coolaj86 <122831+coolaj86@users.noreply.github.com>
2026-03-02 08:04:05 +00:00
copilot-swe-agent[bot]
7465f584d9 docs+fix: add RequestAuthenticator godoc example; use 10_000 numeric literal
Co-authored-by: coolaj86 <122831+coolaj86@users.noreply.github.com>
2026-03-02 08:01:24 +00:00
copilot-swe-agent[bot]
9e7a50b443 feat: add WWWAuthenticate field + Challenge(w) method to RequestAuthenticator
Co-authored-by: coolaj86 <122831+coolaj86@users.noreply.github.com>
2026-03-02 07:58:27 +00:00
copilot-swe-agent[bot]
33960a5f2b refactor: move BasicAuthenticator into RequestAuthenticator; Authenticate takes only *http.Request
Co-authored-by: coolaj86 <122831+coolaj86@users.noreply.github.com>
2026-03-02 07:43:09 +00:00
copilot-swe-agent[bot]
780ad06089 feat: add AuthenticateBasic bool + NewRequestAuthenticator() with sane defaults
Co-authored-by: coolaj86 <122831+coolaj86@users.noreply.github.com>
2026-03-02 07:39:28 +00:00
copilot-swe-agent[bot]
886888d6bb fix: nil AuthorizationSchemes skips Authorization header (not any-scheme wildcard)
Co-authored-by: coolaj86 <122831+coolaj86@users.noreply.github.com>
2026-03-02 07:35:22 +00:00
copilot-swe-agent[bot]
c8eabab03d refactor: extract RequestAuthenticator into auth package; use in smsapid and auth-proxy
Co-authored-by: coolaj86 <122831+coolaj86@users.noreply.github.com>
2026-03-02 07:17:50 +00:00
copilot-swe-agent[bot]
07adfb08c7 feat: expand ping CSV to flat health columns (charging_flags, battery_percent, internet_connected TRUE/FALSE, etc.)
Co-authored-by: coolaj86 <122831+coolaj86@users.noreply.github.com>
2026-03-02 07:07:02 +00:00
copilot-swe-agent[bot]
a708352634 fix: WebhookPing csv tags + JSON payload column; DeviceChecks struct; fix Authenticate deadlock
Co-authored-by: coolaj86 <122831+coolaj86@users.noreply.github.com>
2026-03-02 06:56:04 +00:00
bf3c4a48e4
copilot: add CSV and JSON endpoints to smsapid with csvauth role-based auth 2026-03-01 22:57:39 -07:00
8056a07d10
chore(tools/monorel): add .goreleaser.yaml tools/monorel/v0.6.4 2026-03-01 19:13:50 -07:00
8405be04ad
feat(monorel): use YAML anchors to DRY up multi-binary build matrices
When a module has more than one binary, the shared build options (env,
ldflags, goos) are defined once via a YAML anchor on the first build and
merged into the rest with <<: *build_defaults.  Single-binary modules use
plain fields with no anchor overhead.

  - id: gsheet2csv          - id: gsheet2csv
    binary: gsheet2csv        binary: gsheet2csv
    env:              →       <<: &build_defaults
      - CGO_ENABLED=0           env:
    goos:                         - CGO_ENABLED=0
      - aix                     goos:
      - ...                       - aix
  - id: gsheet2env               - ...
    binary: gsheet2env      - id: gsheet2env
    env:                      binary: gsheet2env
      - CGO_ENABLED=0           <<: *build_defaults
    goos: ...

The commented-out ios stubs follow the same pattern using a separate
build_defaults_ios anchor so they remain consistent when uncommented.

Also extracts defaultGoos to a package-level var to avoid repetition.
2026-03-01 19:13:50 -07:00
445a6e9c07
feat(monorel): expand default build matrix to all practical CGO_ENABLED=0 targets
Replace linux/windows/darwin with the full CGO_ENABLED=0 goos list:
  aix, darwin, dragonfly, freebsd, illumos, js, linux, netbsd, openbsd,
  plan9, solaris, wasip1, windows

Add commented-out stanzas for each binary for platforms that require extra
tooling:
  - iOS  (CGO_ENABLED=1, Xcode toolchain required)
  - Android (CGO_ENABLED=0 arm64-only; NDK required for full CGO builds)

Archive formats unchanged (tar.gz + tar.zst / zip + tar.gz for Windows).
2026-03-01 19:13:50 -07:00
1289f3e5b6
fix(monorel): guard .goreleaser.yaml overwrite in release; loosen compat check
release subcommand:
- Replace yamlLooksCorrect with yamlIsCompatible: file is considered OK if
  {{ .ProjectName }} is absent AND at least one binary's VERSION string is
  present.  Extra hand-edited binaries (like fixtures) no longer trigger a
  rewrite.
- Before overwriting an existing file, prompt the user [Y/n].  --yes does
  not skip this prompt; --force does.  If stdin is not a terminal and
  --force is not set, the command errors rather than silently clobbering.

init subcommand: unchanged — still uses the strict yamlLooksCorrect check
(all binaries must be present, ldflags must include main.version).
2026-03-01 19:13:50 -07:00
c14bc239f5
fix(monorel): always create GitHub release as draft+prerelease, finalise after upload
gh release create now always uses --draft --prerelease so artifacts can be
uploaded before the release becomes visible. A final "Finalise release
visibility" step then runs gh release edit to remove whichever flags should
not remain:

  --draft=false       unless --draft flag given (keep as draft)
  --prerelease=false  unless --prerelease flag given OR the tag has a
                      pre-release suffix (-pre3, .dirty, etc.)

Flag meaning change:
  --draft      = keep release in draft state after uploading (don't publish)
  --prerelease = keep release marked pre-release even for clean vX.Y.Z tags
2026-03-01 19:13:50 -07:00
f87bfeb438
feat(monorel): add --draft and --prerelease flags to release subcommand
The gh release create flags are now explicit and always emitted:
  --draft / --draft=false
  --prerelease / --prerelease=false

Publish step (gh release edit --draft=false) logic:
  --draft --prerelease : NO  (stays as draft pre-release)
  --draft only         : YES (draft needs publishing)
  --prerelease only    : NO  (published immediately as pre-release)
  neither              : NO  (published immediately)

The step prompt adapts: "create draft pre-release / draft / pre-release /
GitHub release <tag>" depending on the flag combination.
2026-03-01 19:13:50 -07:00
b6fd4072bf
refactor(monorel): replace bash script output with interactive step runner
Replace printModuleScript (which emitted a bash script to stdout) with an
interactive step runner for the release subcommand:

- Each step shows the command(s) it will run, then prompts [Y/n]
- --dry-run: show steps without prompting or running (replaces old default)
- --yes: run all steps without prompting (happy-path automation)

New types/functions:
  releaseStep       — title, prompt, display lines, skip flag, run func
  printModuleHeader — extracted header/info block (always shown)
  buildModuleSteps  — constructs the ordered step list for one module
  runSteps          — executes steps per dryRun/yes flags
  execIn            — runs a command streaming to the terminal
  execInEnv         — like execIn with extra environment variables

Goreleaser archive globs are expanded at step-run time (after goreleaser
has built the dist/ directory) rather than being passed as shell globs.
The gh release create --notes flag receives the notes string directly
instead of via a shell variable.
2026-03-01 19:13:50 -07:00
e6f82ed91b
feat(monorel): print "found config ... with monorepo support" when yaml is ok
When an existing .goreleaser.yaml passes yamlLooksCorrect() and needs no
update, both init and release now print:

  found config ./cmd/tcpfwd/.goreleaser.yaml with monorepo support

Path is shown relative to cwd (via cwdRelPath helper).
2026-03-01 19:13:50 -07:00
f3eff585c4
feat(monorel): match goreleaser comment style in generated .goreleaser.yaml
- Replace "Generated by monorel" header with goreleaser-style preamble
  ("This file is generated by monorel ... check the docs at goreleaser.com")
- Add modeline explainer comment block before the yaml-language-server line
- Add "you may remove this if you don't need go generate" comment inline
- Combine name_template comments into goreleaser-style single comment
  ("this name template makes the OS and Arch compatible with uname")
2026-03-01 19:13:50 -07:00
98bdc47746
feat(monorel): validate existing .goreleaser.yaml instead of diffing content
Replace the exact-content comparison with yamlLooksCorrect(), which checks
three semantic markers:

  - -X main.version={{.Env.VERSION}} present (version injection)
  - <binname>_{{ .Env.VERSION }}_ present for every binary (archive naming)
  - {{ .ProjectName }} / {{.ProjectName}} absent (not a stock template)

If the file passes all checks it is left completely untouched, preserving
any compatible local edits. Only files that fail a check are regenerated.
2026-03-01 19:13:50 -07:00
833c68e708
fix(monorel): skip writing .goreleaser.yaml when content is unchanged
Both init (initModuleGroup) and release (processModule) now compare the
generated content against the file on disk before writing. If identical,
the file is left untouched — preserving any compatible local edits.

Also fix processModule commit message scope to chore(<prefix>): add
.goreleaser.yaml, matching the convention already used by initModuleGroup.
2026-03-01 19:13:50 -07:00
ed08958157
fix(monorel): skip writing .goreleaser.yaml when content is unchanged
Compare generated content against the file on disk before writing.
If identical, do nothing — no write, no "wrote" message, no commit attempt.
2026-03-01 19:13:50 -07:00
4bb350d61b
fix(monorel): init only auto-commits new .goreleaser.yaml, not updates
- Track isNewFile before writing so updates to existing files are never
  auto-committed (mirrors the same rule already in processModule/release)
- Gate auto-bump on isNewFile as well — no tag when just refreshing yaml
- Fix commit message scope: chore(<prefix>): add .goreleaser.yaml
  (was chore(release): add .goreleaser.yaml for <name>)
2026-03-01 19:13:50 -07:00
455ceb7d69
fix(monorel): restore cross-module binary discovery in recursive walk
The go.mod boundary check added to findMainPackages was stopping the
--recursive walk from descending into child module directories, so
modules like auth/csvauth/ were never found.

The check was redundant: groupByModule already uses findModuleRoot to
attribute each binary to its nearest go.mod ancestor, and initModuleGroup
already skips modules with uncommitted changes (an untracked go.mod
counts as an uncommitted change).  Removing the stop restores the
pre-regression behaviour.
2026-03-01 19:13:50 -07:00
1c779296e9
feat(monorel): add tar.zst and windows tar.gz archive formats
- Non-Windows archives: tar.gz, tar.zst (zstd for faster decompression)
- Windows archives: zip, tar.gz (Windows ships with bsd tar + gz support)
- Upload script: include *.tar.zst globs alongside *.tar.gz and *.zip
2026-03-01 19:13:50 -07:00
e4402f4a4a
feat(monorel): push commits and tags before creating GitHub release
Add Step 3 "git push && git push --tags" to the generated release script,
between the tag creation step and the goreleaser build step.  This fixes
HTTP 422 "Release.target_commitish is invalid" errors from the GitHub API,
which occur when the local commit (auto-created for a new .goreleaser.yaml)
has not yet been pushed to the remote.
2026-03-01 19:13:50 -07:00
25781032f9
fix(monorel/release): auto-commit+tag only when .goreleaser.yaml is new
If .goreleaser.yaml did not exist:
  → write it, commit it, auto-tag patch (if sole new commit since last tag)

If .goreleaser.yaml already existed:
  → write the updated file, stop — no auto-commit, no auto-tag
2026-03-01 19:13:50 -07:00
c4a31cbee8
feat(monorel/release): write+commit .goreleaser.yaml and auto-tag patch
monorel release now mirrors the init lifecycle for the goreleaser config:

1. Write .goreleaser.yaml (always regenerate; warn on stock {{ .ProjectName }})
2. git add + commit it if the file changed
3. Auto-tag patch — but only if the yaml commit is the sole new commit
   since the last stable tag (same heuristic as monorel init).
   If there are >1 new commits, print a note and skip auto-tag so the
   user can choose the right semver component with monorel bump.
4. Compute version info *after* the yaml commit and auto-tag, so the
   generated script reflects the tag that was just created.
2026-03-01 19:13:50 -07:00
56cce946a8
fix(monorel/release): always write .goreleaser.yaml, never auto-commit
The previous commit was too conservative — it prevented release from
updating an existing config. Restore the original write-always behaviour
but keep the {{ .ProjectName }} monorepo warning. The file is written
unconditionally; the user commits it manually when satisfied.
2026-03-01 19:13:50 -07:00
1e766daa14
feat(monorel/release): write+commit .goreleaser.yaml only when missing
Previously `monorel release` always overwrote .goreleaser.yaml, discarding
any manual customisations the user had made.

New behaviour:
- Missing → write the monorel-generated config and commit it (same as init)
- Exists, stock {{ .ProjectName }} in a monorepo subdir → warn and ask the
  user to update it manually before re-running
- Exists, looks fine → print "leaving unchanged" and continue
2026-03-01 19:13:49 -07:00
bcd70c3843
feat(monorel): pre-compute release notes into the generated script
Instead of emitting a runtime git log command in Step 4, run git log at
monorel-invocation time and embed the actual commit list as a
single-quoted bash string (with '\'' escaping for embedded apostrophes).

  my_release_notes='- abc1234 first commit
  - def5678 fix: don'\''t crash'

This makes the generated script self-contained and shows the user exactly
which commits will appear in the release notes before they run the script.
Removes the now-unused gitPathSpec local variable.
2026-03-01 19:13:49 -07:00
3d065f4a41
feat(monorel): add --cmd flag, git status guard, and go.mod boundary walk
- findMainPackages: stop descending into child directories that have their
  own go.mod on disk (even if untracked), treating them as independent
  module roots so they are never double-counted with the parent module

- initModuleGroup: skip if the module has uncommitted changes (excluding
  files inside child module directories so a freshly-run --cmd step does
  not block the parent); new helpers hasUncommittedChanges,
  findChildModuleRoots

- monorel init --cmd: scans recursively for direct children of cmd/
  directories that contain package main but have no go.mod yet, runs
  go mod init + tidy for each, and prints a suggested commit at the end;
  new helpers initCmdModules, readModulePath, runPrintIn
2026-03-01 19:13:49 -07:00
69148f9edb
feat(monorel): skip untracked directories during recursive walk
Add buildTrackedDirs which runs "git ls-files" once from the walk root
and builds a set of directories containing at least one tracked file.
findMainPackages uses this set to skip untracked directories (dist/,
vendor/, node_modules/, build artifacts, etc.) without having to
enumerate git-ignored paths explicitly.

Falls back to walking everything when git is unavailable or the
directory is not inside a repository.
2026-03-01 19:13:49 -07:00
a28fe8ed55
fix(monorel): skip repo-root modules; add found binary/module headers
- All three subcommands now print "found binary …" and "found module …"
  before processing each module group, with a blank line between groups
- initModuleGroup, bumpModuleTag, processModule: downgrade the prefix==""
  (repository root) case from fatal error to a skip warning so that
  -recursive runs continue past root-level go.mod packages instead of
  aborting
2026-03-01 19:13:49 -07:00
e3ada72168
fix(monorel): init skips auto-bump when other commits precede goreleaser.yaml
Extract findLatestStableTag helper to avoid duplicating tag-scan logic.

In initModuleGroup, count commits since the last stable tag after writing
.goreleaser.yaml. Only auto-bump when ≤1 new commit exists (i.e. the
goreleaser.yaml addition is the sole change). When more commits are present,
print a note and let the user run 'monorel bump' explicitly with the right
semver component.
2026-03-01 19:13:49 -07:00
efb77dca45
fix(monorel): skip same-commit bump; add --dry-run to bump and init
- Same-commit guard: downgrade from fatal error to a skip warning so
  -recursive bump continues to the next module instead of aborting
- bump/init: add -dry-run flag; prints what would happen without making
  any git commits or tags
2026-03-01 19:13:49 -07:00
f39d35d57c
feat(monorel): -A flag, dot/underscore skip, same-commit guard, -force bump
- findMainPackages: skip dot/underscore-prefixed dirs by default; warn
  (not error) on ReadDir failures when -A is set
- expandPaths, runRelease, runBump, runInit: thread -A flag through
- bumpModuleTag: refuse to tag a commit already tagged by the previous
  stable release; -force creates an empty bump commit instead
2026-03-01 19:13:49 -07:00
0706c53de4
feat(monorel): -recursive flag, remove GORELEASER_CURRENT_TAG, indent subshell
-recursive flag (all three subcommands)
  expandPaths() / findMainPackages() walk the directory tree looking for
  `package main` directories, honouring stopMarkers (skips .git dirs so
  the walk never crosses a repository boundary).  Passes collected paths
  straight to groupByModule, which handles module grouping as before.

    monorel init     -recursive .   # init every module under .
    monorel bump     -recursive .   # bump patch for every module
    monorel release  -recursive .   # release script for every module

Remove GORELEASER_CURRENT_TAG from generated release script
  The non-pro goreleaser does not support it.  VERSION is still exported
  for use via {{.Env.VERSION}} in the goreleaser YAML templates.

Indent the goreleaser subshell in the release script
  Before:  ( cd "auth/csvauth" && goreleaser release ... )
  After:
    (
      cd "auth/csvauth"
      goreleaser release --clean --skip=validate,announce
    )
2026-03-01 19:13:49 -07:00
ae46430b7b
fix(monorel): bump tags the latest commit touching the module, not HEAD
In a monorepo the module's most recent commit is often behind HEAD
(other modules may have been committed on top).

  git log --format=%H -1 -- .

run from the module root returns the SHA of the last commit that
touched that directory; we pass it explicitly to `git tag <tag> <sha>`
instead of letting git default to HEAD.
2026-03-01 19:13:49 -07:00
b1be4ce829
fix(monorel): rename bump flag -m → -r
-m was already used by git commit, making copy-pasting confusing.
-r (for "release component") avoids that conflict.
2026-03-01 19:13:49 -07:00
c55a869b82
feat(monorel): add subcommand dispatch (release, bump, init)
Restructure monorel to use flag.FlagSet-based subcommand dispatch so that
future subcommands can each carry their own flags cleanly.

  monorel release <binary-path>...
      Existing behaviour: write .goreleaser.yaml and print a bash release
      script.  Now a named subcommand; no behaviour change.

  monorel bump [-m major|minor|patch] <binary-path>...
      Create a new semver git tag at HEAD for each module.
      -m defaults to "patch".  Using a flag (-m) rather than a positional
      argument avoids ambiguity with binary paths that might literally be
      named "minor" or "patch".

  monorel init <binary-path>...
      For each module (in command-line order): write .goreleaser.yaml,
      commit it (skipped when file is unchanged), then run bump patch.
2026-03-01 19:13:49 -07:00
9812f52ee9
fix(monorel): explicit paths, POSIX vars, goreleaser.yaml warning
1. Script paths relative to invoking CWD (not module root):
   - git log pathspec: "-- relPath/" instead of "-- ./"
   - artifact globs:   relPath/dist/ instead of ./dist/
   - goreleaser only:  ( cd "relPath" && goreleaser ... ) inline subshell
   - when relPath==".": all paths use ./ and no cd is emitted
   The outer ( subshell ) wrapper is removed; each command is now
   copy-pasteable from the directory where monorel was invoked.

2. POSIX variable for release notes:
   RELEASE_NOTES= → <project>_release_notes= (no export; goreleaser
   does not need it; multiple modules no longer share the same name).

3. Warn before overwriting .goreleaser.yaml when:
   - the existing file contains {{ .ProjectName }} (stock config), AND
   - the module is a monorepo subdirectory (go.mod not adjacent to .git/)
   The file is still updated; the warning alerts the user that a
   non-monorel config was replaced.
2026-03-01 19:13:49 -07:00
76fbf74444
fix(monorel): error if binary path is not a main package
Add checkPackageMain(), which uses go/parser with PackageClauseOnly mode
(reads only the package-declaration token of each .go file — very fast)
to verify the resolved binary directory declares `package main`.

Called in groupByModule for every argument after resolving the absolute
path, before findModuleRoot.  Produces a helpful error, e.g.:

  monorel: error: .../io/transform/gsheet2csv is package "gsheet2csv",
                  not a main package
2026-03-01 19:13:49 -07:00
862eeebd6d
feat(monorel): auto-discover module root; submodule-aware .git detection
Major refactor: monorel no longer requires being run from the module root.
It now accepts paths to binary packages from any ancestor directory and
walks up from each path to find go.mod (groupByModule / findModuleRoot).

Binaries sharing a module root are grouped together. When the script must
cd into a module directory (multi-module run or different CWD) the per-module
output is wrapped in a bash subshell to prevent directory leakage.

The .git stop-marker now only triggers on a .git DIRECTORY, not a .git FILE.
A .git file means the path is inside a submodule whose real repository root
is further up the tree, so the search continues upward.
2026-03-01 19:13:49 -07:00
5716f0f757
fix(monorel): guard against nested go.mod in binary paths
Any directory on the path from the module root to a binary that
contains its own go.mod is a separate module; monorel should not try
to manage it.  Two cases are now caught with distinct errors:

  ../other              → "outside the module directory"
  ./cmd/go.mod          → "has its own go.mod" (intermediate dir)
  ./cmd/foo/go.mod      → "has its own go.mod" (binary dir itself)

Both suggest the correct fix: cd into that directory and run monorel
from there.
2026-03-01 19:13:49 -07:00
12c025e5e2
feat(monorel): require binary-path args; support multi-binary modules
The tool now requires at least one positional argument — the path(s) to
the Go main package(s) to build — and must be run from the module root
(the directory containing go.mod).

  # single binary (module root is the main package)
  monorel .

  # multiple binaries under one module
  monorel ./cmd/gsheet2csv ./cmd/gsheet2tsv ./cmd/gsheet2env

Changes:
- Add `binary` struct {name, mainPath} and `parseBinaries()`
  - "." is special-cased: binary name is taken from the CWD, not "."
  - filepath.Clean's "./"-stripping is undone so goreleaser sees an
    explicit relative path (./cmd/foo not cmd/foo)
- `goreleaserYAML` now takes `projectName + []binary`
  - Each binary gets its own `builds` entry (with `id:` and `main:`)
    and its own `archives` entry (with `ids:` to link it to the build)
  - `main:` is omitted when mainPath is "." (goreleaser default)
  - Checksum is named <projectName>_VERSION_checksums.txt
- `printScript` takes `projectName + []binary`
  - Summary line says "Binaries:" (plural) when more than one
  - Upload step globs tar.gz + zip for every binary, then the checksum
- Require go.mod in CWD; error out with usage message when no args given

Also regenerates cmd/tcpfwd/.goreleaser.yaml via the new code path
(adds `id: tcpfwd` to builds/archives; no functional change otherwise).
2026-03-01 19:13:45 -07:00
3676ef0f47
feat: add tools/monorel for monorepo submodule releases
Adds a standalone Go CLI tool (tools/monorel) that automates the
goreleaser + gh release workflow for modules living in a subdirectory
of a monorepo where goreleaser Pro is not available.

Run from any module subdirectory (e.g. cmd/tcpfwd):
  monorel          # writes .goreleaser.yaml + prints release script
  monorel --help   # (flag defaults)

What the tool does:
- Detects module path and binary name from git prefix
- Lists and semver-sorts tags matching <prefix>/v* (e.g. cmd/tcpfwd/v*)
- Computes version: exact tag → stable release; commits/dirty → pre-release
- Writes (or updates) .goreleaser.yaml with the binary name hard-coded,
  {{.Env.VERSION}} used for filenames instead of the prefixed tag,
  and release.disable: true (gh handles the GitHub Release)
- Prints a numbered bash script covering env vars, optional git tag,
  goreleaser build, release notes, and gh release create/upload/publish

Also updates cmd/tcpfwd/.goreleaser.yaml (first output from monorel):
- Fixes stray trailing quote in ldflags
- Sets release.disable: true (was release.footer)
- Adds generated-by header comment
2026-03-01 19:13:39 -07:00