feat(preview): add Gitea/Forgejo support for preview deployments - #1
Closed
ankit8697 wants to merge 2 commits into
Closed
feat(preview): add Gitea/Forgejo support for preview deployments#1ankit8697 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.
Owner
Author
|
Superseded by the upstream pull request: Dokploy#5149 This one existed only to stage the branch and draft the description. The branch ( Generated by Claude Code |
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 were wired exclusively to GitHub.
pages/api/deploy/github.tswas the only handler that reacted topull_requestevents,createPreviewDeploymentthrew"Github Account not configured correctly"unlessapplication.githubIdwas set and then called octokit directly, anddeployPreviewApplicationonly cloned whensourceType === "github"— for any other source type it skipped the build entirely yet still posted a ✅ "success" comment.This PR makes preview deployments work for Gitea and Forgejo repositories. Forgejo's API is Gitea-compatible and Dokploy's existing "Gitea" provider already serves Forgejo users, so one implementation covers both.
Approach. The issue offered two options — extend the deployment webhook to handle pull request events, or add a separate pull request webhook. This takes the first. The existing
/api/deploy/{refreshToken}URL is already per-application, so a pull request event resolves to exactly one application with no installation-id lookup; a Gitea webhook delivers all of its selected events to one URL, so a second webhook would be pure friction; and no new column is needed, hence no migration. Users only tick the pull request events on the webhook they already created for push auto-deploy.Changes
Gitea REST helpers (
packages/server/src/utils/providers/gitea.ts)giteaApiRequest, 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. The GitHub branches delegate to the existingservices/github.tsfunctions, so GitHub behaviour is unchanged.Preview lifecycle (
services/preview-deployment.ts,services/application.ts)createPreviewDeploymentno longer hard-requiresgithubId.deployPreviewApplicationandrebuildPreviewApplicationwrite their status comment through the dispatch layer, anddeployPreviewApplicationclones Gitea repositories.createPreviewDeploymentComment, which became unused once the callers moved toensurePreviewComment— that also drops thegithub.ts↔preview-deployment.tsimport cycle.Webhook handling (
apps/dokploy/server/utils/gitea-preview.ts, new, wired intopages/api/deploy/[refreshToken].ts)autoDeploygate, 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. Collaborator check,previewLabelsfilter, and thepreviewLimitcap (still only for 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 — and a prefix match on the event type would be wrong, see the verification notes below. 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 — the live instance below issued id1for the first PR — so the installation-widefindPreviewDeploymentsByPullRequestIdlookup used 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, since 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-comment is now per-provider).Verification
pnpm typecheckandpnpm server:buildpass. 47 new tests pass alongside the existing suite — 908 passing overall, with the only 5 failures (application.real.test.ts,env-file-literals.test.ts) reproducing 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: repo owner, write collaborator, read collaborator, outsider), opened a real pull request from the write collaborator, pushed a second commit to it, added a label and cleared the labels, and pointed a repository webhook at a capture server. Verified against that instance:
X-Gitea-Eventfor opened / sync / label add / label clearpull_requestin all four cases; 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 repo owner,write,read; 403 when the connected account is not a repo adminTwo things that testing corrected, both now fixed 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.What is still not verified: a full end-to-end preview build and deploy from a running Dokploy instance, which needs Postgres, Redis, Docker Swarm, Traefik and wildcard DNS that I could not stand up here. The Gitea-specific surface this PR changes is covered above; the build/deploy machinery downstream of it is provider-independent and untouched apart from the one new
cloneGiteaRepositorycall, whose emitted command is unit-tested. Please do give it one real end-to-end pass before merging.Other notes for reviewers
X-Gitea-SignatureandX-Hub-Signature-256on every delivery (the live 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 added here 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 don't audit today for any provider), and the preview components have not()calls so there are no i18n keys to add.Checklist
canarybranch.Issues related
closes Dokploy#3828
Screenshots
The only UI change is a provider-aware icon on the "Pull Request" link and an info block explaining which Gitea webhook events previews need:
That last 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.