create-buntubi 0.0.0-stage → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +95 -0
- package/dist/generate.d.ts +32 -0
- package/dist/generate.js +108 -0
- package/package.json +21 -3
- package/templates/README.md +17 -0
- package/templates/_gitignore +13 -0
- package/templates/compose.yaml +96 -0
- package/templates/platform.toml +36 -0
- package/templates/provider.Dockerfile +10 -0
- package/README.md +0 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ibrahima Touré
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `npm create buntubi@latest [folder]`: a Buntubi project run with Docker, with fresh secrets,
|
|
4
|
+
* ready for `pnpm run setup`. Every question has a flag, so it also runs unattended:
|
|
5
|
+
*
|
|
6
|
+
* --name "Acme" --slug acme --url local | https://id.acme.com --console-url --portal-url
|
|
7
|
+
* --email log | resend --resend-key re_... --email-from "Acme <no-reply@acme.com>" --yes
|
|
8
|
+
*/
|
|
9
|
+
import { randomBytes } from 'node:crypto';
|
|
10
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
11
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
12
|
+
import { createInterface } from 'node:readline/promises';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
import { parseArgs } from 'node:util';
|
|
15
|
+
import { generate, SLUG } from './generate.js';
|
|
16
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const own = JSON.parse(readFileSync(join(here, '..', 'package.json'), 'utf8'));
|
|
18
|
+
const { values, positionals } = parseArgs({
|
|
19
|
+
allowPositionals: true,
|
|
20
|
+
options: {
|
|
21
|
+
name: { type: 'string' },
|
|
22
|
+
slug: { type: 'string' },
|
|
23
|
+
url: { type: 'string' },
|
|
24
|
+
'console-url': { type: 'string' },
|
|
25
|
+
'portal-url': { type: 'string' },
|
|
26
|
+
email: { type: 'string' },
|
|
27
|
+
'resend-key': { type: 'string' },
|
|
28
|
+
'email-from': { type: 'string' },
|
|
29
|
+
yes: { type: 'boolean', short: 'y' },
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
const prompt = !values.yes && process.stdin.isTTY ? createInterface({ input: process.stdin, output: process.stdout }) : null;
|
|
33
|
+
async function ask(question, fallback) {
|
|
34
|
+
if (!prompt)
|
|
35
|
+
return fallback;
|
|
36
|
+
return (await prompt.question(`${question} (${fallback}) `)).trim() || fallback;
|
|
37
|
+
}
|
|
38
|
+
const toSlug = (text) => text.normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').replace(/^(\d)/, 'p$1');
|
|
39
|
+
/** An https origin, the only kind a public provider accepts. */
|
|
40
|
+
function origin(text, what) {
|
|
41
|
+
const url = new URL(text);
|
|
42
|
+
if (url.protocol !== 'https:')
|
|
43
|
+
throw new Error(`${what} must be https: ${text}`);
|
|
44
|
+
return url.origin;
|
|
45
|
+
}
|
|
46
|
+
/** On this machine, or at public addresses: the provider's, then the console's and the portal's beside it. */
|
|
47
|
+
async function chooseAddress() {
|
|
48
|
+
const answer = values.url ?? (await ask('Public address of the provider, or local?', 'local'));
|
|
49
|
+
if (answer === 'local')
|
|
50
|
+
return { local: true };
|
|
51
|
+
const provider = origin(answer, 'the provider address');
|
|
52
|
+
const host = new URL(provider).host;
|
|
53
|
+
const consoleUrl = origin(values['console-url'] ?? (await ask('Address of the console?', `https://console.${host}`)), 'the console address');
|
|
54
|
+
const portalUrl = origin(values['portal-url'] ?? (await ask('Address of the developer portal?', `https://portal.${host}`)), 'the portal address');
|
|
55
|
+
return { local: false, provider, console: consoleUrl, portal: portalUrl };
|
|
56
|
+
}
|
|
57
|
+
/** To the log while it runs only here; through Resend once people outside can sign up. */
|
|
58
|
+
async function chooseEmail(isPublic) {
|
|
59
|
+
const answer = values.email ?? (await ask(isPublic ? 'Email through resend?' : 'Email: log (codes in the log) or resend?', isPublic ? 'resend' : 'log'));
|
|
60
|
+
if (answer === 'log')
|
|
61
|
+
return { through: 'log' };
|
|
62
|
+
if (answer !== 'resend')
|
|
63
|
+
throw new Error(`--email is log or resend, not ${answer}`);
|
|
64
|
+
const key = values['resend-key'] ?? (await ask('Resend API key?', ''));
|
|
65
|
+
const from = values['email-from'] ?? (await ask('Sender, on a domain verified at Resend?', ''));
|
|
66
|
+
if (!key || !from)
|
|
67
|
+
throw new Error('Resend needs its API key and a sender');
|
|
68
|
+
return { through: 'resend', key, from };
|
|
69
|
+
}
|
|
70
|
+
async function main() {
|
|
71
|
+
const folder = resolve(positionals[0] ?? (await ask('Folder for the project?', 'my-identity')));
|
|
72
|
+
const name = values.name ?? (await ask('Name on the sign-in pages?', basename(folder)));
|
|
73
|
+
const slug = values.slug ?? (await ask('Short id?', toSlug(name) || 'identity'));
|
|
74
|
+
if (!SLUG.test(slug))
|
|
75
|
+
throw new Error(`"${slug}" must be lowercase letters, digits and hyphens, starting with a letter`);
|
|
76
|
+
const address = await chooseAddress();
|
|
77
|
+
const email = await chooseEmail(!address.local);
|
|
78
|
+
prompt?.close();
|
|
79
|
+
if (existsSync(folder) && readdirSync(folder).length > 0)
|
|
80
|
+
throw new Error(`${folder} is not empty`);
|
|
81
|
+
const files = generate({ name, slug, address, email }, own.version, join(here, '..', 'templates'), () => randomBytes(32).toString('base64url'));
|
|
82
|
+
for (const [path, content] of files) {
|
|
83
|
+
mkdirSync(dirname(join(folder, path)), { recursive: true });
|
|
84
|
+
writeFileSync(join(folder, path), content);
|
|
85
|
+
}
|
|
86
|
+
console.log(`\n${name} is ready in ${folder}. Next:\n\n cd ${folder}\n pnpm install\n pnpm run setup\n pnpm up\n`);
|
|
87
|
+
return 0;
|
|
88
|
+
}
|
|
89
|
+
main().then((code) => {
|
|
90
|
+
process.exitCode = code;
|
|
91
|
+
}, (error) => {
|
|
92
|
+
prompt?.close();
|
|
93
|
+
console.error(`create-buntubi: ${error instanceof Error ? error.message : String(error)}`);
|
|
94
|
+
process.exitCode = 1;
|
|
95
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface Choices {
|
|
2
|
+
/** The platform's name, on its sign-in pages. */
|
|
3
|
+
name: string;
|
|
4
|
+
/** Lowercase letters, digits and hyphens: the compose project and the package name. */
|
|
5
|
+
slug: string;
|
|
6
|
+
/** Where people reach it: on this machine, or at public https addresses. */
|
|
7
|
+
address: {
|
|
8
|
+
local: true;
|
|
9
|
+
} | {
|
|
10
|
+
local: false;
|
|
11
|
+
provider: string;
|
|
12
|
+
console: string;
|
|
13
|
+
portal: string;
|
|
14
|
+
};
|
|
15
|
+
/** How it sends email: to its log (local only), or through Resend. */
|
|
16
|
+
email: {
|
|
17
|
+
through: 'log';
|
|
18
|
+
} | {
|
|
19
|
+
through: 'resend';
|
|
20
|
+
key: string;
|
|
21
|
+
from: string;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** The three addresses the project answers at. */
|
|
25
|
+
export declare const addressesOf: (choices: Choices) => {
|
|
26
|
+
provider: string;
|
|
27
|
+
console: string;
|
|
28
|
+
portal: string;
|
|
29
|
+
};
|
|
30
|
+
export type Files = Map<string, string>;
|
|
31
|
+
export declare const SLUG: RegExp;
|
|
32
|
+
export declare function generate(choices: Choices, version: string, templates: string, secret: () => string): Files;
|
package/dist/generate.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A new Buntubi project, as files: every path it starts with and its contents. Nothing here
|
|
3
|
+
* touches the disk, so a project can be checked before it is written.
|
|
4
|
+
*/
|
|
5
|
+
import { readdirSync, readFileSync, statSync } from 'node:fs';
|
|
6
|
+
import { join, relative } from 'node:path';
|
|
7
|
+
/** The addresses on this machine: the console and the portal share the provider's network. */
|
|
8
|
+
const LOCAL = { provider: 'http://localhost:5010', console: 'http://localhost:3420', portal: 'http://localhost:3410' };
|
|
9
|
+
/** The three addresses the project answers at. */
|
|
10
|
+
export const addressesOf = (choices) => (choices.address.local ? LOCAL : choices.address);
|
|
11
|
+
export const SLUG = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
12
|
+
/** Template files are stored with a leading underscore where npm would drop a dotfile. */
|
|
13
|
+
const DOTFILE = /(^|\/)_(gitignore)$/;
|
|
14
|
+
export function generate(choices, version, templates, secret) {
|
|
15
|
+
if (!SLUG.test(choices.slug))
|
|
16
|
+
throw new Error(`"${choices.slug}" must be lowercase letters, digits and hyphens, starting with a letter`);
|
|
17
|
+
// The provider refuses to start on a public address with email that only goes to its log.
|
|
18
|
+
if (!choices.address.local && choices.email.through === 'log')
|
|
19
|
+
throw new Error('a public address needs email that leaves the machine: choose resend');
|
|
20
|
+
const at = addressesOf(choices);
|
|
21
|
+
const files = new Map();
|
|
22
|
+
for (const file of walk(templates)) {
|
|
23
|
+
const path = relative(templates, file).replace(DOTFILE, '$1.$2');
|
|
24
|
+
const text = readFileSync(file, 'utf8')
|
|
25
|
+
.replaceAll('{{name}}', choices.name)
|
|
26
|
+
.replaceAll('{{slug}}', choices.slug)
|
|
27
|
+
.replaceAll('{{version}}', version)
|
|
28
|
+
.replaceAll('{{consoleUrl}}', at.console)
|
|
29
|
+
.replaceAll('{{portalUrl}}', at.portal);
|
|
30
|
+
files.set(path, text);
|
|
31
|
+
}
|
|
32
|
+
files.set('package.json', `${JSON.stringify({
|
|
33
|
+
name: choices.slug,
|
|
34
|
+
private: true,
|
|
35
|
+
type: 'module',
|
|
36
|
+
scripts: { setup: 'buntubi setup', build: 'buntubi build', up: 'buntubi up', down: 'buntubi down' },
|
|
37
|
+
devDependencies: { '@buntubi/sdk': `^${version}` },
|
|
38
|
+
}, null, 2)}\n`);
|
|
39
|
+
files.set('pnpm-workspace.yaml', 'packages:\n - plugins/*/ui\n');
|
|
40
|
+
// Docker copies plugins/ into the provider's image and mounts ui-plugins/: both must exist.
|
|
41
|
+
files.set('plugins/.gitkeep', '');
|
|
42
|
+
files.set('ui-plugins/.gitkeep', '');
|
|
43
|
+
for (const [file, entries] of envFiles(choices, secret)) {
|
|
44
|
+
files.set(file, env(entries, true));
|
|
45
|
+
files.set(`${file}.example`, env(entries, false));
|
|
46
|
+
}
|
|
47
|
+
return files;
|
|
48
|
+
}
|
|
49
|
+
function walk(dir) {
|
|
50
|
+
return readdirSync(dir).flatMap((entry) => {
|
|
51
|
+
const path = join(dir, entry);
|
|
52
|
+
return statSync(path).isDirectory() ? walk(path) : [path];
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function env(entries, withValues) {
|
|
56
|
+
const secret = /SECRET|PASSWORD|KEY$/;
|
|
57
|
+
return `${entries.map(([key, value, comment]) => `${comment ? `# ${comment}\n` : ''}${key}=${withValues || !secret.test(key) ? value : ''}`).join('\n')}\n`;
|
|
58
|
+
}
|
|
59
|
+
function envFiles(choices, secret) {
|
|
60
|
+
const at = addressesOf(choices);
|
|
61
|
+
const email = choices.email.through === 'log'
|
|
62
|
+
? [['IDP_EMAIL_TEST_MODE', 'true', 'Local only: emails, sign-in codes included, go to the log.']]
|
|
63
|
+
: [
|
|
64
|
+
['IDP_EMAIL_TEST_MODE', 'false', ''],
|
|
65
|
+
['IDP_RESEND_API_KEY', choices.email.key, 'Email through Resend: the API key from resend.com.'],
|
|
66
|
+
['IDP_EMAIL_FROM', choices.email.from, 'The sender, on a domain verified at Resend.'],
|
|
67
|
+
];
|
|
68
|
+
return [
|
|
69
|
+
['.env', [['POSTGRES_PASSWORD', secret(), 'Read by docker compose: the database password, shared by postgres and the provider.']]],
|
|
70
|
+
[
|
|
71
|
+
'identity.env',
|
|
72
|
+
[
|
|
73
|
+
['IDP_ISSUER', at.provider, "The provider's address; every IDP_* setting is documented in Buntubi."],
|
|
74
|
+
['IDP_SECRET_KEY', secret(), 'Never change it: it signs sessions and encrypts authenticator secrets.'],
|
|
75
|
+
['IDP_SECURE_COOKIES', 'true', ''],
|
|
76
|
+
...email,
|
|
77
|
+
['IDP_RATE_LIMIT_STORAGE', 'memory://', 'One provider process; several need a shared store (redis://...).'],
|
|
78
|
+
['IDP_BRAND_NAME', choices.name, ''],
|
|
79
|
+
['IDP_PLUGINS', '[]', 'The provider plugins turned on, by id, in order; `buntubi add plugin` adds to it.'],
|
|
80
|
+
['IDP_GOOGLE_CLIENT_ID', '', 'Sign-in with Google: an OAuth client from Google Cloud, callback <issuer>/signin/google/callback; list google under [social] in platform.toml.'],
|
|
81
|
+
['IDP_GOOGLE_CLIENT_SECRET', '', ''],
|
|
82
|
+
['IDP_GITHUB_CLIENT_ID', '', 'Sign-in with GitHub: an OAuth App, or a GitHub App with Email addresses read-only; callback <issuer>/signin/github/callback.'],
|
|
83
|
+
['IDP_GITHUB_CLIENT_SECRET', '', ''],
|
|
84
|
+
],
|
|
85
|
+
],
|
|
86
|
+
[
|
|
87
|
+
'console.env',
|
|
88
|
+
[
|
|
89
|
+
['IDP_URL', at.provider, ''],
|
|
90
|
+
['APP_URL', at.console, ''],
|
|
91
|
+
['PORTAL_URL', at.portal, ''],
|
|
92
|
+
['CONSOLE_CLIENT_ID', 'owner-console', ''],
|
|
93
|
+
['CONSOLE_CLIENT_SECRET', '', 'Written by `pnpm run setup`.'],
|
|
94
|
+
['SESSION_SECRET', secret(), 'Encrypts the session cookie: at least 32 characters.'],
|
|
95
|
+
],
|
|
96
|
+
],
|
|
97
|
+
[
|
|
98
|
+
'portal.env',
|
|
99
|
+
[
|
|
100
|
+
['IDP_URL', at.provider, ''],
|
|
101
|
+
['APP_URL', at.portal, ''],
|
|
102
|
+
['PORTAL_CLIENT_ID', 'developer-portal', ''],
|
|
103
|
+
['PORTAL_CLIENT_SECRET', '', 'Written by `pnpm run setup`.'],
|
|
104
|
+
['SESSION_SECRET', secret(), 'Encrypts the session cookie: at least 32 characters.'],
|
|
105
|
+
],
|
|
106
|
+
],
|
|
107
|
+
];
|
|
108
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-buntubi",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "Start an identity provider on Buntubi, run with Docker",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"create-buntubi": "./dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"templates"
|
|
13
|
+
],
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@types/node": "^22.0.0",
|
|
16
|
+
"typescript": "^7.0.2",
|
|
17
|
+
"vitest": "^5.0.3"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc -p tsconfig.build.json",
|
|
21
|
+
"typecheck": "tsc --noEmit",
|
|
22
|
+
"test": "vitest run"
|
|
23
|
+
}
|
|
6
24
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# {{name}}
|
|
2
|
+
|
|
3
|
+
An identity provider on Buntubi, run with Docker.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
pnpm install
|
|
7
|
+
pnpm run setup # once: its database, a signing key, the first admin, the apps
|
|
8
|
+
pnpm up # the provider on :5010, the console on :3420, the portal on :3410
|
|
9
|
+
pnpm down
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
On public addresses, a reverse proxy sends each one to its port with https: the provider's to
|
|
13
|
+
5010, the console's to 3420, the portal's to 3410.
|
|
14
|
+
|
|
15
|
+
Its apps are in `platform.toml`, its settings in the `.env` files, written with fresh secrets
|
|
16
|
+
when the project was made. `pnpm exec buntubi add plugin <id>` adds a plugin of its own in
|
|
17
|
+
`plugins/`; `pnpm build` builds the plugins' pages into `ui-plugins/`.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# {{name}}'s identity provider: Buntubi from its published images, its provider built with this
|
|
2
|
+
# project's plugins (provider.Dockerfile), their UI halves mounted from ui-plugins/.
|
|
3
|
+
#
|
|
4
|
+
# `pnpm run setup` the first time (database, signing key, the first admin, the apps in
|
|
5
|
+
# platform.toml), then `pnpm up` and `pnpm down`. The console and the portal share the
|
|
6
|
+
# provider's network namespace, so http://localhost:5010, :3420 and :3410 mean the same thing to
|
|
7
|
+
# the browser and to the containers.
|
|
8
|
+
#
|
|
9
|
+
# Upgrading: change the version below and in provider.Dockerfile, then `pnpm run setup` again (it
|
|
10
|
+
# only adds what is missing) and `pnpm up`.
|
|
11
|
+
|
|
12
|
+
name: {{slug}}
|
|
13
|
+
|
|
14
|
+
services:
|
|
15
|
+
postgres:
|
|
16
|
+
image: postgres:17-alpine
|
|
17
|
+
environment:
|
|
18
|
+
POSTGRES_USER: idp
|
|
19
|
+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
|
|
20
|
+
POSTGRES_DB: idp
|
|
21
|
+
volumes:
|
|
22
|
+
- postgres:/var/lib/postgresql/data
|
|
23
|
+
healthcheck:
|
|
24
|
+
test: ["CMD", "pg_isready", "-U", "idp", "-d", "idp"]
|
|
25
|
+
interval: 5s
|
|
26
|
+
retries: 10
|
|
27
|
+
|
|
28
|
+
provider:
|
|
29
|
+
build:
|
|
30
|
+
context: .
|
|
31
|
+
dockerfile: provider.Dockerfile
|
|
32
|
+
image: {{slug}}-provider
|
|
33
|
+
env_file: identity.env
|
|
34
|
+
environment:
|
|
35
|
+
IDP_DATABASE_URL: postgresql+psycopg://idp:${POSTGRES_PASSWORD}@postgres:5432/idp
|
|
36
|
+
IDP_SIGNING_KEYS_DIR: /keys
|
|
37
|
+
# The issuer is http://localhost:5010, so the provider listens there inside the namespace too.
|
|
38
|
+
command: ["gunicorn", "--bind", "0.0.0.0:5010", "--access-logfile", "-", "buntubi.app:create_app()"]
|
|
39
|
+
healthcheck:
|
|
40
|
+
test: ["CMD", "python", "-c", "import urllib.request,sys; sys.exit(urllib.request.urlopen('http://127.0.0.1:5010/healthz', timeout=4).status != 200)"]
|
|
41
|
+
interval: 10s
|
|
42
|
+
start_period: 20s
|
|
43
|
+
volumes:
|
|
44
|
+
- ./keys:/keys
|
|
45
|
+
- &ui-plugins ./ui-plugins:/app/ui-plugins:ro
|
|
46
|
+
ports:
|
|
47
|
+
- "5010:5010"
|
|
48
|
+
- "3420:3420"
|
|
49
|
+
- "3410:3410"
|
|
50
|
+
extra_hosts:
|
|
51
|
+
- "host.docker.internal:host-gateway"
|
|
52
|
+
depends_on:
|
|
53
|
+
postgres:
|
|
54
|
+
condition: service_healthy
|
|
55
|
+
|
|
56
|
+
# Sends organization events to the apps' webhooks.
|
|
57
|
+
webhooks:
|
|
58
|
+
image: {{slug}}-provider
|
|
59
|
+
env_file: identity.env
|
|
60
|
+
environment:
|
|
61
|
+
IDP_DATABASE_URL: postgresql+psycopg://idp:${POSTGRES_PASSWORD}@postgres:5432/idp
|
|
62
|
+
IDP_SIGNING_KEYS_DIR: /keys
|
|
63
|
+
IDP_PLUGINS_SYNC_ON_START: "false"
|
|
64
|
+
command: ["flask", "webhooks", "deliver"]
|
|
65
|
+
# The image checks its web server, which this process does not run.
|
|
66
|
+
healthcheck:
|
|
67
|
+
disable: true
|
|
68
|
+
volumes:
|
|
69
|
+
- ./keys:/keys
|
|
70
|
+
extra_hosts:
|
|
71
|
+
- "host.docker.internal:host-gateway"
|
|
72
|
+
restart: unless-stopped
|
|
73
|
+
depends_on:
|
|
74
|
+
provider:
|
|
75
|
+
condition: service_started
|
|
76
|
+
|
|
77
|
+
console:
|
|
78
|
+
image: ghcr.io/buntubi/idp-console:{{version}}
|
|
79
|
+
env_file: console.env
|
|
80
|
+
volumes:
|
|
81
|
+
- *ui-plugins
|
|
82
|
+
network_mode: service:provider
|
|
83
|
+
depends_on:
|
|
84
|
+
- provider
|
|
85
|
+
|
|
86
|
+
portal:
|
|
87
|
+
image: ghcr.io/buntubi/idp-portal:{{version}}
|
|
88
|
+
env_file: portal.env
|
|
89
|
+
volumes:
|
|
90
|
+
- *ui-plugins
|
|
91
|
+
network_mode: service:provider
|
|
92
|
+
depends_on:
|
|
93
|
+
- provider
|
|
94
|
+
|
|
95
|
+
volumes:
|
|
96
|
+
postgres:
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# {{name}}'s apps, created by `pnpm run setup`. Running it again only adds what is missing. A
|
|
2
|
+
# confidential app's secret is written into the env file `secret` names, relative to this file,
|
|
3
|
+
# and never printed. Add the apps that sign in through this provider below.
|
|
4
|
+
|
|
5
|
+
[[apps]]
|
|
6
|
+
client_id = "owner-console"
|
|
7
|
+
name = "Owner console"
|
|
8
|
+
redirect_uris = ["{{consoleUrl}}/auth/callback"]
|
|
9
|
+
post_logout_redirect_uris = ["{{consoleUrl}}/"]
|
|
10
|
+
scope = "openid profile email admin"
|
|
11
|
+
homepage_url = "{{consoleUrl}}/"
|
|
12
|
+
backchannel_logout_uri = "{{consoleUrl}}/auth/backchannel-logout"
|
|
13
|
+
secret = { file = "console.env", key = "CONSOLE_CLIENT_SECRET" }
|
|
14
|
+
|
|
15
|
+
[[apps]]
|
|
16
|
+
client_id = "developer-portal"
|
|
17
|
+
name = "Developer portal"
|
|
18
|
+
redirect_uris = ["{{portalUrl}}/auth/callback"]
|
|
19
|
+
post_logout_redirect_uris = ["{{portalUrl}}/"]
|
|
20
|
+
scope = "openid profile email developer"
|
|
21
|
+
homepage_url = "{{portalUrl}}/"
|
|
22
|
+
backchannel_logout_uri = "{{portalUrl}}/auth/backchannel-logout"
|
|
23
|
+
secret = { file = "portal.env", key = "PORTAL_CLIENT_SECRET" }
|
|
24
|
+
|
|
25
|
+
# Social sign-in, once IDP_GOOGLE_* or IDP_GITHUB_* is set in identity.env.
|
|
26
|
+
# [social]
|
|
27
|
+
# providers = ["google", "github"]
|
|
28
|
+
|
|
29
|
+
# Whether people can make an account; invited addresses always can.
|
|
30
|
+
# [signup]
|
|
31
|
+
# open = true
|
|
32
|
+
# terms_version = "2026-10" # asks people to accept the terms at IDP_BRAND_TERMS_URL
|
|
33
|
+
|
|
34
|
+
# The profile fields asked of people: "hidden" (the default), "optional" or "required".
|
|
35
|
+
# [profile]
|
|
36
|
+
# birthdate = "optional"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# The provider: Buntubi's published image with this project's provider plugins installed, each
|
|
2
|
+
# one's plugins/<id>/provider package. Without plugins it is the published image as it is.
|
|
3
|
+
FROM ghcr.io/buntubi/idp-provider:{{version}}
|
|
4
|
+
USER root
|
|
5
|
+
COPY plugins /plugins
|
|
6
|
+
RUN for plugin in /plugins/*/provider; do \
|
|
7
|
+
if [ -f "$plugin/pyproject.toml" ]; then pip install --no-cache-dir "$plugin"; fi; \
|
|
8
|
+
done \
|
|
9
|
+
&& rm -rf /plugins
|
|
10
|
+
USER idp
|
package/README.md
DELETED