feat(preview): add Gitea/Forgejo support for preview deployments - #5149
Open
ankit8697 wants to merge 2 commits into
Open
feat(preview): add Gitea/Forgejo support for preview deployments#5149ankit8697 wants to merge 2 commits into
ankit8697 wants to merge 2 commits into
Conversation
Preview deployments were wired exclusively to GitHub: only
`/api/deploy/github` reacted to `pull_request` events,
`createPreviewDeployment` refused to run without an `application.githubId`
and talked to octokit directly, and `deployPreviewApplication` only cloned
when `sourceType === "github"` - silently reporting success without
building for every other source type.
Gitea/Forgejo repositories now get the same feature, driven by the
per-application webhook that already exists for push auto deployments
(`/api/deploy/{refreshToken}`), so users only have to enable the pull
request events on the webhook they already created.
- Add the Gitea REST helpers preview deployments need: issue comment
create/update/get/list and the collaborator permission lookup, all
going through `findGiteaById` because `findApplicationById` redacts the
access token.
- Introduce `services/preview-comment.ts`, a provider-agnostic layer that
resolves the pull request coordinates of an application and dispatches
comment and permission calls to GitHub or Gitea. The GitHub branches
delegate to the existing functions, so GitHub behaviour is unchanged.
- Teach `createPreviewDeployment`, `deployPreviewApplication` and
`rebuildPreviewApplication` to use that layer, and clone Gitea
repositories for previews.
- Handle Gitea/Forgejo `pull_request` deliveries before the `autoDeploy`
gate, mapping Gitea's action names (`synchronized`, `label_updated`,
`label_cleared`) onto the existing create/redeploy/remove behaviour and
preserving the collaborator check, preview labels and preview limit.
- Validate that the payload repository matches the one the application is
configured for, skip pull requests from forks, and short circuit the
permission lookup for the repository owner (Gitea only answers that
endpoint for repository admins).
- Fail explicitly instead of reporting a successful preview deployment for
source types that cannot build one, and guard the Gitea clone against a
missing owner, repository, branch or access token.
- Show the icon of the configured provider on the pull request link and
explain which Gitea webhook events previews need.
Ran the handler against webhook deliveries captured from a live Gitea 1.24.3 instance (pull request opened by a write collaborator, a second commit pushed to it, a label added, then all labels cleared, plus a comment on the same pull request) and added those payloads as fixtures. Two things the live run corrected: - Gitea sends pull request *comment* deliveries as `X-Gitea-Event: issue_comment` with `X-Gitea-Event-Type: pull_request_comment`. Dokploy posts preview status comments itself, so those deliveries come straight back to the same webhook and must not be treated as pull request events - which the exact match on the generic event name already does, and a prefix match on the event type would not. - On a public repository Gitea reports a non-collaborator as `read`, not as a 404, so the comment claiming otherwise was wrong. Also confirmed live: `owner` is returned as a distinct permission for the repository owner, and the permission endpoint answers 403 when the connected account is not a repository admin, which is the case the handler reports as unverified instead of blaming the pull request author.
| * Actions that refresh an existing preview but never create one - the GitHub | ||
| * handler treats `unlabeled` the same way. | ||
| */ | ||
| const UPDATE_ONLY_ACTIONS = ["label_cleared"]; |
Contributor
There was a problem hiding this comment.
Cleared labels trigger redeployment
When a Gitea pull request with an existing preview loses all labels, the label_cleared payload still contains the previous label, so this update-only action passes the configured-label check and queues a new deployment for an ineligible pull request.
Knowledge Base Used: Application Deployment Flow
3 tasks
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.
What is this PR about?
Preview deployments are currently GitHub-only:
pages/api/deploy/github.tsis the only handler that reacts topull_requestevents,createPreviewDeploymentrefuses to run without anapplication.githubIdand talks to octokit directly, anddeployPreviewApplicationonly clones whensourceType === "github"— for any other source type it skips the build entirely yet still posts a ✅ "success" comment on the pull request. This PR makes preview deployments work for Gitea and Forgejo repositories (Forgejo's API is Gitea-compatible, so the existing "Gitea" provider covers both). Following the first option suggested in #3828, pull request events are handled on the deployment webhook that already exists per application (/api/deploy/{refreshToken}) rather than on a new one, so there is no migration and users only have to tick the pull request events on the webhook they already created for push auto-deploy. Behind that, the preview lifecycle gains a small provider-agnostic layer for pull request comments and permission checks; the GitHub branches of it delegate to the existing functions, so GitHub behaviour is unchanged.Checklist
Before submitting this PR, please make sure that:
canarybranch.Issues related (if applicable)
closes #3828
Screenshots (if applicable)
The only UI change is a provider-aware icon on the "Pull Request" link and an info block on the Preview Deployments tab explaining which Gitea webhook events previews need:
That detail is not cosmetic: Gitea exposes those as separate checkboxes, and enabling only "Pull Request" is the failure mode where previews appear but never refresh.
What changed, file by file
Gitea REST helpers —
packages/server/src/utils/providers/gitea.tsgiteaApiRequest, an authenticated fetch wrapper that resolvesgiteaInternalUrl ?? giteaUrland refreshes the token first.checkGiteaUserRepositoryPermissions.findGiteaById, notapplication.gitea, becausefindApplicationByIdredactsaccessToken.Provider-agnostic preview comments —
packages/server/src/services/preview-comment.ts(new)getPreviewCommentContext(application)resolves the pull request coordinates for a GitHub or Gitea application, and returnsnullfor source types that cannot host previews.createPreviewComment/updatePreviewComment/ensurePreviewComment/createPreviewSecurityBlockedComment/checkPreviewAuthorPermissionsdispatch on the provider inside the function body (not via a module-level table, because of the existinggithub.ts↔preview-deployment.tsimport relationship).Preview lifecycle —
services/preview-deployment.ts,services/application.tscreatePreviewDeploymentno longer hard-requiresgithubId.deployPreviewApplicationandrebuildPreviewApplicationwrite their status comment through the dispatch layer, anddeployPreviewApplicationclones Gitea repositories.createPreviewDeploymentCommentbecame unused once the callers moved toensurePreviewComment, so it is removed — which also drops thegithub.ts↔preview-deployment.tsimport cycle.Webhook handling —
apps/dokploy/server/utils/gitea-preview.ts(new), wired intopages/api/deploy/[refreshToken].tsautoDeploygate, because previews are a separate feature from push auto-deploy and must work with auto-deploy off.synchronized(not GitHub'ssynchronize), andlabel_updated/label_clearedinstead oflabeled/unlabeled. The collaborator check, thepreviewLabelsfilter and thepreviewLimitcap (still applied only to new previews, per 98dbc59) all carry over.X-Gitea-Event/X-Forgejo-Event. Gitea folds every pull request sub-event into the single namepull_requestand keeps the specific one inX-Gitea-Event-Type, so the generic header alone is sufficient. The GitHub compatibility headers Gitea also sends are deliberately ignored so GitHub deliveries keep taking the existing path.Drive-by fixes in the code being touched
deployPreviewApplicationnow throws for source types it cannot build, instead of reporting success without building.cloneGiteaRepositoryuses its (previously dead)getErrorCloneRequirementsguard and checks the access token, so a half-configured provider gets a clear error instead ofgit clone .../null/null.git.Hardening beyond parity with the GitHub handler
The GitHub handler derives its application set from the payload (
repository,owner,branch,githubIdare all in thewhere). Here the application comes from the URL, so the payload needs checking:repository.nameand the repository owner must matchgiteaRepository/giteaOwner, case-insensitively. Without this, a webhook on any repository could deploy an arbitrary branch of the configured one.cloneGiteaRepositoryalways clones the configured repository, so a fork-only branch would just produce a failing build.closedcleanup is scoped to this application. Gitea pull request ids are per-instance auto-increments (a fresh test instance issued id1), so the installation-widefindPreviewDeploymentsByPullRequestIdlookup that is safe for GitHub — whose ids are globally unique — would collide across two Gitea instances./collaborators/{u}/permissionfor site admins, repository admins, or users asking about themselves; everyone else gets a 403. A 403 is therefore reported as unverified and skips the deployment without posting the "you lack access" comment, because it means the Dokploy-side account lacks repository admin, not that the author is untrusted.owneris allow-listed alongsidewriteandadmin. Gitea returnsowneras a distinct role and has nomaintainlevel, so the "Required Level" line in the blocked-deployment comment is now per-provider.How this was verified, and what is still untested
pnpm typecheck,pnpm buildandpnpm testall pass locally — the same three jobspull-request.ymlruns. 40 new tests pass alongside the existing suite; overall 908 pass, and the only 5 failures (application.real.test.ts,env-file-literals.test.ts) reproduce identically on a cleancanarycheckout because they need real nixpacks/Docker.Rather than rely on the API docs, I stood up a real Gitea 1.24.3 instance (SQLite; four users: repository owner, write collaborator, read collaborator, outsider), opened a real pull request as the write collaborator, pushed a second commit to it, added a label, cleared the labels, and pointed a repository webhook at a capture server.
X-Gitea-Eventfor opened / sync / label add / label clearpull_requestin all four cases; the specific type inX-Gitea-Event-Typeopened,synchronized,label_updated,label_clearedpull_request.id/number/title/html_url/user.login/base.ref/head.ref/head.sha/head.repo.owner.login,repository.name/owner.login)/collaborators/{u}/permissionownerfor the repository owner, pluswriteandread; 403 when the connected account is not a repository adminTwo things that testing corrected, both fixed here and covered by fixtures captured from that instance (
__test__/deploy/fixtures/gitea-pull-request-deliveries.json):X-Gitea-Event: issue_commentwithX-Gitea-Event-Type: pull_request_comment. Dokploy posts preview status comments itself, so those deliveries come straight back to the same webhook. The exact match on the generic event name rejects them correctly; a prefix match on the event type, which I had considered, would have routed them into the pull request handler.read, not 404. Harmless for behaviour — still blocked, with an accurate comment — but a code comment claimed otherwise.Still untested: a full end-to-end preview build and deploy from a running Dokploy instance, which needs Postgres, Redis, Docker Swarm, Traefik and wildcard DNS I could not stand up. The Gitea-specific surface this PR changes is covered above; the build and deploy machinery downstream of it is provider-independent and untouched apart from the one new
cloneGiteaRepositorycall, whose emitted command is unit-tested. Worth one real end-to-end pass before merging.Other notes for reviewers
X-Gitea-SignatureandX-Hub-Signature-256on every delivery (the test instance sent both even with no secret configured), and it is tempting to verify one against therefreshToken. Verifying only when the header is present buys nothing — an attacker holding the token simply omits it — and making it mandatory would break every existing push webhook whose secret is something else. The trust model for this endpoint is unchanged: therefreshTokenin the URL is the shared secret, and it is rotatable from the UI. Anyone who needs real HMAC verification wants a provider-level Gitea webhook with a stored secret, which thepreview-comment.tsabstraction makes cheap to add later.gitsource type pointing at a Gitea instance stays unsupported — there is nogiteaIdto authenticate comment writes with. Such a delivery now returns a message saying so, rather than a bare "Branch Not Match".openapi.jsonneeds no regeneration (no new tRPC procedures), no audit-log entry is added (webhooks do not audit today for any provider), and the preview components contain not()calls, so there are no i18n keys to add.Greptile Summary
This PR adds Gitea and Forgejo preview-deployment support through application deployment webhooks and introduces provider-neutral preview comments and permission checks.
Confidence Score: 4/5
The PR should not merge until clearing required Gitea preview labels no longer triggers a fresh deployment of the existing preview.
A captured Gitea
label_clearedpayload retains the former label, so the new handler passes its label filter and queues an existing preview even though that label has just been removed.Files Needing Attention: apps/dokploy/server/utils/gitea-preview.ts
Reviews (1): Last reviewed commit: "test(preview): verify gitea preview webh..." | Re-trigger Greptile
Context used: