diff --git a/.github/workflows/coolify-release-branch-build.yml b/.github/workflows/coolify-release-branch-build.yml deleted file mode 100644 index 8b9e4bb4e6..0000000000 --- a/.github/workflows/coolify-release-branch-build.yml +++ /dev/null @@ -1,144 +0,0 @@ -name: Build Coolify Release Branch - -on: - push: - branches: ["release/v*"] - paths-ignore: - - .github/workflows/coolify-helper.yml - - .github/workflows/coolify-helper-next.yml - - .github/workflows/pr-quality.yaml - - docker/coolify-helper/Dockerfile - - docker/testing-host/Dockerfile - - templates/** - - CHANGELOG.md - -permissions: - contents: read - packages: write - -concurrency: - group: coolify-release-branch-build-${{ github.ref_name }} - cancel-in-progress: false - -env: - GITHUB_REGISTRY: ghcr.io - DOCKER_REGISTRY: docker.io - IMAGE_NAME: coollabsio/coolify - -jobs: - prepare: - runs-on: ubuntu-24.04 - outputs: - dev_version: ${{ steps.version.outputs.dev_version }} - short_sha: ${{ steps.version.outputs.short_sha }} - version: ${{ steps.version.outputs.version }} - steps: - - name: Resolve version from branch name - id: version - env: - BRANCH: ${{ github.ref_name }} - run: | - if [[ ! "${BRANCH}" =~ ^release/v([0-9]+)\.([0-9]+)$ ]]; then - echo "Invalid release branch: ${BRANCH}. Expected release/vX.Y, such as release/v4.5." - exit 1 - fi - - DEV_VERSION="${BASH_REMATCH[1]}.${BASH_REMATCH[2]}-dev" - SHORT_SHA="${GITHUB_SHA::7}" - echo "dev_version=${DEV_VERSION}" >> "$GITHUB_OUTPUT" - echo "short_sha=${SHORT_SHA}" >> "$GITHUB_OUTPUT" - echo "version=${DEV_VERSION}.${SHORT_SHA}" >> "$GITHUB_OUTPUT" - - build: - needs: prepare - strategy: - matrix: - include: - - arch: amd64 - platform: linux/amd64 - runner: ubuntu-24.04 - - arch: aarch64 - platform: linux/aarch64 - runner: ubuntu-24.04-arm - runs-on: ${{ matrix.runner }} - steps: - - uses: actions/checkout@v5 - with: - persist-credentials: false - - - uses: docker/setup-buildx-action@v3 - - - name: Login to ${{ env.GITHUB_REGISTRY }} - uses: docker/login-action@v3 - with: - registry: ${{ env.GITHUB_REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Login to ${{ env.DOCKER_REGISTRY }} - uses: docker/login-action@v3 - with: - registry: ${{ env.DOCKER_REGISTRY }} - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Build and push release branch image (${{ matrix.arch }}) - uses: docker/build-push-action@v6 - with: - context: . - file: docker/production/Dockerfile - platforms: ${{ matrix.platform }} - push: true - build-args: | - COOLIFY_VERSION=${{ needs.prepare.outputs.version }} - tags: | - ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare.outputs.version }}-${{ matrix.arch }} - ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare.outputs.version }}-${{ matrix.arch }} - - publish: - needs: [prepare, build] - runs-on: ubuntu-24.04 - strategy: - matrix: - registry: [ghcr.io, docker.io] - steps: - - uses: docker/setup-buildx-action@v3 - - - name: Login to ${{ env.GITHUB_REGISTRY }} - if: matrix.registry == 'ghcr.io' - uses: docker/login-action@v3 - with: - registry: ${{ env.GITHUB_REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Login to ${{ env.DOCKER_REGISTRY }} - if: matrix.registry == 'docker.io' - uses: docker/login-action@v3 - with: - registry: ${{ env.DOCKER_REGISTRY }} - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Publish release branch manifest on ${{ matrix.registry }} - env: - REGISTRY: ${{ matrix.registry }} - DEV_VERSION: ${{ needs.prepare.outputs.dev_version }} - VERSION: ${{ needs.prepare.outputs.version }} - run: | - IMAGE="${REGISTRY}/${IMAGE_NAME}" - docker buildx imagetools create \ - "${IMAGE}:${VERSION}-amd64" \ - "${IMAGE}:${VERSION}-aarch64" \ - --tag "${IMAGE}:${VERSION}" \ - --tag "${IMAGE}:${DEV_VERSION}" - - notify: - needs: [prepare, build, publish] - if: always() - runs-on: ubuntu-24.04 - steps: - - uses: sarisia/actions-status-discord@v1 - with: - webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }} - status: ${{ contains(needs.*.result, 'failure') && 'failure' || (contains(needs.*.result, 'cancelled') && 'cancelled' || 'success') }} diff --git a/.github/workflows/sync-main-to-next.yml b/.github/workflows/sync-main-to-next.yml new file mode 100644 index 0000000000..6e520633ae --- /dev/null +++ b/.github/workflows/sync-main-to-next.yml @@ -0,0 +1,65 @@ +name: Sync main to next + +on: + schedule: + - cron: '*/10 * * * *' + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +concurrency: + group: sync-main-to-next + cancel-in-progress: false + +jobs: + sync: + name: Merge main into next + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Merge main into next + env: + GH_TOKEN: ${{ github.token }} + run: | + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + git fetch origin main next + git checkout --quiet -B next origin/next + + if git merge --no-edit origin/main; then + git push origin HEAD:refs/heads/next + exit 0 + fi + + conflicts=$(git diff --name-only --diff-filter=U) + git merge --abort + + if [ -z "$conflicts" ]; then + echo '::error title=main to next sync failed::The merge failed without conflicts, so no pull request was created.' + exit 1 + fi + + sync_branch='automation/sync-main-to-next' + existing_pr=$(gh pr list --base next --head "$sync_branch" --state open --json url --jq '.[0].url') + if [ -n "$existing_pr" ]; then + # The conflict was already reported when this pull request was opened. + # Stay green until it is merged instead of failing every scheduled run. + echo "::notice title=main to next sync blocked::main still conflicts with next; resolve the open pull request: $existing_pr" + exit 0 + fi + + git push --force origin origin/main:"refs/heads/$sync_branch" + gh pr create \ + --base next \ + --head "$sync_branch" \ + --title 'chore: merge main into next' \ + --body 'This pull request was created automatically because main could not be merged into next without conflicts. Resolve conflicts on this temporary branch; never update main with next.' + + echo '::error title=main to next sync blocked::main could not be merged into next without conflicts; a pull request was opened to resolve them.' + exit 1 diff --git a/.github/workflows/sync-main-to-release.yml b/.github/workflows/sync-main-to-release.yml deleted file mode 100644 index 6ea7ea0768..0000000000 --- a/.github/workflows/sync-main-to-release.yml +++ /dev/null @@ -1,81 +0,0 @@ -name: Sync main to release branches - -on: - schedule: - - cron: '*/10 * * * *' - workflow_dispatch: - -permissions: - contents: write - pull-requests: write - -concurrency: - group: sync-main-to-release - cancel-in-progress: false - -jobs: - sync: - name: Merge main into release branches - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v5 - with: - fetch-depth: 0 - - - name: Merge main into every release/v* branch - env: - GH_TOKEN: ${{ github.token }} - run: | - git config user.name 'github-actions[bot]' - git config user.email '41898282+github-actions[bot]@users.noreply.github.com' - git fetch origin main '+refs/heads/release/v*:refs/remotes/origin/release/v*' - - branches=$(git for-each-ref --format='%(refname:lstrip=3)' 'refs/remotes/origin/release/v*') - if [ -z "$branches" ]; then - echo 'No release/v* branch exists, so there is nothing to sync.' - exit 0 - fi - - failed=0 - for branch in $branches; do - echo "::group::Sync main into $branch" - git checkout --quiet -B "$branch" "origin/$branch" - - if git merge --no-edit origin/main; then - git push origin "HEAD:refs/heads/$branch" - echo '::endgroup::' - continue - fi - - conflicts=$(git diff --name-only --diff-filter=U) - git merge --abort - echo '::endgroup::' - - if [ -z "$conflicts" ]; then - echo "::error title=main to $branch sync failed::The merge failed without conflicts, so no pull request was created." - failed=1 - continue - fi - - sync_branch="automation/sync-main-to-${branch//\//-}" - existing_pr=$(gh pr list --base "$branch" --head "$sync_branch" --state open --json url --jq '.[0].url') - if [ -n "$existing_pr" ]; then - # The conflict was already reported when this pull request was opened. - # Stay green until it is merged instead of failing every scheduled run. - echo "::notice title=main to $branch sync blocked::main still conflicts with $branch; resolve the open pull request: $existing_pr" - continue - fi - - git push --force origin origin/main:"refs/heads/$sync_branch" - gh pr create \ - --base "$branch" \ - --head "$sync_branch" \ - --title "chore: merge main into $branch" \ - --body "This pull request was created automatically because main could not be merged into $branch without conflicts. Resolve conflicts on this temporary branch; never update main with $branch." - - echo "::error title=main to $branch sync blocked::main could not be merged into $branch without conflicts; a pull request was opened to resolve them." - failed=1 - done - - exit "$failed" diff --git a/AGENTS.md b/AGENTS.md index ce9efdde49..9d5fe44b61 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -251,8 +251,8 @@ The persistent-storage implementation is the reference pattern: `Project\Service ## Git Workflow - Production branch: `main` -- Development branch: `release/v4.x`, where `x` is the next minor release (for example, `release/v4.5` while production is `4.4.x`). Do not use `next`. -- Fix PRs should target the current production branch; feature PRs should target the current `release/v4.x` branch +- Development branch: `next`. Do not create `release/v*` branches. +- Fix PRs should target the current production branch; feature PRs should target `next` === foundation rules === diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 977b90919a..d93150c6bf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -193,12 +193,11 @@ Choose the branch based on the type of change: | --- | --- | --- | | Fixes and small improvements | `main` | `main` | | Security fixes | `main` | `main` | -| New features and larger changes | `release/v4.x` | `release/v4.x` | +| New features and larger changes | `next` | `next` | - For a fix, branch from `main` and target `main`. -- For a feature, branch from `release/v4.x` and target `release/v4.x`. -- `x` is the number of the next minor release. For example, when the production version is `4.4.x`, use `release/v4.5`. There is no `next` branch. -- If a fix is discovered while developing a feature, submit it separately to `main`. Maintainers will merge `main` into `release/v4.x` so the fix is included there too. +- For a feature, branch from `next` and target `next`. +- If a fix is discovered while developing a feature, submit it separately to `main`. `main` is merged into `next` automatically, so the fix is included there too. - Pull requests targeting the wrong branch may be closed or asked to retarget. diff --git a/RELEASE.md b/RELEASE.md index 15cc64527d..e0847d2cd8 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -5,54 +5,54 @@ | Branch | Purpose | | --- | --- | | `main` | Latest production source | -| `release/v4.x` | Feature integration and RC releases for the next minor release, such as `release/v4.5` | -| `feature/*` | New features based on and merged into the current `release/v4.x` branch | +| `next` | Feature integration and RC releases for the next version | +| `feature/*` | New features based on and merged into `next` | | `hotfix/X.Y.Z` | Production fixes based on `main` | -`x` is the number of the next minor release. When `4.4.x` is the production version, the release branch is `release/v4.5`. Create a new `release/v4.x` branch from `main` after each stable minor release. Do not use a long-lived `next` branch. +`next` is the only long-lived development branch. Do not create `release/v*` branches. Release workflows never edit or commit versions. Stable versions come from `config/constants.php`; RC versions come from `coolify.nightly.version` in `versions.json` and `other/nightly/versions.json`. ## Where changes go - Fixes, security updates, and small improvements target `main`. -- New features and larger changes target the current `release/v4.x` branch. -- **Sync main to release branches** merges `main` into every `release/v*` branch every 10 minutes, so every production fix is included in the next release. If a merge conflicts, it opens a `chore: merge main into release/vX.Y` pull request to resolve. -- Do not merge `release/v4.x` into `main` until an RC is approved for a stable release. +- New features and larger changes target `next`. +- **Sync main to next** merges `main` into `next` every 10 minutes, so every production fix is included in the next release. If the merge conflicts, it opens a `chore: merge main into next` pull request to resolve. +- Do not merge `next` into `main` until an RC is approved for a stable release. ## Feature and RC flow ```text -feature/* → release/v4.x → RC +feature/* → next → RC ``` -1. Merge feature branches into `release/v4.x`. +1. Merge feature branches into `next`. 2. Set `coolify.nightly.version` in both version files to the intended RC, such as `4.5-rc.1`. -3. Every push to `release/v4.x` runs **Build Coolify Release Branch**. It calculates the version from the branch name and publishes `4.5-dev.` and the moving `4.5-dev` tag. It never publishes an RC or stable tag. +3. Every push to `next` runs **Build Coolify Next**. It publishes `sha-`, `4.5-rc.1.`, and the moving `next` tag. It never publishes the exact `4.5-rc.1` tag. 4. Create a reviewed draft GitHub Release named `v4.5-rc.1` and mark it as a prerelease. -5. Run **Release Coolify RC** manually from `release/v4.x` and enter `v4.5-rc.1`. +5. Run **Release Coolify RC** manually from `next` and enter `v4.5-rc.1`. 6. The workflow validates the draft and configured nightly version, builds the exact RC, publishes `4.5-rc.1`, updates the `next` image tag, and publishes the draft prerelease. 7. Advance `coolify.nightly.version` to the next intended RC version. ## Stable release flow ```text -release/v4.x → main → stable release +next → main → stable release ``` -1. Temporarily stop merging features into `release/v4.x`. -2. Change the version on `release/v4.x` from the approved RC to the stable version, such as `4.5.0`. -3. Merge `release/v4.x` into `main`. +1. Temporarily stop merging features into `next`. +2. Change the version on `next` from the approved RC to the stable version, such as `4.5.0`. +3. Merge `next` into `main`. 4. Create a reviewed draft GitHub Release named `v4.5.0`. 5. Run the stable release workflow from `main`. 6. The workflow rebuilds the exact stable version, publishes `4.5.0` and `latest`, then publishes the draft. 7. Update the CDN only after the release is approved. -8. Create the next release branch from `main`, such as `release/v4.6`, and set its development version. Delete the old release branch. +8. Advance `next` to the next development version, such as `4.6-rc.1`. ## Hotfix flow ```text -main → hotfix/X.Y.Z → main → release/v4.x +main → hotfix/X.Y.Z → main → next ``` 1. Create `hotfix/X.Y.Z` from `main` when a patch needs an integration branch. A single fix may use a normal branch from `main` instead. @@ -61,7 +61,7 @@ main → hotfix/X.Y.Z → main → release/v4.x 4. Merge the fix into `main`. 5. Create a reviewed draft GitHub Release named `vX.Y.Z`. 6. Run the stable release workflow from `main`. -7. Merge `main` into `release/v4.x`, resolve the version in favor of the next intended RC, and delete the hotfix branch if one was used. +7. **Sync main to next** merges `main` into `next`. If the version conflicts, resolve it in favor of the next intended RC in the sync pull request. Delete the hotfix branch if one was used. 8. Update the CDN only after the release is approved. ## Image tags @@ -69,9 +69,8 @@ main → hotfix/X.Y.Z → main → release/v4.x | Tag | Meaning | | --- | --- | | `latest` | Latest stable release | -| `next` | Latest successful `next` build (legacy branch) | -| `X.Y-dev` | Latest successful `release/vX.Y` build | -| `X.Y-dev.` | Exact `release/vX.Y` commit build | +| `next` | Latest successful `next` build or RC release | +| `X.Y-rc.N.` | Exact `next` commit build | | `X.Y.Z` | Exact stable release | | `X.Y-rc.N` | Exact RC release | | `sha-` | Exact commit build |