Skip to content

Commit 3c63d3d

Browse files
stubbiclaude
andcommitted
fix: implement correct Paperclip admin bootstrap flow
Follow the same three-step flow as Paperclip's docker-onboard-smoke.sh: 1. Sign up admin user via /api/auth/sign-up/email (with cookies) 2. Generate bootstrap invite via CLI (bootstrap-ceo) 3. Accept the invite via /api/invites/{token}/accept with the authenticated session cookies (promotes user to CEO role) The previous implementation only called sign-up with the invite token, which created a regular user without the admin/CEO role. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 1b1a117 commit 3c63d3d

1 file changed

Lines changed: 62 additions & 18 deletions

File tree

internal/resources/bootstrap.go

Lines changed: 62 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,19 @@ func BuildBootstrapJob(instance *paperclipv1alpha1.Instance) *batchv1.Job {
4242
// 1. Wait for the server to accept connections
4343
// 2. Run bootstrap-ceo to get the invite token
4444
// 3. Call the sign-up API with the token and admin credentials
45+
svcURL := fmt.Sprintf("http://%s.%s.svc.cluster.local:%d", svcName, instance.Namespace, port)
46+
47+
// Follow the same flow as Paperclip's docker-onboard-smoke.sh:
48+
// 1. Wait for server
49+
// 2. Sign up admin user (creates account without admin role)
50+
// 3. Generate bootstrap invite via CLI
51+
// 4. Accept the invite with the authenticated session (promotes to admin/CEO)
4552
script := fmt.Sprintf(`
4653
set -e
4754
4855
SERVER_URL="%s"
49-
SVC_URL="http://%s.%s.svc.cluster.local:%d"
56+
SVC_URL="%s"
57+
COOKIE_JAR=$(mktemp /tmp/cookies.XXXXXX)
5058
5159
echo "Waiting for Paperclip server..."
5260
for i in $(seq 1 60); do
@@ -59,39 +67,75 @@ for i in $(seq 1 60); do
5967
sleep 5
6068
done
6169
62-
echo "Running bootstrap-ceo..."
70+
# Step 1: Sign up the admin user (or sign in if already exists)
71+
echo "Creating admin account..."
72+
SIGNUP_STATUS=$(curl -sS -o /tmp/signup.json -w '%%{http_code}' \
73+
-c "$COOKIE_JAR" -b "$COOKIE_JAR" \
74+
-H "Content-Type: application/json" \
75+
-H "Origin: $SERVER_URL" \
76+
-X POST "$SVC_URL/api/auth/sign-up/email" \
77+
-d "{\"name\":\"%s\",\"email\":\"$ADMIN_EMAIL\",\"password\":\"$ADMIN_PASSWORD\"}") || true
78+
79+
if echo "$SIGNUP_STATUS" | grep -q '^2'; then
80+
echo "Admin account created."
81+
else
82+
echo "Sign-up returned HTTP $SIGNUP_STATUS, trying sign-in..."
83+
SIGNIN_STATUS=$(curl -sS -o /tmp/signin.json -w '%%{http_code}' \
84+
-c "$COOKIE_JAR" -b "$COOKIE_JAR" \
85+
-H "Content-Type: application/json" \
86+
-H "Origin: $SERVER_URL" \
87+
-X POST "$SVC_URL/api/auth/sign-in/email" \
88+
-d "{\"email\":\"$ADMIN_EMAIL\",\"password\":\"$ADMIN_PASSWORD\"}") || true
89+
if echo "$SIGNIN_STATUS" | grep -q '^2'; then
90+
echo "Signed in as existing admin."
91+
else
92+
echo "Could not sign up or sign in. Sign-up: $(cat /tmp/signup.json 2>/dev/null), Sign-in: $(cat /tmp/signin.json 2>/dev/null)"
93+
exit 1
94+
fi
95+
fi
96+
97+
# Step 2: Check if instance is already bootstrapped
98+
HEALTH=$(curl -sS -c "$COOKIE_JAR" -b "$COOKIE_JAR" "$SVC_URL/api/health" 2>/dev/null) || true
99+
if echo "$HEALTH" | grep -q '"bootstrapStatus":"ready"'; then
100+
echo "Instance already bootstrapped. Nothing to do."
101+
rm -f "$COOKIE_JAR"
102+
exit 0
103+
fi
104+
105+
# Step 3: Generate bootstrap invite
106+
echo "Generating bootstrap invite..."
63107
BOOTSTRAP_OUTPUT=$(pnpm paperclipai auth bootstrap-ceo --base-url "$SERVER_URL" 2>&1) || true
64108
echo "$BOOTSTRAP_OUTPUT"
65109
66-
# Extract the invite token from the output
67110
INVITE_TOKEN=$(echo "$BOOTSTRAP_OUTPUT" | grep -o 'pcp_bootstrap_[a-f0-9]*' | head -1)
68-
69111
if [ -z "$INVITE_TOKEN" ]; then
70-
if echo "$BOOTSTRAP_OUTPUT" | grep -qi "already exists\|already been"; then
71-
echo "Admin user already exists. Nothing to do."
72-
exit 0
73-
fi
74112
echo "Could not extract invite token."
113+
rm -f "$COOKIE_JAR"
75114
exit 1
76115
fi
77116
78-
echo "Creating admin user with invite token..."
79-
RESPONSE=$(curl -s -X POST "$SERVER_URL/api/auth/sign-up/email" \
117+
# Step 4: Accept the invite with the authenticated session
118+
echo "Accepting bootstrap invite..."
119+
ACCEPT_STATUS=$(curl -sS -o /tmp/accept.json -w '%%{http_code}' \
120+
-c "$COOKIE_JAR" -b "$COOKIE_JAR" \
80121
-H "Content-Type: application/json" \
81-
-d "{\"email\":\"$ADMIN_EMAIL\",\"password\":\"$ADMIN_PASSWORD\",\"name\":\"%s\",\"inviteToken\":\"$INVITE_TOKEN\"}") || true
122+
-H "Origin: $SERVER_URL" \
123+
-X POST "$SVC_URL/api/invites/$INVITE_TOKEN/accept" \
124+
-d '{"requestType":"human"}') || true
82125
83-
if echo "$RESPONSE" | grep -q '"user"'; then
84-
echo "Admin user created successfully."
85-
elif echo "$RESPONSE" | grep -qi "already exists\|duplicate"; then
86-
echo "Admin user already exists."
87-
exit 0
126+
if echo "$ACCEPT_STATUS" | grep -q '^2'; then
127+
echo "Bootstrap complete. Admin user promoted to CEO."
88128
else
89-
echo "Sign-up response: $RESPONSE"
129+
echo "Invite acceptance returned HTTP $ACCEPT_STATUS: $(cat /tmp/accept.json 2>/dev/null)"
130+
rm -f "$COOKIE_JAR"
90131
exit 1
91132
fi
133+
134+
rm -f "$COOKIE_JAR"
135+
echo "Admin bootstrap finished successfully."
92136
`,
93137
baseURL,
94-
svcName, instance.Namespace, port,
138+
svcURL,
95139
adminName,
96140
)
97141

0 commit comments

Comments
 (0)