Skip to content

Commit 36192ce

Browse files
committed
feat(bootstrap): add initial one-time password onboarding flow
1 parent f0058c8 commit 36192ce

10 files changed

Lines changed: 657 additions & 53 deletions

File tree

README.md

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,15 @@ occ install
154154
occ start
155155
```
156156

157+
If this is the first startup with no configured users, the container logs will print an **Initial One-Time Password (IOTP)**.
158+
Open the login page, use the first-time setup panel, and create your first account. The IOTP is invalidated after successful signup.
159+
160+
Quick IOTP extraction from logs:
161+
162+
```bash
163+
occ logs | grep -F "INITIAL ONE-TIME PASSWORD (IOTP): " | tail -n1 | sed 's/.*INITIAL ONE-TIME PASSWORD (IOTP): //'
164+
```
165+
157166
### From source (install locally)
158167

159168
```bash
@@ -338,7 +347,17 @@ occ config show
338347
339348
## Authentication
340349
341-
opencode-cloud uses **PAM (Pluggable Authentication Modules)** for authentication. Users created via `occ user add` authenticate to the opencode web UI.
350+
opencode-cloud uses **PAM (Pluggable Authentication Modules)** for authentication.
351+
352+
First boot path:
353+
- If no users are configured, startup logs print an Initial One-Time Password (IOTP).
354+
- Extract only the IOTP quickly: `occ logs | grep -F "INITIAL ONE-TIME PASSWORD (IOTP): " | tail -n1 | sed 's/.*INITIAL ONE-TIME PASSWORD (IOTP): //'`
355+
- Enter that IOTP in the web login page first-time setup panel.
356+
- Complete signup (username + password) to create the first Linux user account in the container.
357+
- The IOTP is deleted after the first successful signup.
358+
359+
Admin path:
360+
- You can always create/manage users directly via `occ user add`, `occ user passwd`, and related user commands.
342361
343362
### Creating Users
344363
@@ -365,14 +384,6 @@ User accounts (including password hashes and lock status) persist across contain
365384
The CLI stores user records in a managed Docker volume mounted at `/var/lib/opencode-users` inside the container.
366385
No plaintext passwords are stored on the host.
367386
368-
### Legacy Authentication Fields
369-
370-
The `auth_username` and `auth_password` config fields are **deprecated** and ignored. They are kept in the config schema for backward compatibility with existing deployments, but new users should be created via `occ user add` instead.
371-
372-
To migrate from legacy fields:
373-
1. Create a PAM user: `occ user add <username>`
374-
2. The legacy fields will be automatically cleared on next config save
375-
376387
### Rebuilding the Docker Image
377388
378389
When developing locally or after updating opencode-cloud, you may need to rebuild the Docker image to pick up changes in the embedded Dockerfile:

docs/deploy/digitalocean-droplet.md

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -70,18 +70,31 @@ Pull and start the sandbox image:
7070
occ start --pull-sandbox-image
7171
```
7272

73-
### 6) Create the first PAM user
73+
### 6) Complete first-time signup with Initial One-Time Password (IOTP)
7474

75-
Recommended: generate a strong password:
75+
After `occ start`, check logs for the IOTP:
7676

7777
```bash
78-
occ user add admin --generate
78+
occ logs
79+
```
80+
81+
Extract just the IOTP value (optional):
82+
83+
```bash
84+
occ logs | grep -F "INITIAL ONE-TIME PASSWORD (IOTP): " | tail -n1 | sed 's/.*INITIAL ONE-TIME PASSWORD (IOTP): //'
7985
```
8086

81-
Scripting mode:
87+
Open the web login page through your SSH tunnel and use the first-time setup panel:
88+
- Enter the IOTP from logs
89+
- Choose username + password
90+
- Submit signup (this creates the first Linux user inside the container)
91+
92+
The IOTP is invalidated after successful signup.
93+
94+
Admin fallback:
8295

8396
```bash
84-
occ user add admin --generate --print-password-only
97+
occ user add admin --generate
8598
```
8699

87100
### 7) Access via SSH tunnel (recommended default)
@@ -96,7 +109,7 @@ Then open:
96109

97110
- `http://localhost:3000`
98111

99-
Log in with the user you created.
112+
Log in with the account you created in the first-time setup panel.
100113

101114
### 8) Enable reboot persistence via systemd (system-level)
102115

@@ -240,8 +253,6 @@ docker run -d --name opencode-cloud-sandbox \
240253
-v opencode-workspace:/home/opencode/workspace \
241254
-v opencode-config:/home/opencode/.config/opencode \
242255
-v opencode-users:/var/lib/opencode-users \
243-
-e OPENCODE_BOOTSTRAP_USER=admin \
244-
-e OPENCODE_BOOTSTRAP_PASSWORD='change-me' \
245256
prizz/opencode-cloud-sandbox:15.2.0
246257
```
247258

@@ -260,6 +271,20 @@ ssh -L 3000:localhost:3000 root@<droplet-ip>
260271

261272
Then open `http://localhost:3000`.
262273

274+
Before signing in, read the container logs and copy the Initial One-Time Password (IOTP):
275+
276+
```bash
277+
docker logs opencode-cloud-sandbox
278+
```
279+
280+
Extract only the IOTP value:
281+
282+
```bash
283+
docker logs opencode-cloud-sandbox 2>&1 | grep -F "INITIAL ONE-TIME PASSWORD (IOTP): " | tail -n1 | sed 's/.*INITIAL ONE-TIME PASSWORD (IOTP): //'
284+
```
285+
286+
Use the login page first-time setup panel with that IOTP, then create your username/password.
287+
263288
### 5) Expose publicly (optional)
264289

265290
- Change to `-p 0.0.0.0:3000:3000` (or `-p 3000:3000`)
@@ -286,4 +311,3 @@ systemd logs (if installed via `occ install`):
286311
```bash
287312
journalctl -u opencode-cloud -f
288313
```
289-

packages/cli-rust/src/commands/start.rs

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,28 @@ fn display_network_exposure_warning(bind_addr: &str) {
574574
eprintln!();
575575
}
576576

577+
fn display_bootstrap_mode_warning(bind_addr: &str) {
578+
eprintln!();
579+
eprintln!(
580+
"{} {}",
581+
style("Bootstrap mode enabled:").yellow().bold(),
582+
style("no preconfigured users were found.").yellow()
583+
);
584+
eprintln!();
585+
eprintln!(
586+
"The service will start on {} and print an Initial One-Time Password (IOTP) in container logs.",
587+
style(bind_addr).cyan()
588+
);
589+
eprintln!("Open the login page and use the first-time setup panel with that IOTP.");
590+
eprintln!("The IOTP is invalidated immediately after the first successful signup.");
591+
eprintln!();
592+
eprintln!(
593+
"Admin alternative: {}",
594+
style("occ user add <username>").dim()
595+
);
596+
eprintln!();
597+
}
598+
577599
/// Start the opencode service
578600
///
579601
/// This command:
@@ -683,20 +705,13 @@ pub async fn cmd_start(
683705
VersionMismatchAction::Continue => {}
684706
}
685707

686-
// Security check: block first start without security configured
708+
// Determine whether this is first container start
687709
let is_first_start = !container_exists(&client, CONTAINER_NAME).await?;
688710

689-
if is_first_start && config.users.is_empty() && !config.allow_unauthenticated_network {
690-
return Err(anyhow!(
691-
"{}\n\n\
692-
No users are configured for authentication.\n\
693-
The service cannot start without security configured.\n\n\
694-
Quick setup:\n {}\n\n\
695-
Or allow unauthenticated access (not recommended):\n {}",
696-
style("Security not configured").red().bold(),
697-
style("occ setup").cyan(),
698-
style("occ config set allow_unauthenticated_network true").dim()
699-
));
711+
let bootstrap_mode =
712+
is_first_start && config.users.is_empty() && !config.allow_unauthenticated_network;
713+
if bootstrap_mode && !quiet {
714+
display_bootstrap_mode_warning(bind_addr);
700715
}
701716

702717
// Check for port mismatch on existing container

packages/core/README.md

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,15 @@ occ install
154154
occ start
155155
```
156156

157+
If this is the first startup with no configured users, the container logs will print an **Initial One-Time Password (IOTP)**.
158+
Open the login page, use the first-time setup panel, and create your first account. The IOTP is invalidated after successful signup.
159+
160+
Quick IOTP extraction from logs:
161+
162+
```bash
163+
occ logs | grep -F "INITIAL ONE-TIME PASSWORD (IOTP): " | tail -n1 | sed 's/.*INITIAL ONE-TIME PASSWORD (IOTP): //'
164+
```
165+
157166
### From source (install locally)
158167

159168
```bash
@@ -338,7 +347,17 @@ occ config show
338347
339348
## Authentication
340349
341-
opencode-cloud uses **PAM (Pluggable Authentication Modules)** for authentication. Users created via `occ user add` authenticate to the opencode web UI.
350+
opencode-cloud uses **PAM (Pluggable Authentication Modules)** for authentication.
351+
352+
First boot path:
353+
- If no users are configured, startup logs print an Initial One-Time Password (IOTP).
354+
- Extract only the IOTP quickly: `occ logs | grep -F "INITIAL ONE-TIME PASSWORD (IOTP): " | tail -n1 | sed 's/.*INITIAL ONE-TIME PASSWORD (IOTP): //'`
355+
- Enter that IOTP in the web login page first-time setup panel.
356+
- Complete signup (username + password) to create the first Linux user account in the container.
357+
- The IOTP is deleted after the first successful signup.
358+
359+
Admin path:
360+
- You can always create/manage users directly via `occ user add`, `occ user passwd`, and related user commands.
342361
343362
### Creating Users
344363
@@ -365,14 +384,6 @@ User accounts (including password hashes and lock status) persist across contain
365384
The CLI stores user records in a managed Docker volume mounted at `/var/lib/opencode-users` inside the container.
366385
No plaintext passwords are stored on the host.
367386
368-
### Legacy Authentication Fields
369-
370-
The `auth_username` and `auth_password` config fields are **deprecated** and ignored. They are kept in the config schema for backward compatibility with existing deployments, but new users should be created via `occ user add` instead.
371-
372-
To migrate from legacy fields:
373-
1. Create a PAM user: `occ user add <username>`
374-
2. The legacy fields will be automatically cleared on next config save
375-
376387
### Rebuilding the Docker Image
377388
378389
When developing locally or after updating opencode-cloud, you may need to rebuild the Docker image to pick up changes in the embedded Dockerfile:

packages/core/src/docker/Dockerfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,10 @@ RUN ls -la /home/opencode/.config/opencode/opencode.jsonc && cat /home/opencode/
581581
COPY packages/core/src/docker/files/entrypoint.sh /usr/local/bin/entrypoint.sh
582582
RUN chmod +x /usr/local/bin/entrypoint.sh
583583

584+
# Bootstrap helper for first-user onboarding (invoked by entrypoint and auth route via sudo)
585+
COPY packages/core/src/docker/files/opencode-cloud-bootstrap.sh /usr/local/bin/opencode-cloud-bootstrap
586+
RUN chmod 700 /usr/local/bin/opencode-cloud-bootstrap
587+
584588
# Note: Don't set USER here - entrypoint needs root to use runuser
585589
# The tini mode drops privileges to opencode user via runuser
586590

0 commit comments

Comments
 (0)