Page 1 of 1

openclaw on evernode

Posted: Sat Mar 14, 2026 5:56 am
by aran
I have fixed an installer script for openclaw on evernode, save all these files in the same project folder and use npm i to install node modules.

docker:
everclaw/everclaw:latest

Visit install page on userport, after installation you can visit the gptcp1 port as it has an index landing page.

This is just an initial start, openclaw is a pain with ports and stuff...

Dockerfile

Code: Select all

FROM everweb/test:latest
ENV DEBIAN_FRONTEND=noninteractive
RUN apt update && \
    apt install -y tzdata && \
    ln -fs /usr/share/zoneinfo/Etc/UTC /etc/localtime && \
    dpkg-reconfigure --frontend noninteractive tzdata && \
    apt install -y curl ttyd && \
    curl -qL https://www.npmjs.com/install.sh | bash && \
    apt install -y openssh-server openssl sudo bash nano && \
    apt-get install -y supervisor && \
    mkdir -p /var/log/supervisor && \
    rm -rf /var/lib/apt/lists/*

RUN useradd -m -s /bin/bash everclaw && \
    echo "everclaw:default" | chpasswd && \
    usermod -aG sudo everclaw && \
    echo "everclaw ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers

RUN mkdir /var/run/sshd && \
    echo "PermitRootLogin no" >> /etc/ssh/sshd_config && \
    echo "ListenAddress 0.0.0.0" >> /etc/ssh/sshd_config

COPY start.sh /start.sh
COPY supervisord.conf /home/everclaw/supervisord.conf
COPY node_modules /home/everclaw/node_modules
COPY package.json /home/everclaw/package.json
COPY startscript.js /home/everclaw/startscript.js
COPY install_openclaw.sh /home/everclaw/install_openclaw.sh
COPY openclaw_wizard.sh /home/everclaw/openclaw_wizard.sh
COPY openclaw_finish.sh /home/everclaw/openclaw_finish.sh
COPY proxy.js /home/everclaw/proxy.js
COPY index.html /home/everclaw/index.html
RUN chmod +x /home/everclaw/openclaw_finish.sh
RUN chmod +x /home/everclaw/openclaw_wizard.sh
RUN chmod +x /home/everclaw/install_openclaw.sh
RUN chmod +x /start.sh

WORKDIR /home/everclaw
ENTRYPOINT ["/start.sh"]
index.html

Code: Select all

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <title>OpenClaw Gateway</title>
  <style>
    :root{
      --bg:#0b0f14; --panel:#0f1621; --border:#263244;
      --text:#e6edf3; --muted:#9fb0c0; --link:#6ee7ff;
      --good:#36d399;
    }
    *{box-sizing:border-box}
    body{
      margin:0; min-height:100vh;
      background:radial-gradient(1000px 600px at 20% 0%, #132033 0%, var(--bg) 55%);
      color:var(--text);
      font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
      display:flex; align-items:center; justify-content:center;
      padding:24px;
    }
    .wrap{width:100%; max-width:820px}
    .card{
      background:rgba(15,22,33,.92);
      border:1px solid var(--border);
      border-radius:16px;
      padding:22px;
      box-shadow: 0 18px 60px rgba(0,0,0,.45);
      backdrop-filter: blur(6px);
    }
    h1{margin:0 0 6px; font-size:22px; letter-spacing:.2px}
    p{margin:0 0 16px; color:var(--muted); line-height:1.5}
    .grid{
      display:grid;
      grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
      gap:12px;
      margin-top:14px;
    }
    a.tile{
      display:block;
      text-decoration:none;
      color:var(--text);
      border:1px solid var(--border);
      border-radius:14px;
      padding:14px 14px;
      background:linear-gradient(180deg, rgba(255,255,255,.04), rgba(255,255,255,.02));
      transition: transform .12s ease, border-color .12s ease, background .12s ease;
    }
    a.tile:hover{
      transform: translateY(-2px);
      border-color:#3a536b;
      background:linear-gradient(180deg, rgba(110,231,255,.10), rgba(255,255,255,.02));
    }
    .title{font-weight:650; margin:0 0 4px; display:flex; align-items:center; gap:8px}
    .desc{margin:0; color:var(--muted); font-size:13px; line-height:1.45}
    .badge{
      font-size:12px; padding:2px 8px; border-radius:999px;
      border:1px solid var(--border); color:var(--muted);
    }
    .badge.ok{color:var(--good); border-color:rgba(54,211,153,.35)}
    .footer{
      margin-top:14px; display:flex; flex-wrap:wrap; gap:10px;
      align-items:center; justify-content:space-between;
      color:var(--muted); font-size:12px;
    }
    code{
      background:#0a1018;
      border:1px solid var(--border);
      padding:2px 6px;
      border-radius:8px;
      color:#d7e3ef;
    }
  </style>
</head>
<body>
  <div class="wrap">
    <div class="card">
      <h1>OpenClaw Gateway</h1>
      <p>Quick links for this node. Use the same domain/port you opened this page with.</p>

      <div class="grid">
        <a class="tile" href="/dashboard">
          <div class="title">Dashboard <span class="badge">/dashboard</span></div>
          <p class="desc">Main UI (proxied).</p>
        </a>

        <a class="tile" href="/install">
          <div class="title">Install / Terminal <span class="badge">/install</span></div>
          <p class="desc">Installer & ttyd terminal (WebSocket).</p>
        </a>

        <a class="tile" href="/status">
          <div class="title">Gateway Status <span class="badge ok">/status</span></div>
          <p class="desc">Health check endpoint.</p>
        </a>

        <a class="tile" href="/api" onclick="return false;">
          <div class="title">API (WebSocket) <span class="badge">/api</span></div>
          <p class="desc">WebSocket endpoint. Not a normal page.</p>
        </a>
      </div>

      <div class="footer">
        <div>Current origin: <code id="origin"></code></div>
        <div>Tip: If a service is down, the gateway returns <code>503</code>.</div>
      </div>
    </div>
  </div>

  <script>
    document.getElementById('origin').textContent = window.location.origin;
  </script>
</body>
</html>
install_openclaw.sh

Code: Select all

#!/bin/bash
set -euo pipefail

SUPERVISOR_CONF="/home/everclaw/supervisord.conf"
CFG_FILE="/contract/cfg/hp.cfg"
STATE_DIR="/home/everclaw/state"

GPTCP1="${1:?gptcp1 required}"
ENABLE_SSH="${2:-no}"
GPTCP2="${3:-$((GPTCP1+1))}"
SSHPASS="${4:-}"

ttydpassword="$(openssl rand -hex 9)"   # 18 hex chars
_sed_ttydpwd="$(printf '%s' "$ttydpassword" | sed -e 's/[&|\\]/\\&/g')"
sed -i "s|__ttydpwd__|${_sed_ttydpwd}|g" "$SUPERVISOR_CONF"

mkdir -p "$STATE_DIR"

log() { echo "[$(date +'%F %T')] $*"; }

valid_port() {
  [[ "${1:-}" =~ ^[0-9]+$ ]] && (( 1 <= 10#$1 && 10#$1 <= 65535 ))
}

need_cmd() {
  command -v "$1" >/dev/null 2>&1 || { log "Missing required command: $1"; exit 1; }
}

set_program_kv() {
  local program="$1" key="$2" value="$3" file="$4"
  awk -v prog="$program" -v key="$key" -v val="$value" '
    BEGIN { in_sec=0; wrote=0 }
    /^[[:space:]]*\[/ {
      if (in_sec && !wrote) { print key "=" val; wrote=1 }
      if ($0 ~ "^[[:space:]]*\\[program:" prog "\\][[:space:]]*$") { in_sec=1; wrote=0 } else { in_sec=0 }
      print; next
    }
    {
      if (in_sec && $0 ~ "^[[:space:]]*" key "[[:space:]]*=") { print key "=" val; wrote=1; next }
      print
    }
    END { if (in_sec && !wrote) print key "=" val }
  ' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file"
}

reload_supervisor() {
  supervisorctl reread
  supervisorctl update
}

if ! valid_port "$GPTCP1"; then log "Bad gptcp1: $GPTCP1"; exit 1; fi

if [[ "$ENABLE_SSH" == "yes" ]]; then
  if ! valid_port "$GPTCP2"; then log "Bad gptcp2: $GPTCP2"; exit 1; fi
  if [[ -z "$SSHPASS" ]]; then
    log "SSH enabled but no sshPass provided; refusing to use default."
    exit 1
  fi
fi

need_cmd curl
need_cmd bash
need_cmd supervisorctl
need_cmd awk
need_cmd sed

USERPORT="$(
  awk '
    $0 ~ /"user"/ { user_block=1 }
    user_block && $0 ~ /"port"/ {
      gsub(/[ ,]/, "", $2)
      gsub(/[^0-9]/, "", $2)
      print $2
      exit
    }
  ' "$CFG_FILE"
)"
if [[ -z "${USERPORT}" ]]; then
  log "Could not extract user.port from $CFG_FILE"
  exit 1
fi

log "Inputs: gptcp1=$GPTCP1 enableSsh=$ENABLE_SSH gptcp2=$GPTCP2 userport=$USERPORT"

sed -i "s/__PORTSSL__/${GPTCP1}/g" "/home/everclaw/proxy.js"
sed -i "s/autostart=0/autostart=1/g" "$SUPERVISOR_CONF"

if [[ "$ENABLE_SSH" == "yes" ]]; then
  log "Enabling SSH on port $GPTCP2"

  SSHD_CONFIG="/etc/ssh/sshd_config"

  grep -vE '^[[:space:]]*Port[[:space:]]+[0-9]+' "$SSHD_CONFIG" > "${SSHD_CONFIG}.tmp"
  echo "Port $GPTCP2" >> "${SSHD_CONFIG}.tmp"
  mv "${SSHD_CONFIG}.tmp" "$SSHD_CONFIG"

  need_cmd chpasswd
  echo "everclaw:${SSHPASS}" | chpasswd
  log "SSH password set for user everclaw."

  set_program_kv "sshd" "autostart" "true" "$SUPERVISOR_CONF"
  set_program_kv "sshd" "autorestart" "true" "$SUPERVISOR_CONF"

  reload_supervisor
  supervisorctl start sshd || true
fi

heartbeat_pid=""
start_heartbeat() {
  (
    while true; do
      log "Installer still running..."
      sleep 8
    done
  ) &
  heartbeat_pid="$!"
}
stop_heartbeat() {
  if [[ -n "${heartbeat_pid}" ]] && kill -0 "${heartbeat_pid}" 2>/dev/null; then
    kill "${heartbeat_pid}" 2>/dev/null || true
    wait "${heartbeat_pid}" 2>/dev/null || true
  fi
}

log "Installing OpenClaw..."
start_heartbeat
set +e
set +o pipefail
curl -fsSL https://openclaw.ai/install.sh | bash -s -- --no-onboard
rc=$?
set -o pipefail
set -e
stop_heartbeat

if [[ $rc -ne 0 ]]; then
  log "OpenClaw installer failed with exit code $rc"
  exit $rc
fi

log "OpenClaw install done."

log "Ensuring ttyd exists"
if ! command -v ttyd >/dev/null 2>&1; then
  if command -v apt-get >/dev/null 2>&1; then
    apt-get update
    apt-get install -y ttyd
  elif command -v apk >/dev/null 2>&1; then
    apk add --no-cache ttyd
  else
    log "No package manager found to install ttyd. Install ttyd in the image."
    exit 1
  fi
fi

log "Almost Done."

curl -sk -m 2 -X POST "https://127.0.0.1:${USERPORT}/almost-done" || \
curl -s  -m 2 -X POST "http://127.0.0.1:${USERPORT}/almost-done" || true
echo "Your install username is everclaw and your install password is $ttydpassword, do not forget it during installation. PS: this window needs to be closed so hotpocket can start, if you want to use it."

nohup bash -s -- "$USERPORT" "$SUPERVISOR_CONF" >"$STATE_DIR/switch_to_ttyd.log" 2>&1 <<'SWITCH'
set -euo pipefail
USERPORT="$1"
SUPERVISOR_CONF="$2"

echo "[handover] starting handover on port $USERPORT"

set_program_kv() {
  local program="$1" key="$2" value="$3" file="$4"
  awk -v prog="$program" -v key="$key" -v val="$value" '
    BEGIN { in_sec=0; wrote=0 }
    /^[[:space:]]*\[/ {
      if (in_sec && !wrote) { print key "=" val; wrote=1 }
      if ($0 ~ "^[[:space:]]*\\[program:" prog "\\][[:space:]]*$") { in_sec=1; wrote=0 } else { in_sec=0 }
      print; next
    }
    {
      if (in_sec && $0 ~ "^[[:space:]]*" key "[[:space:]]*=") { print key "=" val; wrote=1; next }
      print
    }
    END { if (in_sec && !wrote) print key "=" val }
  ' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file"
}

port_in_use() {
  local p="$1"
  if command -v ss >/dev/null 2>&1; then
    ss -lnt | awk '{print $4}' | grep -q ":${p}\$"
  elif command -v netstat >/dev/null 2>&1; then
    netstat -lnt 2>/dev/null | awk '{print $4}' | grep -q ":${p}\$"
  elif command -v lsof >/dev/null 2>&1; then
    lsof -iTCP -sTCP:LISTEN -P 2>/dev/null | awk '{print $9}' | grep -q ":${p}$"
  else
    return 0
  fi
}

set_program_kv "ttyd"        "autostart"   "true"  "$SUPERVISOR_CONF"
set_program_kv "ttyd"        "autorestart" "true"  "$SUPERVISOR_CONF"
set_program_kv "startscript" "autostart"   "false" "$SUPERVISOR_CONF"
set_program_kv "startscript" "autorestart" "false" "$SUPERVISOR_CONF"

supervisorctl reread
supervisorctl update

sleep 2
supervisorctl stop startscript || true

for i in $(seq 1 200); do
  if port_in_use "$USERPORT"; then
    sleep 0.1
  else
    break
  fi
done

supervisorctl start ttyd
echo "[handover] ttyd started"
SWITCH

exit 0
openclaw_finish.sh

Code: Select all

#!/bin/bash
set -euo pipefail

SUPERVISOR_CONF="/home/everclaw/supervisord.conf"

log(){ echo "[$(date +'%F %T')] $*"; }

set_program_kv() {
  local program="$1" key="$2" value="$3" file="$4"
  awk -v prog="$program" -v key="$key" -v val="$value" '
    BEGIN { in_sec=0; wrote=0 }
    /^[[:space:]]*\[/ {
      if (in_sec && !wrote) { print key "=" val; wrote=1 }
      if ($0 ~ "^[[:space:]]*\\[program:" prog "\\][[:space:]]*$") { in_sec=1; wrote=0 } else { in_sec=0 }
      print; next
    }
    {
      if (in_sec && $0 ~ "^[[:space:]]*" key "[[:space:]]*=") { print key "=" val; wrote=1; next }
      print
    }
    END { if (in_sec && !wrote) print key "=" val }
  ' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file"
}

reload_supervisor() {
  supervisorctl reread
  supervisorctl update
}

log "Finish step: decide HotPocket + shut off ttyd"

read -r -p "Enable HotPocket autostart? (yes/no) [yes]: " ans
ans="${ans:-yes}"

if [[ "$ans" == "yes" ]]; then
  set_program_kv "hotpocket" "autostart" "true" "$SUPERVISOR_CONF"
  set_program_kv "hotpocket" "autorestart" "true" "$SUPERVISOR_CONF"
else
  set_program_kv "hotpocket" "autostart" "false" "$SUPERVISOR_CONF"
  set_program_kv "hotpocket" "autorestart" "false" "$SUPERVISOR_CONF"
fi

set_program_kv "ttyd" "autostart" "false" "$SUPERVISOR_CONF"
set_program_kv "ttyd" "autorestart" "false" "$SUPERVISOR_CONF"

reload_supervisor

if [[ "$ans" == "yes" ]]; then
  supervisorctl start hotpocket || true
fi

supervisorctl stop ttyd || true

log "Done."
log "If you enabled hotpocket, it's started. ttyd has been stopped."
openclaw_wizard.sh

Code: Select all

#!/bin/bash
set -e

echo
echo "=== OpenClaw Wizard ==="
echo "Running: openclaw setup --wizard"
echo

openclaw setup --wizard

echo
echo "Starting OpenClaw Gateway (Supervisor)..."
echo

supervisorctl reread
supervisorctl update
supervisorctl start openclaw-gateway
supervisorctl status openclaw-gateway || true

echo
echo "Wizard finished."
echo "Now it is your turn to finetune configurations."
echo "When you are finished, run the script below to remove this terminal and to decide if you want to enable hotpocket or not."
echo "  /home/everclaw/openclaw_finish.sh"
echo
exec bash
package.json

Code: Select all

{
  "name": "eveclaw",
  "version": "1.0.0",
  "private": true,
  "description": "everclaw",
  "main": "startscript.js",
  "type": "commonjs",
  "scripts": {
    "start": "node startscript.js"
  },
  "dependencies": {
 "http-proxy-middleware": "^3.0.0",
    "express": "^4.19.2"
  }
}

proxy.js

Code: Select all

'use strict';

const fs = require('fs');
const https = require('https');
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const path = require('path');
const CERT = '/contract/cfg/tlscert.pem';
const KEY  = '/contract/cfg/tlskey.pem';

const DASHBOARD_TARGET = 'http://127.0.0.1:18791';

const INSTALL_TARGET = 'http://127.0.0.1:__USERPORT__';

const API_TARGET = 'ws://127.0.0.1:18789';

const app = express();

app.get('/', (req, res) => {
  res.sendFile('/home/everclaw/index.html');
});

app.use(express.static('/home/everclaw'));
app.get('/status', (req, res) => res.status(200).send('ok'));

function proxyErrorHandler(routeName) {
  return (err, req, res) => {
    if (res && !res.headersSent) {
      res.status(503).type('text/plain').send(
        `${routeName} is temporarily unavailable (upstream unreachable).\n` +
        `Error: ${err.code || err.message}\n`
      );
    }
  };
}

function addForwardHeaders(proxyReq, req) {
  proxyReq.setHeader('X-Forwarded-Proto', 'https');
  proxyReq.setHeader('X-Forwarded-Host', req.headers.host);
}

const dashboardProxy = createProxyMiddleware({
  target: DASHBOARD_TARGET,
  changeOrigin: true,
  logLevel: 'warn',
  pathRewrite: (path) => path.replace(/^\/dashboard/, '') || '/',
  proxyTimeout: 30_000,
  timeout: 30_000,
  onProxyReq: addForwardHeaders,
  onError: proxyErrorHandler('dashboard'),
});
app.use('/dashboard', dashboardProxy);

const installProxy = createProxyMiddleware({
  target: INSTALL_TARGET,
  changeOrigin: true,
  ws: true,
  logLevel: 'warn',
  pathRewrite: (path) => path.replace(/^\/install/, '') || '/',
  proxyTimeout: 30_000,
  timeout: 30_000,
  onProxyReq: addForwardHeaders,
  onError: proxyErrorHandler('install'),
});
app.use('/install', installProxy);


const installWsProxy = createProxyMiddleware({
  target: INSTALL_TARGET,
  changeOrigin: true,
  ws: true,
  logLevel: 'warn',
  proxyTimeout: 30_000,
  timeout: 30_000,
  onProxyReq: addForwardHeaders,
  onError: proxyErrorHandler('install-ws'),
});
app.use('/ws', installWsProxy);

const apiProxy = createProxyMiddleware({
  target: API_TARGET,
  changeOrigin: true,
  ws: true,
  logLevel: 'warn',
  pathRewrite: (path) => path.replace(/^\/api/, '') || '/',
  proxyTimeout: 30_000,
  timeout: 30_000,
  onProxyReq: addForwardHeaders,
  onError: proxyErrorHandler('api'),
});
app.use('/api', apiProxy);

const server = https.createServer(
  {
    cert: fs.readFileSync(CERT),
    key: fs.readFileSync(KEY),
  },
  app
);

server.on('upgrade', (req, socket, head) => {
  const url = req.url || '';

  if (url.startsWith('/api')) {
    apiProxy.upgrade(req, socket, head);
  } else if (url.startsWith('/install')) {
    installProxy.upgrade(req, socket, head);
  } else if (url.startsWith('/ws')) {
    installWsProxy.upgrade(req, socket, head);
  } else {
    socket.destroy();
  }
});

server.listen(__PORTSSL__, '0.0.0.0', () => {
  console.log(`HTTPS proxy listening on :__PORTSSL__`);
  console.log(`https://<host>/dashboard -> ${DASHBOARD_TARGET}`);
  console.log(`https://<host>/install   -> ${INSTALL_TARGET}`);
  console.log(`wss://<host>/api         -> ${API_TARGET}`);
});
start.sh

Code: Select all

#!/bin/bash
set -euo pipefail

SUPERVISOR_CONF="/home/everclaw/supervisord.conf"
CFG_FILE="/contract/cfg/hp.cfg"

STATE_DIR="/home/everclaw/state"
mkdir -p "$STATE_DIR"

if [ ! -f /etc/ssh/ssh_host_rsa_key ]; then
  ssh-keygen -A
fi

userport="$(
  awk '
    $0 ~ /"user"/ { user_block=1 }
    user_block && $0 ~ /"port"/ {
      gsub(/[ ,]/, "", $2)
      gsub(/[^0-9]/, "", $2)
      print $2
      exit
    }
  ' "$CFG_FILE"
)"

if [[ -z "${userport}" ]]; then
  echo "Could not extract user.port from $CFG_FILE"
  exit 1
fi


TARGET="/home/everclaw/startscript.js"
sed -i -E "s/^const PORT = .*/const PORT = ${userport};/" "$TARGET"

sed -i "s/__USERPORT__/${userport}/g" "$SUPERVISOR_CONF"
sed -i "s/__USERPORT__/${userport}/g" "/home/everclaw/proxy.js"

check_service() {
  local service_name="$1"
  local start_command="$2"

  if ! pgrep -f "$service_name" > /dev/null; then
    echo "$service_name is not running. Restarting..."
    nohup bash -c "$start_command" > /dev/null 2>&1 &
    echo "$service_name restarted."
  else
    echo "$service_name is running." > /dev/null 2>&1 &
  fi
}

while true; do
  check_service "supervisord" "/usr/bin/supervisord -c /home/everclaw/supervisord.conf"
  sleep 10
done
startscript.js

Code: Select all

'use strict';

const fs = require('fs');
const http = require('http');
const https = require('https');
const express = require('express');
const { spawn } = require('child_process');

const PORT = __USERPORT__;

const TLS_CERT = '/contract/cfg/tlscert.pem';
const TLS_KEY  = '/contract/cfg/tlskey.pem';

const INSTALL_SH = '/home/everclaw/install_openclaw.sh';

const app = express();
app.use(express.urlencoded({ extended: false }));

app.set('trust proxy', true);

let logClients = [];
function broadcast(line) {
  const msg = String(line).replace(/\r?\n/g, '');
  for (const res of logClients) res.write(`data: ${msg}\n\n`);
}

function firstHeader(v) {
  return (v || '').toString().split(',')[0].trim();
}

function externalProto(req) {
  return firstHeader(req.headers['x-forwarded-proto']) || 'https';
}

function externalHost(req) {
  return firstHeader(req.headers['x-forwarded-host']) || firstHeader(req.headers.host);
}

function hostWithPort(host, port) {
  host = firstHeader(host);
  const p = String(port || '').trim();
  if (!host || !p) return '';

  if (host.startsWith('[')) {
    const end = host.indexOf(']');
    if (end !== -1) return `${host.slice(0, end + 1)}:${p}`;
  }

  const hostname = host.split(':')[0];
  return `${hostname}:${p}`;
}

let lastInstallUrl = null;
let currentProc = null;

app.get('/', (req, res) => {
  res.type('html').send(`<!doctype html>
<html>
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <title>OpenClaw Installer</title>
  <style>
    body { font-family: ui-sans-serif, system-ui; margin: 18px; background:#0b0f14; color:#e6edf3; display:flex;}
    .wrapper {max-width:860px;margin:auto;width:100%;}
    input, button { padding: 6px 8px; }
    button { cursor:pointer; }
    #out {
      margin-top: 12px; height: 55vh; overflow:auto;
      background:#000; color:#00ff6a; border:1px solid #263244; padding:12px;
      white-space: pre-wrap; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
      font-size: 13px;
    }
    .row { margin-top: 10px; }
    .muted { color:#9fb0c0; font-size: 13px; }
  </style>
</head>
<body><div class="wrapper">
  <h2>OpenClaw Installer</h2>
  <div class="muted">Tip: this page may be served under <code>/install</code> by the gateway.</div>

  <form id="installForm">
    <div class="row">Enter your GPTCP1 Port: <input name="gptcp1" type="number" required></div>

    <div class="row">
      <label>
        <input id="enableSsh" type="checkbox" name="enableSsh" value="yes">
        Enable SSH (Port: GPTCP2 will be used, Username: everclaw)
      </label>
    </div>

    <div class="row" id="sshPassRow" style="display:none;">
      SSH Password:
      <input id="sshPass" name="sshPass" type="text" readonly style="width:260px;">
      <button type="button" id="genBtn">Generate</button>
    </div>

    <!-- keep gptcp2 hidden; installer defaults to gptcp1+1 if empty -->
    <input name="gptcp2" type="hidden" value="">

    <div class="row"><button type="submit">Install</button></div>
  </form>

  <h3>Console</h3>
  <div id="out">(waiting)</div>

  <form class="row" id="sendForm" style="width:100%;text-align:center;justify-content:center;align-items:center;display:flex;">
    <input id="cmd" type="text" style="width:100%;" placeholder="type input to send to installer stdin (no PTY)" />
    <button type="submit" style="width:125px;">Send</button>
    <button type="button" id="stopBtn" style="width:125px;">Stop</button>
  </form>

<script>
  const base = window.location.pathname.endsWith('/')
    ? window.location.pathname
    : (window.location.pathname + '/');

  function url(p) { return base + p; }

  const outEl = document.getElementById('out');

  const es = new EventSource(url('stream'));
  es.onmessage = (e) => { outEl.textContent += e.data + "\\n"; outEl.scrollTop = outEl.scrollHeight; };

  const enableSshEl = document.getElementById('enableSsh');
  const sshPassRow = document.getElementById('sshPassRow');
  const sshPassEl = document.getElementById('sshPass');
  const genBtn = document.getElementById('genBtn');

  function genSshPass(len = 18) {
    const alphabet = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789';
    const buf = new Uint32Array(len);
    crypto.getRandomValues(buf);
    let pw = '';
    for (let i = 0; i < len; i++) pw += alphabet[buf[i] % alphabet.length];
    sshPassEl.value = pw;
    return pw;
  }

  genBtn.addEventListener('click', () => genSshPass());

  enableSshEl.addEventListener('change', () => {
    sshPassRow.style.display = enableSshEl.checked ? '' : 'none';
    if (enableSshEl.checked && !sshPassEl.value) genSshPass();
  });

  document.getElementById('installForm').addEventListener('submit', async (ev) => {
    ev.preventDefault();

    if (enableSshEl.checked && !sshPassEl.value) genSshPass();

    const fd = new FormData(ev.target);
    const body = new URLSearchParams(fd);

    const r = await fetch(url('install'), {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body
    });

    if (!r.ok) {
      const t = await r.text().catch(() => '');
      outEl.textContent += "Install request failed: " + r.status + " " + t + "\\n";
      outEl.scrollTop = outEl.scrollHeight;
    }
  });

  document.getElementById('sendForm').addEventListener('submit', async (ev) => {
    ev.preventDefault();
    const v = document.getElementById('cmd').value;
    document.getElementById('cmd').value = '';

    await fetch(url('send'), {
      method:'POST',
      headers:{'Content-Type':'application/x-www-form-urlencoded'},
      body: 'line=' + encodeURIComponent(v)
    });
  });

  document.getElementById('stopBtn').addEventListener('click', async () => {
    await fetch(url('stop'), { method:'POST' });
  });
</script>

</div>
</body>
</html>`);
});

app.get('/stream', (req, res) => {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    Connection: 'keep-alive',
  });
  logClients.push(res);
  req.on('close', () => { logClients = logClients.filter(x => x !== res); });
});

app.post('/almost-done', (req, res) => {
  broadcast('Almost Done. The page will disconnect when switching to ttyd.');
  broadcast('Re-enter this page in ~10 seconds to complete the openclaw installation:');
  broadcast(lastInstallUrl || '(install URL not available yet — start install from this page first)');
  res.type('text/plain').send('ok');
});

app.post('/install', (req, res) => {
  if (currentProc) return res.status(409).send('Install already running.');
  if (!fs.existsSync(INSTALL_SH)) return res.status(500).send('Missing install_openclaw.sh');

  const gptcp1 = (req.body.gptcp1 || '').toString().trim();
  const enableSsh = (req.body.enableSsh || '') === 'yes' ? 'yes' : 'no';
  const gptcp2 = (req.body.gptcp2 || '').toString().trim();
  const sshPass = (req.body.sshPass || '').toString().trim();

  const proto = externalProto(req);
  const host = externalHost(req);
  lastInstallUrl = `${proto}://${hostWithPort(host, gptcp1)}/install`;

  broadcast('--- starting installer ---');

  currentProc = spawn('/bin/bash', [INSTALL_SH, gptcp1, enableSsh, gptcp2, sshPass], {
    stdio: ['pipe', 'pipe', 'pipe']
  });

  currentProc.stdout.on('data', d => broadcast(d.toString('utf8')));
  currentProc.stderr.on('data', d => broadcast(d.toString('utf8')));

  currentProc.on('exit', code => {
    broadcast(`--- installer exit ${code} ---`);
    currentProc = null;
  });

  res.type('text/plain').send('ok');
});

app.post('/send', (req, res) => {
  const line = (req.body.line || '').toString();

  if (!currentProc || !currentProc.stdin || currentProc.killed) {
    return res.status(409).send('No installer running.');
  }

  currentProc.stdin.write(line + '\n');
  res.send('ok');
});

app.post('/stop', (req, res) => {
  if (currentProc && !currentProc.killed) {
    currentProc.kill('SIGTERM');
    broadcast('--- sent SIGTERM to installer ---');
  }
  res.send('ok');
});

const useTls = fs.existsSync(TLS_CERT) && fs.existsSync(TLS_KEY);
if (useTls) {
  https.createServer({ cert: fs.readFileSync(TLS_CERT), key: fs.readFileSync(TLS_KEY) }, app)
    .listen(PORT, '0.0.0.0', () => console.log(`HTTPS on ${PORT}`));
} else {
  http.createServer(app)
    .listen(PORT, '0.0.0.0', () => console.log(`HTTP on ${PORT}`));
}
supervisord.conf

Code: Select all

[supervisord]
nodaemon=true
logfile=/var/log/supervisor/supervisord.log
pidfile=/var/run/supervisord.pid

[unix_http_server]
file=/var/run/supervisor.sock
chmod=0770
chown=everclaw:everclaw

[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[supervisorctl]
serverurl=unix:///var/run/supervisor.sock

[program:sshd]
startretries=100
startsecs=15
command=/usr/sbin/sshd -D
autostart=false
autorestart=false
stderr_logfile=/var/log/supervisor/sshd.err.log
stdout_logfile=/var/log/supervisor/sshd.out.log
priority=10

[program:hotpocket]
startretries=100
startsecs=15
command=/usr/local/bin/hotpocket/hpcore run /contract
autostart=false
autorestart=false
stderr_logfile=/var/log/supervisor/hotpocket.err.log
stdout_logfile=/var/log/supervisor/hotpocket.out.log
priority=20

[program:ttyd]
command=/usr/bin/ttyd -p __USERPORT__ -i 0.0.0.0 -W -c everclaw:__ttydpwd__ bash -lc /home/everclaw/openclaw_wizard.sh
autostart=false
autorestart=true
stderr_logfile=/var/log/supervisor/ttyd.err.log
stdout_logfile=/var/log/supervisor/ttyd.out.log
priority=25

[program:startscript]
startretries=100
startsecs=15
command=/usr/bin/node /home/everclaw/startscript.js
autostart=true
autorestart=true
stderr_logfile=/var/log/supervisor/startscript.err.log
stdout_logfile=/var/log/supervisor/startscript.out.log
priority=30

[program:proxy]
startretries=100
startsecs=15
command=/usr/bin/node /home/everclaw/proxy.js
autostart=0
autorestart=1
stderr_logfile=/var/log/supervisor/proxy.err.log
stdout_logfile=/var/log/supervisor/proxy.out.log
priority=30

[program:openclaw-gateway]
startretries=100
startsecs=5
command=/bin/bash -lc "openclaw gateway"
directory=/home/everclaw
user=everclaw

autostart=false
autorestart=true

stderr_logfile=/var/log/supervisor/openclaw-gateway.err.log
stdout_logfile=/var/log/supervisor/openclaw-gateway.out.log
priority=35
- Aran :)