Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
509155e
Improve source control provider setup guidance
roomote Jul 13, 2026
b154e71
Add directional setup step transitions
brunobergher Jul 14, 2026
299281e
fix gitea base URL normalization
brunobergher Jul 14, 2026
5d16d85
ui details
brunobergher Jul 14, 2026
13176d2
Animate pre-sign-in setup steps
brunobergher Jul 14, 2026
3a78ac4
Match setup transition timing
brunobergher Jul 14, 2026
b038f7d
normalize gitea URLs when saving config
brunobergher Jul 14, 2026
3d002c0
ui
brunobergher Jul 14, 2026
2761611
feat(gitlab): add deployment OAuth connection
brunobergher Jul 14, 2026
878e9f5
feat(gitea): make OAuth the primary source-control connection
brunobergher Jul 14, 2026
095cb07
test(gitlab): update OAuth API expectations
brunobergher Jul 14, 2026
50e330c
feat(setup): show Gitea OAuth redirect instructions
brunobergher Jul 14, 2026
1c66a7e
Remove GitLab PAT deployment fallback
brunobergher Jul 14, 2026
2f26b09
feat(setup): authorize Gitea after saving credentials
brunobergher Jul 14, 2026
e17104d
docs(setup): clarify Gitea OAuth instructions
brunobergher Jul 14, 2026
f5ab6c0
gitlab oauth fixes
brunobergher Jul 14, 2026
f28dee8
sync fix
brunobergher Jul 14, 2026
e6f23df
Automatically sync repositories during onboarding
brunobergher Jul 14, 2026
52bcf37
invoke
brunobergher Jul 14, 2026
baa138f
fix GitLab webhook setup after OAuth sync
brunobergher Jul 14, 2026
a3f9541
fix GitLab OAuth API authentication
brunobergher Jul 14, 2026
aa9c0a1
chore(web): clean up setup lint exports
brunobergher Jul 14, 2026
ffa01f8
default gitea url
brunobergher Jul 14, 2026
0c071dc
fix gitea oauth webhook registration
brunobergher Jul 14, 2026
7c4b1ef
invoke
brunobergher Jul 14, 2026
1a00ab3
fix(source-control): resolve CI test and CodeQL findings
brunobergher Jul 14, 2026
561706b
fix gitea comment setup guidance
brunobergher Jul 14, 2026
42eca04
fix gitea oauth callback setup
brunobergher Jul 14, 2026
b4a83fb
address source control oauth review feedback
brunobergher Jul 14, 2026
e8bfcfe
fix: address remaining source control review feedback
roomote Jul 14, 2026
623d44e
fix: avoid polynomial URL normalization regexes
roomote Jul 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .env.production.example
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,8 @@ DEFAULT_COMPUTE_PROVIDER=docker
# R_GITHUB_CLIENT_ID=
# R_GITHUB_CLIENT_SECRET=
# R_GITHUB_WEBHOOK_SECRET=
# GITLAB_TOKEN=
# GITLAB_CLIENT_ID=
# GITLAB_CLIENT_SECRET=
# GITLAB_WEBHOOK_SECRET=
# GITLAB_WEBHOOK_SIGNING_TOKEN=

Expand Down
23 changes: 18 additions & 5 deletions apps/api/src/handlers/gitea/__tests__/handleComment.test.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 43 additions & 0 deletions apps/api/src/handlers/gitea/__tests__/index.test.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 10 additions & 27 deletions apps/api/src/handlers/gitea/handleComment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ import {
findActiveGitHubPrReviewTask,
findReusableGitHubPrFollowUpOwner,
} from '@roomote/db/server';
import {
createGiteaPullRequestComment,
getGiteaDeploymentUser,
} from '@roomote/gitea';
import { createGiteaPullRequestComment } from '@roomote/gitea';
import {
type TaskPayload,
TaskPayloadKind,
Expand All @@ -15,7 +12,10 @@ import {
} from '@roomote/types';

import type { WebhookResponse } from '../../types';
import { buildSourceControlAccountLinkRequiredMessage } from '../source-control-account-linking';
import {
buildSourceControlAccountLinkRequiredMessage,
buildSourceControlEnvironmentRequiredMessage,
} from '../source-control-account-linking';
import {
sendMessageToTask,
steerMessageToTask,
Expand All @@ -42,23 +42,6 @@ function isGiteaMention(commentBody: string): boolean {
return commentBody.toLowerCase().includes(GITEA_MENTION_HANDLE);
}

async function isDeploymentTokenAuthor(username: string): Promise<boolean> {
try {
const deploymentUser = await getGiteaDeploymentUser();

return (
!!deploymentUser &&
deploymentUser.login.toLowerCase() === username.toLowerCase()
);
} catch (error) {
console.warn(
`[handleGiteaComment] failed to resolve Gitea deployment token identity: ${error instanceof Error ? error.message : String(error)}`,
);

return false;
}
}

async function postMentionResponseComment({
repositoryFullName,
pullRequestNumber,
Expand Down Expand Up @@ -224,10 +207,7 @@ export async function handleGiteaComment(
return { status: 'ok', message: 'no_comment_author' };
}

if (
isRoomoteGiteaUsername(commenter) ||
(await isDeploymentTokenAuthor(commenter))
) {
if (isRoomoteGiteaUsername(commenter)) {
return { status: 'ok', message: 'roomote_authored_comment' };
}

Expand Down Expand Up @@ -265,7 +245,10 @@ export async function handleGiteaComment(
targetsResult.status === 'error' &&
targetsResult.code === 'account_link_required'
? buildSourceControlAccountLinkRequiredMessage('gitea')
: buildReviewerGateMissComment(),
: targetsResult.status === 'error' &&
targetsResult.message.includes('no environment mapping')
? buildSourceControlEnvironmentRequiredMessage('gitea')
: buildReviewerGateMissComment(),
});

return {
Expand Down
22 changes: 14 additions & 8 deletions apps/api/src/handlers/gitea/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,20 +62,26 @@ gitea.post('/', async (c) => {
const eventName = getGiteaEventName(headers);
const deliveryId = getGiteaDeliveryId({ body, headers });

if (
eventName === 'pull_request_comment' ||
(eventName === 'issue_comment' &&
typeof parsedJson === 'object' &&
parsedJson !== null &&
(parsedJson as { is_pull?: unknown }).is_pull === true)
) {
if (eventName === 'pull_request_comment' || eventName === 'issue_comment') {
const payload = giteaPullRequestCommentWebhookSchema.parse(parsedJson);
const isPullRequestComment =
eventName === 'pull_request_comment' || payload.is_pull === true;

await recordWebhook(
deliveryId,
`${eventName}.${payload.action}`,
payload,
() => handleGiteaComment(payload),
async () => {
if (!isPullRequestComment) {
return { status: 'ok', message: 'not_pull_request_comment' };
}

const result = await handleGiteaComment(payload);
apiLogger.info?.(
`[Gitea] ${eventName}.${payload.action} delivery ${deliveryId}: ${result.message ?? result.status}`,
);
return result;
},
{ provider: 'gitea' },
);

Expand Down
20 changes: 20 additions & 0 deletions apps/api/src/handlers/source-control-account-linking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,26 @@ function getLinkedAccountsSettingsUrl(
}
}

function getEnvironmentsSettingsUrl(): string | null {
try {
return new URL('/settings/environments', Env.R_APP_URL).toString();
} catch {
return null;
}
}

export function buildSourceControlEnvironmentRequiredMessage(
provider: SourceControlCommentProvider,
): string {
const copy = sourceControlCommentProviderCopy[provider];
const settingsUrl = getEnvironmentsSettingsUrl();
const settingsText = settingsUrl
? `[Settings -> Environments](${settingsUrl})`
: 'Settings -> Environments';

return `I saw the mention, but no Roomote environment is mapped to this ${copy.accountLabel} repository. Set up an environment and map this repository from ${settingsText}, then mention me again.`;
}

export function buildSourceControlAccountLinkRequiredMessage(
provider: SourceControlCommentProvider,
): string {
Expand Down
75 changes: 36 additions & 39 deletions apps/docs/environment-variables.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -236,45 +236,42 @@ as per-task auth tokens or workspace paths.

### Source control providers

| Env var | Required | Used for |
| ------------------------------ | ------------ | ------------------------------------------------------------------------------------------------ |
| `R_GITHUB_APP_SLUG` | GitHub | GitHub App slug. |
| `R_GITHUB_APP_ID` | GitHub | GitHub App ID. |
| `R_GITHUB_APP_PRIVATE_KEY` | GitHub | Raw GitHub App private key PEM, usually with newlines escaped as `\\n`. |
| `R_GITHUB_CLIENT_ID` | GitHub | GitHub OAuth client ID. |
| `R_GITHUB_CLIENT_SECRET` | GitHub | GitHub OAuth client secret. |
| `R_GITHUB_WEBHOOK_SECRET` | GitHub | GitHub webhook secret. |
| `GITHUB_MCP_SERVER_URL` | Optional | GitHub MCP server URL override. |
| `GITHUB_AUTOMATED_SKIP_REPOS` | Optional | Repository skip list for automated GitHub processing. |
| `GITHUB_AUTOMATED_SKIP_OWNERS` | Optional | Owner skip list for automated GitHub processing. |
| `GITLAB_TOKEN` | GitLab | GitLab personal access token for source-control setup. |
| `GITLAB_BASE_URL` | Optional | GitLab base URL for self-managed GitLab. |
| `GITLAB_CLIENT_ID` | Optional | GitLab OAuth application ID for personal account linking and merge request comment triggers. |
| `GITLAB_CLIENT_SECRET` | Optional | GitLab OAuth application secret for personal account linking and merge request comment triggers. |
| `GITLAB_WEBHOOK_SIGNING_TOKEN` | Optional | GitLab webhook signing token. |
| `GITLAB_WEBHOOK_SECRET` | Optional | GitLab webhook secret. |
| `GITEA_BASE_URL` | Gitea | Gitea base URL. |
| `GITEA_TOKEN` | Gitea | Gitea access token. |
| `GITEA_USERNAME` | Optional | Gitea username. |
| `GITEA_CLIENT_ID` | Optional | Gitea OAuth client ID. |
| `GITEA_CLIENT_SECRET` | Optional | Gitea OAuth client secret. |
| `GITEA_WEBHOOK_SECRET` | Optional | Gitea webhook secret. |
| `BITBUCKET_TOKEN` | Bitbucket | Atlassian API token with Bitbucket scopes. |
| `BITBUCKET_USERNAME` | Bitbucket | Atlassian account email that owns the API token. |
| `BITBUCKET_BASE_URL` | Optional | Bitbucket base URL. Defaults to `https://bitbucket.org` (Cloud only). |
| `BITBUCKET_CLIENT_ID` | Optional | Bitbucket OAuth client ID for personal account linking. |
| `BITBUCKET_CLIENT_SECRET` | Optional | Bitbucket OAuth client secret for personal account linking. |
| `BITBUCKET_WEBHOOK_SECRET` | Optional | Bitbucket webhook secret. |
| `ADO_ORGANIZATION` | Azure DevOps | Azure DevOps organization. |
| `ADO_TOKEN` | Azure DevOps | Azure DevOps access token. |
| `ADO_AUTH_MODE` | Optional | `pat`, `entra`, or `delegated`; omitted values retain legacy PAT/Entra inference. |
| `ADO_LINKED_ACCOUNT_ID` | Optional | Linked Azure DevOps account key used by delegated authentication. |
| `ADO_BASE_URL` | Optional | Azure DevOps base URL. |
| `ADO_USERNAME` | Optional | Azure DevOps username. |
| `ADO_CLIENT_ID` | Optional | Microsoft Entra client ID for Azure DevOps service-principal authentication. |
| `ADO_CLIENT_SECRET` | Optional | Microsoft Entra client secret for Azure DevOps service-principal authentication. |
| `ADO_TENANT_ID` | Optional | Microsoft Entra tenant ID for Azure DevOps service-principal authentication. |
| `ADO_WEBHOOK_SECRET` | Optional | Azure DevOps webhook secret. Generated automatically when Roomote configures service hooks. |
| Env var | Required | Used for |
| ------------------------------ | ------------ | ------------------------------------------------------------------------------------------- |
| `R_GITHUB_APP_SLUG` | GitHub | GitHub App slug. |
| `R_GITHUB_APP_ID` | GitHub | GitHub App ID. |
| `R_GITHUB_APP_PRIVATE_KEY` | GitHub | Raw GitHub App private key PEM, usually with newlines escaped as `\\n`. |
| `R_GITHUB_CLIENT_ID` | GitHub | GitHub OAuth client ID. |
| `R_GITHUB_CLIENT_SECRET` | GitHub | GitHub OAuth client secret. |
| `R_GITHUB_WEBHOOK_SECRET` | GitHub | GitHub webhook secret. |
| `GITHUB_MCP_SERVER_URL` | Optional | GitHub MCP server URL override. |
| `GITHUB_AUTOMATED_SKIP_REPOS` | Optional | Repository skip list for automated GitHub processing. |
| `GITHUB_AUTOMATED_SKIP_OWNERS` | Optional | Owner skip list for automated GitHub processing. |
| `GITLAB_BASE_URL` | Optional | GitLab base URL for self-managed GitLab. |
| `GITLAB_CLIENT_ID` | GitLab | GitLab OAuth application ID. |
| `GITLAB_CLIENT_SECRET` | GitLab | GitLab OAuth application secret. |
| `GITLAB_WEBHOOK_SIGNING_TOKEN` | Optional | GitLab webhook signing token. |
| `GITLAB_WEBHOOK_SECRET` | Optional | GitLab webhook secret. |
| `GITEA_BASE_URL` | Gitea | Gitea base URL. |
| `GITEA_CLIENT_ID` | Gitea | Gitea OAuth application client ID for deployment and personal account-linking grants. |
| `GITEA_CLIENT_SECRET` | Gitea | Gitea OAuth application client secret. |
| `GITEA_WEBHOOK_SECRET` | Optional | Gitea webhook secret. |
| `BITBUCKET_TOKEN` | Bitbucket | Atlassian API token with Bitbucket scopes. |
| `BITBUCKET_USERNAME` | Bitbucket | Atlassian account email that owns the API token. |
| `BITBUCKET_BASE_URL` | Optional | Bitbucket base URL. Defaults to `https://bitbucket.org` (Cloud only). |
| `BITBUCKET_CLIENT_ID` | Optional | Bitbucket OAuth client ID for personal account linking. |
| `BITBUCKET_CLIENT_SECRET` | Optional | Bitbucket OAuth client secret for personal account linking. |
| `BITBUCKET_WEBHOOK_SECRET` | Optional | Bitbucket webhook secret. |
| `ADO_ORGANIZATION` | Azure DevOps | Azure DevOps organization. |
| `ADO_TOKEN` | Azure DevOps | Azure DevOps access token. |
| `ADO_AUTH_MODE` | Optional | `pat`, `entra`, or `delegated`; omitted values retain legacy PAT/Entra inference. |
| `ADO_LINKED_ACCOUNT_ID` | Optional | Linked Azure DevOps account key used by delegated authentication. |
| `ADO_BASE_URL` | Optional | Azure DevOps base URL. |
| `ADO_USERNAME` | Optional | Azure DevOps username. |
| `ADO_CLIENT_ID` | Optional | Microsoft Entra client ID for Azure DevOps service-principal authentication. |
| `ADO_CLIENT_SECRET` | Optional | Microsoft Entra client secret for Azure DevOps service-principal authentication. |
| `ADO_TENANT_ID` | Optional | Microsoft Entra tenant ID for Azure DevOps service-principal authentication. |
| `ADO_WEBHOOK_SECRET` | Optional | Azure DevOps webhook secret. Generated automatically when Roomote configures service hooks. |

### Communications and sign-in

Expand Down
Loading
Loading