File size: 3,101 Bytes
930c3ef
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63ac17f
 
 
 
 
 
 
 
930c3ef
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a38523f
 
edf5b8c
a38523f
 
 
 
930c3ef
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// opencode-cloud as a GitHub Codespace — a third home alongside Hugging Face Spaces and
// Railway.
//
// The same Dockerfile serves all three. What differs here is that a dev container starts with
// its entrypoint overridden, so the image's ENTRYPOINT never runs: the server has to be
// started explicitly, and started again after every idle stop.
//
// Codespaces stop after 30 minutes idle by default. That is fine for this use — a stopped
// codespace keeps its disk and restarts under the same name and URL — but it does mean
// postStartCommand, not postCreateCommand, is what boots the server, because only the former
// runs on every resume.
{
  "name": "opencode-cloud",
  "build": {
    "dockerfile": "../Dockerfile"
  },

  // The image ships no SSH server, and without one `gh codespace ssh` and `gh codespace logs`
  // both refuse — so a codespace that fails to start the server cannot be inspected at all.
  // This costs nothing at runtime and is the difference between a debuggable box and a black
  // one.
  "features": {
    "ghcr.io/devcontainers/features/sshd:1": {}
  },

  // uid 1000, matching the image. The Dockerfile creates nothing else.
  "remoteUser": "node",

  // Hugging Face has no $PORT and falls back to 7860; keep the same here so one number
  // describes the server everywhere.
  "containerEnv": {
    "PORT": "7860",
    // entrypoint.sh persists sessions, logins and code under this root when it is writable.
    // /data does not exist in a codespace, but /workspaces survives stop/start, so state
    // outlives an idle timeout and is lost only when the codespace itself is deleted.
    "OPENCODE_STATE_ROOT": "/workspaces/.opencode-state"
  },

  "forwardPorts": [7860],
  "portsAttributes": {
    "7860": {
      "label": "opencode",
      // The only way to expose a port without a human toggling it: there is no REST endpoint
      // for port visibility, so a client that provisions codespaces has to declare it here.
      //
      // Public is safe *because* entrypoint.sh refuses to start without
      // OPENCODE_SERVER_PASSWORD. Every route on this server runs shell commands, so basic
      // auth is the only thing in front of it; a missing password fails loudly rather than
      // publishing an open shell.
      "visibility": "public",
      "onAutoForward": "silent"
    }
  },

  // Runs on create *and* on every resume from an idle stop, which is what makes the codespace
  // behave like a server rather than a dev box you have to restart by hand. Verified across a
  // stop/start cycle: the hook runs again and the server is serving within seconds.
  //
  // `bash -c` is not decoration. The hook is otherwise run by /bin/sh, where this line does
  // not reliably detach and the server is lost the moment the hook returns — leaving a
  // codespace that looks healthy and serves nothing.
  "postStartCommand": "bash -c 'setsid nohup ${containerWorkspaceFolder}/.devcontainer/start-opencode.sh >> /tmp/opencode.log 2>&1 < /dev/null & sleep 2'",

  "customizations": {
    "vscode": {
      "extensions": []
    }
  }
}