Skip to content

feat(preview): Gitea/Forgejo preview deployments (port of upstream #5149) - #208

Merged
AminDhouib merged 84 commits into
canaryfrom
port/upr-5149
Sep 6, 2026
Merged

AminDhouib merged 84 commits into
canaryfrom
port/upr-5149

Conversation

@AminDhouib

Copy link
Copy Markdown
Member

What is this PR about?

Preview deployments in this fork work for GitHub and GitLab (applications and compose stacks). This PR adds Gitea / Forgejo preview deployments: pull request events arrive on the per-service deployment webhook that already exists (/api/deploy/{refreshToken} and /api/deploy/compose/{refreshToken}), so users only have to tick the pull request events on the webhook they already created. No migration.

Ported from upstream Dokploy/dokploy#5149 by @ankit8697, which answers upstream issue #3828. The upstream work is preserved as its own commit with the original author; a second commit carries the fork-specific adaptations.

What landed

Provider layerpackages/server/src/utils/providers/gitea.ts

  • giteaApiRequest, an authenticated fetch wrapper that prefers giteaInternalUrl and refreshes the token first. Tokens are read through findGiteaById because findApplicationById redacts accessToken.
  • Issue comment create / update / get / list, checkGiteaUserRepositoryPermissions, and getGiteaPullRequests (fork-only, for the manual build dialog).
  • cloneGiteaRepository now uses its previously dead getErrorCloneRequirements guard and checks the access token, so a half-configured provider gets a clear error instead of git clone .../null/null.git.

Provider-agnostic preview commentspackages/server/src/services/preview-comment.ts (new)

  • getPreviewCommentContext(resource) resolves the pull request coordinates for a GitHub or Gitea application/compose, and returns null otherwise. GitLab previews keep reporting status as MR notes from the GitLab webhook handler, so they resolve to null and the comment writer is a no-op for them (upstream throws here; this fork must not, or GitLab previews would break).
  • createPreviewComment / updatePreviewComment / ensurePreviewComment / createPreviewSecurityBlockedComment / checkPreviewAuthorPermissions dispatch on the provider. The GitHub branches delegate to the existing functions, so GitHub behaviour is unchanged.

Preview lifecycleservices/preview-deployment.ts, services/application.ts

  • Application and compose previews write their status comment through the dispatch layer, and application previews clone Gitea repositories on deploy and rebuild (this fork re-clones on rebuild so PR synchronize picks up the new tip).
  • Compose previews override giteaBranch on the build entity, or the base branch would be built instead of the pull request tip.
  • A failing status comment can no longer mask the real build error.

Webhook handlingapps/dokploy/server/utils/gitea-preview.ts (new), wired into both refresh-token endpoints

  • Pull request deliveries are handled before the autoDeploy gate, because previews are a separate feature from push auto-deploy.
  • Routing is an exact comparison on X-Gitea-Event / X-Forgejo-Event. Gitea folds every pull request sub-event into pull_request and keeps the specific one in X-Gitea-Event-Type; comment deliveries arrive as issue_comment with type pull_request_comment (Dokploy posts preview comments itself, so those come straight back) and are correctly rejected. The GitHub compatibility headers Gitea also sends are deliberately ignored so GitHub deliveries keep taking the existing path.
  • Gitea's action names map onto the existing behaviour: synchronized (not synchronize), label_updated / label_cleared instead of labeled / unlabeled.

UI

  • Provider-aware icon on the "Pull Request" link, an info block on both preview tabs explaining which Gitea webhook events previews need (Custom Events → Pull Request + Pull Request Synchronized, plus Pull Request Label when the labels filter is used), and the "Build Pull Request" dialog lists open Gitea pull requests.

Author gate (fork requirement)

The webhook path authorizes the pull request author (pull_request.user.login), never the event actor — the lesson from the GitLab MR webhook fix — and fails closed:

Case Result
author has write / admin / owner deploys
author has read / none blocked, "deployment blocked" comment posted once (marker-deduped)
Gitea answers 403 (the connected account is not a repository admin) skipped, no comment — reported as unverified, i.e. a Dokploy-side misconfiguration, not a statement about the author
lookup throws skipped
author is the repository owner allowed without calling the endpoint (Gitea only answers it for repository admins)
previewRequireCollaboratorPermissions === false skipped with a warning, as for GitHub/GitLab

owner is allow-listed alongside write and admin (Gitea returns it as a distinct role and has no maintain level), so the "Required Level" line of the blocked comment is now per-provider.

The manual Build Pull Request path runs the same check: assertPreviewAuthorAllowed gained a Gitea branch (preview-author-gate.ts), and the dialog passes pullRequestAuthor from the listing exactly as the GitHub/GitLab paths do. Positive and negative tests included, mock-never-called style.

Webhook signature verification — deviation, please read

This port does not verify X-Gitea-Signature, matching upstream's deliberate decision, and I could not find a way to add it that is not either theatre or a regression:

  • The shared secret for this endpoint is the refreshToken in the URL, which is per-service and rotatable from the UI — the same trust model push auto-deploy has used for every provider since it shipped.
  • Verifying only when the header is present buys nothing: an attacker holding the token simply omits it.
  • Making it mandatory, or rejecting a present-but-mismatching signature, breaks every existing Gitea webhook whose secret is something other than the refresh token — the same webhook is used for push auto-deploy.
  • Doing the HMAC over JSON.stringify(req.body) (what the GitHub App handler does) is unreliable for Gitea: Go's encoding/json HTML-escapes <, > and &, so any PR title containing them would fail verification. Getting the raw bytes means disabling the body parser for the whole deploy endpoint, i.e. touching every provider's push path for zero security gain over a secret that is already in the URL.

What the handler does instead, all of which is genuinely load-bearing because the URL identifies the service and not the repository:

  • Repository identity: repository.name and the repository owner must match giteaRepository / giteaOwner, case-insensitively — without it a webhook on any repository could deploy an arbitrary branch of the configured one.
  • Fork pull requests are skipped (the clone always targets the configured repository).
  • closed cleanup is scoped to this service: Gitea pull request ids are per-instance auto-increments, so the installation-wide lookup that is safe for GitHub would collide across two Gitea instances.
  • Dedupe: label deliveries never redeploy an existing preview (below), and createPreviewDeployment / createComposePreview still insert-first against the unique (applicationId|composeId, pullRequestId) index, so concurrent deliveries cannot create duplicate previews.

A provider-level Gitea webhook with a stored secret would allow real HMAC verification; the preview-comment.ts abstraction makes that cheap to add later. Follow-up, not in this PR.

Deviations from upstream Dokploy#5149

  1. Compose previews covered. Upstream only touches application previews. Here the handler takes a small resource adapter, so pages/api/deploy/compose/[refreshToken].ts routes Gitea pull requests through the identical flow (own limit, labels, author gate, composeId-scoped close cleanup) and compose preview comments go through the dispatch layer.
  2. Label events never redeploy an existing preview — mirrors shouldDeployPreviewDeployment in pages/api/deploy/github.ts. Upstream puts label_updated in its create-actions and redeploys on it, but label_updated also fires when a label is removed and Gitea still ships the removed label in the payload, so the label filter passes and the preview redeploys. (Greptile flagged the same thing on the upstream PR.) Covered by tests.
  3. No-op instead of throw when there is no comment context, so GitLab previews keep working.
  4. previewRequireCollaboratorPermissions on the manual path: upstream has no manual build button; this fork does, so the gate got a Gitea branch.
  5. supportsPreviewDeployments accepts gitea, gating the preview tabs and the create procedure.
  6. gitea.getGiteaPullRequests is a new tRPC query (upstream needed none). It is a query, so the MCP scope table maps it to dokploy:read by rule; the scope snapshot is updated in this PR.
  7. Fork's services/application.ts (swarm stability wait, deployment error messages, build servers, template variables) and services/preview-deployment.ts (nullable applicationId for compose previews) keep all their fork-specific lines — the upstream refactor was applied on top, not over them.

Tests

All four upstream test files are ported and adapted, plus fork-specific ones:

  • __test__/deploy/gitea-webhook-preview.test.ts — 22 cases: create/redeploy, repository mismatch, forks, branch filter, author blocked / unverified / owner / disabled, preview limit, labels, plus fork-only label-event and compose cases.
  • __test__/deploy/gitea-webhook-live-payloads.test.ts + fixtures/gitea-pull-request-deliveries.json — real deliveries captured from a Gitea 1.24.3 instance (opened / synchronized / label added / labels cleared / a PR comment), asserting header routing and the handler's behaviour on them.
  • __test__/git-provider/gitea-preview-api.test.ts — comment helpers, URL shapes, internal-URL preference, permission mapping (write/admin/owner vs read/none, 404 verified-none, 403 unverified) and the clone guards.
  • __test__/preview-deployment/author-gate.test.ts — 8 new Gitea cases (positive, blocked, unverified, owner short circuit, missing author, misconfigured provider, throwing lookup, check disabled).
  • __test__/preview-deployment/source-type.test.ts, __test__/mcp/__snapshots__/scopes-snapshot.test.ts.snap updated.

Local: pnpm --filter=dokploy run typecheck clean, pnpm --filter=@dokploy/server build clean, the Gitea/preview/MCP/GitHub/GitLab suites pass (279 tests). The only failures in a full local run are the known Windows-only ones (*.real.test.ts needing Docker Swarm, readValidDirectory), unrelated to this change.

Not verified: a real end-to-end preview build against a live Gitea instance (needs Postgres, Redis, Swarm, Traefik and wildcard DNS). The Gitea-specific surface is unit-tested against captured payloads; everything downstream of the clone is provider-independent and untouched.

Base

This branch is based on #205 (sync/upstream-v0.30.5), not on canary, so the diff shown here includes that sync. It shrinks to just the Gitea preview changes once #205 merges.

TuroYT and others added 30 commits April 9, 2026 13:17
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a one-click option to redeploy Docker Compose services with clean
volumes, removing the need to manually edit the run command.

When triggered, it runs `docker compose down --volumes` before the
standard deploy build step. The feature is only available for
docker-compose type projects (not swarm stacks).
Consistent with the rest of the codebase where every docker compose
invocation uses `env -i PATH="$PATH"` for a clean environment.
…ests

Two cases where the UI silently did nothing:

- DashboardLayout declared a `metaName` prop and 18 settings pages pass
  it, but the component only destructured `children`, so the value was
  discarded. None of those pages render their own <Head> either, so every
  settings page fell back to the default title from _app. The layout now
  renders a <title>, using the whitelabeling app name so the suffix is
  correct on rebranded instances.

- The requests table's fallback cell only rendered its message when
  `statsLogs?.data.length === 0`. While the query is in flight statsLogs
  is undefined, so that guard is false and the cell rendered nothing at
  all -- a blank panel, indistinguishable from a broken page. The query's
  isLoading flag wasn't even destructured. It now shows a spinner, matching
  the queue and deployments tables.
Enable deploy-time ${{vault.*}} resolution from Phase via the REST API
(Service Account token + SSE-enabled apps), matching existing Infisical/Doppler providers.

Fixes Dokploy#5122

Co-authored-by: Cursor <cursoragent@cursor.com>
Providers, domains and records now each have their own page instead of
a stack of modals. A provider opens its domains as cards, with the
record count loading separately so the domains appear right away. A
domain opens its records as a table with search, type filter and
pagination. Creating or editing a record happens in a panel that
slides in next to the table.

The type list was capped at A and CNAME in the zod schema, so widening
the dropdown alone would not have worked. AAAA, MX, TXT, NS, SRV, CAA
and PTR work now. MX carries a priority that Cloudflare takes as a
separate field and Route53 takes inline in the value, so the
Cloudflare client splits it out on write and puts it back on read.
The form keeps one value field for both providers.

Cloudflare proxy status is now editable and visible. A, AAAA and CNAME
records get a Proxied / DNS only toggle in the form and a cloud icon
in the table. The proxy field only goes to the API when the caller
sets it, so an update from another path cannot silently disable the
proxy.

Tests cover the MX priority round trip and the proxy rules in the
Cloudflare client.
Cloudflare treats content as read-only for SRV and CAA and expects a
data object instead, so both types were rejected on write. The client
now parses the inline value into the fields Cloudflare wants, and
builds the payload before the lookup request so a malformed value
fails without spending an API call.

The form rejects a malformed SRV or CAA value up front and shows the
expected shape, so the error lands on the field instead of coming
back from the provider.
Filtering from a later page left pageIndex past the end of the
filtered set, so the table said no records matched while the counter
above it reported matches. The page index is clamped to the available
page count, and changing the search or the type filter goes back to
the first page.
Route53 returns a record set as a list of values, but listing joined them
into one string and writing sent that string back as a single
ResourceRecord. Editing a multi-value NS, MX or TXT set therefore either
failed validation or collapsed the set into one bogus value, and creating
a record for a name that already had values replaced them silently.

Values are now newline separated end to end: listing joins with a
newline, writing splits back into one ResourceRecord per line, and
creating merges into the existing set instead of replacing it. Unquoted
TXT values get the quotes Route53 requires. The record panel shows a
textarea for Route53 and validates every line.
Adds Porkbun as a supported DNS provider alongside Cloudflare and
AWS Route53, allowing Dokploy to automatically create DNS records
for domains managed on Porkbun.

- New DnsClient implementation for the Porkbun API v3
- porkbun enum value and config schema (apiKey/secretApiKey)
- Drizzle migration for the new DnsProviderType enum value
- UI: provider icon, form fields and provider selector entry
- Unit tests covering listZones/listRecords/upsertRecord/updateRecord/deleteRecord/testConnection
getCurrentPlanForUser and getProducts only inspected subscriptions.data[0],
so a customer with more than one active Stripe subscription (e.g. Startup
plus a separately purchased additional server) could have their plan
resolved from the wrong subscription, resulting in currentPlan !== "startup"
and the HubSpot chat bubble not rendering.
Summing across all active subscriptions could mix amounts with
different billing intervals (isAnnualCurrent only reflects the
matched plan subscription), producing an inconsistent total.
…kerContextPath" lied that the default path is ".", while being the Dockerfile directory.
Previously reset-password only reset the owner account's password.
Passing an email as an argument now resets that specific user's
password instead, while omitting it keeps the existing owner-reset
behavior.

Also scopes the update to the credential (password-based) account
row via providerId, and fixes the success check to verify a row was
actually updated instead of always reporting success.
Trims and lowercases the CLI email arg to match the normalization
used elsewhere for user emails, so a differently-cased or
whitespace-padded email no longer falsely reports "User not found".
…ery fails

The loading branch added in the previous commit split the fallback cell
two ways: spinner while in flight, "No results." otherwise. That second
branch also catches the failure case -- when readStatsLogs errors,
statsLogs stays undefined and isLoading goes false, so a failed request
renders as a successful empty response.

The query's isError/error were not destructured. The cell now branches
three ways and reports the error through AlertBlock, matching how
ShowTraefikSystem surfaces a failed readDirectories query.
…it-hash-overflow-mobile

fix: prevent deployment commit hash overflow on mobile
feat: allow reset-password script to reset a specific user by email
feat: allow reset-password script to reset a specific user by email
(cherry picked from commit 5345c00)

[skip ci]
Siumauricio and others added 29 commits September 1, 2026 02:41
…nagement

feat(dns): rework provider management and support all record types
…ubscription-plan

fix: detect billing plan across all active Stripe subscriptions
Adds a modal to select and import multiple secrets from an assigned
vault provider at once, instead of typing each ${{vault.x.y}} reference
by hand. Existing keys are skipped by default and can be overridden per
row; also adds an "Access all" shortcut to the vault provider assignment
picker.
writeTraefikConfigRemote moved from an execAsyncRemote echo command to
writeFileRemote (SFTP) a while back; the test still mocked the old
execAsyncRemote path and asserted on an echo command that no longer runs.
feat: bulk import secrets from vault providers
Show the services attached to a remote server directly in its delete
confirmation modal, with a link to each service and a per-service
delete action, instead of only showing a generic 'has active
services' blocker.
feat: list associated services in delete server modal
gitlab.one, github.one, gitea.one and bitbucket.one returned the full
DB record (OAuth access/refresh tokens, client secrets, private keys,
webhook secrets, app passwords) to any org member who merely had
access to *use* a shared provider (sharedWithOrganization: true),
not just its owner or an org owner/admin.

Add canViewGitProviderSecrets() and null out the secret fields in
each .one response when the caller isn't the provider owner or an
org owner/admin.
findComposeById embedded the full github/gitlab/bitbucket/gitea
relations (client secrets, OAuth tokens, private keys, app passwords)
and compose.one only used canEditDeployGitSource to set a
hasGitProviderAccess flag, never to hide the fields — so any member
with read access to a compose service got the connected git
provider's raw credentials, regardless of their access to that
provider itself.

Exclude the same secret columns findApplicationById already excludes.
Deploys are unaffected: the actual clone step always re-fetches the
provider fresh by id (findGithubById/findGitlabById/...), it never
reads secrets off the embedded relation.
…leak

fix: stop leaking git provider secrets to non-owner org members
…server setup

- Onboarding wizard (Welcome -> Plan -> Project -> Server -> Deploy ->
  Complete), shown once to an org owner with zero projects and no active
  plan/trial; skippable per step or entirely
- Billing page shows the org's current plan, and a no-card 14-day trial
  card when eligible
- Post-checkout "Welcome to Dokploy Cloud" modal simplified to reuse the
  onboarding wizard's own project/server/deploy steps behind a modal
  instead of its previous standalone 6-step flow, using the app's regular
  typography instead of the wizard's display serif
- Onboarding wizard validates a persisted project still exists before
  resuming a stale session, and the dashboard layout no longer gets stuck
  redirecting to /dashboard/home once the local onboarding-active flag
  goes stale mid-session
- onboardingCompletedAt column on user, with a backfill so existing users
  aren't shown the wizard
- pnpm reset-onboarding dev script to reset a test account's onboarding
  state end to end
…an-gate

Cloud onboarding wizard, billing trial card, and post-checkout server setup
…h-default

fix/docker-context-path-default: Placeholder with name attribute "doc…
…sh-volumes

feat: add Deploy with Fresh Volumes for Docker Compose
Merges the upstream release tag v0.30.5 (v0.30.4 + v0.30.5, 78 commits)
into the fork and sets the version to v0.30.5-community.1.

Conflict resolutions (12):

- apps/dokploy/package.json: theirs + fork version.
- drizzle/meta/0188-0190_snapshot.json: ours. The fork released migrations
  at those numbers long ago, so upstream's colliding .sql files and journal
  entries were dropped and their schema delta is re-issued as a fork
  migration (follow-up commit).
- db/schema/notification.ts + settings/notifications/handle-notifications.tsx:
  upstream's serverThreshold for gotify/ntfy, plus the fork's SMTP-no-auth
  nullable credentials and scheduleFailure flag re-applied on top.
- compose/general/actions.tsx: theirs (upstream now ships the fresh-volumes
  action the fork had ported); the fork's Load Template action re-applied.
- services/compose.ts: theirs for the git-provider secret stripping in
  findComposeById and the freshVolumes plumbing; the fork's runComposeBuild
  helper is kept because the compose-preview path calls it, and it already
  performs the identical clone -> patches -> down --volumes -> build
  sequence, so fresh volumes is not applied twice. Backup-policy hooks and
  the deployment error-message fix survive.
- utils/builders/compose.ts: theirs for --project-directory-only-with-mounts
  and the new --env-file flag; the fork's isolated-network MTU handling and
  pullImagesOnDeploy flag re-applied.
- utils/docker/utils.ts: both kept. Upstream's waitForSwarmServiceConvergence
  is a new function with new call sites in the six database services; the
  fork's waitForSwarmServiceStable is only used by services/application.ts.
  The conflict was positional (both blocks land before checkPostgresHealth),
  not semantic. Upstream's check is not a superset: it returns as soon as
  running >= desired, with no post-running stability window, no exclusion of
  pre-existing tasks and no daemon-clock anchoring, so replacing the fork's
  helper with it would reintroduce the rolling-update and crash-loop bugs
  fixed in #184/#188.
- docker/logs/terminal-line.tsx: theirs (identical change, upstream spelling).
- docker/logs/docker-logs-id.tsx: upstream's responsive classes kept; the
  viewport stays rounded-t because the fork's command input form is attached
  below it. Stream termination on CONNECTING and the paused-input disable
  survive.

Adaptation in an auto-merged file: upstream's new
application.deployNginxQuickstart called generateTraefikMeDomain expecting a
bare host string. The fork's version takes a projectId and returns
{ domain, baseDomain, source }, so the call site now passes the project id
and uses .domain.

Two tests were retargeted at upstream's replacements: the Traefik remote
writer now goes through writeFileRemote (SFTP) instead of a base64 shell
command, and the MCP tool scope snapshot gains six new upstream procedures.
Upstream v0.30.4/v0.30.5 added migrations 0188_volatile_piledriver,
0189_wooden_nextwave and 0190_nappy_anita_blake. The fork released its own
0188/0189/0190 long ago, so instances have already run migrations at those
numbers and upstream's copies cannot be adopted as-is. Per the playbook's
number-collision rule, upstream's three .sql files, their snapshots and their
_journal.json entries were dropped, the fork's kept, and the schema delta
regenerated at the next free fork slot.

The generated SQL contains exactly the three upstream schema changes and
nothing else - no DROP COLUMN, so no fork column was lost in the schema
merge (organization.wildcard_domain, project.wildcardDomain,
project.useOrganizationWildcard and the mcp-oauth tables all survive).

Edits on top of the generated file:

- IF NOT EXISTS guards on both ADD VALUE and the ADD COLUMN, so an instance
  that already has the objects is a no-op.
- The data backfill from upstream's 0190 is hand-carried verbatim after a
  statement breakpoint; drizzle cannot emit data migrations. It is
  deliberately not a column DEFAULT: existing users must skip the onboarding
  wizard, new users must keep a NULL onboardingCompletedAt so it still shows.

Journal validated: idx unique, contiguous and monotonic 0..199, `when`
monotonic, and 0199's when (1788711192772) is above the previous fork tail
(0198 at 1788561607051). Files kept at LF.
Adds four notes for the next sync:

- Rule 4 (number collisions) now records the v0.30.5 case: upstream 0188/0189/
  0190 re-issued as the fork's 0199, including the hand-carried onboarding
  backfill and why it must not become a column DEFAULT.
- Adapted at v0.30.5: why waitForSwarmServiceConvergence and
  waitForSwarmServiceStable both stay, with the specific behaviours upstream's
  check lacks.
- Adapted at v0.30.5: every upstream call site of generateTraefikMeDomain has
  to be adapted to the fork's projectId argument and object return - the new
  onboarding quickstart auto-merged cleanly and only the typecheck caught it.
- Superseded at v0.30.5: remote Traefik writes moved to SFTP; the fork's
  regression test was retargeted rather than deleted.
- The cloud onboarding wizard's gate on self-hosted instances.
…lt context

Upstream f1e2467 ("fix/docker-context-path-default") changed the default
Docker build context for dockerfile apps from "the directory containing the
Dockerfile" to the repository root, so the UI placeholder that always claimed
the default is "." is now accurate. getDockerContextPath no longer returns null
and builders/docker-file.ts lost its defaultContextPath fallback.

The fork's real-execution test builds Dokploy/examples /deno, whose Dockerfile
does `COPY deno.json .`. With the context now at the repo root that COPY cannot
resolve and the build fails in about a second. Set dockerContextPath explicitly
- exactly the migration every existing subdirectory-Dockerfile app has to make.

Also add getDeploymentErrorMessage to the services/deployment mock. It is called
from deployApplication's catch block, so without it every real deploy failure
was reported as "No getDeploymentErrorMessage export is defined on the mock" and
the actual build error never reached the log - which is what hid this one.

The behaviour break is recorded in docs/UPSTREAM_SYNC.md so it reaches the
release notes: users whose Dockerfile lives in a subdirectory and copies paths
relative to it must now set dockerContextPath.
Preview deployments were wired to GitHub (and, in this fork, GitLab):
`createPreviewDeployment` talked to octokit directly, and the preview
build only cloned github/gitlab repositories.

Gitea/Forgejo repositories now get the same feature, driven by the
per-service 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. No migration.

- 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 a service 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 service is
  configured for, skip pull requests from forks, scope the `closed`
  cleanup to this service (Gitea pull request ids are only unique per
  instance) and short circuit the permission lookup for the repository
  owner (Gitea only answers that endpoint for repository admins).
- Guard the Gitea clone against a missing owner, repository, branch or
  access token, and never let a failing status comment mask the real
  build error.
- Show the icon of the configured provider on the pull request link and
  explain which Gitea webhook events previews need.

Ported from upstream Dokploy PR Dokploy#5149.
…manual builds

Adapts the upstream Gitea preview port to the fork's preview stack, which
also ships compose previews, a manual "Build Pull Request" dialog and a
pull-request-author collaborator gate.

- Compose previews: the compose refresh-token webhook routes Gitea pull
  request deliveries through the same handler (one generic resource
  adapter serves applications and compose services), compose preview
  status comments go through the provider dispatch layer, and the compose
  preview build overrides `giteaBranch` so it clones the pull request tip
  instead of the base branch.
- Manual build path: `getGiteaPullRequests` + a `gitea.getGiteaPullRequests`
  query (read scope, snapshot updated) list open pull requests, the dialog
  offers them, and `assertPreviewAuthorAllowed` gained a Gitea branch so a
  manually triggered preview runs the very same author check the webhook
  runs — including the repository-owner short circuit and treating an
  unverified answer as blocked.
- Label deliveries never redeploy an existing preview, mirroring
  `shouldDeployPreviewDeployment` in the GitHub handler. Upstream
  redeploys on `label_updated`, which also fires when a label is removed,
  and Gitea still ships the removed label in the payload — so the label
  filter would pass for a pull request that no longer carries it.
- `supportsPreviewDeployments` accepts gitea, so the preview tabs and the
  create procedure allow it for applications and compose services.
- Tests: author-gate coverage for Gitea (positive, blocked, unverified,
  owner short circuit, missing author, misconfigured provider, throwing
  lookup, check disabled), compose webhook coverage, and label-event
  coverage on an existing preview.
@AminDhouib
AminDhouib merged commit e25406a into canary Sep 6, 2026
3 checks passed
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.