@opencode-cloud/core 3.1.1 → 3.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "opencode-cloud-core"
3
- version = "3.1.1"
3
+ version = "3.1.3"
4
4
  edition = "2024"
5
5
  rust-version = "1.88"
6
6
  license = "MIT"
package/README.md CHANGED
@@ -129,6 +129,39 @@ occ uninstall
129
129
  occ config show
130
130
  ```
131
131
 
132
+ ## Authentication
133
+
134
+ opencode-cloud uses **PAM (Pluggable Authentication Modules)** for authentication. Users created via `occ user add` can authenticate to both:
135
+ - **opencode web UI** - Access the coding interface
136
+ - **Cockpit** - System administration interface
137
+
138
+ ### Creating Users
139
+
140
+ Create a user with a password:
141
+ ```bash
142
+ occ user add <username>
143
+ ```
144
+
145
+ Generate a random password:
146
+ ```bash
147
+ occ user add <username> --generate
148
+ ```
149
+
150
+ ### Managing Users
151
+
152
+ - List users: `occ user list`
153
+ - Change password: `occ user passwd <username>`
154
+ - Remove user: `occ user remove <username>`
155
+ - Enable/disable account: `occ user enable <username>` / `occ user disable <username>`
156
+
157
+ ### Legacy Authentication Fields
158
+
159
+ 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.
160
+
161
+ To migrate from legacy fields:
162
+ 1. Create a PAM user: `occ user add <username>`
163
+ 2. The legacy fields will be automatically cleared on next config save
164
+
132
165
  ### Rebuilding the Docker Image
133
166
 
134
167
  When developing locally or after updating opencode-cloud, you may need to rebuild the Docker image to pick up changes in the embedded Dockerfile:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencode-cloud/core",
3
- "version": "3.1.1",
3
+ "version": "3.1.3",
4
4
  "description": "Core NAPI bindings for opencode-cloud (internal package)",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -42,11 +42,18 @@ pub struct Config {
42
42
  #[serde(default = "default_restart_delay")]
43
43
  pub restart_delay: u32,
44
44
 
45
- /// Username for opencode basic auth (default: None, triggers wizard)
45
+ /// Username for opencode basic auth (DEPRECATED - use PAM users via `occ user add` instead)
46
+ ///
47
+ /// This field is kept for backward compatibility but is ignored.
48
+ /// New deployments should create users via `occ user add` which uses PAM authentication.
49
+ /// Legacy deployments can migrate by running `occ user add <username>`.
46
50
  #[serde(default)]
47
51
  pub auth_username: Option<String>,
48
52
 
49
- /// Password for opencode basic auth (default: None, triggers wizard)
53
+ /// Password for opencode basic auth (DEPRECATED - use PAM users via `occ user add` instead)
54
+ ///
55
+ /// This field is kept for backward compatibility but is ignored.
56
+ /// Passwords are stored in the container's /etc/shadow via PAM, not in config files.
50
57
  #[serde(default)]
51
58
  pub auth_password: Option<String>,
52
59
 
@@ -233,8 +240,11 @@ impl Config {
233
240
  /// Check if required auth credentials are configured
234
241
  ///
235
242
  /// Returns true if:
236
- /// - Both auth_username and auth_password are Some and non-empty (legacy), OR
237
- /// - The users array is non-empty (PAM-based auth)
243
+ /// - The users array is non-empty (PAM-based auth - preferred), OR
244
+ /// - Both auth_username and auth_password are Some and non-empty (legacy - deprecated)
245
+ ///
246
+ /// **Note:** Legacy auth_username/auth_password fields are deprecated and ignored in favor of PAM users.
247
+ /// This method still checks them for backward compatibility, but new deployments should use `occ user add`.
238
248
  ///
239
249
  /// This is used to determine if the setup wizard needs to run.
240
250
  pub fn has_required_auth(&self) -> bool {
@@ -40,7 +40,10 @@ ENV DEBIAN_FRONTEND=noninteractive
40
40
  ENV TZ=UTC
41
41
 
42
42
  # Install build essentials (2026-01-22)
43
- RUN apt-get update && apt-get install -y --no-install-recommends \
43
+ # Use BuildKit cache mount for APT package lists and cache
44
+ RUN --mount=type=cache,target=/var/lib/apt/lists \
45
+ --mount=type=cache,target=/var/cache/apt \
46
+ apt-get update && apt-get install -y --no-install-recommends \
44
47
  build-essential=12.* \
45
48
  # UNPINNED: ca-certificates - security-critical root certs, needs auto-updates
46
49
  ca-certificates \
@@ -74,7 +77,10 @@ ENV LC_ALL=C.UTF-8
74
77
  # Install core system packages in logical groups for better caching
75
78
 
76
79
  # Group 1: Core utilities and build tools (2026-01-22)
77
- RUN apt-get update && apt-get install -y --no-install-recommends \
80
+ # Use BuildKit cache mount for APT package lists and cache
81
+ RUN --mount=type=cache,target=/var/lib/apt/lists \
82
+ --mount=type=cache,target=/var/cache/apt \
83
+ apt-get update && apt-get install -y --no-install-recommends \
78
84
  # Init systems
79
85
  tini=0.19.* \
80
86
  dumb-init=1.2.* \
@@ -120,6 +126,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
120
126
  netcat-openbsd=1.226-* \
121
127
  iputils-ping=3:20240117-* \
122
128
  dnsutils=1:9.18.* \
129
+ # Reverse proxy for opencode UI + API
130
+ nginx=1.24.* \
123
131
  # Compression
124
132
  zip=3.0-* \
125
133
  unzip=6.0-* \
@@ -136,14 +144,20 @@ RUN systemctl mask \
136
144
  systemd-remount-fs.service
137
145
 
138
146
  # Group 2: Database clients (2026-01-22)
139
- RUN apt-get update && apt-get install -y --no-install-recommends \
147
+ # Use BuildKit cache mount for APT package lists and cache
148
+ RUN --mount=type=cache,target=/var/lib/apt/lists \
149
+ --mount=type=cache,target=/var/cache/apt \
150
+ apt-get update && apt-get install -y --no-install-recommends \
140
151
  sqlite3=3.45.* \
141
152
  postgresql-client=16+* \
142
153
  default-mysql-client=1.1.* \
143
154
  && rm -rf /var/lib/apt/lists/*
144
155
 
145
156
  # Group 3: Development libraries for compiling tools (2026-01-22)
146
- RUN apt-get update && apt-get install -y --no-install-recommends \
157
+ # Use BuildKit cache mount for APT package lists and cache
158
+ RUN --mount=type=cache,target=/var/lib/apt/lists \
159
+ --mount=type=cache,target=/var/cache/apt \
160
+ apt-get update && apt-get install -y --no-install-recommends \
147
161
  libssl-dev=3.0.* \
148
162
  libffi-dev=3.4.* \
149
163
  zlib1g-dev=1:1.3.* \
@@ -151,6 +165,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
151
165
  libreadline-dev=8.2-* \
152
166
  libsqlite3-dev=3.45.* \
153
167
  libncurses-dev=6.4+* \
168
+ libpam0g-dev=1.5.* \
154
169
  liblzma-dev=5.6.* \
155
170
  && rm -rf /var/lib/apt/lists/*
156
171
 
@@ -221,6 +236,7 @@ ENV PATH="/home/opencode/.local/share/mise/shims:${PATH}"
221
236
  # Uses stable toolchain (rustup manages toolchain versioning)
222
237
  RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable \
223
238
  && . /home/opencode/.cargo/env \
239
+ && rustup default stable \
224
240
  && rustup component add rust-analyzer rustfmt clippy
225
241
 
226
242
  ENV PATH="/home/opencode/.cargo/bin:${PATH}"
@@ -241,15 +257,20 @@ ENV PNPM_HOME="/home/opencode/.local/share/pnpm"
241
257
  ENV PATH="${PNPM_HOME}:${PATH}"
242
258
  RUN mkdir -p "${PNPM_HOME}"
243
259
 
260
+
244
261
  # uv - self-managing installer, trusted to handle versions (fast Python package manager)
245
262
  RUN curl -LsSf https://astral.sh/uv/install.sh | sh
246
263
 
247
264
  # Install pipx for isolated Python application installs
248
- RUN eval "$(/home/opencode/.local/bin/mise activate bash)" \
265
+ # Use BuildKit cache mount for pip cache
266
+ RUN --mount=type=cache,target=/home/opencode/.cache/pip,uid=1000,gid=1000,mode=0755 \
267
+ mkdir -p /home/opencode/.cache/pip \
268
+ && eval "$(/home/opencode/.local/bin/mise activate bash)" \
249
269
  && pip install --user pipx \
250
270
  && pipx ensurepath
251
271
 
252
272
  # Install global TypeScript compiler
273
+ # NOTE: Avoid cache mount here to prevent pnpm store permission issues
253
274
  RUN eval "$(/home/opencode/.local/bin/mise activate bash)" \
254
275
  && pnpm add -g typescript
255
276
 
@@ -258,7 +279,9 @@ RUN eval "$(/home/opencode/.local/bin/mise activate bash)" \
258
279
  # -----------------------------------------------------------------------------
259
280
  # ripgrep 15.1.0 - fast regex search
260
281
  # eza 0.23.4 - modern ls replacement
261
- RUN . /home/opencode/.cargo/env \
282
+ # NOTE: Avoid cache mounts here due to permission issues with non-root Cargo cache
283
+ RUN mkdir -p /home/opencode/.cargo/registry /home/opencode/.cargo/git \
284
+ && . /home/opencode/.cargo/env \
262
285
  && cargo install --locked ripgrep@15.1.0 eza@0.23.4
263
286
 
264
287
  # lazygit v0.58.1 (2026-01-12) - terminal UI for git
@@ -304,8 +327,11 @@ RUN . /home/opencode/.cargo/env \
304
327
  # && go install mvdan.cc/sh/v3/cmd/shfmt@v3.12.0
305
328
 
306
329
  # Install btop system monitor (2026-01-22)
330
+ # Use BuildKit cache mount for APT package lists and cache
307
331
  USER root
308
- RUN apt-get update && apt-get install -y --no-install-recommends btop=1.3.* \
332
+ RUN --mount=type=cache,target=/var/lib/apt/lists \
333
+ --mount=type=cache,target=/var/cache/apt \
334
+ apt-get update && apt-get install -y --no-install-recommends btop=1.3.* \
309
335
  && rm -rf /var/lib/apt/lists/*
310
336
  USER opencode
311
337
 
@@ -313,7 +339,10 @@ USER opencode
313
339
  # GitHub CLI
314
340
  # -----------------------------------------------------------------------------
315
341
  USER root
316
- RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \
342
+ # Use BuildKit cache mount for APT package lists and cache
343
+ RUN --mount=type=cache,target=/var/lib/apt/lists \
344
+ --mount=type=cache,target=/var/cache/apt \
345
+ curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \
317
346
  && chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg \
318
347
  && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" > /etc/apt/sources.list.d/github-cli.list \
319
348
  && apt-get update && apt-get install -y --no-install-recommends gh \
@@ -325,8 +354,11 @@ USER opencode
325
354
  # -----------------------------------------------------------------------------
326
355
  # Cockpit provides web-based administration for the container
327
356
  # Ubuntu noble has cockpit 316 in main repos
357
+ # Use BuildKit cache mount for APT package lists and cache
328
358
  USER root
329
- RUN apt-get update && \
359
+ RUN --mount=type=cache,target=/var/lib/apt/lists \
360
+ --mount=type=cache,target=/var/cache/apt \
361
+ apt-get update && \
330
362
  apt-get install -y --no-install-recommends \
331
363
  cockpit-ws \
332
364
  cockpit-system \
@@ -416,59 +448,6 @@ USER opencode
416
448
  # RUN eval "$(/home/opencode/.local/bin/mise activate bash)" \
417
449
  # && go install github.com/fullstorydev/grpcurl/cmd/grpcurl@v1.9.3
418
450
 
419
- # -----------------------------------------------------------------------------
420
- # opencode Installation
421
- # -----------------------------------------------------------------------------
422
- # opencode - self-managing installer, trusted to handle versions
423
- # The script installs to ~/.opencode/bin/
424
- # Retry logic added because opencode.ai API can be flaky during parallel builds
425
- RUN for i in 1 2 3 4 5; do \
426
- curl -fsSL https://opencode.ai/install | bash && break || \
427
- echo "Attempt $i failed, retrying in 10s..." && sleep 10; \
428
- done \
429
- && ls -la /home/opencode/.opencode/bin/opencode \
430
- && /home/opencode/.opencode/bin/opencode --version
431
-
432
- # Add opencode to PATH
433
- ENV PATH="/home/opencode/.opencode/bin:${PATH}"
434
-
435
- # -----------------------------------------------------------------------------
436
- # GSD Plugin Installation
437
- # -----------------------------------------------------------------------------
438
- # Install the GSD (Get Shit Done) plugin for opencode
439
- # Note: If this fails in container builds due to "~" path resolution, retry with
440
- # OPENCODE_CONFIG_DIR=/home/opencode/.config/opencode set explicitly.
441
- RUN npx --yes get-shit-done-cc --opencode --global
442
-
443
- # -----------------------------------------------------------------------------
444
- # opencode systemd Service (2026-01-22)
445
- # -----------------------------------------------------------------------------
446
- # Create opencode as a systemd service for Cockpit integration
447
- USER root
448
- RUN printf '%s\n' \
449
- '[Unit]' \
450
- 'Description=opencode Web Interface' \
451
- 'After=network.target' \
452
- '' \
453
- '[Service]' \
454
- 'Type=simple' \
455
- 'User=opencode' \
456
- 'WorkingDirectory=/home/opencode/workspace' \
457
- 'ExecStart=/home/opencode/.opencode/bin/opencode web --port 3000 --hostname 0.0.0.0' \
458
- 'Restart=always' \
459
- 'RestartSec=5' \
460
- 'Environment=PATH=/home/opencode/.opencode/bin:/home/opencode/.local/bin:/home/opencode/.cargo/bin:/home/opencode/.local/share/mise/shims:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' \
461
- '' \
462
- '[Install]' \
463
- 'WantedBy=multi-user.target' \
464
- > /etc/systemd/system/opencode.service
465
-
466
- # Enable opencode service to start at boot (manual symlink since systemctl doesn't work during build)
467
- RUN mkdir -p /etc/systemd/system/multi-user.target.wants \
468
- && ln -sf /etc/systemd/system/opencode.service /etc/systemd/system/multi-user.target.wants/opencode.service
469
-
470
- USER opencode
471
-
472
451
  # -----------------------------------------------------------------------------
473
452
  # Sensible Defaults Configuration
474
453
  # -----------------------------------------------------------------------------
@@ -536,6 +515,279 @@ RUN printf '%s\n' \
536
515
  # Set up pipx path
537
516
  RUN echo 'export PATH="/home/opencode/.local/bin:$PATH"' >> /home/opencode/.zshrc
538
517
 
518
+ # -----------------------------------------------------------------------------
519
+ # opencode Setup (Fork + Broker + Proxy)
520
+ # -----------------------------------------------------------------------------
521
+ # Keep this block near the end to preserve cache for earlier layers. We expect
522
+ # opencode fork changes to happen more often than base tooling changes.
523
+ #
524
+ # This block includes:
525
+ # - opencode build (backend binary) + app build (frontend dist)
526
+ # - opencode-broker build
527
+ # - nginx config for single-endpoint UI + API proxy
528
+ # - PAM configuration + systemd services
529
+ # - opencode config file
530
+ #
531
+ # NOTE: This section switches between opencode and root users as needed.
532
+
533
+ # -----------------------------------------------------------------------------
534
+ # opencode Installation (Fork from pRizz/opencode)
535
+ # -----------------------------------------------------------------------------
536
+ # Clone the fork and build opencode from source (as non-root user)
537
+ # Pin to specific commit for reproducibility: 50fd5d1a6a17dc1b378a0e60b464a75dd876d6e2
538
+ # Build opencode from source (BuildKit cache mounts disabled for now)
539
+ RUN rm -rf /tmp/opencode-repo \
540
+ && git clone --depth 1 https://github.com/pRizz/opencode.git /tmp/opencode-repo \
541
+ && cd /tmp/opencode-repo \
542
+ && git checkout 50fd5d1a6a17dc1b378a0e60b464a75dd876d6e2 \
543
+ && curl -fsSL https://bun.sh/install | bash -s "bun-v1.3.5" \
544
+ && export PATH="/home/opencode/.bun/bin:${PATH}" \
545
+ && bun install --frozen-lockfile \
546
+ && bun run packages/opencode/script/build.ts --single \
547
+ && cd packages/app \
548
+ && bun run build \
549
+ && cd /tmp/opencode-repo \
550
+ && mkdir -p /home/opencode/.local/share/opencode/bin \
551
+ && cp /tmp/opencode-repo/packages/opencode/dist/opencode-*/bin/opencode /home/opencode/.local/share/opencode/bin/opencode \
552
+ && chown -R opencode:opencode /home/opencode/.local/share/opencode \
553
+ && chmod +x /home/opencode/.local/share/opencode/bin/opencode \
554
+ && /home/opencode/.local/share/opencode/bin/opencode --version
555
+
556
+ # Add opencode to PATH
557
+ ENV PATH="/home/opencode/.local/share/opencode/bin:${PATH}"
558
+
559
+ # Copy UI assets to standard web root (requires root)
560
+ USER root
561
+ RUN mkdir -p /var/www/opencode \
562
+ && cp -R /tmp/opencode-repo/packages/app/dist/. /var/www/opencode/ \
563
+ && chown -R root:root /var/www/opencode \
564
+ && chmod 755 /var/www /var/www/opencode \
565
+ && chmod -R a+rX /var/www/opencode
566
+
567
+ # -----------------------------------------------------------------------------
568
+ # opencode-broker Installation
569
+ # -----------------------------------------------------------------------------
570
+ # Build opencode-broker from source (Rust service for PAM authentication)
571
+ # The broker handles PAM authentication and user process spawning
572
+ # NOTE: Requires root privileges for setuid bit (chmod 4755) to allow broker
573
+ # to run with elevated privileges for PAM authentication
574
+ USER root
575
+ RUN cd /tmp/opencode-repo/packages/opencode-broker \
576
+ && runuser -u opencode -- bash -c '. /home/opencode/.cargo/env && cargo build --release' \
577
+ && mkdir -p /usr/local/bin \
578
+ && cp target/release/opencode-broker /usr/local/bin/opencode-broker \
579
+ && chmod 4755 /usr/local/bin/opencode-broker \
580
+ && rm -rf /tmp/opencode-repo
581
+
582
+ # Verify broker binary exists and is executable
583
+ RUN ls -la /usr/local/bin/opencode-broker \
584
+ && test -x /usr/local/bin/opencode-broker \
585
+ && echo "Broker installed"
586
+
587
+ # -----------------------------------------------------------------------------
588
+ # Nginx Reverse Proxy for UI + API
589
+ # -----------------------------------------------------------------------------
590
+ # Serve the built UI from /var/www/opencode and proxy API/WebSocket
591
+ # TODO: Explore a sustainable routing strategy (e.g., backend-driven base URL or
592
+ # TODO: content-type aware proxying without hardcoded path lists).
593
+ RUN rm -f /etc/nginx/sites-enabled/default /etc/nginx/conf.d/default.conf 2>/dev/null || true \
594
+ && printf '%s\n' \
595
+ 'server {' \
596
+ ' listen 3000;' \
597
+ ' server_name _;' \
598
+ '' \
599
+ ' root /var/www/opencode;' \
600
+ ' index index.html;' \
601
+ '' \
602
+ ' location = /health {' \
603
+ ' proxy_pass http://127.0.0.1:3001;' \
604
+ ' }' \
605
+ '' \
606
+ ' location /assets/ {' \
607
+ ' try_files $uri =404;' \
608
+ ' }' \
609
+ '' \
610
+ ' location ~* \.(?:js|css|png|jpg|jpeg|gif|svg|ico|webp|woff2?|ttf|map)$ {' \
611
+ ' try_files $uri =404;' \
612
+ ' }' \
613
+ '' \
614
+ ' location ~ ^/(auth|session|sessions|pty|event|events|api|ws|v1|rpc|config|global|path|project|provider) {' \
615
+ ' proxy_pass http://127.0.0.1:3001;' \
616
+ ' proxy_http_version 1.1;' \
617
+ ' proxy_set_header Upgrade $http_upgrade;' \
618
+ ' proxy_set_header Connection "upgrade";' \
619
+ ' proxy_set_header Host $host;' \
620
+ ' proxy_set_header X-Real-IP $remote_addr;' \
621
+ ' proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;' \
622
+ ' proxy_set_header X-Forwarded-Proto $scheme;' \
623
+ ' }' \
624
+ '' \
625
+ ' location / {' \
626
+ ' try_files $uri $uri/ @opencode_fallback;' \
627
+ ' }' \
628
+ '' \
629
+ ' location @opencode_fallback {' \
630
+ ' if ($http_accept ~* "text/html") { rewrite ^ /index.html break; }' \
631
+ ' proxy_pass http://127.0.0.1:3001;' \
632
+ ' proxy_http_version 1.1;' \
633
+ ' proxy_set_header Upgrade $http_upgrade;' \
634
+ ' proxy_set_header Connection "upgrade";' \
635
+ ' proxy_set_header Host $host;' \
636
+ ' proxy_set_header X-Real-IP $remote_addr;' \
637
+ ' proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;' \
638
+ ' proxy_set_header X-Forwarded-Proto $scheme;' \
639
+ ' }' \
640
+ '}' \
641
+ > /etc/nginx/conf.d/opencode.conf
642
+
643
+ # -----------------------------------------------------------------------------
644
+ # PAM Configuration
645
+ # -----------------------------------------------------------------------------
646
+ # Install PAM configuration for opencode authentication
647
+ # This allows opencode to authenticate users via PAM (same users as Cockpit)
648
+ # NOTE: Requires root privileges to write to /etc/pam.d/
649
+ RUN printf '%s\n' \
650
+ '# PAM configuration for OpenCode authentication' \
651
+ '# Install to /etc/pam.d/opencode' \
652
+ '' \
653
+ '# Standard UNIX authentication' \
654
+ 'auth required pam_unix.so' \
655
+ 'account required pam_unix.so' \
656
+ '' \
657
+ '# Optional: Enable TOTP 2FA (uncomment when pam_google_authenticator is installed)' \
658
+ '# auth required pam_google_authenticator.so' \
659
+ > /etc/pam.d/opencode \
660
+ && chmod 644 /etc/pam.d/opencode
661
+
662
+ # Verify PAM config file exists
663
+ RUN ls -la /etc/pam.d/opencode && cat /etc/pam.d/opencode
664
+
665
+ # -----------------------------------------------------------------------------
666
+ # opencode-broker systemd Service
667
+ # -----------------------------------------------------------------------------
668
+ # Create opencode-broker service for PAM authentication
669
+ # NOTE: Requires root privileges to write to /etc/systemd/system/
670
+ RUN printf '%s\n' \
671
+ '[Unit]' \
672
+ 'Description=OpenCode Authentication Broker' \
673
+ 'Documentation=https://github.com/pRizz/opencode' \
674
+ 'After=network.target' \
675
+ '' \
676
+ '[Service]' \
677
+ 'Type=notify' \
678
+ 'ExecStart=/usr/local/bin/opencode-broker' \
679
+ 'ExecReload=/bin/kill -HUP $MAINPID' \
680
+ 'Restart=always' \
681
+ 'RestartSec=5' \
682
+ '' \
683
+ '# Security hardening' \
684
+ 'NoNewPrivileges=false' \
685
+ 'ProtectSystem=strict' \
686
+ 'ProtectHome=read-only' \
687
+ 'PrivateTmp=true' \
688
+ 'ReadWritePaths=/run/opencode' \
689
+ '' \
690
+ '# Socket directory' \
691
+ 'RuntimeDirectory=opencode' \
692
+ 'RuntimeDirectoryMode=0755' \
693
+ '' \
694
+ '# Logging' \
695
+ 'StandardOutput=journal' \
696
+ 'StandardError=journal' \
697
+ 'SyslogIdentifier=opencode-broker' \
698
+ '' \
699
+ '[Install]' \
700
+ 'WantedBy=multi-user.target' \
701
+ > /etc/systemd/system/opencode-broker.service
702
+
703
+ # Enable opencode-broker service
704
+ RUN mkdir -p /etc/systemd/system/multi-user.target.wants \
705
+ && ln -sf /etc/systemd/system/opencode-broker.service /etc/systemd/system/multi-user.target.wants/opencode-broker.service
706
+
707
+ USER opencode
708
+
709
+ # -----------------------------------------------------------------------------
710
+ # GSD Plugin Installation
711
+ # -----------------------------------------------------------------------------
712
+ # Install the GSD (Get Shit Done) plugin for opencode
713
+ # Note: If this fails in container builds due to "~" path resolution, retry with
714
+ # OPENCODE_CONFIG_DIR=/home/opencode/.config/opencode set explicitly.
715
+ RUN mkdir -p /home/opencode/.npm \
716
+ && npx --yes get-shit-done-cc --opencode --global
717
+
718
+ # -----------------------------------------------------------------------------
719
+ # opencode systemd Service (2026-01-22)
720
+ # -----------------------------------------------------------------------------
721
+ # Create opencode as a systemd service for Cockpit integration (backend only)
722
+ # NOTE: Requires root privileges to write to /etc/systemd/system/
723
+ USER root
724
+ RUN printf '%s\n' \
725
+ '[Unit]' \
726
+ 'Description=opencode Web Interface' \
727
+ 'After=network.target opencode-broker.service' \
728
+ '' \
729
+ '[Service]' \
730
+ 'Type=simple' \
731
+ 'User=opencode' \
732
+ 'WorkingDirectory=/home/opencode/workspace' \
733
+ 'ExecStart=/home/opencode/.local/share/opencode/bin/opencode --port 3001 --hostname 0.0.0.0' \
734
+ 'Restart=always' \
735
+ 'RestartSec=5' \
736
+ 'Environment=PATH=/home/opencode/.local/share/opencode/bin:/home/opencode/.local/bin:/home/opencode/.cargo/bin:/home/opencode/.local/share/mise/shims:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' \
737
+ '' \
738
+ '[Install]' \
739
+ 'WantedBy=multi-user.target' \
740
+ > /etc/systemd/system/opencode.service
741
+
742
+ # Enable opencode service to start at boot (manual symlink since systemctl doesn't work during build)
743
+ RUN mkdir -p /etc/systemd/system/multi-user.target.wants \
744
+ && ln -sf /etc/systemd/system/opencode.service /etc/systemd/system/multi-user.target.wants/opencode.service
745
+
746
+ # Nginx service for serving UI + proxying API
747
+ RUN printf '%s\n' \
748
+ '[Unit]' \
749
+ 'Description=Nginx reverse proxy for opencode UI' \
750
+ 'After=network.target opencode.service' \
751
+ '' \
752
+ '[Service]' \
753
+ 'Type=simple' \
754
+ 'ExecStart=/usr/sbin/nginx -g "daemon off;"' \
755
+ 'ExecReload=/usr/sbin/nginx -s reload' \
756
+ 'Restart=always' \
757
+ 'RestartSec=5' \
758
+ '' \
759
+ '[Install]' \
760
+ 'WantedBy=multi-user.target' \
761
+ > /etc/systemd/system/opencode-nginx.service
762
+
763
+ # Enable nginx service
764
+ RUN mkdir -p /etc/systemd/system/multi-user.target.wants \
765
+ && ln -sf /etc/systemd/system/opencode-nginx.service /etc/systemd/system/multi-user.target.wants/opencode-nginx.service
766
+
767
+ # Prevent the distro nginx service from also starting (port 3000 conflict)
768
+ RUN rm -f /etc/systemd/system/multi-user.target.wants/nginx.service \
769
+ && ln -sf /dev/null /etc/systemd/system/nginx.service
770
+
771
+ # -----------------------------------------------------------------------------
772
+ # opencode Configuration
773
+ # -----------------------------------------------------------------------------
774
+ # Create opencode.json config file with PAM authentication enabled
775
+ RUN mkdir -p /home/opencode/.config/opencode \
776
+ && printf '%s\n' \
777
+ '{' \
778
+ ' "auth": {' \
779
+ ' "enabled": true' \
780
+ ' }' \
781
+ '}' \
782
+ > /home/opencode/.config/opencode/opencode.json \
783
+ && chown -R opencode:opencode /home/opencode/.config/opencode \
784
+ && chmod 644 /home/opencode/.config/opencode/opencode.json
785
+
786
+ # Verify config file exists
787
+ RUN ls -la /home/opencode/.config/opencode/opencode.json && cat /home/opencode/.config/opencode/opencode.json
788
+
789
+ USER opencode
790
+
539
791
  # -----------------------------------------------------------------------------
540
792
  # Entrypoint Script (Hybrid Init Support)
541
793
  # -----------------------------------------------------------------------------
@@ -549,7 +801,8 @@ RUN printf '%s\n' \
549
801
  ' exec /sbin/init' \
550
802
  'else' \
551
803
  ' # Use runuser to switch to opencode user without password prompt' \
552
- ' exec /usr/bin/tini -- runuser -u opencode -- /home/opencode/.opencode/bin/opencode web --port 3000 --hostname 0.0.0.0' \
804
+ ' runuser -u opencode -- /home/opencode/.local/share/opencode/bin/opencode --port 3001 --hostname 0.0.0.0 &' \
805
+ ' exec /usr/sbin/nginx -g "daemon off;"' \
553
806
  'fi' \
554
807
  > /usr/local/bin/entrypoint.sh && chmod +x /usr/local/bin/entrypoint.sh
555
808