mirror of
https://github.com/arjunkomath/openclaw-railway-template.git
synced 2026-08-14 00:48:11 +00:00
+1
-1
@@ -14,7 +14,7 @@ RUN apt-get update \
|
||||
unzip \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN npm install -g openclaw@2026.5.27
|
||||
RUN npm install -g openclaw@2026.6.1
|
||||
RUN npm install -g clawhub@latest
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
- The container runs a wrapper web server.
|
||||
- The wrapper protects `/setup` with `SETUP_PASSWORD`.
|
||||
- During setup, the wrapper runs `openclaw onboard ...` inside the container, writes state to the volume, and then starts the gateway. API-key providers use non-interactive setup; ChatGPT/Codex device pairing uses OpenClaw's interactive device-code flow so the login URL and code can be streamed to `/setup`.
|
||||
- During setup, the wrapper runs `openclaw onboard ...` inside the container, writes state to the volume, and then starts the gateway. API-key providers use non-interactive setup; ChatGPT/Codex and xAI/Grok device pairing use OpenClaw's interactive device-code flows so the login URL and code can be streamed to `/setup`.
|
||||
- After setup, **`/` is OpenClaw**. The wrapper reverse-proxies all traffic (including WebSockets) to the local gateway process.
|
||||
|
||||
## Getting chat tokens (so you don't have to scramble)
|
||||
@@ -28,6 +28,12 @@ In `/setup`, choose **OpenAI → OpenAI Codex device pairing**. Setup will strea
|
||||
|
||||
This uses OpenClaw's `openai-codex-device-code` onboarding flow, so you do not need to paste an OpenAI API key.
|
||||
|
||||
### Grok / xAI subscription login
|
||||
|
||||
In `/setup`, choose **xAI (Grok) → xAI device code**. Setup will stream an xAI URL and a short device code; open the URL in your browser, enter the code, and keep the setup page open until OpenClaw finishes saving the OAuth profile.
|
||||
|
||||
This uses OpenClaw's `xai-device-code` onboarding flow, so you can use an eligible SuperGrok or X Premium subscription without pasting an xAI API key.
|
||||
|
||||
### Telegram bot token
|
||||
|
||||
1. Open Telegram and message **@BotFather**
|
||||
|
||||
+18
-3
@@ -31,6 +31,7 @@
|
||||
log: '',
|
||||
loginUrl: '',
|
||||
loginCode: '',
|
||||
loginLabel: 'Device Login',
|
||||
setupError: '',
|
||||
setupStreamTail: '',
|
||||
loading: false,
|
||||
@@ -62,6 +63,7 @@
|
||||
return ![
|
||||
'openai-codex',
|
||||
'openai-codex-device-code',
|
||||
'xai-device-code',
|
||||
'google-gemini-cli',
|
||||
'github-copilot',
|
||||
'ollama',
|
||||
@@ -74,6 +76,9 @@
|
||||
if (this.selectedAuth === 'openai-codex-device-code') {
|
||||
return 'Run setup, then open the ChatGPT/Codex URL and enter the device code shown in the log.';
|
||||
}
|
||||
if (this.selectedAuth === 'xai-device-code') {
|
||||
return 'Run setup, then open the xAI URL and enter the device code shown in the log. Use an eligible SuperGrok or X Premium account.';
|
||||
}
|
||||
if (this.selectedAuth === 'openai-codex') {
|
||||
return 'Browser login needs an interactive terminal to paste the redirect URL. Device pairing is recommended here.';
|
||||
}
|
||||
@@ -136,6 +141,7 @@
|
||||
async runSetup() {
|
||||
this.loginUrl = '';
|
||||
this.loginCode = '';
|
||||
this.loginLabel = this.loginLabelForAuth(this.selectedAuth);
|
||||
this.setupError = '';
|
||||
this.setupStreamTail = '';
|
||||
const payload = {
|
||||
@@ -201,14 +207,23 @@
|
||||
},
|
||||
|
||||
extractLoginDetails(text) {
|
||||
const urlMatch = text.match(/https:\/\/auth\.openai\.com\/codex\/device[^\s)]*/);
|
||||
const urlMatch = text.match(/https:\/\/auth\.openai\.com\/codex\/device[^\s)]*/)
|
||||
|| text.match(/\bURL:\s*(https:\/\/(?:auth|accounts)\.x\.ai\/[^\s)]*)/)
|
||||
|| text.match(/\bOpen(?: manually)?:\s*(https:\/\/(?:auth|accounts)\.x\.ai\/[^\s)]*)/);
|
||||
if (urlMatch) this.loginUrl = urlMatch[0];
|
||||
if (urlMatch && urlMatch[1]) this.loginUrl = urlMatch[1];
|
||||
|
||||
const codeMatches = [...text.matchAll(/\bCode:\s*([A-Z0-9][A-Z0-9-]{3,})\b/g)];
|
||||
const visibleCode = codeMatches.map(m => m[1]).find(code => code !== 'shown');
|
||||
if (visibleCode) this.loginCode = visibleCode;
|
||||
},
|
||||
|
||||
loginLabelForAuth(auth) {
|
||||
if (auth === 'xai-device-code') return 'xAI Login';
|
||||
if (auth === 'openai-codex-device-code') return 'ChatGPT Login';
|
||||
return 'Device Login';
|
||||
},
|
||||
|
||||
extractSetupFailure(text) {
|
||||
if (text.includes('[setup] Internal error')) {
|
||||
this.setupError = 'Setup hit an internal error. Review the log below.';
|
||||
@@ -743,7 +758,7 @@
|
||||
|
||||
<div class="form-group">
|
||||
<label class="label">Model</label>
|
||||
<input x-model="model" type="text" placeholder="e.g. openai-codex/gpt-5.5, openai/gpt-4.1" class="input" />
|
||||
<input x-model="model" type="text" placeholder="e.g. xai/grok-4.3, openai-codex/gpt-5.5, openai/gpt-4.1" class="input" />
|
||||
<p class="form-hint">Format: provider/model-name</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -842,7 +857,7 @@
|
||||
|
||||
<div x-show="loginUrl || loginCode" class="login-code-panel">
|
||||
<div>
|
||||
<p class="login-code-label">ChatGPT Login</p>
|
||||
<p class="login-code-label" x-text="loginLabel"></p>
|
||||
<a x-show="loginUrl" :href="loginUrl" target="_blank" rel="noopener noreferrer" class="login-code-url" x-text="loginUrl"></a>
|
||||
</div>
|
||||
<div x-show="loginCode" class="login-code-value" x-text="loginCode"></div>
|
||||
|
||||
+20
-4
@@ -681,8 +681,15 @@ app.get("/setup/api/status", requireSetupAuth, async (_req, res) => {
|
||||
{
|
||||
value: "xai",
|
||||
label: "xAI (Grok)",
|
||||
hint: "API key",
|
||||
options: [{ value: "xai-api-key", label: "xAI API key" }],
|
||||
hint: "API key / device code",
|
||||
options: [
|
||||
{ value: "xai-api-key", label: "xAI API key" },
|
||||
{
|
||||
value: "xai-device-code",
|
||||
label: "xAI device code",
|
||||
hint: "SuperGrok or X Premium login without an API key",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: "mistral",
|
||||
@@ -874,7 +881,15 @@ app.get("/setup/api/status", requireSetupAuth, async (_req, res) => {
|
||||
});
|
||||
|
||||
function requiresInteractiveOnboarding(payload) {
|
||||
return payload.authChoice === "openai-codex-device-code";
|
||||
return [
|
||||
"openai-codex-device-code",
|
||||
"xai-device-code",
|
||||
].includes(payload.authChoice);
|
||||
}
|
||||
|
||||
function interactiveOnboardingLabel(payload) {
|
||||
if (payload.authChoice === "xai-device-code") return "xAI device code";
|
||||
return "OpenAI Codex device pairing";
|
||||
}
|
||||
|
||||
function buildOnboardArgs(payload) {
|
||||
@@ -1070,6 +1085,7 @@ const VALID_AUTH_CHOICES = [
|
||||
"deepseek-api-key",
|
||||
"openrouter-api-key",
|
||||
"xai-api-key",
|
||||
"xai-device-code",
|
||||
"mistral-api-key",
|
||||
"together-api-key",
|
||||
"huggingface-api-key",
|
||||
@@ -1171,7 +1187,7 @@ app.post("/setup/api/run", requireSetupAuth, async (req, res) => {
|
||||
const interactive = requiresInteractiveOnboarding(payload);
|
||||
stream(
|
||||
interactive
|
||||
? "Starting OpenAI Codex device pairing. Use the URL and code below, then keep this page open until it completes.\n\n"
|
||||
? `Starting ${interactiveOnboardingLabel(payload)}. Use the URL and code below, then keep this page open until it completes.\n\n`
|
||||
: "Starting OpenClaw onboarding...\n\n",
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user