Skip to content

Commit 7ede00a

Browse files
committed
feat: adopt passkey-first bootstrap and opencoder defaults
1 parent 30b39d7 commit 7ede00a

22 files changed

Lines changed: 273 additions & 183 deletions

README.md

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,8 @@ occ start
155155
```
156156

157157
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.
158+
Open the login page, use the first-time setup panel, and enroll a passkey for the default `opencoder` account.
159+
The IOTP is invalidated after successful passkey enrollment.
159160

160161
Quick IOTP extraction from logs:
161162

@@ -266,7 +267,7 @@ occ mount clean --force
266267
occ mount clean --purge --force
267268

268269
# Mount a local project into the workspace
269-
occ mount add /Users/<username>/Desktop/opencode:/home/opencode/workspace
270+
occ mount add /Users/<username>/Desktop/opencode:/home/opencoder/workspace
270271

271272
# Apply mount changes (you may be prompted to recreate the container)
272273
occ restart
@@ -276,21 +277,21 @@ occ reset host --force
276277

277278
### Workspace Mounts
278279

279-
Use `/home/opencode/workspace` when you want your host project folder to appear in the
280+
Use `/home/opencoder/workspace` when you want your host project folder to appear in the
280281
web UI's project picker and inside the container workspace.
281282
282283
Important behavior:
283-
- `/home/opencode/workspace` is a single mount target.
284+
- `/home/opencoder/workspace` is a single mount target.
284285
- Adding a new mount to this same target replaces the previous mount entry.
285286
286287
Recommended workflow:
287288
```bash
288-
occ mount add /Users/<username>/Desktop/opencode:/home/opencode/workspace
289+
occ mount add /Users/<username>/Desktop/opencode:/home/opencoder/workspace
289290
occ restart
290291
```
291292
292293
Verify the mount:
293-
1. Run `occ status` and check `Mounts` -> `Bind mounts` includes your host path mapped to `/home/opencode/workspace`.
294+
1. Run `occ status` and check `Mounts` -> `Bind mounts` includes your host path mapped to `/home/opencoder/workspace`.
294295
2. In the web UI, open the project picker and confirm your project files appear under `~/workspace`.
295296
296297
### Container Mode
@@ -321,7 +322,7 @@ to request an update.
321322
322323
Default paths (with default bind mounts enabled):
323324
- Host: `~/.local/state/opencode/opencode-cloud/commands/update-command.json`
324-
- Container: `/home/opencode/.local/state/opencode/opencode-cloud/commands/update-command.json`
325+
- Container: `/home/opencoder/.local/state/opencode/opencode-cloud/commands/update-command.json`
325326
326327
Example payload:
327328
```json
@@ -353,12 +354,17 @@ First boot path:
353354
- If no users are configured, startup logs print an Initial One-Time Password (IOTP).
354355
- Extract only the IOTP quickly: `occ logs | grep -F "INITIAL ONE-TIME PASSWORD (IOTP): " | tail -n1 | sed 's/.*INITIAL ONE-TIME PASSWORD (IOTP): //'`
355356
- 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.
357+
- Enroll a passkey for the default `opencoder` account.
358+
- The IOTP is deleted after successful passkey enrollment.
358359
359360
Admin path:
360361
- You can always create/manage users directly via `occ user add`, `occ user passwd`, and related user commands.
361362
363+
Login UX:
364+
- Passkey sign-in is the primary option on the login page.
365+
- Username/password sign-in remains available as fallback.
366+
- 2FA setup/management is available from the upper-right session menu after login.
367+
362368
### Creating Users
363369
364370
Create a user with a password:

docs/deploy/digitalocean-droplet.md

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

73-
### 6) Complete first-time signup with Initial One-Time Password (IOTP)
73+
### 6) Complete first-time setup with Initial One-Time Password (IOTP)
7474

7575
After `occ start`, check logs for the IOTP:
7676

@@ -86,10 +86,10 @@ occ logs | grep -F "INITIAL ONE-TIME PASSWORD (IOTP): " | tail -n1 | sed 's/.*IN
8686

8787
Open the web login page through your SSH tunnel and use the first-time setup panel:
8888
- Enter the IOTP from logs
89-
- Choose username + password
90-
- Submit signup (this creates the first Linux user inside the container)
89+
- Continue to passkey setup
90+
- Enroll a passkey for the default `opencoder` account
9191

92-
The IOTP is invalidated after successful signup.
92+
The IOTP is invalidated after successful passkey enrollment.
9393

9494
Admin fallback:
9595

@@ -247,11 +247,11 @@ docker volume create opencode-users
247247
docker run -d --name opencode-cloud-sandbox \
248248
--restart unless-stopped \
249249
-p 127.0.0.1:3000:3000 \
250-
-v opencode-data:/home/opencode/.local/share/opencode \
251-
-v opencode-state:/home/opencode/.local/state/opencode \
252-
-v opencode-cache:/home/opencode/.cache/opencode \
253-
-v opencode-workspace:/home/opencode/workspace \
254-
-v opencode-config:/home/opencode/.config/opencode \
250+
-v opencode-data:/home/opencoder/.local/share/opencode \
251+
-v opencode-state:/home/opencoder/.local/state/opencode \
252+
-v opencode-cache:/home/opencoder/.cache/opencode \
253+
-v opencode-workspace:/home/opencoder/workspace \
254+
-v opencode-config:/home/opencoder/.config/opencode \
255255
-v opencode-users:/var/lib/opencode-users \
256256
prizz/opencode-cloud-sandbox:15.2.0
257257
```
@@ -283,7 +283,7 @@ Extract only the IOTP value:
283283
docker logs opencode-cloud-sandbox 2>&1 | grep -F "INITIAL ONE-TIME PASSWORD (IOTP): " | tail -n1 | sed 's/.*INITIAL ONE-TIME PASSWORD (IOTP): //'
284284
```
285285

286-
Use the login page first-time setup panel with that IOTP, then create your username/password.
286+
Use the login page first-time setup panel with that IOTP, then enroll a passkey for `opencoder`.
287287

288288
### 5) Expose publicly (optional)
289289

docs/update-flow.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,14 @@ processed. Ensure the service is installed and running.
3737
The command file lives under the state bind mount. With default mounts, the paths are:
3838

3939
- Host: `~/.local/state/opencode/opencode-cloud/commands/update-command.json`
40-
- Container: `/home/opencode/.local/state/opencode/opencode-cloud/commands/update-command.json`
40+
- Container: `/home/opencoder/.local/state/opencode/opencode-cloud/commands/update-command.json`
4141
- Result file (host): `~/.local/state/opencode/opencode-cloud/commands/update-command.result.json`
42-
- Result file (container): `/home/opencode/.local/state/opencode/opencode-cloud/commands/update-command.result.json`
42+
- Result file (container): `/home/opencoder/.local/state/opencode/opencode-cloud/commands/update-command.result.json`
4343

4444
If you customize mounts, the container path is still under:
4545

4646
```
47-
/home/opencode/.local/state/opencode/opencode-cloud/commands/
47+
/home/opencoder/.local/state/opencode/opencode-cloud/commands/
4848
```
4949

5050
## Command File Contract

packages/cli-rust/src/commands/container/users.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use std::path::Path;
1818
use std::process::Command;
1919

2020
const USERS_STORE_DIR: &str = MOUNT_USERS;
21-
const PROTECTED_USER: &str = "opencode";
21+
const PROTECTED_USER: &str = "opencoder";
2222

2323
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2424
struct PersistedUserRecord {
@@ -60,7 +60,7 @@ async fn cmd_user_add_container(args: &UserAddArgs, quiet: bool, _verbose: u8) -
6060
} else {
6161
Input::new()
6262
.with_prompt("Username")
63-
.default("opencode".to_string())
63+
.default("admin".to_string())
6464
.validate_with(|input: &String| validate_username(input))
6565
.interact_text()?
6666
};

packages/cli-rust/src/commands/mount/add.rs

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -184,13 +184,13 @@ mod tests {
184184
#[test]
185185
fn upsert_mount_by_target_exact_same_is_noop() {
186186
let existing = vec![
187-
"/host/a:/home/opencode/workspace".to_string(),
188-
"/host/b:/home/opencode/.cache/opencode".to_string(),
187+
"/host/a:/home/opencoder/workspace".to_string(),
188+
"/host/b:/home/opencoder/.cache/opencode".to_string(),
189189
];
190-
let parsed = ParsedMount::parse("/host/a:/home/opencode/workspace").unwrap();
190+
let parsed = ParsedMount::parse("/host/a:/home/opencoder/workspace").unwrap();
191191

192192
let (updated, outcome) =
193-
upsert_mount_by_target(&existing, "/host/a:/home/opencode/workspace", &parsed);
193+
upsert_mount_by_target(&existing, "/host/a:/home/opencoder/workspace", &parsed);
194194

195195
assert_eq!(updated, existing);
196196
assert_eq!(outcome, MountUpsertOutcome::AlreadyConfigured);
@@ -199,26 +199,26 @@ mod tests {
199199
#[test]
200200
fn upsert_mount_by_target_replaces_same_target_with_new_host() {
201201
let existing = vec![
202-
"/host/old:/home/opencode/workspace".to_string(),
203-
"/host/cache:/home/opencode/.cache/opencode".to_string(),
202+
"/host/old:/home/opencoder/workspace".to_string(),
203+
"/host/cache:/home/opencoder/.cache/opencode".to_string(),
204204
];
205-
let parsed = ParsedMount::parse("/host/new:/home/opencode/workspace").unwrap();
205+
let parsed = ParsedMount::parse("/host/new:/home/opencoder/workspace").unwrap();
206206

207207
let (updated, outcome) =
208-
upsert_mount_by_target(&existing, "/host/new:/home/opencode/workspace", &parsed);
208+
upsert_mount_by_target(&existing, "/host/new:/home/opencoder/workspace", &parsed);
209209

210210
assert_eq!(
211211
updated,
212212
vec![
213-
"/host/cache:/home/opencode/.cache/opencode".to_string(),
214-
"/host/new:/home/opencode/workspace".to_string(),
213+
"/host/cache:/home/opencoder/.cache/opencode".to_string(),
214+
"/host/new:/home/opencoder/workspace".to_string(),
215215
]
216216
);
217217
assert_eq!(
218218
outcome,
219219
MountUpsertOutcome::Replaced {
220220
replaced_mounts: vec![
221-
ParsedMount::parse("/host/old:/home/opencode/workspace").unwrap(),
221+
ParsedMount::parse("/host/old:/home/opencoder/workspace").unwrap(),
222222
],
223223
}
224224
);
@@ -227,28 +227,28 @@ mod tests {
227227
#[test]
228228
fn upsert_mount_by_target_replaces_multiple_stale_targets() {
229229
let existing = vec![
230-
"/host/old1:/home/opencode/workspace".to_string(),
231-
"/host/cache:/home/opencode/.cache/opencode".to_string(),
232-
"/host/old2:/home/opencode/workspace:ro".to_string(),
230+
"/host/old1:/home/opencoder/workspace".to_string(),
231+
"/host/cache:/home/opencoder/.cache/opencode".to_string(),
232+
"/host/old2:/home/opencoder/workspace:ro".to_string(),
233233
];
234-
let parsed = ParsedMount::parse("/host/new:/home/opencode/workspace").unwrap();
234+
let parsed = ParsedMount::parse("/host/new:/home/opencoder/workspace").unwrap();
235235

236236
let (updated, outcome) =
237-
upsert_mount_by_target(&existing, "/host/new:/home/opencode/workspace", &parsed);
237+
upsert_mount_by_target(&existing, "/host/new:/home/opencoder/workspace", &parsed);
238238

239239
assert_eq!(
240240
updated,
241241
vec![
242-
"/host/cache:/home/opencode/.cache/opencode".to_string(),
243-
"/host/new:/home/opencode/workspace".to_string(),
242+
"/host/cache:/home/opencoder/.cache/opencode".to_string(),
243+
"/host/new:/home/opencoder/workspace".to_string(),
244244
]
245245
);
246246
assert_eq!(
247247
outcome,
248248
MountUpsertOutcome::Replaced {
249249
replaced_mounts: vec![
250-
ParsedMount::parse("/host/old1:/home/opencode/workspace").unwrap(),
251-
ParsedMount::parse("/host/old2:/home/opencode/workspace:ro").unwrap(),
250+
ParsedMount::parse("/host/old1:/home/opencoder/workspace").unwrap(),
251+
ParsedMount::parse("/host/old2:/home/opencoder/workspace:ro").unwrap(),
252252
],
253253
}
254254
);

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -287,24 +287,24 @@ mod tests {
287287

288288
#[test]
289289
fn resolve_mount_mismatch_action_mismatch_quiet_returns_error() {
290-
let current = vec![current_mount("/old", "/home/opencode/workspace")];
291-
let configured = vec![parsed_mount("/new", "/home/opencode/workspace")];
290+
let current = vec![current_mount("/old", "/home/opencoder/workspace")];
291+
let configured = vec![parsed_mount("/new", "/home/opencoder/workspace")];
292292
let action = resolve_mount_mismatch_action(&current, &configured, true);
293293
assert!(matches!(action, MountMismatchAction::QuietError(_)));
294294
}
295295

296296
#[test]
297297
fn resolve_mount_mismatch_action_mismatch_non_quiet_prompts_recreate() {
298-
let current = vec![current_mount("/old", "/home/opencode/workspace")];
299-
let configured = vec![parsed_mount("/new", "/home/opencode/workspace")];
298+
let current = vec![current_mount("/old", "/home/opencoder/workspace")];
299+
let configured = vec![parsed_mount("/new", "/home/opencoder/workspace")];
300300
let action = resolve_mount_mismatch_action(&current, &configured, false);
301301
assert_eq!(action, MountMismatchAction::PromptRecreate);
302302
}
303303

304304
#[test]
305305
fn resolve_mount_mismatch_action_no_mismatch() {
306-
let current = vec![current_mount("/same", "/home/opencode/workspace")];
307-
let configured = vec![parsed_mount("/same", "/home/opencode/workspace")];
306+
let current = vec![current_mount("/same", "/home/opencoder/workspace")];
307+
let configured = vec![parsed_mount("/same", "/home/opencoder/workspace")];
308308
let action = resolve_mount_mismatch_action(&current, &configured, false);
309309
assert_eq!(action, MountMismatchAction::NoMismatch);
310310
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1540,15 +1540,15 @@ git fetch --depth 1 origin "$OPENCODE_REF"
15401540
15411541
mkdir -p /opt/opencode
15421542
git rev-parse HEAD > /opt/opencode/COMMIT
1543-
chown opencode:opencode /opt/opencode/COMMIT
1543+
chown opencoder:opencoder /opt/opencode/COMMIT
15441544
1545-
runuser -u opencode -- bash -lc 'export PATH="/home/opencode/.bun/bin:$PATH"; cd /tmp/opencode-repo; bun install --frozen-lockfile; cd packages/opencode; bun run build-single-ui'
1546-
runuser -u opencode -- bash -lc '. /home/opencode/.cargo/env; cd /tmp/opencode-repo/packages/opencode-broker; cargo build --release'
1545+
runuser -u opencoder -- bash -lc 'export PATH="/home/opencoder/.bun/bin:$PATH"; cd /tmp/opencode-repo; bun install --frozen-lockfile; cd packages/opencode; bun run build-single-ui'
1546+
runuser -u opencoder -- bash -lc '. /home/opencoder/.cargo/env; cd /tmp/opencode-repo/packages/opencode-broker; cargo build --release'
15471547
15481548
mkdir -p /opt/opencode/bin /opt/opencode/ui
15491549
cp /tmp/opencode-repo/packages/opencode/dist/opencode-*/bin/opencode /opt/opencode/bin/opencode
15501550
cp -R /tmp/opencode-repo/packages/opencode/dist/opencode-*/ui/. /opt/opencode/ui/
1551-
chown -R opencode:opencode /opt/opencode
1551+
chown -R opencoder:opencoder /opt/opencode
15521552
chmod +x /opt/opencode/bin/opencode
15531553
cp /tmp/opencode-repo/packages/opencode-broker/target/release/opencode-broker /usr/local/bin/opencode-broker
15541554
chmod 4755 /usr/local/bin/opencode-broker

packages/cli-rust/src/commands/user/add.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use opencode_cloud_core::{load_config_or_default, save_config};
1818
after_help = "Tip: Press Enter at the password prompt to auto-generate and display a secure password, or use --generate (-g) for non-interactive use."
1919
)]
2020
pub struct UserAddArgs {
21-
/// Username to create (default: opencode if not provided)
21+
/// Username to create (default: admin if not provided)
2222
pub username: Option<String>,
2323

2424
/// Generate a random secure password instead of prompting
@@ -68,7 +68,7 @@ pub async fn cmd_user_add(
6868
} else {
6969
Input::new()
7070
.with_prompt("Username")
71-
.default("opencode".to_string())
71+
.default("admin".to_string())
7272
.validate_with(|input: &String| validate_username(input))
7373
.interact_text()?
7474
};

packages/cli-rust/src/commands/user/remove.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ pub struct UserRemoveArgs {
2323
}
2424

2525
/// System user that cannot be removed (container depends on it)
26-
const PROTECTED_USER: &str = "opencode";
26+
const PROTECTED_USER: &str = "opencoder";
2727

2828
/// Remove a user from the container
2929
pub async fn cmd_user_remove(

0 commit comments

Comments
 (0)