diff --git a/Dockerfile b/Dockerfile
index 3395a8d..d3d935d 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,97 +1,28 @@
-# Build openclaw from source to avoid npm packaging gaps (some dist files are not shipped).
-FROM node:24.1.0-bookworm AS openclaw-build
+FROM node:24-bookworm
-# Dependencies needed for openclaw build
RUN apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
+ ca-certificates \
+ curl \
git \
- ca-certificates \
- curl \
- python3 \
- make \
- g++ \
- && rm -rf /var/lib/apt/lists/*
-
-# Install Bun (openclaw build uses it)
-RUN curl -fsSL https://bun.sh/install | bash
-ENV PATH="/root/.bun/bin:${PATH}"
-
-RUN corepack enable
-
-WORKDIR /openclaw
-
-# Pin to a known ref (tag/branch). If it doesn't exist, fall back to main.
-ARG OPENCLAW_GIT_REF=main
-RUN git clone --depth 1 --branch "${OPENCLAW_GIT_REF}" https://github.com/openclaw/openclaw.git .
-
-# Patch: relax version requirements for packages that may reference unpublished versions.
-# Apply to all extension package.json files to handle workspace protocol (workspace:*).
-RUN set -eux; \
- find ./extensions -name 'package.json' -type f | while read -r f; do \
- sed -i -E 's/"openclaw"[[:space:]]*:[[:space:]]*">=[^"]+"/"openclaw": "*"/g' "$f"; \
- sed -i -E 's/"openclaw"[[:space:]]*:[[:space:]]*"workspace:[^"]+"/"openclaw": "*"/g' "$f"; \
- done
-
-RUN pnpm install --no-frozen-lockfile
-RUN pnpm build
-ENV OPENCLAW_PREFER_PNPM=1
-RUN pnpm ui:install && pnpm ui:build
-
-
-# Runtime image
-FROM node:24.1.0-bookworm
-ENV NODE_ENV=production
-
-RUN apt-get update \
- && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
- ca-certificates \
- curl \
- build-essential \
- gcc \
- g++ \
- make \
procps \
- file \
- git \
- python3 \
- pkg-config \
- sudo \
&& rm -rf /var/lib/apt/lists/*
-# Install Homebrew (must run as non-root user)
-# Create a user for Homebrew installation, install it, then make it accessible to all users
-RUN useradd -m -s /bin/bash linuxbrew \
- && echo 'linuxbrew ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
-
-USER linuxbrew
-RUN NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
-
-USER root
-RUN chown -R root:root /home/linuxbrew/.linuxbrew
-ENV PATH="/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin:${PATH}"
-
-RUN corepack enable
+RUN npm install -g openclaw@latest
WORKDIR /app
-# Wrapper deps
COPY package.json pnpm-lock.yaml ./
-RUN pnpm install --frozen-lockfile --prod
-
-# Copy built openclaw
-COPY --from=openclaw-build /openclaw /openclaw
-
-# Provide an openclaw executable
-RUN printf '%s\n' '#!/usr/bin/env bash' 'exec node /openclaw/dist/entry.js "$@"' > /usr/local/bin/openclaw \
- && chmod +x /usr/local/bin/openclaw
+RUN corepack enable && pnpm install --frozen-lockfile --prod
COPY src ./src
RUN useradd -m -s /bin/bash openclaw \
- && chown -R openclaw:openclaw /app /openclaw \
+ && chown -R openclaw:openclaw /app \
&& mkdir -p /data && chown openclaw:openclaw /data
ENV PORT=8080
+ENV OPENCLAW_ENTRY=/usr/local/lib/node_modules/openclaw/dist/entry.js
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s \
diff --git a/src/public/setup.html b/src/public/setup.html
index 61fec55..10ed012 100644
--- a/src/public/setup.html
+++ b/src/public/setup.html
@@ -6,8 +6,17 @@
OpenClaw Setup
+
- OpenClaw Setup
+
This wizard configures OpenClaw by running the same onboarding command it uses in the terminal, but from the browser.
@@ -271,18 +289,18 @@
left: 0;
right: 0;
bottom: 0;
- background: rgba(0, 0, 0, 0.5);
+ background: var(--overlay-bg);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal {
- background: #1a1a2e;
+ background: var(--modal-bg);
padding: 1.5rem;
border-radius: 8px;
min-width: 300px;
- border: 1px solid #333;
+ border: 1px solid var(--modal-border);
}
.modal h3 {
margin-top: 0;
diff --git a/src/public/styles.css b/src/public/styles.css
index a647c1f..e06fe25 100644
--- a/src/public/styles.css
+++ b/src/public/styles.css
@@ -1,24 +1,94 @@
+:root {
+ --bg-primary: #050810;
+ --bg-card: #111827;
+ --bg-input: #1f2937;
+ --bg-code: #1f2937;
+ --bg-pre: #0d1117;
+ --border-color: #1f2937;
+ --border-input: #374151;
+ --text-primary: #f0f4ff;
+ --text-muted: #8892b0;
+ --text-code: #00e5cc;
+ --accent: #ff4d4d;
+ --accent-hover: #e63946;
+ --secondary-bg: #1f2937;
+ --secondary-hover: #374151;
+ --tertiary-bg: #374151;
+ --tertiary-hover: #4b5563;
+ --modal-bg: #1a1a2e;
+ --modal-border: #333;
+ --overlay-bg: rgba(0, 0, 0, 0.5);
+}
+
+[data-theme="light"] {
+ --bg-primary: #f8fafc;
+ --bg-card: #ffffff;
+ --bg-input: #f1f5f9;
+ --bg-code: #e2e8f0;
+ --bg-pre: #f1f5f9;
+ --border-color: #e2e8f0;
+ --border-input: #cbd5e1;
+ --text-primary: #1e293b;
+ --text-muted: #64748b;
+ --text-code: #0d9488;
+ --accent: #dc2626;
+ --accent-hover: #b91c1c;
+ --secondary-bg: #e2e8f0;
+ --secondary-hover: #cbd5e1;
+ --tertiary-bg: #cbd5e1;
+ --tertiary-hover: #94a3b8;
+ --modal-bg: #ffffff;
+ --modal-border: #e2e8f0;
+ --overlay-bg: rgba(0, 0, 0, 0.3);
+}
+
+@media (prefers-color-scheme: light) {
+ :root:not([data-theme="dark"]) {
+ --bg-primary: #f8fafc;
+ --bg-card: #ffffff;
+ --bg-input: #f1f5f9;
+ --bg-code: #e2e8f0;
+ --bg-pre: #f1f5f9;
+ --border-color: #e2e8f0;
+ --border-input: #cbd5e1;
+ --text-primary: #1e293b;
+ --text-muted: #64748b;
+ --text-code: #0d9488;
+ --accent: #dc2626;
+ --accent-hover: #b91c1c;
+ --secondary-bg: #e2e8f0;
+ --secondary-hover: #cbd5e1;
+ --tertiary-bg: #cbd5e1;
+ --tertiary-hover: #94a3b8;
+ --modal-bg: #ffffff;
+ --modal-border: #e2e8f0;
+ --overlay-bg: rgba(0, 0, 0, 0.3);
+ }
+}
+
body {
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial;
margin: 2rem;
max-width: 900px;
- background: #050810;
- color: #f0f4ff;
+ background: var(--bg-primary);
+ color: var(--text-primary);
+ transition: background 0.2s, color 0.2s;
}
.card {
- border: 1px solid #1f2937;
+ border: 1px solid var(--border-color);
border-radius: 12px;
padding: 1.25rem;
margin: 1rem 0;
- background: #111827;
+ background: var(--bg-card);
+ transition: background 0.2s, border-color 0.2s;
}
label {
display: block;
margin-top: 0.75rem;
font-weight: 600;
- color: #f0f4ff;
+ color: var(--text-primary);
}
input,
@@ -26,23 +96,24 @@ select {
width: 100%;
padding: 0.6rem;
margin-top: 0.25rem;
- background: #1f2937;
- border: 1px solid #374151;
+ background: var(--bg-input);
+ border: 1px solid var(--border-input);
border-radius: 8px;
- color: #f0f4ff;
+ color: var(--text-primary);
+ transition: background 0.2s, border-color 0.2s, color 0.2s;
}
input:focus,
select:focus {
outline: none;
- border-color: #ff4d4d;
+ border-color: var(--accent);
}
button {
padding: 0.8rem 1.2rem;
border-radius: 10px;
border: 0;
- background: #ff4d4d;
+ background: var(--accent);
color: #fff;
font-weight: 700;
cursor: pointer;
@@ -50,30 +121,32 @@ button {
}
button:hover {
- background: #e63946;
+ background: var(--accent-hover);
}
.secondary-button {
- background: #1f2937;
+ background: var(--secondary-bg);
+ color: var(--text-primary);
margin-left: 0.5rem;
}
.secondary-button:hover {
- background: #374151;
+ background: var(--secondary-hover);
}
.tertiary-button {
- background: #374151;
+ background: var(--tertiary-bg);
+ color: var(--text-primary);
margin-left: 0.5rem;
}
.tertiary-button:hover {
- background: #4b5563;
+ background: var(--tertiary-hover);
}
code {
- background: #1f2937;
- color: #00e5cc;
+ background: var(--bg-code);
+ color: var(--text-code);
padding: 0.1rem 0.3rem;
border-radius: 6px;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
@@ -81,31 +154,51 @@ code {
pre {
white-space: pre-wrap;
- background: #0d1117;
- border: 1px solid #1f2937;
+ background: var(--bg-pre);
+ border: 1px solid var(--border-color);
border-radius: 8px;
padding: 1rem;
margin-top: 1rem;
- color: #8892b0;
+ color: var(--text-muted);
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
font-size: 0.875rem;
overflow-x: auto;
+ transition: background 0.2s, border-color 0.2s, color 0.2s;
}
a {
- color: #00e5cc;
+ color: var(--text-code);
text-decoration: none;
transition: color 0.2s;
}
a:hover {
- color: #ff4d4d;
+ color: var(--accent);
}
.muted {
- color: #8892b0;
+ color: var(--text-muted);
}
h1, h2, h3 {
- color: #f0f4ff;
+ color: var(--text-primary);
+}
+
+.header-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 0.5rem;
+}
+
+.theme-toggle {
+ background: var(--secondary-bg);
+ color: var(--text-primary);
+ padding: 0.5rem 0.75rem;
+ font-size: 0.875rem;
+ border-radius: 8px;
+}
+
+.theme-toggle:hover {
+ background: var(--secondary-hover);
}