From 4450f03790b595f742252b8b65053feee837a7d5 Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Mon, 9 Feb 2026 15:55:10 +0100 Subject: [PATCH 1/6] feat: make api work with external Git source --- package-lock.json | 40 ++++++++++++++++++++++++++++++++++----- src/app.ts | 15 +++++---------- src/git/connect.ts | 31 ++++++++++++++++++++++++++++++ src/gitea/connect.ts | 29 ---------------------------- src/openapi/settings.yaml | 36 +++++++++++++++++++++++++++++++++++ src/otomi-stack.ts | 7 ++++--- 6 files changed, 111 insertions(+), 47 deletions(-) create mode 100644 src/git/connect.ts delete mode 100644 src/gitea/connect.ts diff --git a/package-lock.json b/package-lock.json index 82a037877..a68d7c918 100644 --- a/package-lock.json +++ b/package-lock.json @@ -196,6 +196,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -2218,6 +2219,7 @@ "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", @@ -2651,6 +2653,7 @@ "integrity": "sha512-RsUFrSB0oQHEBnR8yarKIReUPwSu2ROpbjhdVKi4T/nQhMaS+TnIQPBwkMtb2r8A1KS2Hijw4D/4bV/XHoFQWw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=20" } @@ -2732,7 +2735,8 @@ "resolved": "https://registry.npmjs.org/@cspell/dict-css/-/dict-css-4.0.19.tgz", "integrity": "sha512-VYHtPnZt/Zd/ATbW3rtexWpBnHUohUrQOHff/2JBhsVgxOrksAxJnLAO43Q1ayLJBJUUwNVo+RU0sx0aaysZfg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@cspell/dict-dart": { "version": "2.3.2", @@ -2872,14 +2876,16 @@ "resolved": "https://registry.npmjs.org/@cspell/dict-html/-/dict-html-4.0.14.tgz", "integrity": "sha512-2bf7n+kS92g+cMKV0wr9o/Oq9n8JzU7CcrB96gIh2GHgnF+0xDOqO2W/1KeFAqOfqosoOVE48t+4dnEMkkoJ2Q==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@cspell/dict-html-symbol-entities": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@cspell/dict-html-symbol-entities/-/dict-html-symbol-entities-4.0.5.tgz", "integrity": "sha512-429alTD4cE0FIwpMucvSN35Ld87HCyuM8mF731KU5Rm4Je2SG6hmVx7nkBsLyrmH3sQukTcr1GaiZsiEg8svPA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@cspell/dict-java": { "version": "5.0.12", @@ -3077,7 +3083,8 @@ "resolved": "https://registry.npmjs.org/@cspell/dict-typescript/-/dict-typescript-3.2.3.tgz", "integrity": "sha512-zXh1wYsNljQZfWWdSPYwQhpwiuW0KPW1dSd8idjMRvSD0aSvWWHoWlrMsmZeRl4qM4QCEAjua8+cjflm41cQBg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@cspell/dict-vue": { "version": "3.0.5", @@ -4300,6 +4307,7 @@ "integrity": "sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/environment": "30.2.0", "@jest/expect": "30.2.0", @@ -4835,6 +4843,7 @@ "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.3", @@ -6289,7 +6298,8 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/json5": { "version": "0.0.29", @@ -6371,6 +6381,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -6537,6 +6548,7 @@ "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", @@ -7092,6 +7104,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -8120,6 +8133,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -10859,6 +10873,7 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -10919,6 +10934,7 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -11045,6 +11061,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -11608,6 +11625,7 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -11694,6 +11712,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -14318,6 +14337,7 @@ "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/core": "30.2.0", "@jest/types": "30.2.0", @@ -15201,6 +15221,7 @@ "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz", "integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==", "license": "MIT", + "peer": true, "engines": { "node": ">= 10.16.0" } @@ -16575,6 +16596,7 @@ "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", "dev": true, "license": "MIT", + "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -19806,6 +19828,7 @@ "dev": true, "inBundle": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -21062,6 +21085,7 @@ "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -22181,6 +22205,7 @@ "integrity": "sha512-WRgl5GcypwramYX4HV+eQGzUbD7UUbljVmS+5G1uMwX/wLgYuJAxGeerXJDMO2xshng4+FXqCgyB5QfClV6WjA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@semantic-release/commit-analyzer": "^13.0.1", "@semantic-release/error": "^4.0.0", @@ -24353,6 +24378,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -24575,6 +24601,7 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -24839,6 +24866,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -25018,6 +25046,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "napi-postinstall": "^0.3.0" }, @@ -25449,6 +25478,7 @@ "version": "7.5.7", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==", + "peer": true, "engines": { "node": ">=8.3.0" }, diff --git a/src/app.ts b/src/app.ts index 0e6adea4f..0320b66df 100644 --- a/src/app.ts +++ b/src/app.ts @@ -27,19 +27,15 @@ import { CHECK_LATEST_COMMIT_INTERVAL, cleanEnv, EXPRESS_PAYLOAD_LIMIT, - GIT_PASSWORD, GIT_PUSH_RETRIES, - GIT_USER, TRUST_PROXY, } from 'src/validators' import swaggerUi from 'swagger-ui-express' -import giteaCheckLatest from './gitea/connect' +import getLatestRemoteCommitSha from './git/connect' import { getBuildStatus, getSealedSecretStatus, getServiceStatus, getWorkloadStatus } from './k8s_operations' const env = cleanEnv({ CHECK_LATEST_COMMIT_INTERVAL, - GIT_USER, - GIT_PASSWORD, EXPRESS_PAYLOAD_LIMIT, GIT_PUSH_RETRIES, TRUST_PROXY, @@ -55,13 +51,12 @@ type OtomiSpec = { } // get the latest commit from Gitea and checks it against the local values -const checkAgainstGitea = async () => { - const encodedToken = Buffer.from(`${env.GIT_USER}:${env.GIT_PASSWORD}`).toString('base64') +const checkAgainstGit = async () => { const otomiStack = await getSessionStack() - const latestOtomiVersion = await giteaCheckLatest(encodedToken) + const latestOtomiVersion = await getLatestRemoteCommitSha() // check the local version against the latest online version // if the latest online is newer it will be pulled locally - if (latestOtomiVersion && latestOtomiVersion.data[0].sha !== otomiStack.git.commitSha) { + if (latestOtomiVersion && latestOtomiVersion !== otomiStack.git.commitSha) { debug('Local values differentiate from Git repository, retrieving latest values') // Remove all .dec files await otomiStack.git.git.clean([CleanOptions.FORCE, CleanOptions.IGNORED_ONLY, CleanOptions.RECURSIVE]) @@ -207,7 +202,7 @@ export async function initApp(inOtomiStack?: OtomiStack) { if (!env.isTest) { const gitCheckVersionInterval = env.CHECK_LATEST_COMMIT_INTERVAL * 60 * 1000 setInterval(async function () { - await checkAgainstGitea() + await checkAgainstGit() }, gitCheckVersionInterval) } let server: Server | undefined diff --git a/src/git/connect.ts b/src/git/connect.ts new file mode 100644 index 000000000..0ed69d738 --- /dev/null +++ b/src/git/connect.ts @@ -0,0 +1,31 @@ +import Debug from 'debug' +import simpleGit from 'simple-git' +import { cleanEnv, GIT_BRANCH, GIT_PASSWORD, GIT_REPO_URL, GIT_USER } from 'src/validators' + +const debug = Debug('otomi:git-connect') + +const env = cleanEnv({ + GIT_REPO_URL, + GIT_BRANCH, + GIT_USER, + GIT_PASSWORD, +}) + +export default async function getLatestRemoteCommitSha(): Promise { + try { + const git = simpleGit() + const repoUrl = new URL(env.GIT_REPO_URL) + repoUrl.username = encodeURIComponent(env.GIT_USER) + repoUrl.password = encodeURIComponent(env.GIT_PASSWORD) + const result = await git.listRemote(['--refs', repoUrl.toString(), env.GIT_BRANCH]) + const [sha] = result.trim().split(/\s/) + if (!sha) { + debug('No remote commit found for branch %s', env.GIT_BRANCH) + return undefined + } + return sha + } catch (error: any) { + debug('Git remote error: ', error.message) + return undefined + } +} diff --git a/src/gitea/connect.ts b/src/gitea/connect.ts deleted file mode 100644 index edcc616f9..000000000 --- a/src/gitea/connect.ts +++ /dev/null @@ -1,29 +0,0 @@ -import axios from 'axios' -import Debug from 'debug' -import { cleanEnv, GIT_REPO_URL } from 'src/validators' - -const debug = Debug('otomi:gitea-connect') - -const env = cleanEnv({ - GIT_REPO_URL, -}) - -// get call to the api to retrieve all the commits -export default async function giteaCheckLatest(token: string): Promise { - // Extracts "http://gitea-http.gitea.svc.cluster.local:3000" or "https://gitea." - const baseDomain = new URL(env.GIT_REPO_URL).origin - if (baseDomain) { - const giteaUrl = `${baseDomain}/api/v1/repos/otomi/values/commits` - const response = await axios({ - url: giteaUrl, - method: 'GET', - headers: { - 'Content-Type': 'application/json', - Authorization: `Basic ${token}`, - }, - }).catch((error) => { - debug('Gitea error: ', error.message) - }) - return response - } -} diff --git a/src/openapi/settings.yaml b/src/openapi/settings.yaml index bbb95c6a3..a2af75941 100644 --- a/src/openapi/settings.yaml +++ b/src/openapi/settings.yaml @@ -228,6 +228,42 @@ Settings: type: object additionalProperties: false properties: + git: + type: object + title: Git Configuration + description: | + Git configuration for APL values repository. + additionalProperties: false + properties: + repoUrl: + type: string + description: | + The base URL of the Git repository (without credentials). Auto-derived for internal Gitea. + pattern: '^https?://.+' + username: + type: string + description: | + Username for authenticating with the Git repository. + Defaults to 'otomi-admin' for internal Gitea. + password: + type: string + description: Password or token for authenticating with the Git repository + x-secret: '{{ randAlphaNum 20 }}' + email: + type: string + description: | + Email address to use for Git commits. + Defaults to 'pipeline@cluster.local' for internal Gitea. + format: email + branch: + type: string + description: The branch to use in the Git repository + required: + - repoUrl + - username + - password + - email + - branch adminPassword: description: Master admin password that will be used for all apps that are not configured to use their own password. $ref: 'definitions.yaml#/adminPassword' diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index a14e57ac7..5fb61f2b7 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -188,6 +188,7 @@ function getTeamDatabaseValuesFilePath(teamId: string, databaseName: string): st return `env/teams/${teamId}/databases/${databaseName}` } +const DEFAULT_GITEA_URL = 'gitea-http.gitea.svc.cluster.local' export default class OtomiStack { private coreValues: Core editor?: string @@ -1280,10 +1281,10 @@ export default class OtomiStack { async getInternalRepoUrls(teamId: string): Promise { if (env.isDev || !teamId || teamId === 'admin') return [] + if (!env.GIT_REPO_URL.includes(DEFAULT_GITEA_URL)) return [] const { cluster, otomi } = this.getSettings(['cluster', 'otomi']) - const gitea = this.getApp('gitea') - const username = (gitea?.values?.adminUsername ?? '') as string - const password = (gitea?.values?.adminPassword ?? otomi?.adminPassword ?? '') as string + const username = (otomi?.git?.username ?? '') as string + const password = (otomi?.git?.password ?? '') as string const orgName = `team-${teamId}` const domainSuffix = cluster?.domainSuffix const internalRepoUrls = (await getGiteaRepoUrls(username, password, orgName, domainSuffix)) || [] From 524bac757570cbbd833868eea06d50d762c8e7a0 Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Tue, 10 Feb 2026 10:20:29 +0100 Subject: [PATCH 2/6] feat: only get internalRepoUrls if Gitea is enabled --- src/otomi-stack.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index 5fb61f2b7..9c4f90f6a 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -1281,7 +1281,8 @@ export default class OtomiStack { async getInternalRepoUrls(teamId: string): Promise { if (env.isDev || !teamId || teamId === 'admin') return [] - if (!env.GIT_REPO_URL.includes(DEFAULT_GITEA_URL)) return [] + const gitea = this.getApp('gitea') + if (!gitea.enabled) return [] const { cluster, otomi } = this.getSettings(['cluster', 'otomi']) const username = (otomi?.git?.username ?? '') as string const password = (otomi?.git?.password ?? '') as string From ac87e3b015c96b5e768399b2bcdd96e729128ded Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Tue, 10 Feb 2026 15:37:18 +0100 Subject: [PATCH 3/6] fix: teamApps setting enabled correctly --- src/otomi-stack.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index 9c4f90f6a..241cfb89f 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -593,7 +593,7 @@ export default class OtomiStack { return teamApps.map((app) => { return { id: app.id, - enabled: Boolean(app.values?.enabled ?? true), + enabled: app.enabled ?? true, } }) } From c05ab697b36f489c6199da24e489ca2cbab9d8bd Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Thu, 12 Feb 2026 09:57:46 +0100 Subject: [PATCH 4/6] fix: rename gitea references to git --- docs/gitops.md | 2 +- src/app.ts | 2 +- src/otomi-stack.ts | 3 +-- src/validators.ts | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/gitops.md b/docs/gitops.md index f83c9adf3..34640c311 100644 --- a/docs/gitops.md +++ b/docs/gitops.md @@ -116,7 +116,7 @@ sequenceDiagram ## Version two -The locking mechanism is removed by removing the master session controller concept. The session repo controller pulls and pushes from/to Gitea instead of master repo. The Session Controller is also renamed to `Git handler` to not confuse it with user session. +The locking mechanism is removed by removing the master session controller concept. The session repo controller pulls and pushes from/to Git instead of master repo. The Session Controller is also renamed to `Git handler` to not confuse it with user session. **Sequence diagram for the accepted request** The following diagram presents GitOps without locking mechanism. It is worth noting that is performs eight operations less comparing to its predecessor. diff --git a/src/app.ts b/src/app.ts index 0320b66df..7d98f3117 100644 --- a/src/app.ts +++ b/src/app.ts @@ -50,7 +50,7 @@ type OtomiSpec = { valuesSchema: Record } -// get the latest commit from Gitea and checks it against the local values +// get the latest commit from Git and checks it against the local values const checkAgainstGit = async () => { const otomiStack = await getSessionStack() const latestOtomiVersion = await getLatestRemoteCommitSha() diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index eb05058da..f78944b0d 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -1,4 +1,4 @@ -import { CoreV1Api, User as k8sUser, KubeConfig, V1ObjectReference } from '@kubernetes/client-node' +import { CoreV1Api, KubeConfig, User as k8sUser, V1ObjectReference } from '@kubernetes/client-node' import Debug from 'debug' import { getRegions, ObjectStorageKeyRegions } from '@linode/api-v4' @@ -191,7 +191,6 @@ function getTeamDatabaseValuesFilePath(teamId: string, databaseName: string): st return `env/teams/${teamId}/databases/${databaseName}` } -const DEFAULT_GITEA_URL = 'gitea-http.gitea.svc.cluster.local' export default class OtomiStack { private coreValues: Core editor?: string diff --git a/src/validators.ts b/src/validators.ts index 459b9a141..d97f7874b 100644 --- a/src/validators.ts +++ b/src/validators.ts @@ -28,7 +28,7 @@ export const EDITOR_INACTIVITY_TIMEOUT = num({ }) export const GIT_BRANCH = str({ desc: 'The git repo branch', default: 'main' }) export const CHECK_LATEST_COMMIT_INTERVAL = num({ - desc: 'Interval in minutes for how much time in between each gitea latest commit check', + desc: 'Interval in minutes for how much time in between each git latest commit check', default: 2, }) export const GIT_EMAIL = str({ desc: 'The git user email', default: 'not@us.ed' }) From 37b43087db0c1ea0f6d34b72ad002d51a0733d14 Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Thu, 12 Feb 2026 13:53:46 +0100 Subject: [PATCH 5/6] feat: add git repoUrl to settingsinfo.yaml --- src/openapi/settings.yaml | 2 +- src/openapi/settingsinfo.yaml | 9 +++++++++ src/otomi-stack.ts | 13 ++++++++++++- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/openapi/settings.yaml b/src/openapi/settings.yaml index a2af75941..1d140dfc5 100644 --- a/src/openapi/settings.yaml +++ b/src/openapi/settings.yaml @@ -238,7 +238,7 @@ Settings: repoUrl: type: string description: | - The base URL of the Git repository (without credentials). Auto-derived for internal Gitea. + The base URL of the Git repository (without credentials). pattern: '^https?://.+' username: type: string diff --git a/src/openapi/settingsinfo.yaml b/src/openapi/settingsinfo.yaml index a926e9735..1f6084c67 100644 --- a/src/openapi/settingsinfo.yaml +++ b/src/openapi/settingsinfo.yaml @@ -50,6 +50,15 @@ SettingsInfo: hasExternalIDP: type: boolean default: false + git: + properties: + repoUrl: + type: string + description: The base URL of the Git repository (without credentials). + pattern: '^https?://.+' + branch: + type: string + description: The branch to use in the Git repository. smtp: properties: smarthost: diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index f78944b0d..12ccb533d 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -295,10 +295,21 @@ export default class OtomiStack { getSettingsInfo(): SettingsInfo { const settings = this.getSettings(['cluster', 'dns', 'otomi', 'smtp', 'ingress']) + const otomiInfo = pick(settings.otomi, [ + 'hasExternalDNS', + 'hasExternalIDP', + 'isPreInstalled', + 'aiEnabled', + 'git.repoUrl', + 'git.branch', + ]) + if (otomiInfo.git?.repoUrl?.includes('gitea-http.gitea.svc.cluster.local')) { + otomiInfo.git.repoUrl = `https://gitea.${settings.cluster?.domainSuffix}/otomi/values` + } return { cluster: pick(settings.cluster, ['name', 'domainSuffix', 'apiServer', 'provider', 'linode']), dns: pick(settings.dns, ['zones']), - otomi: pick(settings.otomi, ['hasExternalDNS', 'hasExternalIDP', 'isPreInstalled', 'aiEnabled']), + otomi: otomiInfo, smtp: pick(settings.smtp, ['smarthost']), ingressClassNames: map(settings.ingress?.classes, 'className') ?? [], } as SettingsInfo From c00298833607a369db54075c47457e8dab3f2e28 Mon Sep 17 00:00:00 2001 From: CasLubbers Date: Thu, 12 Feb 2026 14:23:43 +0100 Subject: [PATCH 6/6] feat: update src/otomi-stack.ts Co-authored-by: Ferruh <63190600+ferruhcihan@users.noreply.github.com> --- src/otomi-stack.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index 12ccb533d..ba115f6d1 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -1304,7 +1304,7 @@ export default class OtomiStack { async getInternalRepoUrls(teamId: string): Promise { if (env.isDev || !teamId || teamId === 'admin') return [] const gitea = this.getApp('gitea') - if (!gitea.enabled) return [] + if (!gitea?.values?.enabled) return [] const { cluster, otomi } = this.getSettings(['cluster', 'otomi']) const username = (otomi?.git?.username ?? '') as string const password = (otomi?.git?.password ?? '') as string