feat(bootstrap): forgejo admin + org + repo + operator key (T09)
bootstrapForgejo (idempotent, docker-exec — ADR-007) creates the headless admin via `forgejo admin user create` (run as the git user; no web installer, no default credentials — PLAN-002 §9.3), then via the image's own curl against the API: the olsitec org, an auto-init'd olsitec/foundation repo, and the operator's SSH public key. credentials.ts gains the forgejo admin slice (@pulumi/random) and writeCredentialsToVault now also writes foundation/forgejo/service-credentials. Live on cx33 Helsinki: admin + org + repo + key created. GOAL MET — `git clone git@git.olsitec.net:olsitec/foundation.git` (scp-form, :22) and `ssh://git@git.olsitec.net:2222/olsitec/foundation.git` both clone the repo. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f1e1d6facd
commit
3a297d021e
3 changed files with 134 additions and 6 deletions
|
|
@ -13,7 +13,11 @@ import * as docker from "@pulumi/docker";
|
|||
import * as command from "@pulumi/command";
|
||||
import { DeployCtx } from "../lib/context";
|
||||
import { vmConnection } from "../lib/remote";
|
||||
import { PostgresCredentials, RustfsCredentials } from "./credentials";
|
||||
import {
|
||||
ForgejoCredentials,
|
||||
PostgresCredentials,
|
||||
RustfsCredentials,
|
||||
} from "./credentials";
|
||||
import { PostgresOutputs } from "./postgres";
|
||||
import { RustfsOutputs } from "./rustfs";
|
||||
|
||||
|
|
@ -160,3 +164,88 @@ exit 1`,
|
|||
|
||||
return { container, ready, endpoint: "http://foundation-forgejo:3000" };
|
||||
}
|
||||
|
||||
// ─── T09: headless first-admin + org + repo + operator SSH key ───────────────
|
||||
// All idempotent, over docker-exec (ADR-007): `forgejo admin` CLI for the admin
|
||||
// (run as the git user so /data stays git-owned), then the Forgejo API via the
|
||||
// image's own curl against localhost:3000. The admin password arrives on stdin
|
||||
// (secret); the operator SSH public key on stdin (non-secret, but has spaces).
|
||||
// No web installer, no default credentials (PLAN-002 §9.3).
|
||||
const BOOTSTRAP = `set -eu
|
||||
IFS= read -r ADMIN_PW
|
||||
IFS= read -r SSH_PUBKEY
|
||||
C=foundation-forgejo
|
||||
BASE=http://127.0.0.1:3000/api/v1
|
||||
|
||||
if docker exec -u git "$C" forgejo admin user list 2>/dev/null | grep -qw "$ADMIN"; then
|
||||
echo "admin exists"
|
||||
else
|
||||
docker exec -u git "$C" forgejo admin user create --admin --username "$ADMIN" --password "$ADMIN_PW" --email "$EMAIL" --must-change-password=false >/dev/null
|
||||
echo "admin created"
|
||||
fi
|
||||
|
||||
au() { docker exec -i "$C" curl -fsS -u "$ADMIN:$ADMIN_PW" -H 'Content-Type: application/json' "$@"; }
|
||||
code() { docker exec "$C" curl -s -o /dev/null -w '%{http_code}' -u "$ADMIN:$ADMIN_PW" "$@"; }
|
||||
|
||||
if [ "$(code "$BASE/orgs/$ORG")" = 200 ]; then
|
||||
echo "org exists"
|
||||
else
|
||||
au -X POST "$BASE/orgs" -d "{\\"username\\":\\"$ORG\\"}" >/dev/null
|
||||
echo "org created"
|
||||
fi
|
||||
|
||||
if [ "$(code "$BASE/repos/$ORG/$REPO")" = 200 ]; then
|
||||
echo "repo exists"
|
||||
else
|
||||
au -X POST "$BASE/orgs/$ORG/repos" -d "{\\"name\\":\\"$REPO\\",\\"auto_init\\":true,\\"private\\":false,\\"default_branch\\":\\"main\\",\\"description\\":\\"olsitec-foundation platform repo\\"}" >/dev/null
|
||||
echo "repo created"
|
||||
fi
|
||||
|
||||
if au "$BASE/user/keys" | grep -q "\\"title\\":\\"$KEYTITLE\\""; then
|
||||
echo "ssh key exists"
|
||||
else
|
||||
au -X POST "$BASE/user/keys" -d "{\\"title\\":\\"$KEYTITLE\\",\\"key\\":\\"$SSH_PUBKEY\\"}" >/dev/null
|
||||
echo "ssh key added"
|
||||
fi
|
||||
echo "forgejo bootstrap complete: $ORG/$REPO"`;
|
||||
|
||||
export interface ForgejoBootstrapArgs {
|
||||
forgejo: ForgejoOutputs;
|
||||
adminCreds: ForgejoCredentials;
|
||||
acmeEmail: string;
|
||||
orgName: string;
|
||||
repoName: string;
|
||||
sshPublicKey: string;
|
||||
}
|
||||
|
||||
/** Create the admin, the org, a seeded repo, and register the operator SSH key. */
|
||||
export function bootstrapForgejo(
|
||||
ctx: DeployCtx,
|
||||
args: ForgejoBootstrapArgs,
|
||||
): command.remote.Command {
|
||||
const create = pulumi.interpolate`ADMIN='${args.adminCreds.adminUser}'
|
||||
EMAIL='${args.acmeEmail}'
|
||||
ORG='${args.orgName}'
|
||||
REPO='${args.repoName}'
|
||||
KEYTITLE='operator-foundation'
|
||||
${BOOTSTRAP}`;
|
||||
|
||||
return new command.remote.Command(
|
||||
"foundation-forgejo-bootstrap",
|
||||
{
|
||||
connection: vmConnection(ctx),
|
||||
create,
|
||||
update: create,
|
||||
stdin: pulumi.interpolate`${args.adminCreds.adminPassword}
|
||||
${args.sshPublicKey}
|
||||
`,
|
||||
addPreviousOutputInEnv: false,
|
||||
triggers: [
|
||||
args.forgejo.ready.id,
|
||||
args.adminCreds.adminPassword,
|
||||
args.sshPublicKey,
|
||||
],
|
||||
},
|
||||
{ dependsOn: [args.forgejo.ready] },
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue