From 4d1261352f83f3a812fca0692dc7aec6ab99952a Mon Sep 17 00:00:00 2001 From: Suliman Abdulrazzaq Date: Sun, 9 Aug 2026 10:10:01 +0300 Subject: [PATCH] feat(catalog): add Kan.bn app Closes #513 --- apps/api/test/lib/app-templates.test.ts | 34 +++++++ packages/core/src/apps/catalog.json | 106 ++++++++++++++++++++++ packages/core/src/apps/catalog/kanbn.json | 83 +++++++++++++++++ 3 files changed, 223 insertions(+) create mode 100644 packages/core/src/apps/catalog/kanbn.json diff --git a/apps/api/test/lib/app-templates.test.ts b/apps/api/test/lib/app-templates.test.ts index 65b71c334..2dbe2e673 100644 --- a/apps/api/test/lib/app-templates.test.ts +++ b/apps/api/test/lib/app-templates.test.ts @@ -41,4 +41,38 @@ describe("app catalog", () => { expect(svc.volumes).toContain("n8n_data:/home/node/.n8n"); expect(n8n.configFields?.some((f) => f.key === "N8N_ENCRYPTION_KEY" && f.generate === "secret")).toBe(true); }); + + it("adds Kan.bn with its migration job, Postgres database, and web app", () => { + const kanbn = getAppTemplate("kanbn")!; + expect(kanbn.repository).toBe("https://github.com/kanbn/kan"); + expect(kanbn.endpoints).toEqual([ + { service: "web", port: 3000, label: "Kan.bn", kind: "http" }, + ]); + + const postgres = kanbn.services!.find((s) => s.name === "postgres")!; + expect(postgres.image).toBe("postgres:15"); + expect(postgres.volumes).toContain("kan_postgres_data:/var/lib/postgresql/data"); + expect(postgres.secretEnv).toContain("POSTGRES_PASSWORD"); + expect(postgres.healthcheck?.test).toEqual(["CMD-SHELL", "pg_isready -U kan -d kan_db"]); + + const migrate = kanbn.services!.find((s) => s.name === "migrate")!; + expect(migrate.image).toBe("ghcr.io/kanbn/kan-migrate:latest"); + expect(migrate.dependsOn).toEqual(["postgres"]); + + const web = kanbn.services!.find((s) => s.name === "web")!; + expect(web.image).toBe("ghcr.io/kanbn/kan:latest"); + expect(web.dependsOn).toEqual(["migrate"]); + expect(web.exposedPort).toBe(3000); + expect(web.environment).toMatchObject({ + NEXT_PUBLIC_BASE_URL: "{{publicUrl:web}}", + NEXT_PUBLIC_ALLOW_CREDENTIALS: "true", + }); + expect(web.secretEnv).toEqual(["POSTGRES_URL", "BETTER_AUTH_SECRET"]); + expect( + kanbn.configFields?.some((f) => f.key === "POSTGRES_PASSWORD" && f.generate === "secret"), + ).toBe(true); + expect( + kanbn.configFields?.some((f) => f.key === "BETTER_AUTH_SECRET" && f.generate === "secret"), + ).toBe(true); + }); }); diff --git a/packages/core/src/apps/catalog.json b/packages/core/src/apps/catalog.json index f71348b63..fb949f253 100644 --- a/packages/core/src/apps/catalog.json +++ b/packages/core/src/apps/catalog.json @@ -1829,6 +1829,112 @@ } ] } + }, + { + "available": true, + "verified": true, + "id": "kanbn", + "name": "Kan.bn", + "description": "Open-source project management and Kanban boards — a self-hosted Trello alternative.", + "repository": "https://github.com/kanbn/kan", + "kind": "template", + "logo": "kanbn", + "category": "other", + "tags": [ + "project-management", + "kanban", + "collaboration", + "trello" + ], + "framework": "docker-compose", + "services": [ + { + "name": "postgres", + "image": "postgres:15", + "environment": { + "POSTGRES_DB": "kan_db", + "POSTGRES_USER": "kan" + }, + "secretEnv": [ + "POSTGRES_PASSWORD" + ], + "volumes": [ + "kan_postgres_data:/var/lib/postgresql/data" + ], + "healthcheck": { + "test": [ + "CMD-SHELL", + "pg_isready -U kan -d kan_db" + ], + "interval": "5s", + "timeout": "5s", + "retries": 10 + }, + "restart": "unless-stopped" + }, + { + "name": "migrate", + "image": "ghcr.io/kanbn/kan-migrate:latest", + "dependsOn": [ + "postgres" + ], + "environment": { + "POSTGRES_URL": "postgres://kan:{{config:POSTGRES_PASSWORD}}@postgres:5432/kan_db" + }, + "secretEnv": [ + "POSTGRES_URL" + ], + "restart": "no" + }, + { + "name": "web", + "image": "ghcr.io/kanbn/kan:latest", + "ports": [ + "3000:3000" + ], + "exposedPort": 3000, + "exposed": true, + "dependsOn": [ + "migrate" + ], + "environment": { + "NEXT_PUBLIC_BASE_URL": "{{publicUrl:web}}", + "POSTGRES_URL": "postgres://kan:{{config:POSTGRES_PASSWORD}}@postgres:5432/kan_db", + "NEXT_PUBLIC_ALLOW_CREDENTIALS": "true" + }, + "secretEnv": [ + "POSTGRES_URL", + "BETTER_AUTH_SECRET" + ], + "restart": "unless-stopped" + } + ], + "configFields": [ + { + "key": "POSTGRES_PASSWORD", + "service": "postgres", + "label": "Database password", + "help": "Auto-generated. The password used by Kan.bn's Postgres database.", + "generate": "secret", + "secret": true + }, + { + "key": "BETTER_AUTH_SECRET", + "service": "web", + "label": "Authentication secret", + "help": "Auto-generated. Encrypts Kan.bn authentication data.", + "generate": "secret", + "secret": true + } + ], + "endpoints": [ + { + "service": "web", + "port": 3000, + "label": "Kan.bn", + "kind": "http" + } + ] } ] } diff --git a/packages/core/src/apps/catalog/kanbn.json b/packages/core/src/apps/catalog/kanbn.json new file mode 100644 index 000000000..93d36ff91 --- /dev/null +++ b/packages/core/src/apps/catalog/kanbn.json @@ -0,0 +1,83 @@ +{ + "available": true, + "verified": true, + "id": "kanbn", + "name": "Kan.bn", + "description": "Open-source project management and Kanban boards — a self-hosted Trello alternative.", + "repository": "https://github.com/kanbn/kan", + "kind": "template", + "logo": "kanbn", + "category": "other", + "tags": ["project-management", "kanban", "collaboration", "trello"], + "framework": "docker-compose", + "services": [ + { + "name": "postgres", + "image": "postgres:15", + "environment": { + "POSTGRES_DB": "kan_db", + "POSTGRES_USER": "kan" + }, + "secretEnv": ["POSTGRES_PASSWORD"], + "volumes": ["kan_postgres_data:/var/lib/postgresql/data"], + "healthcheck": { + "test": ["CMD-SHELL", "pg_isready -U kan -d kan_db"], + "interval": "5s", + "timeout": "5s", + "retries": 10 + }, + "restart": "unless-stopped" + }, + { + "name": "migrate", + "image": "ghcr.io/kanbn/kan-migrate:latest", + "dependsOn": ["postgres"], + "environment": { + "POSTGRES_URL": "postgres://kan:{{config:POSTGRES_PASSWORD}}@postgres:5432/kan_db" + }, + "secretEnv": ["POSTGRES_URL"], + "restart": "no" + }, + { + "name": "web", + "image": "ghcr.io/kanbn/kan:latest", + "ports": ["3000:3000"], + "exposedPort": 3000, + "exposed": true, + "dependsOn": ["migrate"], + "environment": { + "NEXT_PUBLIC_BASE_URL": "{{publicUrl:web}}", + "POSTGRES_URL": "postgres://kan:{{config:POSTGRES_PASSWORD}}@postgres:5432/kan_db", + "NEXT_PUBLIC_ALLOW_CREDENTIALS": "true" + }, + "secretEnv": ["POSTGRES_URL", "BETTER_AUTH_SECRET"], + "restart": "unless-stopped" + } + ], + "configFields": [ + { + "key": "POSTGRES_PASSWORD", + "service": "postgres", + "label": "Database password", + "help": "Auto-generated. The password used by Kan.bn's Postgres database.", + "generate": "secret", + "secret": true + }, + { + "key": "BETTER_AUTH_SECRET", + "service": "web", + "label": "Authentication secret", + "help": "Auto-generated. Encrypts Kan.bn authentication data.", + "generate": "secret", + "secret": true + } + ], + "endpoints": [ + { + "service": "web", + "port": 3000, + "label": "Kan.bn", + "kind": "http" + } + ] +}