forked from marcpope/borgbackupserver
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathentrypoint.sh
More file actions
406 lines (356 loc) · 16.9 KB
/
Copy pathentrypoint.sh
File metadata and controls
406 lines (356 loc) · 16.9 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
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
#!/bin/bash
set -e
echo "=== BBS Container Starting ==="
echo "Version: $(cat /var/www/bbs/VERSION 2>/dev/null || echo 'unknown')"
# --- SSH host key persistence ---
# Persist host keys on the data volume so agents don't see "host key changed"
# errors after a container rebuild
SSH_KEY_DIR="/var/bbs/.ssh-host-keys"
if [ -d "$SSH_KEY_DIR" ] && [ "$(ls -A $SSH_KEY_DIR 2>/dev/null)" ]; then
echo "Restoring SSH host keys from volume..."
cp "$SSH_KEY_DIR"/ssh_host_* /etc/ssh/ 2>/dev/null || true
chmod 600 /etc/ssh/ssh_host_*_key 2>/dev/null || true
chmod 644 /etc/ssh/ssh_host_*_key.pub 2>/dev/null || true
else
echo "Saving SSH host keys to volume..."
mkdir -p "$SSH_KEY_DIR"
cp /etc/ssh/ssh_host_* "$SSH_KEY_DIR/" 2>/dev/null || true
fi
# NOTE: sshd is started AFTER SSH users are recreated (see below)
# --- MariaDB ---
# Store database files on the persistent volume
MYSQL_DATADIR="/var/bbs/mysql"
mkdir -p "$MYSQL_DATADIR"
chown mysql:mysql "$MYSQL_DATADIR"
echo "Starting MariaDB..."
if [ ! -d "$MYSQL_DATADIR/mysql" ]; then
echo "Initializing MariaDB data directory..."
mysql_install_db --user=mysql --datadir="$MYSQL_DATADIR" > /dev/null 2>&1
fi
# Use persistent volume for temp files so large catalog imports and MySQL
# temp tables don't fill the container's overlay filesystem
mkdir -p /var/bbs/tmp
chown www-data:www-data /var/bbs/tmp
chmod 1777 /var/bbs/tmp
export TMPDIR=/var/bbs/tmp
# Force MariaDB to use UTC so CURRENT_TIMESTAMP values are consistent
# with what TimeHelper::format() expects, regardless of the Docker host timezone.
mkdir -p /etc/mysql/conf.d
cat > /etc/mysql/conf.d/timezone.cnf << 'MYCNF'
[mysqld]
default-time-zone = '+00:00'
tmpdir = /var/bbs/tmp
MYCNF
mysqld_safe --datadir="$MYSQL_DATADIR" &
sleep 3
# Wait for MySQL to be ready
echo "Waiting for MariaDB..."
for i in {1..30}; do
if mysqladmin ping -h localhost --silent 2>/dev/null; then
break
fi
sleep 1
done
# Start ClickHouse (catalog engine)
echo "Starting ClickHouse..."
if command -v clickhouse-server &>/dev/null; then
# Store ClickHouse data on persistent volume (same pattern as MariaDB)
mkdir -p /var/bbs/clickhouse /var/log/clickhouse-server /etc/clickhouse-server/config.d
chown -R clickhouse:clickhouse /var/bbs/clickhouse /var/log/clickhouse-server
# Install config override to disable system log tables (reduces idle disk I/O)
if [ -f "/var/www/bbs/config/clickhouse-server-override.xml" ]; then
cp /var/www/bbs/config/clickhouse-server-override.xml /etc/clickhouse-server/config.d/bbs-override.xml
fi
# Point ClickHouse data to persistent volume so it survives container recreation
cat > /etc/clickhouse-server/config.d/bbs-docker-paths.xml << 'CHXML'
<clickhouse>
<path>/var/bbs/clickhouse/</path>
<tmp_path>/var/bbs/clickhouse/tmp/</tmp_path>
</clickhouse>
CHXML
TMPDIR=/var/bbs/tmp sudo -u clickhouse clickhouse-server --daemon --config-file=/etc/clickhouse-server/config.xml 2>/dev/null || true
for i in {1..30}; do
curl -sf http://localhost:8123/ping >/dev/null 2>&1 && break
sleep 1
done
if curl -sf http://localhost:8123/ping >/dev/null 2>&1; then
echo " ClickHouse started"
# Drop old system log tables to reclaim disk space
for tbl in trace_log text_log metric_log asynchronous_metric_log part_log \
processors_profile_log query_log query_thread_log query_views_log \
query_metric_log session_log opentelemetry_span_log \
asynchronous_insert_log backup_log s3_queue_log blob_storage_log \
background_schedule_pool_log error_log; do
clickhouse-client --query "DROP TABLE IF EXISTS system.$tbl" 2>/dev/null || true
done
else
echo " Warning: ClickHouse failed to start"
fi
else
echo " Warning: ClickHouse not installed — catalog features will not work"
fi
# --- Application configuration ---
# Extract hostname from APP_URL (strip protocol and trailing path)
SERVER_HOST="$(echo "${APP_URL:-http://localhost}" | sed -E 's|https?://||' | sed 's|/.*||')"
# Persist .env on the data volume so it survives container recreation.
# The APP_KEY inside .env encrypts SSH keys and S3 credentials — if lost,
# all encrypted data becomes unrecoverable.
ENV_VOLUME="/var/bbs/config/.env"
ENV_APP="/var/www/bbs/config/.env"
mkdir -p /var/bbs/config /var/www/bbs/config
# Migration: move existing .env from container filesystem to volume
if [ -f "$ENV_APP" ] && [ ! -L "$ENV_APP" ] && [ ! -f "$ENV_VOLUME" ]; then
echo "Migrating .env to persistent volume..."
cp "$ENV_APP" "$ENV_VOLUME"
rm -f "$ENV_APP"
fi
# Generate APP_KEY if not provided via env
if [ -z "$APP_KEY" ]; then
APP_KEY="$(openssl rand -hex 32)"
fi
# Generate a random DB password for new installs
DB_PASS="$(openssl rand -base64 16 | tr -d '/+=' | head -c 20)"
# Create .env on volume if it doesn't exist (first run)
if [ ! -f "$ENV_VOLUME" ]; then
echo "Creating .env configuration..."
cat > "$ENV_VOLUME" << EOF
DB_HOST=127.0.0.1
DB_NAME=bbs
DB_USER=bbs
DB_PASS=$DB_PASS
APP_URL=${APP_URL:-http://localhost}
APP_KEY=$APP_KEY
EOF
fi
chown www-data:www-data "$ENV_VOLUME"
chmod 600 "$ENV_VOLUME"
# Symlink so the app reads from the expected path
ln -sf "$ENV_VOLUME" "$ENV_APP"
# Ensure APP_KEY exists in .env (for containers upgraded from older versions)
if ! grep -q '^APP_KEY=' "$ENV_VOLUME" 2>/dev/null; then
echo "Adding APP_KEY to existing .env..."
echo "APP_KEY=$APP_KEY" >> "$ENV_VOLUME"
fi
# Add ClickHouse env vars if missing
if ! grep -q 'CLICKHOUSE_HOST' "$ENV_VOLUME" 2>/dev/null; then
echo "" >> "$ENV_VOLUME"
echo "CLICKHOUSE_HOST=localhost" >> "$ENV_VOLUME"
echo "CLICKHOUSE_PORT=8123" >> "$ENV_VOLUME"
echo "CLICKHOUSE_DB=bbs" >> "$ENV_VOLUME"
fi
# Read DB password from .env (may be the random one we just wrote, or an existing one)
DB_PASS=$(grep '^DB_PASS=' "$ENV_VOLUME" | cut -d= -f2-)
# Create database and user if needed
if ! mysql -e "SELECT 1 FROM mysql.user WHERE user='bbs'" 2>/dev/null | grep -q 1; then
echo "Creating BBS database and user..."
mysql -e "CREATE DATABASE IF NOT EXISTS bbs CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
mysql -e "CREATE USER IF NOT EXISTS 'bbs'@'localhost' IDENTIFIED BY '$DB_PASS';"
mysql -e "CREATE USER IF NOT EXISTS 'bbs'@'127.0.0.1' IDENTIFIED BY '$DB_PASS';"
mysql -e "GRANT ALL PRIVILEGES ON bbs.* TO 'bbs'@'localhost';"
mysql -e "GRANT ALL PRIVILEGES ON bbs.* TO 'bbs'@'127.0.0.1';"
mysql -e "FLUSH PRIVILEGES;"
fi
# --- Storage directories ---
mkdir -p /var/bbs/home
mkdir -p /var/bbs/cache
mkdir -p /var/bbs/backups
# Set permissions on persistent volume directories
# Only chown the top-level dirs (not -R) — per-user subdirs under home/ and cache/
# have their own ownership (user:www-data) set by bbs-ssh-helper. A recursive chown
# would clobber .ssh/authorized_keys and per-user borg cache directories.
chown www-data:www-data /var/bbs/home /var/bbs/cache
chown -R www-data:www-data /var/bbs/backups
chown -R mysql:mysql "$MYSQL_DATADIR"
# Create ClickHouse database and tables
if curl -sf http://localhost:8123/ping >/dev/null 2>&1; then
echo "Setting up ClickHouse catalog tables..."
clickhouse-client --query "CREATE DATABASE IF NOT EXISTS bbs" 2>/dev/null || true
if [ -f "/var/www/bbs/schema-clickhouse.sql" ]; then
clickhouse-client -d bbs --multiquery < /var/www/bbs/schema-clickhouse.sql 2>/dev/null || true
fi
echo " ClickHouse catalog tables ready"
fi
# --- Database setup ---
FRESH_INSTALL=0
if ! mysql -u bbs -p"$DB_PASS" bbs -e "SELECT 1 FROM settings LIMIT 1" 2>/dev/null; then
FRESH_INSTALL=1
fi
# Import schema on fresh install
if [ -f "/var/www/bbs/schema.sql" ]; then
if [ "$FRESH_INSTALL" -eq 1 ]; then
echo "Importing database schema..."
mysql -u bbs -p"$DB_PASS" bbs < /var/www/bbs/schema.sql
fi
fi
# Set essential settings
echo "Configuring server settings..."
mysql -u bbs -p"$DB_PASS" bbs -e "INSERT INTO settings (\`key\`, \`value\`) VALUES ('storage_path', '/var/bbs/home') ON DUPLICATE KEY UPDATE \`value\` = IF(\`value\` = '' OR \`value\` IS NULL, '/var/bbs/home', \`value\`);"
mysql -u bbs -p"$DB_PASS" bbs -e "INSERT INTO settings (\`key\`, \`value\`) VALUES ('server_host', '$SERVER_HOST') ON DUPLICATE KEY UPDATE \`value\` = IF(\`value\` = '' OR \`value\` IS NULL, '$SERVER_HOST', \`value\`);"
# Set SSH port (must match the host-side port mapping for borg agent connections)
if [ -n "$SSH_PORT" ]; then
# SSH_PORT explicitly provided via env — sync to database and mark Docker setup complete
mysql -u bbs -p"$DB_PASS" bbs -e "INSERT INTO settings (\`key\`, \`value\`) VALUES ('ssh_port', '$SSH_PORT') ON DUPLICATE KEY UPDATE \`value\` = '$SSH_PORT';"
mysql -u bbs -p"$DB_PASS" bbs -e "INSERT INTO settings (\`key\`, \`value\`) VALUES ('docker_setup_complete', '1') ON DUPLICATE KEY UPDATE \`value\` = '1';"
else
# Not provided — seed default on fresh install, don't overwrite existing
mysql -u bbs -p"$DB_PASS" bbs -e "INSERT INTO settings (\`key\`, \`value\`) VALUES ('ssh_port', '22') ON DUPLICATE KEY UPDATE \`value\` = \`value\`;"
fi
# Set admin password on fresh install
if [ "$FRESH_INSTALL" -eq 1 ]; then
if [ -z "$ADMIN_PASS" ]; then
ADMIN_PASS="$(openssl rand -base64 12 | tr -d '/+=' | head -c 16)"
echo ""
echo "========================================"
echo " ADMIN CREDENTIALS (SAVE THESE!)"
echo "========================================"
echo " URL: ${APP_URL:-http://localhost}"
echo " Username: admin"
echo " Password: $ADMIN_PASS"
echo "========================================"
echo ""
fi
# Generate bcrypt hash and update admin password
export ADMIN_PASS
ADMIN_HASH=$(php -r "echo password_hash(\$_SERVER['ADMIN_PASS'], PASSWORD_BCRYPT, ['cost' => 12]);")
mysql -u bbs -p"$DB_PASS" bbs -e "UPDATE users SET password_hash = '$ADMIN_HASH' WHERE username = 'admin';"
fi
# Run pending migrations
if [ -d "/var/www/bbs/migrations" ]; then
echo "Running migrations..."
for migration in /var/www/bbs/migrations/*.sql; do
if [ -f "$migration" ]; then
mysql -u bbs -p"$DB_PASS" bbs < "$migration" 2>/dev/null || true
fi
done
fi
# Sync borg versions from GitHub on fresh install or if table is empty
BORG_COUNT=$(mysql -u bbs -p"$DB_PASS" bbs -N -e "SELECT COUNT(*) FROM borg_versions" 2>/dev/null || echo "0")
if [ "$BORG_COUNT" -eq 0 ]; then
echo "Syncing borg versions from GitHub..."
cd /var/www/bbs && php -r "
require 'vendor/autoload.php';
\$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/config');
\$dotenv->load();
\$svc = new BBS\Services\BorgVersionService();
\$result = \$svc->syncVersionsFromGitHub();
echo isset(\$result['added']) ? \"{$result['added']} versions synced\" : (\$result['error'] ?? 'unknown error');
echo PHP_EOL;
" 2>/dev/null || echo "Warning: Could not sync borg versions"
fi
# --- Regenerate allowed-storage-paths from database ---
# This file lives in the container filesystem and is lost on recreation.
# bbs-ssh-helper uses it to validate repo paths outside /var/bbs/.
echo "Regenerating allowed storage paths..."
STORAGE_LOCATIONS=$(mysql -u bbs -p"$DB_PASS" bbs -N -e "SELECT path FROM storage_locations" 2>/dev/null)
if [ -n "$STORAGE_LOCATIONS" ]; then
mkdir -p /etc/bbs
echo "$STORAGE_LOCATIONS" > /etc/bbs/allowed-storage-paths
echo " $(echo "$STORAGE_LOCATIONS" | wc -l) storage location(s) registered"
fi
# --- Recreate SSH users from database (needed after container restart) ---
# Home directories are named by agent ID (e.g., /var/bbs/home/1), not by username.
# Query the database for the username-to-directory mapping.
STORAGE_PATH=$(mysql -u bbs -p"$DB_PASS" bbs -N -e "SELECT value FROM settings WHERE \`key\` = 'storage_path'" 2>/dev/null)
STORAGE_PATH="${STORAGE_PATH:-/var/bbs/home}"
RESTORED_USERS=0
echo "Recreating SSH users from database..."
mysql -u bbs -p"$DB_PASS" bbs -N -e "SELECT ssh_unix_user, id, IFNULL(ssh_home_dir, '') FROM agents WHERE ssh_unix_user IS NOT NULL AND ssh_unix_user != ''" 2>/dev/null | while read SSH_USER AGENT_ID SSH_HOME_DIR; do
# Use stored ssh_home_dir if available, fall back to STORAGE_PATH/AGENT_ID for pre-migration agents
USER_HOME="${SSH_HOME_DIR:-$STORAGE_PATH/$AGENT_ID}"
[ -d "$USER_HOME" ] || continue
# If user already exists, just fix ownership (may have been clobbered by old entrypoint)
if id "$SSH_USER" &>/dev/null; then
SSH_UID=$(id -u "$SSH_USER")
SSH_GID=$(id -g "$SSH_USER")
if [ -d "$USER_HOME/.ssh" ]; then
chown -R "$SSH_UID:$SSH_GID" "$USER_HOME/.ssh"
fi
# Fix per-user borg cache dir (old entrypoint's chown -R on /var/bbs/cache clobbered it)
CACHE_DIR="/var/bbs/cache/$SSH_USER"
if [ -d "$CACHE_DIR" ]; then
chown -R "$SSH_UID:$SSH_GID" "$CACHE_DIR"
fi
continue
fi
# Get UID from stored file, or allocate a fresh one
if [ -f "$USER_HOME/.uid" ]; then
STORED_UID=$(cat "$USER_HOME/.uid")
else
# Try directory ownership, but skip if it belongs to an existing user
# (e.g., www-data uid 33 from old chown -R that clobbered ownership)
STORED_UID=$(stat -c %u "$USER_HOME" 2>/dev/null || echo "0")
if [ -z "$STORED_UID" ] || [ "$STORED_UID" = "0" ] || getent passwd "$STORED_UID" >/dev/null 2>&1; then
# UID is root, empty, or already belongs to another user — allocate fresh
STORED_UID=$(awk -F: '($3>=1000)&&($3<60000){print $3}' /etc/passwd | sort -n | tail -1)
STORED_UID=$((STORED_UID + 1))
[ "$STORED_UID" -lt 1000 ] && STORED_UID=1000
fi
fi
# Validate chosen UID/GID doesn't collide with existing system users or groups
EXISTING_GROUP=$(getent group "$STORED_UID" 2>/dev/null | cut -d: -f1)
EXISTING_USER=$(getent passwd "$STORED_UID" 2>/dev/null | cut -d: -f1)
if { [ -n "$EXISTING_GROUP" ] && [ "$EXISTING_GROUP" != "$SSH_USER" ]; } || \
{ [ -n "$EXISTING_USER" ] && [ "$EXISTING_USER" != "$SSH_USER" ]; }; then
echo " UID/GID $STORED_UID collides with existing user/group '$EXISTING_GROUP$EXISTING_USER' — reallocating"
STORED_UID=$(awk -F: '($3>=1000)&&($3<60000){print $3}' /etc/passwd | sort -n | tail -1)
STORED_UID=$((STORED_UID + 1))
[ "$STORED_UID" -lt 1000 ] && STORED_UID=1000
fi
# Create group and user with the chosen UID
groupadd -g "$STORED_UID" "$SSH_USER" 2>/dev/null || true
useradd -u "$STORED_UID" -g "$STORED_UID" -d "$USER_HOME" -s /bin/bash "$SSH_USER" 2>/dev/null || true
# Verify the user was actually created before fixing ownership
if ! id "$SSH_USER" &>/dev/null; then
echo " Warning: could not create user $SSH_USER (uid=$STORED_UID) — skipping"
continue
fi
# Fix ownership — home dir user:www-data (750), .ssh dir user:user (700)
chown "$SSH_USER:www-data" "$USER_HOME"
chmod 750 "$USER_HOME"
if [ -d "$USER_HOME/.ssh" ]; then
chown -R "$STORED_UID:$STORED_UID" "$USER_HOME/.ssh"
chmod 700 "$USER_HOME/.ssh"
chmod 600 "$USER_HOME/.ssh/authorized_keys" 2>/dev/null || true
fi
# Fix per-user borg cache dir if it exists
CACHE_DIR="/var/bbs/cache/$SSH_USER"
if [ -d "$CACHE_DIR" ]; then
chown -R "$STORED_UID:$STORED_UID" "$CACHE_DIR"
fi
# Save UID for future restarts
echo "$STORED_UID" > "$USER_HOME/.uid"
echo " Restored user: $SSH_USER (uid=$STORED_UID)"
done
# --- Legacy SSH compatibility ---
# OpenSSH 10+ dropped ssh-rsa (SHA-1) from default accepted algorithms.
# Re-enable it so agents on older OS (CentOS 6/7, Ubuntu 14/16) can connect.
# This config lives inside the container filesystem and is lost on recreation,
# so we must re-create it every startup (not just during bbs-update).
SSHD_LEGACY="/etc/ssh/sshd_config.d/bbs-legacy-compat.conf"
SSHD_CONF_DIR="/etc/ssh/sshd_config.d"
if [ -d "$SSHD_CONF_DIR" ] && [ ! -f "$SSHD_LEGACY" ]; then
cat > "$SSHD_LEGACY" <<'SSHEOF'
# Added by BBS to support agents on older OS with legacy SSH clients
HostKeyAlgorithms +ssh-rsa
PubkeyAcceptedAlgorithms +ssh-rsa
SSHEOF
chmod 644 "$SSHD_LEGACY"
echo " Enabled legacy SSH compatibility"
fi
# --- Start SSH server (after users are recreated and config is ready) ---
echo "Starting SSH server..."
/usr/sbin/sshd
# --- Cron ---
echo "Setting up scheduler cron..."
touch /var/log/bbs-scheduler.log
chown www-data:www-data /var/log/bbs-scheduler.log
cat > /etc/cron.d/bbs-scheduler << 'CRON'
TMPDIR=/var/bbs/tmp
* * * * * www-data cd /var/www/bbs && /usr/local/bin/php scheduler.php >> /var/log/bbs-scheduler.log 2>&1
# Save UIDs for any user home dirs that have .ssh/ but no .uid file yet
*/5 * * * * root for d in /var/bbs/home/*/; do [ -d "$d/.ssh" ] && [ ! -f "$d/.uid" ] && stat -c \%u "$d" > "$d/.uid" 2>/dev/null; done
CRON
chmod 644 /etc/cron.d/bbs-scheduler
cron
echo "=== BBS Container Ready ==="
exec apache2-foreground