Skip to content

feat: signed release pipeline + fix issue #3 web crash + green CI#5

Merged
momenbasel merged 2 commits into
mainfrom
feat/release-pipeline
May 14, 2026
Merged

feat: signed release pipeline + fix issue #3 web crash + green CI#5
momenbasel merged 2 commits into
mainfrom
feat/release-pipeline

Conversation

@momenbasel

Copy link
Copy Markdown
Owner

Summary

  • Fix issue bug: Fails to load web-service #3 (FileNotFoundError 500 on dashboard): harden samba_mgr._exec so the dashboard degrades to an empty session list, and replace the unreliable docker.io apt package in web/Dockerfile with the pinned static docker CLI binary from download.docker.com.
  • Unbreak CI: drop an unused get_settings import (ruff F401) and add targeted # shellcheck disable=SC2016 directives next to the intentional single-quoted envsubst calls. Unblocks dependabot PR build(deps): bump python-multipart from 0.0.19 to 0.0.27 in /web #4.
  • Release pipeline:
    • docker.yml now installs sigstore/cosign-installer and signs every pushed image keylessly via GitHub OIDC.
    • New release.yml (tag-triggered) builds a macOS .pkg, codesigns with Developer ID Installer, notarizes + staples via notarytool, attaches it to the GitHub release, then pushes a Formula/timenest.rb bump to momenbasel/homebrew-timenest.
    • scripts/build-pkg.sh is a reproducible local + CI builder (productbuild).
    • bin/timenest is the CLI shim used by both Homebrew and the .pkg.
    • homebrew/timenest.rb is the canonical formula; the workflow rewrites url/sha256/version on each tag.
    • docs/release-pipeline.md documents 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.md for full details. TL;DR:

  • APPLE_DEVELOPER_ID_INSTALLER_CERT_BASE64
  • APPLE_DEVELOPER_ID_INSTALLER_CERT_PASSWORD
  • APPLE_DEVELOPER_ID_INSTALLER_IDENTITY
  • APPLE_KEYCHAIN_PASSWORD
  • APPLE_ID
  • APPLE_TEAM_ID
  • APPLE_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 locally
  • shellcheck (with CI opts) passes on all scripts incl. new bin/timenest and scripts/build-pkg.sh
  • CI green on this PR
  • After merge: comment @dependabot rebase on build(deps): bump python-multipart from 0.0.19 to 0.0.27 in /web #4, confirm green, merge
  • Tag a v* after secrets are added; verify cosign signature on a pushed image and pkgutil --check-signature on the produced .pkg
  • brew tap momenbasel/timenest && brew install timenest && timenest --help

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.
Copilot AI review requested due to automatic review settings May 14, 2026 22:02
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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.io install with a pinned static Docker CLI download; make samba_mgr._exec degrade gracefully when docker is 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 .pkg and updates a Homebrew tap.
  • Distribution: add a timenest CLI shim, a .pkg build 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 thread scripts/build-pkg.sh
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 thread homebrew/timenest.rb
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
Comment thread homebrew/timenest.rb
# 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 thread web/Dockerfile
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; \
@momenbasel momenbasel merged commit e7ddd53 into main May 14, 2026
6 checks passed
@momenbasel momenbasel deleted the feat/release-pipeline branch May 14, 2026 22:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: Fails to load web-service

2 participants