-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsetup.sh
More file actions
executable file
·462 lines (388 loc) · 14.1 KB
/
Copy pathsetup.sh
File metadata and controls
executable file
·462 lines (388 loc) · 14.1 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
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
#!/usr/bin/env bash
set -euo pipefail
# ──────────────────────────────────────────────
# LightDrive — Universal Linux Install / Update Script
# Usage: curl -fsSL https://raw.githubusercontent.com/lbirkert/lightdrive/main/setup.sh | bash
#
# Run the same command again to update an existing installation.
# ──────────────────────────────────────────────
RED='\033[0;31m'; GREEN='\033[0;32m'; CYAN='\033[0;36m'; YELLOW='\033[1;33m'; NC='\033[0m'
info() { echo -e "${CYAN}[INFO]${NC} $*"; }
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
err() { echo -e "${RED}[ERROR]${NC} $*"; }
# ── Banner ────────────────────────────────────
echo ""
echo -e "${CYAN} ⚡ LightDrive${NC}"
echo -e "${CYAN} Self-hosted file sharing${NC}"
echo ""
# ── Pre-flight ───────────────────────────────
if [[ $EUID -eq 0 ]]; then
err "Do not run this script as root. It will use sudo where needed."
exit 1
fi
UNAME_M="$(uname -m)"
info "Architecture: $UNAME_M"
if [[ "$(uname -s)" != "Linux" ]]; then
err "This script is designed for Linux. For other platforms, see the README."
exit 1
fi
# ── Distro Detection ─────────────────────────
detect_pkg_manager() {
if command -v apt &>/dev/null; then
echo "apt"
elif command -v dnf &>/dev/null; then
echo "dnf"
elif command -v yum &>/dev/null; then
echo "yum"
elif command -v pacman &>/dev/null; then
echo "pacman"
elif command -v zypper &>/dev/null; then
echo "zypper"
else
echo "unknown"
fi
}
PKG_MANAGER=$(detect_pkg_manager)
info "Package manager: $PKG_MANAGER"
install_pkg() {
local pkg="$1"
case "$PKG_MANAGER" in
apt) sudo apt install -y "$pkg" ;;
dnf) sudo dnf install -y "$pkg" ;;
yum) sudo yum install -y "$pkg" ;;
pacman) sudo pacman --noconfirm -S "$pkg" ;;
zypper) sudo zypper install -y "$pkg" ;;
*) err "Unsupported package manager"; return 1 ;;
esac
}
update_pkg_lists() {
case "$PKG_MANAGER" in
apt) sudo apt update ;;
dnf|yum) sudo "$PKG_MANAGER" check-update || true ;;
pacman) sudo pacman -Sy ;;
zypper) sudo zypper refresh ;;
esac
}
pkg_installed() {
case "$PKG_MANAGER" in
apt) dpkg -s "$1" &>/dev/null 2>&1 ;;
dnf|yum) rpm -q "$1" &>/dev/null 2>&1 ;;
pacman) pacman -Qi "$1" &>/dev/null 2>&1 ;;
zypper) rpm -q "$1" &>/dev/null 2>&1 ;;
*) return 1 ;;
esac
}
# ── Determine Install Directory ───────────────
prompt_default() {
local var_name="$1" prompt="$2" default="$3"
read -rp "$prompt [$default]: " val
printf -v "$var_name" "%s" "${val:-$default}"
}
confirm() {
read -rp "$1 [y/N] " reply
case "$reply" in [yY]|[yY][eE][sS]) return 0;; *) return 1;; esac
}
# ── Clone / Pull Repository ──────────────────
INSTALL_DIR=""
# If run from a clone (i.e. script lives in repo), use that
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
if [[ -f "$SCRIPT_DIR/package.json" ]] && grep -q '"name": "lightdrive"' "$SCRIPT_DIR/package.json" 2>/dev/null; then
INSTALL_DIR="$SCRIPT_DIR"
info "Using repository at $INSTALL_DIR"
elif [[ -f "$PWD/package.json" ]] && grep -q '"name": "lightdrive"' "$PWD/package.json" 2>/dev/null; then
INSTALL_DIR="$PWD"
info "Using repository at $INSTALL_DIR"
fi
if [[ -z "$INSTALL_DIR" ]]; then
echo ""
info "=== Clone Repository ==="
prompt_default INSTALL_DIR "Install directory" "$HOME/lightdrive"
if [[ -d "$INSTALL_DIR" ]]; then
warn "Directory $INSTALL_DIR already exists"
if confirm "Pull latest changes?"; then
cd "$INSTALL_DIR" && git pull
fi
else
mkdir -p "$(dirname "$INSTALL_DIR")"
git clone https://github.com/lbirkert/lightdrive.git "$INSTALL_DIR"
ok "Repository cloned"
fi
fi
cd "$INSTALL_DIR"
# ── System Dependencies ──────────────────────
echo ""
info "=== System Dependencies ==="
if confirm "Update package lists?"; then
update_pkg_lists
fi
DEPS=()
# Build tools for native modules (sharp, argon2)
if ! pkg_installed git; then DEPS+=(git); fi
if command -v curl &>/dev/null; then true; else DEPS+=(curl); fi
case "$PKG_MANAGER" in
apt)
if ! pkg_installed build-essential; then DEPS+=(build-essential); fi
if ! pkg_installed python3; then DEPS+=(python3); fi
;;
dnf|yum)
if ! pkg_installed "@Development Tools"; then DEPS+=( "@Development Tools" ); fi
if ! pkg_installed python3; then DEPS+=(python3); fi
;;
pacman)
if ! pkg_installed base-devel; then DEPS+=(base-devel); fi
if ! pkg_installed python; then DEPS+=(python); fi
;;
zypper)
if ! pkg_installed patterns-devel-base-devel_basis; then DEPS+=(patterns-devel-base-devel_basis); fi
if ! pkg_installed python3; then DEPS+=(python3); fi
;;
esac
if [[ ${#DEPS[@]} -gt 0 ]]; then
info "Installing: ${DEPS[*]}"
install_pkg "${DEPS[@]}"
ok "System dependencies installed"
else
ok "All system dependencies already present"
fi
# ── Node.js via nvm ──────────────────────────
echo ""
info "=== Node.js ==="
install_node() {
if ! command -v nvm &>/dev/null && [[ ! -s "$HOME/.nvm/nvm.sh" ]]; then
info "Installing nvm..."
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash
fi
export NVM_DIR="$HOME/.nvm"
[[ -s "$NVM_DIR/nvm.sh" ]] && source "$NVM_DIR/nvm.sh"
if command -v nvm &>/dev/null; then
info "Installing Node 24 LTS via nvm..."
nvm install 24
nvm use 24
nvm alias default 24
ok "Node $(node -v), npm $(npm -v)"
fi
}
if command -v node &>/dev/null; then
NODE_MAJOR=$(node -v | sed 's/v//' | cut -d. -f1)
if [[ "$NODE_MAJOR" -lt 18 ]]; then
warn "Node $(node -v) is too old. Installing newer version..."
install_node
else
ok "Node $(node -v) already installed"
fi
else
install_node
fi
# ── Environment ──────────────────────────────
echo ""
info "=== Configuration ==="
if [[ ! -f .env ]]; then
cp .env.example .env
ok "Created .env from .env.example"
fi
source_env() {
set -a; source .env 2>/dev/null; set +a
}
source_env
# ── Database ─────────────────────────────────
echo ""
info "=== Database ==="
if [[ -z "${DATABASE_URL:-}" || "$DATABASE_URL" == *dev.db* ]]; then
info "Configuring SQLite (default — zero config)..."
mkdir -p data
sed -i '/^DATABASE_URL/d' .env
echo 'DATABASE_URL="file:./data/lightdrive.db"' >> .env
ok "SQLite database: data/lightdrive.db"
else
ok "Using existing DATABASE_URL"
fi
# ── Public URL ───────────────────────────────
echo ""
info "=== Public URL (ORIGIN) ==="
if [[ -z "${ORIGIN:-}" ]]; then
DEFAULT_ORIGIN="http://$(hostname -I 2>/dev/null | awk '{print $1}'):3000"
[[ -z "$DEFAULT_ORIGIN" ]] && DEFAULT_ORIGIN="http://localhost:3000"
prompt_default ORIGIN_VAL "Public URL for CSRF protection" "$DEFAULT_ORIGIN"
echo "ORIGIN=$ORIGIN_VAL" >> .env
ok "ORIGIN set to $ORIGIN_VAL"
else
ok "ORIGIN already set to $ORIGIN"
fi
# ── Port ─────────────────────────────────────
echo ""
info "=== Port ==="
if [[ -z "${PORT:-}" ]]; then
prompt_default PORT_VAL "Port" "3000"
echo "PORT=$PORT_VAL" >> .env
PORT="$PORT_VAL"
ok "PORT set to $PORT_VAL"
else
ok "PORT already set to $PORT"
fi
echo ""
info "=== Body Size Limit ==="
if ! grep -q "^BODY_SIZE_LIMIT" .env 2>/dev/null; then
echo 'BODY_SIZE_LIMIT=52428800' >> .env
ok "BODY_SIZE_LIMIT set to 50 MB"
fi
echo ""
info "=== Signups ==="
if ! grep -q "^DISABLE_SIGNUP" .env 2>/dev/null; then
if confirm "Disable public signups?"; then
echo 'DISABLE_SIGNUP=true' >> .env
ok "Public signups disabled — create users via: npx tsx scripts/create-user.ts [name] [email] [password] [--admin]"
else
echo 'DISABLE_SIGNUP=false' >> .env
ok "Public signups enabled"
fi
fi
# ── npm Dependencies ─────────────────────────
echo ""
info "=== Install npm Dependencies ==="
npm install
ok "Dependencies installed"
# ── SvelteKit Sync ──────────────────────────
echo ""
info "=== SvelteKit Sync ==="
npx svelte-kit sync
ok "SvelteKit synced"
# ── Database Setup ──────────────────────────
echo ""
info "=== Database Setup ==="
npx prisma generate
if grep -q "^DATABASE_URL=\"\?file:" .env; then
DB_PATH=$(grep ^DATABASE_URL .env | sed 's/^DATABASE_URL=//' | tr -d '"' | sed 's/^file://')
mkdir -p "$(dirname "$DB_PATH")" 2>/dev/null || true
npx prisma db push --accept-data-loss
else
npx prisma db push --accept-data-loss
fi
ok "Database schema applied"
# ── Migrations ───────────────────────────────
info "=== Involved Users Migration ==="
npx tsx scripts/migrate-involved.ts 2>/dev/null || true
ok "Involved users migrated"
info "=== Avatar Colors Migration ==="
npx tsx scripts/migrate-avatar-colors.ts 2>/dev/null || true
ok "Avatar colors migrated"
info "=== Folder Sizes Migration ==="
npx tsx scripts/migrate-folder-sizes.ts 2>/dev/null || true
ok "Folder sizes migrated"
info "=== Avatar WebP Migration ==="
npx tsx scripts/migrate-avatars.ts 2>/dev/null || true
ok "Avatar WebP migration complete"
info "=== Preview Small Variant Migration ==="
npx tsx scripts/migrate-previews.ts 2>/dev/null || true
ok "Preview small variant migration complete"
# ── Build ────────────────────────────────────
echo ""
info "=== Build ==="
npm run build
ok "Build complete"
# ── Systemd Service ─────────────────────────
echo ""
info "=== Systemd Service ==="
SERVICE_NAME="lightdrive"
SERVICE_FILE="/etc/systemd/system/$SERVICE_NAME.service"
if systemctl is-active --quiet "$SERVICE_NAME" 2>/dev/null; then
info "Stopping running $SERVICE_NAME service..."
sudo systemctl stop "$SERVICE_NAME"
fi
if [[ -f "$SERVICE_FILE" ]]; then
warn "Service $SERVICE_NAME already exists"
if confirm "Recreate service file?"; then
sudo rm "$SERVICE_FILE"
else
warn "Skipping service setup"
fi
fi
if [[ ! -f "$SERVICE_FILE" ]]; then
NODE_BIN="$(command -v node)"
if [[ -f "$HOME/.nvm/nvm.sh" ]]; then
NVM_NODE="$HOME/.nvm/versions/node/$(cat "$HOME/.nvm/alias/default" 2>/dev/null || echo "default")/bin/node"
[[ -x "$NVM_NODE" ]] && NODE_BIN="$NVM_NODE"
fi
sudo tee "$SERVICE_FILE" > /dev/null <<UNIT
[Unit]
Description=LightDrive — Self-hosted file sharing
Documentation=https://github.com/lbirkert/lightdrive
After=network.target
[Service]
Type=exec
User=$USER
WorkingDirectory=$INSTALL_DIR
ExecStart=$NODE_BIN --import dotenv/config $INSTALL_DIR/build/index.js
Restart=on-failure
RestartSec=5
Environment=NODE_ENV=production
Environment=HOST=0.0.0.0
Environment=BODY_SIZE_LIMIT=52428800
[Install]
WantedBy=multi-user.target
UNIT
sudo systemctl daemon-reload
sudo systemctl enable "$SERVICE_NAME"
sudo systemctl restart "$SERVICE_NAME"
sleep 1
if sudo systemctl is-active --quiet "$SERVICE_NAME"; then
ok "Service $SERVICE_NAME created and running"
else
warn "Service failed to start — check: sudo journalctl -u $SERVICE_NAME -n 30"
fi
fi
# ── Firewall ─────────────────────────────────
echo ""
info "=== Firewall ==="
if command -v ufw &>/dev/null; then
if confirm "Open port $PORT in UFW?"; then
sudo ufw allow "$PORT/tcp"
ok "Port $PORT opened"
fi
elif command -v firewall-cmd &>/dev/null; then
if confirm "Open port $PORT in firewalld?"; then
sudo firewall-cmd --permanent --add-port="$PORT/tcp"
sudo firewall-cmd --reload
ok "Port $PORT opened"
fi
else
info "No firewall tool detected — ensure port $PORT is reachable"
fi
# ── Admin User ───────────────────────────────
if grep -q "^DISABLE_SIGNUP=true" .env 2>/dev/null; then
echo ""
if confirm "Create an admin user?"; then
echo ""
read -rp "Name: " ADMIN_NAME
read -rp "Email: " ADMIN_EMAIL
read -rsp "Password: " ADMIN_PASSWORD
echo ""
if [[ -n "$ADMIN_NAME" && -n "$ADMIN_EMAIL" && -n "$ADMIN_PASSWORD" ]]; then
npx tsx scripts/create-user.ts "$ADMIN_NAME" "$ADMIN_EMAIL" "$ADMIN_PASSWORD" --admin
else
warn "Skipping — all fields required."
fi
fi
fi
# ── Done ─────────────────────────────────────
echo ""
echo "────────────────────────────────────────────────────"
echo -e "${GREEN} LightDrive is running!${NC}"
echo ""
SERVICE_STATUS=$(sudo systemctl is-active "$SERVICE_NAME" 2>/dev/null || echo "unknown")
echo " Service status: $SERVICE_STATUS"
if command -v hostname &>/dev/null; then
IP=$(hostname -I | awk '{print $1}')
echo " Local URL: http://$IP:$PORT"
fi
echo ""
echo " Manage service:"
echo " sudo systemctl status $SERVICE_NAME"
echo " sudo journalctl -u $SERVICE_NAME -f"
echo ""
echo " Update:"
echo " cd $INSTALL_DIR && git pull && npm install && npm run build && sudo systemctl restart $SERVICE_NAME"
echo ""
echo " To change the port or ORIGIN, edit $INSTALL_DIR/.env"
echo " then restart: sudo systemctl restart $SERVICE_NAME"
echo "────────────────────────────────────────────────────"