2023-07-20 10:10:40 +00:00
|
|
|
#!/usr/bin/env node
|
|
|
|
import { Octokit } from "@octokit/rest";
|
|
|
|
import gunzip from "gunzip-maybe";
|
|
|
|
import { spawn } from "node:child_process";
|
|
|
|
import fs from "node:fs";
|
|
|
|
import path from "node:path";
|
|
|
|
import { pipeline } from "node:stream/promises";
|
|
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
import { extract } from "tar-fs";
|
2023-10-20 13:53:16 +00:00
|
|
|
import { parseArgs } from "node:util";
|
2023-07-20 10:10:40 +00:00
|
|
|
|
|
|
|
const DIR_NAME = path.dirname(fileURLToPath(import.meta.url));
|
|
|
|
const SERVER_DIR = path.resolve(DIR_NAME, "../server");
|
2023-10-20 13:53:16 +00:00
|
|
|
const LOCAL_QUARKUS = path.resolve(DIR_NAME, "../../../../quarkus/dist/target");
|
|
|
|
const LOCAL_DIST_NAME = "keycloak-999.0.0-SNAPSHOT.tar.gz";
|
2023-07-20 10:10:40 +00:00
|
|
|
const SCRIPT_EXTENSION = process.platform === "win32" ? ".bat" : ".sh";
|
|
|
|
const ADMIN_USERNAME = "admin";
|
|
|
|
const ADMIN_PASSWORD = "admin";
|
2024-08-21 07:30:24 +00:00
|
|
|
const CLIENT_ID = "temporary-admin-service";
|
|
|
|
const CLIENT_SECRET = "temporary-admin-service";
|
2023-07-20 10:10:40 +00:00
|
|
|
|
2023-10-20 13:53:16 +00:00
|
|
|
const options = {
|
|
|
|
local: {
|
|
|
|
type: "boolean",
|
|
|
|
},
|
2024-06-12 09:55:14 +00:00
|
|
|
"account-dev": {
|
|
|
|
type: "boolean",
|
|
|
|
},
|
|
|
|
"admin-dev": {
|
|
|
|
type: "boolean",
|
|
|
|
},
|
2023-10-20 13:53:16 +00:00
|
|
|
};
|
|
|
|
|
2023-07-20 10:10:40 +00:00
|
|
|
await startServer();
|
|
|
|
|
|
|
|
async function startServer() {
|
2023-10-20 13:53:16 +00:00
|
|
|
let { scriptArgs, keycloakArgs } = handleArgs(process.argv.slice(2));
|
2023-07-20 10:10:40 +00:00
|
|
|
|
2023-10-20 13:53:16 +00:00
|
|
|
await downloadServer(scriptArgs.local);
|
2023-07-20 10:10:40 +00:00
|
|
|
|
2024-06-12 09:55:14 +00:00
|
|
|
const env = {
|
2024-07-11 16:07:57 +00:00
|
|
|
KC_BOOTSTRAP_ADMIN_USERNAME: ADMIN_USERNAME,
|
|
|
|
KC_BOOTSTRAP_ADMIN_PASSWORD: ADMIN_PASSWORD,
|
2024-08-21 07:30:24 +00:00
|
|
|
KC_BOOTSTRAP_ADMIN_CLIENT_ID: CLIENT_ID,
|
|
|
|
KC_BOOTSTRAP_ADMIN_CLIENT_SECRET: CLIENT_SECRET,
|
2024-06-12 09:55:14 +00:00
|
|
|
...process.env,
|
|
|
|
};
|
|
|
|
|
|
|
|
if (scriptArgs["account-dev"]) {
|
|
|
|
env.KC_ACCOUNT_VITE_URL = "http://localhost:5173";
|
|
|
|
}
|
|
|
|
|
|
|
|
if (scriptArgs["admin-dev"]) {
|
|
|
|
env.KC_ADMIN_VITE_URL = "http://localhost:5174";
|
|
|
|
}
|
|
|
|
|
2023-10-20 13:53:16 +00:00
|
|
|
console.info("Starting server…");
|
2024-06-12 09:55:14 +00:00
|
|
|
|
2023-07-20 10:10:40 +00:00
|
|
|
const child = spawn(
|
|
|
|
path.join(SERVER_DIR, `bin/kc${SCRIPT_EXTENSION}`),
|
|
|
|
[
|
|
|
|
"start-dev",
|
2024-10-03 10:09:36 +00:00
|
|
|
`--features="login:v2,account:v3,admin-fine-grained-authz,transient-users,oid4vc-vci,organization"`,
|
2023-10-20 13:53:16 +00:00
|
|
|
...keycloakArgs,
|
2023-07-20 10:10:40 +00:00
|
|
|
],
|
|
|
|
{
|
2024-02-09 12:22:00 +00:00
|
|
|
shell: true,
|
2024-06-12 09:55:14 +00:00
|
|
|
env,
|
2023-07-20 10:10:40 +00:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
child.stdout.pipe(process.stdout);
|
|
|
|
child.stderr.pipe(process.stderr);
|
|
|
|
}
|
|
|
|
|
2023-10-20 13:53:16 +00:00
|
|
|
function handleArgs(args) {
|
|
|
|
const { values, tokens } = parseArgs({
|
|
|
|
args,
|
|
|
|
options,
|
|
|
|
strict: false,
|
|
|
|
tokens: true,
|
|
|
|
});
|
|
|
|
// we need to remove the args that belong to the script so that we can pass the rest through to keycloak
|
|
|
|
tokens
|
|
|
|
.filter((token) => Object.hasOwn(options, token.name))
|
|
|
|
.forEach((token) => {
|
|
|
|
let tokenRaw = token.rawName;
|
|
|
|
if (token.value) {
|
|
|
|
tokenRaw += `=${token.value}`;
|
|
|
|
}
|
|
|
|
args.splice(args.indexOf(tokenRaw), 1);
|
|
|
|
});
|
|
|
|
return { scriptArgs: values, keycloakArgs: args };
|
|
|
|
}
|
|
|
|
|
|
|
|
async function downloadServer(local) {
|
2023-07-20 10:10:40 +00:00
|
|
|
const directoryExists = fs.existsSync(SERVER_DIR);
|
|
|
|
|
|
|
|
if (directoryExists) {
|
|
|
|
console.info("Server installation found, skipping download.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-10-20 13:53:16 +00:00
|
|
|
let assetStream;
|
|
|
|
if (local) {
|
|
|
|
console.info(`Looking for ${LOCAL_DIST_NAME} at ${LOCAL_QUARKUS}`);
|
|
|
|
assetStream = fs.createReadStream(
|
|
|
|
path.join(LOCAL_QUARKUS, LOCAL_DIST_NAME),
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
console.info("Downloading and extracting server…");
|
|
|
|
const nightlyAsset = await getNightlyAsset();
|
|
|
|
assetStream = await getAssetAsStream(nightlyAsset);
|
|
|
|
}
|
2023-07-20 10:10:40 +00:00
|
|
|
await extractTarball(assetStream, SERVER_DIR, { strip: 1 });
|
|
|
|
}
|
|
|
|
|
|
|
|
async function getNightlyAsset() {
|
|
|
|
const api = new Octokit();
|
|
|
|
const release = await api.repos.getReleaseByTag({
|
|
|
|
owner: "keycloak",
|
|
|
|
repo: "keycloak",
|
|
|
|
tag: "nightly",
|
|
|
|
});
|
|
|
|
|
|
|
|
return release.data.assets.find(
|
|
|
|
({ name }) => name === "keycloak-999.0.0-SNAPSHOT.tar.gz",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function getAssetAsStream(asset) {
|
|
|
|
const response = await fetch(asset.browser_download_url);
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
throw new Error("Something went wrong requesting the nightly release.");
|
|
|
|
}
|
|
|
|
|
|
|
|
return response.body;
|
|
|
|
}
|
|
|
|
|
|
|
|
function extractTarball(stream, path, options) {
|
|
|
|
return pipeline(stream, gunzip(), extract(path, options));
|
|
|
|
}
|