Hosted onnoosphere.hyper.mediavia theHypermedia Protocol

Channel-Based Release workflowReleasing by pushing a tag is not the best way to trigger app releases

The main purpose of this project is to remove the need of pushing a tag to create a new desktop release. This will improve the development workflow and experience when creating new app releases, making it easier to build and trigger new app versions by anyone in the team.

Current State Analysis

Desktop App Release Process

Production Releases:

  • Triggered by git tags matching *.*.* pattern (e.g., 2025.9.4)

  • Version locked to git tag via VITE_VERSION: ${{ github.ref_name }}

  • Build process:

    1. Create git tag (e.g., 2025.9.4)

    2. Push tag to trigger .github/workflows/release-desktop.yml

    3. Builds for all platforms (macOS x64/arm64, Linux, Windows)

    4. Publishes to GitHub Releases as prerelease

    5. Generates latest.json via separate workflow when release is published

Dev Releases:

  • Triggered by schedule (weekdays at 8 AM UTC) or manual dispatch

  • Auto-generates versions: {latest-prod-tag}-dev.{increment} (e.g., 2025.9.4-dev.1)

  • Publishes to S3: s3://seedappdev/dev/

  • Includes auto-update support via update-electron-app

Current Version in package.json: 0.0.0 (placeholder, overwritten during build)

Web App Deployment

Current State:

  • Triggered by git tags (same as desktop)

  • Landing page deploys on push to main via SSH to hyper.media

  • Main web app deployment process unclear

Problems with Current Workflow

  1. Tag-Version Lock Problem:

    • Each version permanently tied to git tag

    • Cannot update/republish version once tagged

    • Hotfixes require creating new version (2025.9.4 → 2025.9.5)

  2. Hotfix Pain:

    Bug in 2025.9.4 → Must create 2025.9.5
    Another bug → Must create 2025.9.6
    Minor fix → Must create 2025.9.7
    

    Version numbers inflate quickly for patch fixes

  3. Separate Workflows:

    • Different workflows for dev vs prod

    • Confusing to maintain and understand

  4. Scheduled Dev Builds:

    • Daily builds even when no changes

    • Wastes CI resources

Proposed Solution: Channel-Based Release Flow

Core Concept

Decouple versions from git tags, using deployment channels and S3 as source of truth:

  • Version = Auto-calculated based on channel and latest S3 version

  • Channel = Where to deploy (dev or stable)

  • S3 JSON files = Source of truth for current versions (no git tag dependency)

Channel Strategy

Two channels only:

Version Strategy

Continue using CalVer: YYYY.M.PATCH

Stable Channel (Incremental within month, resets on month change):

October 2025:
  2025.10.1 → 2025.10.2 → 2025.10.3

November 2025 (month changes, patch resets):
  2025.11.1 → 2025.11.2

January 2026 (year changes):
  2026.1.1 → 2026.1.2

Dev Channel (Based on Stable):

Scenario 1: stable=2025.10.1, no dev version
→ dev=2025.10.1-dev.1

Scenario 2: stable=2025.10.1, dev=2025.10.1-dev.1
→ next dev=2025.10.1-dev.2 (increment)

Scenario 3: stable=2025.10.2, dev=2025.10.1-dev.3
→ next dev=2025.10.2-dev.1 (reset to new stable base)

Version NOT committed to package.json - Auto-calculated in GitHub Actions and set temporarily via scripts/set-desktop-version.mjs during build (same as current approach)

Architecture Changes

1. S3 Bucket Structure

Use seedreleases bucket with channel folders:

s3://seedreleases/
├── dev/
│   ├── latest/              # Mutable - always points to newest dev build
│   │   ├── latest.json
│   │   ├── RELEASES.json    # Contains current dev version
│   │   ├── SeedDev-2025.10.1-dev.5.dmg
│   │   └── ...
│   └── archive/             # Immutable historical dev builds
│       ├── 2025.10.1-dev.1/
│       ├── 2025.10.1-dev.2/
│       └── ...
└── stable/
    ├── latest/              # Mutable - current stable version
    │   ├── latest.json
    │   ├── Seed-2025.10.2.dmg
    │   └── ...
    └── archive/             # Immutable historical stable builds
        ├── 2025.10.1/
        ├── 2025.10.2/
        └── ...

2. Update Configuration Per Channel

Desktop apps read channel from build config:

// forge.config.ts
const IS_PROD_DEV = version.includes('dev')
const channel = IS_PROD_DEV ? 'dev' : 'stable'
const updateUrl = `https://seedreleases.s3.amazonaws.com/${channel}/latest/latest.json`

3. GitHub Releases (Stable Only)

  • Continue creating GitHub releases as prerelease

  • Git tag created AFTER successful build (not before)

  • Tag format: YYYY.M.PATCH (e.g., 2025.10.2)

  • Dev releases do NOT create GitHub releases or tags

Workflow Changes

ONE Unified Desktop Release Workflow

Updated workflow: .github/workflows/release-desktop.yml

Triggers: Manual dispatch only (no automatic tag-based releases):

on:
  workflow_dispatch:
    inputs:
      channel:
        type: choice
        options: [dev, stable]
        required: true
        default: 'dev'

Navigate to: Actions → Release - Desktop App (Unified) → Run workflow

Select:

  • Branch: main (or feature branch for testing)

  • Channel: dev or stable

Process:

  1. Determine channel (from manual input)

  2. Auto-calculate version:

    • Stable: Fetch latest from S3, check current date → increment or reset

    • Dev: Fetch stable + dev versions from S3 → increment or reset

  3. Run tests and type checks

  4. Set version temporarily in package.json via scripts/set-desktop-version.mjs (SAME AS CURRENT)

  5. Build binaries for all platforms

  6. Publish to S3 at s3://seedreleases/{channel}/latest/

  7. Archive copy to s3://seedreleases/{channel}/archive/{version}/

  8. Generate latest.json with download URLs

  9. Update RELEASES.json (dev channel only)

  10. If stable: Create git tag + GitHub prerelease (tag created AFTER build)

Web App Deployment (Docker Images)

Update existing Docker workflows for channel-based deployment:

Current workflows:

  • dev-docker-images.yml: Push to main → builds seedhypermedia/web:dev + other services

  • release-docker-images.yml: Tag push → builds seedhypermedia/web:latest + other services

Changes needed:

  1. Both workflows continue working as-is (triggers unchanged)

  2. Add version metadata to Docker images:

    • Dev: Use calculated dev version (e.g., 2025.10.1-dev.5)

    • Stable: Use calculated stable version (e.g., 2025.10.2)

  3. Docker images built:

    • seedhypermedia/web:{dev|latest}

    • seedhypermedia/notify:{dev|latest}

    • seedhypermedia/site:{dev|latest} (backend daemon)

    • seedhypermedia/monitord:{dev|latest}

    • seedhypermedia/relayd:{dev|latest}

Web and desktop share same version - calculated once, used for both

Updated Release Processes

Scenario 1: Regular Stable Release (Manual Trigger)

# Current stable: 2025.10.1
# Goal: Release 2025.10.2

# 1. Merge changes to main
git checkout main
git pull
git push

# 2. Manually trigger release via GitHub Actions UI
# Navigate to: Actions → Release - Desktop App (Unified) → Run workflow
# Select:
#   - Branch: main
#   - Channel: stable

# 3. GitHub Actions automatically:
#    - Calculates next version (2025.10.2)
#    - Runs tests
#    - Builds for all platforms
#    - Publishes to S3 stable/latest/
#    - Archives to S3 stable/archive/2025.10.2/
#    - Creates git tag 2025.10.2
#    - Creates GitHub prerelease
#    - Updates latest.json

# 4. Users on auto-update get 2025.10.2

# Version automatically calculated based on:
# - Current date: 2025.10
# - Latest stable from S3: 2025.10.1
# - Result: 2025.10.2 (patch increment)

Scenario 2: Hotfix with Version Increment

# Current stable: 2025.10.2
# Critical bug discovered

# 1. Create hotfix branch
git checkout -b hotfix/critical-bug main

# 2. Fix the bug
# ... make changes ...
git commit -m "fix: critical bug"

# 3. Merge to main
git checkout main
git merge hotfix/critical-bug
git push

# 4. Manually trigger release
# GitHub Actions UI → Run workflow
#   - Branch: main
#   - Channel: stable

# 5. GitHub Actions automatically:
#    - Calculates version: 2025.10.3 (increments from 2025.10.2)
#    - Builds and releases
#    - Creates tag 2025.10.3
#    - Users get 2025.10.3 via auto-update

Scenario 3: Manual Dev Build for Testing

# Current stable: 2025.10.2
# Current dev: 2025.10.2-dev.4
# Want to test new feature

# 1. Merge feature to main
git checkout main
git merge feat/new-feature
git push

# 2. Manually trigger dev release
# GitHub Actions → Run release-desktop.yml
#   Input: channel = dev

# 3. GitHub Actions automatically:
#    - Calculates version: 2025.10.2-dev.5
#    - Builds for all platforms
#    - Publishes to S3 dev/latest/
#    - Archives to S3 dev/archive/2025.10.2-dev.5/
#    - Updates RELEASES.json with currentRelease: 2025.10.2-dev.5
#    - NO GitHub release created

# 4. Team tests 2025.10.2-dev.5 internally

Scenario 4: Stable Release After Dev Testing

# Current stable: 2025.10.2
# Current dev: 2025.10.2-dev.7
# Dev build tested and ready for stable

# Manually trigger stable release
# GitHub Actions UI → Run workflow
#   - Branch: main
#   - Channel: stable

# GitHub Actions automatically:
#   - Calculates version: 2025.10.3
#   - Builds and releases
#   - Creates tag 2025.10.3
#   - Creates GitHub prerelease

# Next dev build after stable 2025.10.3
# Will auto-calculate as 2025.10.3-dev.1 (reset)

Scenario 5: Month Change (CalVer Reset)

# Current date: October 31, 2025
# Current stable: 2025.10.3
# Current dev: 2025.10.3-dev.2

# === November 1, 2025 arrives ===

# Create first November release
git checkout main
git pull

# Manually trigger release
# GitHub Actions UI → Run workflow
#   - Branch: main
#   - Channel: stable

# GitHub Actions automatically:
#    - Detects current month (November = 11)
#    - Fetches latest stable from S3: 2025.10.3 (October)
#    - Resets patch to 1 for new month
#    - Version: 2025.11.1 ✓
#    - Builds and publishes
#    - Creates git tag 2025.11.1 AFTER build

# Next release in November
# Trigger workflow again → calculates 2025.11.2

# Next dev build after month change
# GitHub Actions UI → Run workflow
#   - Branch: main
#   - Channel: dev
# Calculates: 2025.11.1-dev.1 (reset to new month base)

# === January 2026 arrives ===

# First release of new year
# Trigger workflow → calculates 2026.1.1 (year and month change, patch resets)

Migration Plan (Multi-Phase)

Phase 1: Foundation

Goal: Update scripts and test version calculation logic

Validation:

  • Version calculation matches expected values for all scenarios:

    • Within same month: 2025.10.1 → 2025.10.2 ✓

    • New month: 2025.10.3 → 2025.11.1 (if Nov) ✓

    • New year: 2025.12.5 → 2026.1.1 (if Jan) ✓

    • Dev reset: stable=2025.11.1, dev=2025.10.3-dev.5 → 2025.11.1-dev.1 ✓

  • No breaking changes to existing workflows

Phase 2: Workflow Creation

Goal: Create unified release workflow alongside existing workflows

Validation:

  • Dev builds work correctly via manual trigger

  • Stable builds work via manual trigger

  • Version calculation from S3 works correctly

  • Both channels publish to correct S3 paths

  • Git tags created after successful builds (stable only)

  • No interference with existing workflows

Phase 3: Desktop Release Cut-Over

Goal: Switch production desktop releases to new workflow

Validation:

  • Stable release succeeds via manual trigger

  • Version calculated correctly from S3

  • All platforms build correctly

  • Auto-update works for end users

  • GitHub prerelease created correctly

  • Git tag created after build

Phase 4: Docker Image Integration

Goal: Update Docker workflows to use calculated versions

Validation:

  • Docker images built with correct version metadata

  • Web and desktop share same version number

  • Existing deployment mechanisms unchanged

  • All 5 services (web, notify, site, monitord, relayd) build successfully

Phase 5: Documentation & Training

Goal: Complete documentation and train team

Validation:

  • Team members can successfully trigger releases

  • Documentation covers all scenarios

  • Emergency procedures are clear

Phase 6: Cleanup

Goal: Remove old workflows and clean up

Validation:

  • Only new workflow in use

  • No references to old workflows in docs

  • Team comfortable with new process

Benefits Summary

For Development

Unified workflow - One workflow for dev and stable, easier to maintain ✅ Faster dev testing - Manual trigger instead of scheduled, build only when needed ✅ Auto version calculation - No manual version management in package.json ✅ Clear separation - Dev vs stable clearly defined by channel

For Releases

Manual control - All releases triggered manually (no tag dependency) ✅ Incremental versions - Stable versions always increment (2025.10.1 → 2025.10.2) ✅ Hotfix friendly - Just trigger workflow, version auto-calculated ✅ GitHub prereleases - Automatically created for stable releases ✅ Git tags as byproduct - Tags created after successful builds (not required)

For Operations

Less CI waste - No scheduled builds, only on-demand dev builds ✅ Clear audit trail - S3 JSON files as source of truth, git tags optional ✅ Rollback ready - Previous versions archived in S3 ✅ Channel isolation - Dev and stable completely separate ✅ No git dependency - Version calculation entirely based on S3 state

Risk Mitigation

Risk 1: Version Calculation Error

Mitigation:

  • Extensive testing in Phase 1-2

  • Version calculation logged in workflow

  • Manual override possible via workflow_dispatch

Risk 2: Auto-Update Breakage

Mitigation:

  • Test auto-update in both channels before cut-over

  • Keep S3 structure compatible with existing update mechanism

  • Rollback plan: revert to old workflow if issues

Risk 3: S3 as Source of Truth

Mitigation:

  • Version calculation fails if S3 is unavailable (explicit error)

  • S3 JSON files are authoritative (not git tags)

  • Git tags created as byproduct of successful stable releases only

Risk 4: S3 Path Changes

Mitigation:

  • Maintain backward compatibility with existing paths

  • Gradual migration of users to new update URLs

  • Keep both old and new paths working during transition

Success Criteria

Phase 1-2 Success:

  • [x] Version calculation working correctly fetching from S3

  • [x] Dev builds successfully via manual trigger

  • [x] Stable builds successfully via manual trigger

  • [x] No git tag dependency for version calculation

  • [x] No impact on existing release process

Phase 3 Success:

  • [ ] First stable release (2025.11.1) completes successfully via new workflow

  • [ ] All platforms build and publish correctly

  • [ ] GitHub prerelease created automatically

  • [ ] Users receive auto-update without issues

Phase 4 Success:

  • [ ] Web deployment integrated with desktop releases

  • [ ] Correct environment variables per channel

  • [ ] Web and desktop versions in sync

Overall Success:

  • [ ] Team confident using new workflow

  • [ ] Zero production incidents from workflow change

  • [ ] Faster hotfix process demonstrated

  • [ ] Reduced CI costs from removed scheduled builds

Decisions Made

  1. ✅ S3 Bucket Strategy:

    • Use seedreleases bucket

    • Folders: /dev/ and /stable/

    • Add /archive/ subfolder to each for historical builds

  2. ✅ Web App Deployment:

    • Web uses existing Docker workflows

    • dev-docker-images.yml: Push to main → seedhypermedia/*:dev

    • release-docker-images.yml: Tag push → seedhypermedia/*:latest

    • No changes to deployment mechanism, just add version metadata

  3. ✅ Channel Count:

    • Two channels only: dev and stable

    • No beta channel for now

  4. ✅ Auto-Update Strategy:

    • Both dev and stable check for updates the same way

    • No changes to update checking mechanism

    • Only change: which S3 path to check (/dev/latest/ or /stable/latest/)

  5. ✅ Version Management:

    • Web and desktop share same version

    • Version calculated in GitHub Actions (not committed)

    • Set temporarily during build via scripts/set-desktop-version.mjs

    • Same approach as current workflows

  6. ✅ Version Mismatch Handling:

    • Workflow validates tag matches calculated version

    • Fails fast with clear error if mismatch

    • Shows expected version, actual tag, and instructions

  7. ✅ Dev Channel Access:

    • Manual trigger only (remove scheduled builds)

    • Restricted to team members via GitHub Actions permissions

Next Steps

  1. Review this proposal - Team discussion (you are here)

  2. Answer open questions - Team decides on recommendations

  3. Approve Phase 1 - Get sign-off to start implementation

  4. Implement Phase 1 - Update scripts (Week 1-2)

  5. Implement Phase 2 - Create workflows (Week 3-4)

  6. Execute Phase 3 - Cut over desktop releases (Week 5)

  7. Continue phases 4-6 - Web integration, docs, cleanup (Week 6-9

Questions

1. Does a tag gets added to the released code?

Yes. but the tag gets added AFTER the release is made. this happens when we create the release in Github.

2. Can we prevent release of code to the stable channel from another branch that is not main?

Yes. we can make sure the stable channel will only gets generated from the main branch.

3. Who can trigger workflows manually from Github?

Only maintainers or anyone with "Write" access to the repo. so is safe to rely on manual triggers (workflow_dispatch) on our repo. only Maintainers will be able to trigger releases.

Do you like what you are reading?. Subscribe to receive updates.

Unsubscribe anytime