-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinstall_and_harden.sh
More file actions
executable file
·301 lines (255 loc) · 12.2 KB
/
install_and_harden.sh
File metadata and controls
executable file
·301 lines (255 loc) · 12.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
#!/usr/bin/env bash
###############################################################################
# install_and_harden.sh — Idempotent provisioning for OpenClaw on Ubuntu 22.04
# Run as root: sudo bash install_and_harden.sh
###############################################################################
set -euo pipefail
# ─── 1. Preamble ────────────────────────────────────────────────────────────
if [[ "$(id -u)" -ne 0 ]]; then
echo "ERROR: Must run as root." >&2
exit 1
fi
if ! grep -q 'Ubuntu 22.04' /etc/os-release 2>/dev/null; then
echo "ERROR: This script targets Ubuntu 22.04 (Jammy)." >&2
exit 1
fi
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [[ ! -f "${SCRIPT_DIR}/.env" ]]; then
echo "ERROR: ${SCRIPT_DIR}/.env not found. Copy .env.example and fill in secrets." >&2
exit 1
fi
# Source .env without echoing
set -a
# shellcheck source=/dev/null
source "${SCRIPT_DIR}/.env"
set +a
# Validate required secrets
: "${OPENCLAW_GATEWAY_PASSWORD:?ERROR: OPENCLAW_GATEWAY_PASSWORD is not set in .env}"
: "${TELEGRAM_BOT_TOKEN:?ERROR: TELEGRAM_BOT_TOKEN is not set in .env}"
echo "==> All required environment variables present."
# ─── 2. System updates + unattended-upgrades ────────────────────────────────
echo "==> Updating system packages..."
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get upgrade -y -qq
apt-get install -y -qq unattended-upgrades apt-transport-https ca-certificates \
curl gnupg lsb-release jq gettext-base
cat > /etc/apt/apt.conf.d/20auto-upgrades <<'EOF'
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
APT::Periodic::AutocleanInterval "7";
EOF
cat > /etc/apt/apt.conf.d/50unattended-upgrades <<'EOF'
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}";
"${distro_id}:${distro_codename}-security";
"${distro_id}ESMApps:${distro_codename}-apps-security";
"${distro_id}ESM:${distro_codename}-infra-security";
};
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Automatic-Reboot "false";
EOF
echo "==> Unattended-upgrades configured."
# ─── 3. Create openclaw user (UID 1000) ─────────────────────────────────────
if ! id -u openclaw &>/dev/null; then
# If UID 1000 is taken by another user, warn and use next available
if getent passwd 1000 &>/dev/null; then
EXISTING_USER="$(getent passwd 1000 | cut -d: -f1)"
echo "WARNING: UID 1000 is already taken by '${EXISTING_USER}'."
echo " The container runs as UID 1000. Either reassign that user's UID"
echo " or adjust docker-compose.yml. Creating openclaw with next free UID."
useradd --system --create-home --shell /usr/sbin/nologin openclaw
else
useradd --system --create-home --shell /usr/sbin/nologin --uid 1000 openclaw
fi
echo "==> User 'openclaw' created."
else
echo "==> User 'openclaw' already exists."
fi
# ─── 4. Node.js 22 ──────────────────────────────────────────────────────────
REQUIRED_NODE_MAJOR=22
CURRENT_NODE_MAJOR="$(node --version 2>/dev/null | grep -oP '(?<=v)\d+' || echo 0)"
if [[ "${CURRENT_NODE_MAJOR}" -lt "${REQUIRED_NODE_MAJOR}" ]]; then
echo "==> Installing Node.js ${REQUIRED_NODE_MAJOR}..."
curl -fsSL https://deb.nodesource.com/setup_${REQUIRED_NODE_MAJOR}.x | bash -
apt-get install -y -qq nodejs
else
echo "==> Node.js ${CURRENT_NODE_MAJOR} already installed."
fi
# ─── 5. Docker Engine ───────────────────────────────────────────────────────
if ! command -v docker &>/dev/null; then
echo "==> Installing Docker CE..."
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
| gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" \
| tee /etc/apt/sources.list.d/docker.list > /dev/null
apt-get update -qq
apt-get install -y -qq docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
else
echo "==> Docker already installed."
fi
systemctl enable --now docker
# Add openclaw to docker group
usermod -aG docker openclaw 2>/dev/null || true
echo "==> Docker ready; openclaw added to docker group."
# ─── 6. Tailscale ───────────────────────────────────────────────────────────
if ! command -v tailscale &>/dev/null; then
echo "==> Installing Tailscale..."
curl -fsSL https://tailscale.com/install.sh | sh
else
echo "==> Tailscale already installed."
fi
systemctl enable --now tailscaled
echo "==> Tailscale daemon enabled."
echo "NOTE: Run 'sudo tailscale up' interactively to authenticate this node."
# ─── 7. UFW ─────────────────────────────────────────────────────────────────
echo "==> Configuring UFW..."
apt-get install -y -qq ufw
ufw default deny incoming
ufw default allow outgoing
ufw allow OpenSSH
ufw allow in on tailscale0
ufw deny 18789/tcp comment "Block external access to OpenClaw gateway"
ufw --force enable
echo "==> UFW configured and enabled."
# ─── 8. Fail2ban ────────────────────────────────────────────────────────────
echo "==> Configuring Fail2ban..."
apt-get install -y -qq fail2ban
cat > /etc/fail2ban/jail.local <<'EOF'
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 5
bantime = 3600
findtime = 600
EOF
systemctl enable --now fail2ban
systemctl restart fail2ban
echo "==> Fail2ban configured."
# ─── 9. OpenClaw dirs + config ──────────────────────────────────────────────
echo "==> Setting up OpenClaw directories and config..."
OPENCLAW_HOME="/home/openclaw"
mkdir -p "${OPENCLAW_HOME}/.openclaw/workspace"
mkdir -p "${OPENCLAW_HOME}/.openclaw/credentials"
mkdir -p "${OPENCLAW_HOME}/.openclaw/agents/main/sessions"
mkdir -p /var/log/openclaw
# Generate config from template if it does not already exist
CONFIG_FILE="${OPENCLAW_HOME}/.openclaw/openclaw.json"
if [[ ! -f "${CONFIG_FILE}" ]]; then
envsubst < "${SCRIPT_DIR}/openclaw.config.template.json" > "${CONFIG_FILE}"
echo "==> Config written to ${CONFIG_FILE}."
else
echo "==> Config already exists at ${CONFIG_FILE}; skipping to preserve edits."
fi
chown -R openclaw:openclaw "${OPENCLAW_HOME}/.openclaw"
chmod 700 "${OPENCLAW_HOME}/.openclaw"
chmod 700 "${OPENCLAW_HOME}/.openclaw/credentials"
chmod 600 "${CONFIG_FILE}" 2>/dev/null || true
chown -R openclaw:openclaw /var/log/openclaw
chmod 750 /var/log/openclaw
echo "==> Directories and permissions set."
# ─── 10. Deploy Docker Compose ──────────────────────────────────────────────
echo "==> Deploying Docker Compose..."
cp "${SCRIPT_DIR}/docker-compose.yml" "${OPENCLAW_HOME}/docker-compose.yml"
cp "${SCRIPT_DIR}/.env" "${OPENCLAW_HOME}/.env"
chown openclaw:openclaw "${OPENCLAW_HOME}/docker-compose.yml" "${OPENCLAW_HOME}/.env"
chmod 600 "${OPENCLAW_HOME}/.env"
cd "${OPENCLAW_HOME}"
# Build so OPENCLAW_DOCKER_APT_PACKAGES (if set) is baked into the image.
sudo -u openclaw docker compose build
sudo -u openclaw docker compose up -d
echo "==> Docker Compose services started."
# ─── 10a. Patch existing config (idempotent) ────────────────────────────────
# Since the config may already exist (skipped by envsubst guard above),
# apply critical fixes via openclaw config set commands.
echo "==> Applying config patches..."
sudo -u openclaw openclaw config set gateway.mode local 2>/dev/null || true
sudo -u openclaw openclaw config set channels.telegram.enabled true 2>/dev/null || true
sudo -u openclaw openclaw config set channels.telegram.groups.*.allowFrom pairing 2>/dev/null || true
# Remove password from config file — rely on OPENCLAW_GATEWAY_PASSWORD env var
if sudo -u openclaw jq -e '.gateway.auth.password' "${CONFIG_FILE}" &>/dev/null; then
sudo -u openclaw jq 'del(.gateway.auth.password)' "${CONFIG_FILE}" > "${CONFIG_FILE}.tmp" \
&& mv "${CONFIG_FILE}.tmp" "${CONFIG_FILE}"
chown openclaw:openclaw "${CONFIG_FILE}"
chmod 600 "${CONFIG_FILE}"
echo "==> Removed gateway password from config (using env var instead)."
fi
# ─── 10b. Build sandbox image ───────────────────────────────────────────────
if ! docker image inspect openclaw-sandbox:bookworm-slim &>/dev/null; then
echo "==> Building sandbox base image..."
# Use the container's bundled setup script
sudo -u openclaw docker compose run --rm --entrypoint "" gateway \
sh -c "[ -f /app/scripts/sandbox-setup.sh ] && /app/scripts/sandbox-setup.sh" 2>/dev/null || \
echo "WARNING: Could not build sandbox image automatically."
echo " If sandbox is needed, run: openclaw sandbox setup"
else
echo "==> Sandbox image already exists."
fi
# Restart gateway to pick up config changes
cd "${OPENCLAW_HOME}"
sudo -u openclaw docker compose restart gateway
echo "==> Gateway restarted with updated config."
# ─── 11. Tailscale Serve ────────────────────────────────────────────────────
if tailscale status &>/dev/null; then
echo "==> Configuring Tailscale Serve..."
tailscale serve --bg --https=443 http://127.0.0.1:18789
echo "==> Tailscale Serve: HTTPS :443 → localhost:18789."
else
echo "WARNING: Tailscale not authenticated. After running 'tailscale up',"
echo " re-run this script or manually run:"
echo " tailscale serve --bg --https=443 http://127.0.0.1:18789"
fi
# ─── 12. Log rotation ───────────────────────────────────────────────────────
echo "==> Configuring log rotation..."
cat > /etc/logrotate.d/openclaw <<'EOF'
/var/log/openclaw/*.log {
daily
rotate 14
compress
delaycompress
missingok
notifempty
copytruncate
create 0640 openclaw openclaw
}
EOF
echo "==> Log rotation configured."
# ─── 13. openclaw doctor + security audit ────────────────────────────────────
echo "==> Running openclaw diagnostics..."
if ! command -v openclaw &>/dev/null; then
npm install -g openclaw
fi
sudo -u openclaw openclaw doctor --non-interactive || true
sudo -u openclaw openclaw security audit --deep || true
echo ""
echo "=========================================="
echo " OpenClaw Deployment Summary"
echo "=========================================="
echo ""
echo "--- UFW Status ---"
ufw status verbose
echo ""
echo "--- Fail2ban Status ---"
fail2ban-client status sshd 2>/dev/null || echo "(fail2ban not yet active)"
echo ""
echo "--- Docker Containers ---"
cd "${OPENCLAW_HOME}" && sudo -u openclaw docker compose ps
echo ""
echo "--- Tailscale Status ---"
tailscale status 2>/dev/null || echo "(Tailscale not authenticated)"
echo ""
echo "=========================================="
echo " Deployment complete."
echo " Next steps:"
echo " 1. Run 'sudo tailscale up' if not authenticated"
echo " 2. Re-run this script to enable Tailscale Serve"
echo " 3. Run 'openclaw auth' via SSH tunnel for OAuth"
echo "=========================================="