feat: signed release pipeline + fix issue #3 web crash + green CI#5
Merged
Conversation
Bundle a cosign / Homebrew / notarized-pkg release pipeline together with the prerequisite fixes needed to make main green again. Fixes ----- - web/app/samba_mgr.py: translate FileNotFoundError from asyncio.create_subprocess_exec into RuntimeError so list_sessions() degrades to [] instead of returning 500 on the dashboard (issue #3). - web/Dockerfile: replace the unreliable `docker.io` apt package (~200 MB, occasionally missing the CLI on arm64 slim images) with the pinned static docker CLI binary from download.docker.com, verified at build time via `docker --version`. Fixes the underlying cause of the FileNotFoundError reported in issue #3. - web/app/auth.py: drop unused get_settings import (ruff F401). - scripts/entrypoint-{avahi,samba}.sh: silence SC2016 around envsubst with a targeted shellcheck disable directive; the single-quoted variable list is intentional. Release pipeline ---------------- - .github/workflows/docker.yml: install sigstore cosign and sign every pushed image keylessly via GitHub OIDC. - .github/workflows/release.yml: on tag push, build a macOS .pkg, codesign with Developer ID Installer, notarize + staple via notarytool, attach to the GitHub release, then push a Formula bump commit to momenbasel/homebrew-timenest. - scripts/build-pkg.sh: reproducible local + CI .pkg builder (productbuild) that stages the compose stack under /usr/local/timenest and installs a `timenest` CLI shim at /usr/local/bin/timenest. - bin/timenest: thin docker compose wrapper used by both Homebrew and the .pkg. - homebrew/timenest.rb: canonical Homebrew formula; the tap repo mirrors this file, the release workflow rewrites url/sha256/version. - docs/release-pipeline.md: required secrets, bootstrap steps, and artifact verification commands.
mypy saw LoginDep: Callable[..., str] = Depends(...) and complained that six endpoint signatures used `user: str = LoginDep` — Callable default incompatible with str argument. Cast through fastapi's Depends so the module-level alias is typed as str (its effective post-resolution type) without changing any call sites.
There was a problem hiding this comment.
Pull request overview
This PR introduces a signed release pipeline (macOS .pkg + Homebrew tap bump + cosign-signed container images), fixes the dashboard crash from issue #3 by hardening Docker CLI invocation handling, and unblocks CI by addressing lint/shellcheck issues.
Changes:
- Web: replace
docker.ioinstall with a pinned static Docker CLI download; makesamba_mgr._execdegrade gracefully whendockeris missing; remove an unused import. - CI/release automation: add keyless cosign signing to image pushes; add a tag-triggered release workflow that builds/signs/notarizes a macOS
.pkgand updates a Homebrew tap. - Distribution: add a
timenestCLI shim, a.pkgbuild script, a canonical Homebrew formula, and release-pipeline documentation.
Reviewed changes
Copilot reviewed 10 out of 11 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
web/Dockerfile |
Switches from docker.io apt package to downloading a pinned static Docker CLI. |
web/app/samba_mgr.py |
Converts missing docker CLI (FileNotFoundError) into RuntimeError so callers can degrade gracefully. |
web/app/auth.py |
Removes unused get_settings import to fix linting. |
scripts/entrypoint-samba.sh |
Adds targeted shellcheck suppression for intentional envsubst quoting. |
scripts/entrypoint-avahi.sh |
Adds targeted shellcheck suppression for intentional envsubst quoting. |
scripts/build-pkg.sh |
Adds a macOS .pkg build script used in release workflow and local repro. |
homebrew/timenest.rb |
Adds a canonical Homebrew formula that the release workflow copies/rewrites into the tap repo. |
docs/release-pipeline.md |
Documents the release pipeline, required secrets, bootstrap, and verification steps. |
bin/timenest |
Adds a CLI shim over docker compose for Homebrew and .pkg installs. |
.github/workflows/release.yml |
Adds tag-triggered workflow to build/sign/notarize .pkg, publish GH release, and bump tap formula. |
.github/workflows/docker.yml |
Adds cosign installer + keyless OIDC signing for pushed images. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+33
to
+39
| cp "${REPO_ROOT}/docker-compose.yml" "${ROOT}${PREFIX}/docker-compose.yml" | ||
| cp "${REPO_ROOT}/.env.example" "${ROOT}${PREFIX}/.env.example" | ||
| cp "${REPO_ROOT}/install.sh" "${ROOT}${PREFIX}/install.sh" | ||
| cp -R "${REPO_ROOT}/samba" "${ROOT}${PREFIX}/samba" | ||
| cp -R "${REPO_ROOT}/avahi" "${ROOT}${PREFIX}/avahi" | ||
| cp -R "${REPO_ROOT}/scripts" "${ROOT}${PREFIX}/scripts" | ||
| cp -R "${REPO_ROOT}/web" "${ROOT}${PREFIX}/web" |
Comment on lines
+23
to
+29
| libexec.install "docker-compose.yml" | ||
| libexec.install ".env.example" | ||
| libexec.install "install.sh" | ||
| libexec.install "samba" | ||
| libexec.install "avahi" | ||
| libexec.install "scripts" | ||
| libexec.install "web" |
Comment on lines
+219
to
+232
| - name: Commit + push tap update | ||
| working-directory: tap | ||
| env: | ||
| VERSION: ${{ needs.tarball-hash.outputs.version }} | ||
| run: | | ||
| set -euo pipefail | ||
| git config user.name "timenest-release-bot" | ||
| git config user.email "release-bot@users.noreply.github.com" | ||
| git add Formula/timenest.rb | ||
| if git diff --cached --quiet; then | ||
| echo "no changes; nothing to commit"; exit 0 | ||
| fi | ||
| git commit -m "timenest ${VERSION}" | ||
| git push origin HEAD |
| # Canonical source lives in the main repo at homebrew/timenest.rb. The | ||
| # tap (momenbasel/homebrew-timenest) hosts an identical copy. The | ||
| # release workflow rewrites url + sha256 + version on every tagged | ||
| # release and opens a PR against the tap. |
Comment on lines
+26
to
+30
| curl -fsSL "https://download.docker.com/linux/static/stable/${dockerArch}/docker-${DOCKER_CLI_VERSION}.tgz" \ | ||
| -o /tmp/docker.tgz; \ | ||
| tar -xzf /tmp/docker.tgz -C /tmp; \ | ||
| install -m 0755 /tmp/docker/docker /usr/local/bin/docker; \ | ||
| rm -rf /tmp/docker /tmp/docker.tgz; \ |
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.
Summary
FileNotFoundError500 on dashboard): hardensamba_mgr._execso the dashboard degrades to an empty session list, and replace the unreliabledocker.ioapt package inweb/Dockerfilewith the pinned static docker CLI binary from download.docker.com.get_settingsimport (ruff F401) and add targeted# shellcheck disable=SC2016directives next to the intentional single-quotedenvsubstcalls. Unblocks dependabot PR build(deps): bump python-multipart from 0.0.19 to 0.0.27 in /web #4.docker.ymlnow installssigstore/cosign-installerand signs every pushed image keylessly via GitHub OIDC.release.yml(tag-triggered) builds a macOS.pkg, codesigns with Developer ID Installer, notarizes + staples vianotarytool, attaches it to the GitHub release, then pushes aFormula/timenest.rbbump tomomenbasel/homebrew-timenest.scripts/build-pkg.shis a reproducible local + CI builder (productbuild).bin/timenestis the CLI shim used by both Homebrew and the.pkg.homebrew/timenest.rbis the canonical formula; the workflow rewritesurl/sha256/versionon each tag.docs/release-pipeline.mddocuments the required secrets, the one-time bootstrap (tap repo, Apple cert, notary password, PAT), and verify commands.Closes #3.
Required secrets before cutting the first release
See
docs/release-pipeline.mdfor full details. TL;DR:APPLE_DEVELOPER_ID_INSTALLER_CERT_BASE64APPLE_DEVELOPER_ID_INSTALLER_CERT_PASSWORDAPPLE_DEVELOPER_ID_INSTALLER_IDENTITYAPPLE_KEYCHAIN_PASSWORDAPPLE_IDAPPLE_TEAM_IDAPPLE_NOTARY_PASSWORD(app-specific password)HOMEBREW_TAP_TOKEN(fine-grained PAT scoped to the tap repo)Cosign needs no secrets; it uses the workflow's OIDC token + Sigstore Fulcio.
Test plan
ruff check web/passes locallyshellcheck(with CI opts) passes on all scripts incl. newbin/timenestandscripts/build-pkg.sh@dependabot rebaseon build(deps): bump python-multipart from 0.0.19 to 0.0.27 in /web #4, confirm green, mergev*after secrets are added; verify cosign signature on a pushed image andpkgutil --check-signatureon the produced.pkgbrew tap momenbasel/timenest && brew install timenest && timenest --help