mirror of
https://github.com/arjunkomath/openclaw-railway-template.git
synced 2026-08-14 00:48:11 +00:00
Simplify install and docker setup
This commit is contained in:
+7
-76
@@ -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 \
|
||||
|
||||
+22
-4
@@ -6,8 +6,17 @@
|
||||
<title>OpenClaw Setup</title>
|
||||
<link rel="stylesheet" href="/setup/styles.css" />
|
||||
<script>
|
||||
(function() {
|
||||
const saved = localStorage.getItem('theme');
|
||||
if (saved) {
|
||||
document.documentElement.setAttribute('data-theme', saved);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('setupApp', () => ({
|
||||
theme: localStorage.getItem('theme') || (window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark'),
|
||||
status: 'Loading...',
|
||||
configured: false,
|
||||
openclawVersion: '',
|
||||
@@ -36,6 +45,12 @@
|
||||
await this.refreshStatus();
|
||||
},
|
||||
|
||||
toggleTheme() {
|
||||
this.theme = this.theme === 'dark' ? 'light' : 'dark';
|
||||
document.documentElement.setAttribute('data-theme', this.theme);
|
||||
localStorage.setItem('theme', this.theme);
|
||||
},
|
||||
|
||||
async httpJson(url, opts = {}) {
|
||||
opts.credentials = 'same-origin';
|
||||
const res = await fetch(url, opts);
|
||||
@@ -165,7 +180,10 @@
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.8/dist/cdn.min.js"></script>
|
||||
</head>
|
||||
<body x-data="setupApp()">
|
||||
<h1>OpenClaw Setup</h1>
|
||||
<div class="header-row">
|
||||
<h1>OpenClaw Setup</h1>
|
||||
<button @click="toggleTheme()" class="theme-toggle" x-text="theme === 'dark' ? 'Light mode' : 'Dark mode'"></button>
|
||||
</div>
|
||||
<p class="muted">This wizard configures OpenClaw by running the same onboarding command it uses in the terminal, but from the browser.</p>
|
||||
|
||||
<div class="card">
|
||||
@@ -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;
|
||||
|
||||
+117
-24
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user