arfandi7322 commited on
Commit
f072685
·
1 Parent(s): 577eec6

Rework OpenClaw native runtime

Browse files
Files changed (6) hide show
  1. Dockerfile +26 -23
  2. README.md +45 -5
  3. nginx.conf.template +59 -0
  4. start-openclaw.sh +0 -271
  5. start.sh +40 -0
  6. sync-root-data.sh +60 -0
Dockerfile CHANGED
@@ -1,33 +1,36 @@
1
  FROM node:22-slim
2
 
3
- # 1. Base dependencies + code-server deps + chromium deps + tmux
 
 
 
 
 
 
4
  RUN apt-get update && apt-get install -y --no-install-recommends \
5
- git openssh-client build-essential python3 python3-pip \
6
- g++ make ca-certificates curl wget procps tmux \
7
- # Chromium/Playwright dependencies
8
- libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 \
9
- libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 \
10
- libxss1 libxtst6 libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 \
11
- libcups2 libdrm2 libgbm1 libpango-1.0-0 libcairo2 libasound2 \
12
- libxshmfence1 libglib2.0-0 fonts-liberation xdg-utils \
 
 
13
  && rm -rf /var/lib/apt/lists/*
14
 
15
- # 2. Python packages
16
- RUN pip3 install --no-cache-dir huggingface_hub --break-system-packages
17
-
18
- # 3. Install OpenClaw
19
- RUN npm install -g openclaw@latest --unsafe-perm
20
 
21
- # 4. Install code-server
22
- RUN curl -fsSL https://code-server.dev/install.sh | sh
23
 
24
- # 5. Set workdir & copy scripts
25
- WORKDIR /app
26
- COPY start-openclaw.sh .
27
- RUN chmod +x start-openclaw.sh
28
 
29
- # 6. Environment variables
30
- ENV PORT=7860 HOME=/root
31
 
32
  EXPOSE 7860
33
- CMD ["./start-openclaw.sh"]
 
 
1
  FROM node:22-slim
2
 
3
+ ENV DEBIAN_FRONTEND=noninteractive \
4
+ HOME=/root \
5
+ PROXY_PORT=7860 \
6
+ OPENCLAW_PORT=8080 \
7
+ SYNC_INTERVAL=300 \
8
+ PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
9
+
10
  RUN apt-get update && apt-get install -y --no-install-recommends \
11
+ ca-certificates \
12
+ curl \
13
+ gettext-base \
14
+ git \
15
+ nginx \
16
+ procps \
17
+ python3 \
18
+ rsync \
19
+ unzip \
20
+ wget \
21
  && rm -rf /var/lib/apt/lists/*
22
 
23
+ RUN npm install -g openclaw@latest playwright \
24
+ && playwright install --with-deps chromium
 
 
 
25
 
26
+ WORKDIR /root/workspace
 
27
 
28
+ COPY start.sh /app/start.sh
29
+ COPY sync-root-data.sh /app/sync-root-data.sh
30
+ COPY nginx.conf.template /app/nginx.conf.template
 
31
 
32
+ RUN chmod +x /app/start.sh /app/sync-root-data.sh
 
33
 
34
  EXPOSE 7860
35
+
36
+ CMD ["/app/start.sh"]
README.md CHANGED
@@ -1,11 +1,51 @@
1
  ---
2
- title: Openclaw
3
- emoji: 🐨
4
- colorFrom: pink
5
  colorTo: indigo
6
  sdk: docker
7
  pinned: false
8
  license: apache-2.0
 
9
  ---
10
- # trigger build
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: OpenClaw
3
+ emoji: 🦀
4
+ colorFrom: purple
5
  colorTo: indigo
6
  sdk: docker
7
  pinned: false
8
  license: apache-2.0
9
+ app_port: 7860
10
  ---
11
+
12
+ # OpenClaw Space
13
+
14
+ Native OpenClaw runtime for Hugging Face Spaces. VS Code/code-server is intentionally not installed.
15
+
16
+ ## Routing
17
+
18
+ ```text
19
+ / -> OpenClaw control UI
20
+ /{port}/ -> localhost:{port}
21
+ /proxy/{port}/ -> localhost:{port}
22
+ ```
23
+
24
+ ## Persistence
25
+
26
+ On startup, `/data/root` is restored into `/root` when `/data` is available.
27
+
28
+ During runtime, `/root` is synced back to `/data/root` every 5 minutes by default.
29
+
30
+ ```text
31
+ SYNC_INTERVAL=300
32
+ ```
33
+
34
+ Large generated folders such as `node_modules`, `.next`, `dist`, `build`, and cache directories are excluded.
35
+
36
+ ## Environment
37
+
38
+ ```text
39
+ PROXY_PORT=7860
40
+ OPENCLAW_PORT=8080
41
+ SYNC_INTERVAL=300
42
+ OPENCLAW_CMD=
43
+ ```
44
+
45
+ Use `OPENCLAW_CMD` only if your installed OpenClaw CLI needs a different start command.
46
+
47
+ Example:
48
+
49
+ ```text
50
+ OPENCLAW_CMD=openclaw --host 127.0.0.1 --port 8080
51
+ ```
nginx.conf.template ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ worker_processes auto;
2
+ pid /tmp/nginx.pid;
3
+
4
+ events {
5
+ worker_connections 1024;
6
+ }
7
+
8
+ http {
9
+ access_log /dev/stdout;
10
+ error_log /dev/stderr info;
11
+ include /etc/nginx/mime.types;
12
+ default_type application/octet-stream;
13
+ sendfile on;
14
+ tcp_nodelay on;
15
+
16
+ map $http_upgrade $connection_upgrade {
17
+ default upgrade;
18
+ '' close;
19
+ }
20
+
21
+ server {
22
+ listen ${PROXY_PORT};
23
+ server_name _;
24
+ client_max_body_size 0;
25
+
26
+ proxy_http_version 1.1;
27
+ proxy_set_header Host $host;
28
+ proxy_set_header X-Real-IP $remote_addr;
29
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
30
+ proxy_set_header X-Forwarded-Proto $scheme;
31
+ proxy_set_header Upgrade $http_upgrade;
32
+ proxy_set_header Connection $connection_upgrade;
33
+ proxy_read_timeout 86400;
34
+ proxy_send_timeout 86400;
35
+ proxy_buffering off;
36
+
37
+ location ~ ^/proxy/([0-9]+)/(.*)$ {
38
+ proxy_set_header X-Forwarded-Prefix /proxy/$1;
39
+ proxy_pass http://127.0.0.1:$1/$2$is_args$args;
40
+ }
41
+
42
+ location ~ ^/proxy/([0-9]+)$ {
43
+ return 308 /proxy/$1/;
44
+ }
45
+
46
+ location ~ ^/([0-9]+)/(.*)$ {
47
+ proxy_set_header X-Forwarded-Prefix /$1;
48
+ proxy_pass http://127.0.0.1:$1/$2$is_args$args;
49
+ }
50
+
51
+ location ~ ^/([0-9]+)$ {
52
+ return 308 /$1/;
53
+ }
54
+
55
+ location / {
56
+ proxy_pass http://127.0.0.1:${OPENCLAW_PORT};
57
+ }
58
+ }
59
+ }
start-openclaw.sh DELETED
@@ -1,271 +0,0 @@
1
- #!/bin/bash
2
-
3
- # ─────────────────────────────────────────────────────────────
4
- # start-openclaw.sh — VS Code Proxy & OpenClaw Bucket Setup
5
- # ─────────────────────────────────────────────────────────────
6
-
7
- set +e
8
-
9
- echo "===== VS Code Proxy & Storage Setup ====="
10
-
11
- # ── 1. Setup Persistent Data Bucket (/data) ──────────────────
12
- mkdir -p /data/.openclaw/agents/main/sessions
13
- mkdir -p /data/.openclaw/credentials
14
- mkdir -p /data/.openclaw/sessions
15
- mkdir -p /data/.openclaw/browsers
16
- mkdir -p /data/workspace
17
-
18
- # Link persistent store ke /root agar terbaca OpenClaw default
19
- ln -sfn /data/.openclaw /root/.openclaw
20
-
21
- # Link workspace code-server untuk kemudahan akses
22
- mkdir -p /home/coder
23
- ln -sfn /data/workspace /home/coder/workspace
24
-
25
- echo ">>> Persistent directories under /data ready."
26
-
27
- # ── 2. Fix DNS ────────────────────────────────────────────────
28
- echo "nameserver 1.1.1.1" >> /etc/resolv.conf
29
- echo "nameserver 8.8.8.8" >> /etc/resolv.conf
30
- echo "nameserver 8.8.4.4" >> /etc/resolv.conf
31
- echo ">>> DNS fixed."
32
-
33
- # ── 3. Chromium Persistent Install ──────────────────────────────
34
- export PLAYWRIGHT_BROWSERS_PATH=/data/.openclaw/browsers
35
- CHROMIUM_PATH=$(find /data/.openclaw/browsers -name "chrome" -type f 2>/dev/null | head -1)
36
-
37
- if [ -z "$CHROMIUM_PATH" ]; then
38
- echo ">>> Installing Chromium to persistent storage /data..."
39
- if timeout 300 npx playwright install chromium; then
40
- echo ">>> Chromium OK"
41
- else
42
- if timeout 300 npx playwright-core install chromium; then
43
- echo ">>> Chromium OK (via playwright-core)"
44
- else
45
- echo ">>> WARN: Chromium install failed"
46
- fi
47
- fi
48
- # Re-check path after install
49
- CHROMIUM_PATH=$(find /data/.openclaw/browsers -name "chrome" -type f 2>/dev/null | head -1)
50
- else
51
- echo ">>> Chromium found in persistent storage: $CHROMIUM_PATH"
52
- fi
53
-
54
- # ── 4. OpenClaw Configuration ──────────────────────────────────
55
- CLEAN_BASE=$(echo "$OPENAI_API_BASE" | sed "s|/chat/completions||g" | sed "s|/v1/|/v1|g" | sed "s|/v1$|/v1|g")
56
- ALLOW_ID_SAFE=${TELEGRAM_ALLOW_ID:-0}
57
-
58
- generate_safe_json() {
59
- cat > /data/.openclaw/openclaw.json <<JSONEOF
60
- {
61
- "models": {
62
- "providers": {
63
- "kilo_gateway": {
64
- "baseUrl": "https://api.kilo.ai/api/gateway",
65
- "apiKey": "anonymous",
66
- "api": "openai-completions",
67
- "models": [
68
- { "id": "kilo-auto/free", "name": "Kilo Auto Free", "contextWindow": 128000, "maxTokens": 16000 }
69
- ]
70
- },
71
- "nvidia": {
72
- "baseUrl": "$CLEAN_BASE",
73
- "apiKey": "$OPENAI_API_KEY",
74
- "api": "openai-completions",
75
- "models": [
76
- { "id": "$MODEL", "name": "$MODEL", "contextWindow": 128000 },
77
- { "id": "${VISION_MODEL:-$MODEL}", "name": "Nvidia Vision", "contextWindow": 128000, "input": ["text", "image"] }
78
- ]
79
- }
80
- }
81
- },
82
- "agents": {
83
- "defaults": {
84
- "model": {
85
- "primary": "nvidia/$MODEL",
86
- "fallbacks": ["kilo_gateway/kilo-auto/free"]
87
- },
88
- "imageModel": {
89
- "primary": "nvidia/${VISION_MODEL:-$MODEL}"
90
- },
91
- "llm": {
92
- "idleTimeoutSeconds": 0,
93
- "maxTokens": 16384
94
- }
95
- }
96
- },
97
- "commands": { "restart": true },
98
- "browser": {
99
- "enabled": true,
100
- "requirePairing": false,
101
- "headless": true,
102
- "noSandbox": true,
103
- "executablePath": "$CHROMIUM_PATH",
104
- "defaultProfile": "openclaw",
105
- "ssrfPolicy": { "dangerouslyAllowPrivateNetwork": true },
106
- "profiles": {
107
- "openclaw": {
108
- "cdpPort": 18800,
109
- "color": "0088FF"
110
- }
111
- }
112
- },
113
- "channels": {
114
- "telegram": {
115
- "enabled": true,
116
- "botToken": "$TELEGRAM_BOT_TOKEN",
117
- "dmPolicy": "allowlist",
118
- "allowFrom": [$ALLOW_ID_SAFE],
119
- "apiRoot": "https://tg-relay.markdevil11.workers.dev",
120
- "webhookUrl": "https://elysiadev11-openclaw.hf.space/tg-webhook",
121
- "webhookSecret": "$OPENCLAW_GATEWAY_PASSWORD",
122
- "webhookPath": "/tg-webhook",
123
- "webhookHost": "0.0.0.0",
124
- "webhookPort": 8787,
125
- "streaming": {
126
- "mode": "partial"
127
- }
128
- }
129
- },
130
- "gateway": {
131
- "mode": "local",
132
- "bind": "lan",
133
- "port": 7862,
134
- "trustedProxies": ["0.0.0.0/0"],
135
- "auth": { "mode": "token", "token": "$OPENCLAW_GATEWAY_PASSWORD" },
136
- "http": {
137
- "endpoints": {
138
- "chatCompletions": { "enabled": true }
139
- }
140
- },
141
- "controlUi": {
142
- "enabled": true,
143
- "allowInsecureAuth": true,
144
- "dangerouslyDisableDeviceAuth": true,
145
- "dangerouslyAllowHostHeaderOriginFallback": true
146
- }
147
- }
148
- }
149
- JSONEOF
150
- }
151
-
152
- # Cek integritas JSON
153
- echo ">>> Checking /data/.openclaw/openclaw.json..."
154
- if [ ! -f /data/.openclaw/openclaw.json ] || ! python3 -c 'import json; json.load(open("/data/.openclaw/openclaw.json"))' 2>/dev/null; then
155
- echo ">>> Generating /data/.openclaw/openclaw.json..."
156
- generate_safe_json
157
- else
158
- echo ">>> Valid openclaw.json found in /data. Keeping it intact."
159
- fi
160
-
161
- # ── 5. Start code-server ─────────────────────────────────────
162
- echo ">>> Starting code-server on port 8443..."
163
- mkdir -p /root/.config/code-server
164
- VSCODE_PASSWORD="${OPENCLAW_GATEWAY_PASSWORD:-openclaw}"
165
-
166
- cat > /root/.config/code-server/config.yaml <<EOF
167
- bind-addr: 0.0.0.0:8443
168
- auth: password
169
- password: ${VSCODE_PASSWORD}
170
- cert: false
171
- EOF
172
-
173
- # Start VS Code explicit dengan PORT=8443 agar mengabaikan global PORT=7860
174
- PORT=8443 code-server --bind-addr 0.0.0.0:8443 \
175
- --auth password \
176
- --disable-telemetry \
177
- --disable-update-check \
178
- /data/workspace &
179
- CODE_SERVER_PID=$!
180
- echo ">>> code-server started (PID: $CODE_SERVER_PID)"
181
-
182
- # ── 6. Node.js Reverse Proxy ─────────────────────────────────
183
- # Routes:
184
- # /code/* → 8443 (VS Code)
185
- # /tg-webhook → 8787 (OpenClaw Telegram)
186
- # /* → 7862 (OpenClaw UI)
187
- node -e "
188
- const http = require('http');
189
- const net = require('net');
190
-
191
- function proxyHttp(req, res, targetPort, rewritePath) {
192
- const opts = {
193
- hostname: '127.0.0.1',
194
- port: targetPort,
195
- path: rewritePath || req.url,
196
- method: req.method,
197
- headers: { ...req.headers, host: req.headers.host },
198
- };
199
- const pr = http.request(opts, (r) => {
200
- const headers = { ...r.headers };
201
- if (targetPort === 8443 && headers.location) {
202
- if (headers.location === '/' || headers.location === '') {
203
- headers.location = '/code/';
204
- } else if (!headers.location.startsWith('/code') && headers.location.startsWith('/')) {
205
- headers.location = '/code' + headers.location;
206
- }
207
- }
208
- res.writeHead(r.statusCode, headers);
209
- r.pipe(res, { end: true });
210
- });
211
- pr.on('error', (e) => {
212
- res.writeHead(502);
213
- if (targetPort === 7862) {
214
- res.end('<h1>OpenClaw Gateway</h1><p>The UI is not reachable. You must start OpenClaw manually via the VS Code terminal (available at /code/).</p>');
215
- } else {
216
- res.end('502 Bad Gateway');
217
- }
218
- });
219
- req.pipe(pr, { end: true });
220
- }
221
-
222
- function proxyWs(req, socket, head, targetPort, rewritePath) {
223
- const conn = net.connect(targetPort, '127.0.0.1', () => {
224
- const path = rewritePath || req.url;
225
- conn.write(
226
- 'GET ' + path + ' HTTP/1.1\r\n' +
227
- Object.entries(req.headers).map(([k,v]) => k+': '+v).join('\r\n') +
228
- '\r\n\r\n'
229
- );
230
- if (head && head.length) conn.write(head);
231
- socket.pipe(conn);
232
- conn.pipe(socket);
233
- });
234
- conn.on('error', () => { socket.destroy(); });
235
- socket.on('error', () => conn.destroy());
236
- }
237
-
238
- const server = http.createServer((req, res) => {
239
- if (req.url.startsWith('/code')) {
240
- let newPath = req.url.replace(/^\/code\/?/, '/');
241
- if (newPath === '') newPath = '/';
242
- proxyHttp(req, res, 8443, newPath);
243
- } else if (req.url.startsWith('/tg-webhook')) {
244
- proxyHttp(req, res, 8787);
245
- } else {
246
- proxyHttp(req, res, 7862);
247
- }
248
- });
249
-
250
- server.on('upgrade', (req, socket, head) => {
251
- if (req.url.startsWith('/code')) {
252
- let newPath = req.url.replace(/^\/code\/?/, '/');
253
- if (newPath === '') newPath = '/';
254
- proxyWs(req, socket, head, 8443, newPath);
255
- } else {
256
- proxyWs(req, socket, head, 7862);
257
- }
258
- });
259
-
260
- server.listen(7860, '0.0.0.0', () => {
261
- console.log('Proxy routing: /code -> VSCode (8443), /tg-webhook -> Telegram (8787), /* -> OpenClaw (7862)');
262
- });
263
- " &
264
-
265
- echo "============================================="
266
- echo "READY! Open HF space at \`/code/\` to access VS Code,"
267
- echo "then start openclaw manually from the terminal."
268
- echo "============================================="
269
-
270
- # Keep container running
271
- sleep infinity
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
start.sh ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ export PROXY_PORT="${PROXY_PORT:-7860}"
5
+ export OPENCLAW_PORT="${OPENCLAW_PORT:-8080}"
6
+ export SYNC_INTERVAL="${SYNC_INTERVAL:-300}"
7
+ export PLAYWRIGHT_BROWSERS_PATH="${PLAYWRIGHT_BROWSERS_PATH:-/ms-playwright}"
8
+
9
+ mkdir -p /root/workspace
10
+
11
+ /app/sync-root-data.sh restore
12
+ /app/sync-root-data.sh loop &
13
+
14
+ run_openclaw() {
15
+ while true; do
16
+ echo "Starting OpenClaw on 127.0.0.1:${OPENCLAW_PORT}..."
17
+ if [ -n "${OPENCLAW_CMD:-}" ]; then
18
+ sh -lc "$OPENCLAW_CMD"
19
+ else
20
+ openclaw --host 127.0.0.1 --port "${OPENCLAW_PORT}"
21
+ fi
22
+ echo "OpenClaw exited; restarting in 2 seconds..."
23
+ sleep 2
24
+ done
25
+ }
26
+
27
+ run_nginx() {
28
+ while true; do
29
+ envsubst '${PROXY_PORT} ${OPENCLAW_PORT}' < /app/nginx.conf.template > /tmp/nginx.conf
30
+ nginx -c /tmp/nginx.conf -g 'daemon off;'
31
+ echo "Nginx exited; restarting in 2 seconds..."
32
+ sleep 2
33
+ done
34
+ }
35
+
36
+ run_openclaw &
37
+ run_nginx &
38
+
39
+ wait -n
40
+ exit 1
sync-root-data.sh ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ DATA_ROOT="${DATA_ROOT:-/data/root}"
5
+ SYNC_INTERVAL="${SYNC_INTERVAL:-300}"
6
+
7
+ EXCLUDES=(
8
+ "--exclude=.cache"
9
+ "--exclude=.npm/_cacache"
10
+ "--exclude=.npm/_update-notifier-last-checked*"
11
+ "--exclude=.pnpm-store"
12
+ "--exclude=node_modules"
13
+ "--exclude=.next"
14
+ "--exclude=dist"
15
+ "--exclude=build"
16
+ "--exclude=coverage"
17
+ "--exclude=tmp"
18
+ )
19
+
20
+ data_available() {
21
+ [ -d /data ] || mkdir -p /data 2>/dev/null || return 1
22
+ mkdir -p "$DATA_ROOT" 2>/dev/null || return 1
23
+ }
24
+
25
+ restore_root() {
26
+ if data_available; then
27
+ echo "Restoring persistent data from $DATA_ROOT to /root..."
28
+ rsync -rlptD --no-specials --no-devices "${EXCLUDES[@]}" "$DATA_ROOT/" /root/ || true
29
+ else
30
+ echo "Persistent /data is not available; skipping restore."
31
+ fi
32
+ }
33
+
34
+ persist_root() {
35
+ if data_available; then
36
+ echo "Syncing /root to $DATA_ROOT..."
37
+ rsync -rlptD --no-specials --no-devices --delete "${EXCLUDES[@]}" /root/ "$DATA_ROOT/" || true
38
+ else
39
+ echo "Persistent /data is not available; skipping sync."
40
+ fi
41
+ }
42
+
43
+ case "${1:-loop}" in
44
+ restore)
45
+ restore_root
46
+ ;;
47
+ persist)
48
+ persist_root
49
+ ;;
50
+ loop)
51
+ while true; do
52
+ persist_root
53
+ sleep "$SYNC_INTERVAL"
54
+ done
55
+ ;;
56
+ *)
57
+ echo "Usage: $0 {restore|persist|loop}"
58
+ exit 2
59
+ ;;
60
+ esac