- Update Nextcloud version to 31.0.5 - Add auto-start (lxc.start.auto) for boot persistence - Add memory limit cgroup configuration - Fix nginx /apps/ path for static assets (CSS, JS, SVG) - Add Storage tab with disk usage visualization - Add delete backup functionality - Add RPCD methods: uninstall, get_storage, delete_backup - Update ACL permissions for new methods - Rewrite README.md with LXC architecture docs Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1074 lines
26 KiB
Bash
1074 lines
26 KiB
Bash
#!/bin/sh
|
|
# SecuBox Nextcloud Platform Controller
|
|
# Copyright (C) 2025 CyberMind.fr
|
|
#
|
|
# Manages Nextcloud in Debian-based LXC container with MariaDB & Redis
|
|
|
|
CONFIG="nextcloud"
|
|
LXC_NAME="nextcloud"
|
|
NEXTCLOUD_VERSION="31.0.5"
|
|
|
|
# Paths
|
|
LXC_PATH="/srv/lxc"
|
|
LXC_ROOTFS="$LXC_PATH/$LXC_NAME/rootfs"
|
|
LXC_CONFIG="$LXC_PATH/$LXC_NAME/config"
|
|
DATA_PATH="/srv/nextcloud"
|
|
BACKUP_PATH="/srv/nextcloud/backups"
|
|
|
|
# Logging
|
|
log_info() { echo "[INFO] $*"; logger -t nextcloud "$*"; }
|
|
log_error() { echo "[ERROR] $*" >&2; logger -t nextcloud -p err "$*"; }
|
|
log_debug() { [ "$DEBUG" = "1" ] && echo "[DEBUG] $*"; }
|
|
|
|
# Helpers
|
|
require_root() {
|
|
[ "$(id -u)" -eq 0 ] || {
|
|
log_error "This command requires root privileges"
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
has_lxc() { command -v lxc-start >/dev/null 2>&1; }
|
|
|
|
ensure_dir() { [ -d "$1" ] || mkdir -p "$1"; }
|
|
|
|
uci_get() { uci -q get ${CONFIG}.$1; }
|
|
uci_set() { uci set ${CONFIG}.$1="$2" && uci commit ${CONFIG}; }
|
|
|
|
# Generate random password
|
|
gen_password() {
|
|
head -c 16 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c 16
|
|
}
|
|
|
|
# Load configuration
|
|
load_config() {
|
|
http_port="$(uci_get main.http_port)" || http_port="8080"
|
|
data_path="$(uci_get main.data_path)" || data_path="/srv/nextcloud"
|
|
domain="$(uci_get main.domain)" || domain="cloud.local"
|
|
admin_user="$(uci_get main.admin_user)" || admin_user="admin"
|
|
admin_password="$(uci_get main.admin_password)"
|
|
memory_limit="$(uci_get main.memory_limit)" || memory_limit="1G"
|
|
upload_max="$(uci_get main.upload_max)" || upload_max="512M"
|
|
trusted_proxies="$(uci_get main.trusted_proxies)" || trusted_proxies="127.0.0.1"
|
|
|
|
# Database settings
|
|
db_name="$(uci_get db.name)" || db_name="nextcloud"
|
|
db_user="$(uci_get db.user)" || db_user="nextcloud"
|
|
db_password="$(uci_get db.password)"
|
|
|
|
# Redis settings
|
|
redis_enabled="$(uci_get redis.enabled)" || redis_enabled="1"
|
|
redis_memory="$(uci_get redis.memory)" || redis_memory="128M"
|
|
|
|
# HAProxy/SSL settings
|
|
ssl_enabled="$(uci_get ssl.enabled)" || ssl_enabled="0"
|
|
ssl_domain="$(uci_get ssl.domain)"
|
|
ssl_acme="$(uci_get ssl.acme)" || ssl_acme="1"
|
|
|
|
# Backup settings
|
|
backup_enabled="$(uci_get backup.enabled)" || backup_enabled="1"
|
|
backup_keep="$(uci_get backup.keep)" || backup_keep="7"
|
|
|
|
DATA_PATH="$data_path"
|
|
BACKUP_PATH="$data_path/backups"
|
|
|
|
ensure_dir "$data_path"
|
|
ensure_dir "$data_path/data"
|
|
ensure_dir "$data_path/config"
|
|
ensure_dir "$BACKUP_PATH"
|
|
}
|
|
|
|
# Usage
|
|
usage() {
|
|
cat <<EOF
|
|
SecuBox Nextcloud Platform Controller
|
|
|
|
Usage: $(basename $0) <command> [options]
|
|
|
|
Commands:
|
|
install Create Debian LXC and install Nextcloud stack
|
|
uninstall Remove container (preserves data)
|
|
update Update Nextcloud to latest version
|
|
start Start Nextcloud service (via init)
|
|
stop Stop Nextcloud service (via init)
|
|
restart Restart Nextcloud service
|
|
status Show service status (JSON format)
|
|
logs [-f] Show container logs
|
|
shell Open shell in container
|
|
|
|
occ <cmd> Run Nextcloud OCC command
|
|
backup [name] Create backup of data and database
|
|
restore <name> Restore from backup
|
|
list-backups List available backups
|
|
|
|
ssl-enable <domain> Register with HAProxy for SSL
|
|
ssl-disable Remove HAProxy registration
|
|
|
|
service-run Start service (used by init)
|
|
service-stop Stop service (used by init)
|
|
|
|
Configuration:
|
|
/etc/config/nextcloud
|
|
|
|
Data directory:
|
|
/srv/nextcloud
|
|
|
|
EOF
|
|
}
|
|
|
|
# Check prerequisites
|
|
lxc_check_prereqs() {
|
|
if ! has_lxc; then
|
|
log_error "LXC not installed. Install with: opkg install lxc lxc-common"
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# Create LXC rootfs from Debian
|
|
lxc_create_rootfs() {
|
|
local rootfs="$LXC_ROOTFS"
|
|
local arch=$(uname -m)
|
|
|
|
log_info "Creating Debian 12 rootfs for Nextcloud..."
|
|
|
|
ensure_dir "$rootfs"
|
|
|
|
# Use Debian mini rootfs from LXC images
|
|
case "$arch" in
|
|
x86_64) deb_arch="amd64" ;;
|
|
aarch64) deb_arch="arm64" ;;
|
|
armv7l) deb_arch="armhf" ;;
|
|
*) log_error "Unsupported architecture: $arch"; return 1 ;;
|
|
esac
|
|
|
|
# Download Debian rootfs from LXC image server
|
|
local base_url="https://images.linuxcontainers.org/images/debian/bookworm/${deb_arch}/default"
|
|
local index_url="${base_url}/index.json"
|
|
local tmpdir="/tmp/debian-rootfs-$$"
|
|
mkdir -p "$tmpdir"
|
|
|
|
log_info "Fetching latest Debian 12 rootfs..."
|
|
local latest_date
|
|
# Parse HTML directory listing for latest date folder
|
|
latest_date=$(wget -q -O- "${base_url}/" 2>/dev/null | grep -oE "20[0-9]{6}_[0-9]{2}:[0-9]{2}" | tail -1)
|
|
|
|
if [ -z "$latest_date" ]; then
|
|
# Fallback: use today's date with common time
|
|
latest_date="$(date +%Y%m%d)_05:24"
|
|
log_info "Using fallback date: $latest_date"
|
|
fi
|
|
|
|
# URL encode the colon
|
|
local latest_encoded=$(echo "$latest_date" | sed 's/:/%3A/g')
|
|
local rootfs_url="${base_url}/${latest_encoded}/rootfs.tar.xz"
|
|
local tmpfile="$tmpdir/rootfs.tar.xz"
|
|
|
|
log_info "Downloading from: $rootfs_url"
|
|
wget -q -O "$tmpfile" "$rootfs_url" 2>&1 || {
|
|
log_error "Failed to download Debian rootfs from $rootfs_url"
|
|
rm -rf "$tmpdir"
|
|
return 1
|
|
}
|
|
|
|
# Verify download
|
|
if [ ! -s "$tmpfile" ]; then
|
|
log_error "Downloaded file is empty"
|
|
rm -rf "$tmpdir"
|
|
return 1
|
|
fi
|
|
|
|
log_info "Extracting rootfs..."
|
|
tar -xJf "$tmpfile" -C "$rootfs" || {
|
|
log_error "Failed to extract rootfs"
|
|
rm -rf "$tmpdir"
|
|
return 1
|
|
}
|
|
rm -rf "$tmpdir"
|
|
|
|
# Setup resolv.conf
|
|
cp /etc/resolv.conf "$rootfs/etc/resolv.conf" 2>/dev/null || \
|
|
echo "nameserver 1.1.1.1" > "$rootfs/etc/resolv.conf"
|
|
|
|
# Create required directories
|
|
mkdir -p "$rootfs/opt"
|
|
mkdir -p "$rootfs/run"
|
|
mkdir -p "$rootfs/var/www/nextcloud"
|
|
|
|
log_info "Rootfs created successfully"
|
|
return 0
|
|
}
|
|
|
|
# Mount filesystems for chroot
|
|
mount_chroot_fs() {
|
|
local rootfs="$1"
|
|
mount --bind /dev "$rootfs/dev" 2>/dev/null || true
|
|
mount --bind /dev/pts "$rootfs/dev/pts" 2>/dev/null || true
|
|
mount -t proc proc "$rootfs/proc" 2>/dev/null || true
|
|
mount -t sysfs sysfs "$rootfs/sys" 2>/dev/null || true
|
|
}
|
|
|
|
# Unmount chroot filesystems
|
|
umount_chroot_fs() {
|
|
local rootfs="$1"
|
|
umount "$rootfs/sys" 2>/dev/null || true
|
|
umount "$rootfs/proc" 2>/dev/null || true
|
|
umount "$rootfs/dev/pts" 2>/dev/null || true
|
|
umount "$rootfs/dev" 2>/dev/null || true
|
|
}
|
|
|
|
# Install packages inside container
|
|
install_container_packages() {
|
|
local rootfs="$LXC_ROOTFS"
|
|
|
|
log_info "Installing Nextcloud stack packages..."
|
|
|
|
# Create install script
|
|
cat > "$rootfs/tmp/install-deps.sh" << 'SCRIPT'
|
|
#!/bin/bash
|
|
set -e
|
|
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
|
|
# Install gpg first for apt verification
|
|
apt-get update || true
|
|
apt-get install -y --no-install-recommends gnupg gpgv ca-certificates || true
|
|
|
|
# Update and install packages
|
|
apt-get update
|
|
apt-get install -y --no-install-recommends \
|
|
nginx \
|
|
mariadb-server \
|
|
redis-server \
|
|
php-fpm \
|
|
php-gd \
|
|
php-mysql \
|
|
php-curl \
|
|
php-mbstring \
|
|
php-intl \
|
|
php-gmp \
|
|
php-bcmath \
|
|
php-xml \
|
|
php-zip \
|
|
php-imagick \
|
|
php-redis \
|
|
php-apcu \
|
|
php-ldap \
|
|
unzip \
|
|
curl \
|
|
wget \
|
|
cron
|
|
|
|
# Clean up
|
|
apt-get clean
|
|
rm -rf /var/lib/apt/lists/*
|
|
|
|
touch /tmp/.deps-installed
|
|
SCRIPT
|
|
chmod +x "$rootfs/tmp/install-deps.sh"
|
|
|
|
# Mount filesystems for chroot
|
|
mount_chroot_fs "$rootfs"
|
|
|
|
# Run via chroot (container not started yet)
|
|
log_info "Running package installation via chroot..."
|
|
chroot "$rootfs" /tmp/install-deps.sh
|
|
local result=$?
|
|
|
|
# Cleanup mounts
|
|
umount_chroot_fs "$rootfs"
|
|
|
|
if [ $result -ne 0 ]; then
|
|
log_error "Failed to install packages"
|
|
return 1
|
|
fi
|
|
|
|
rm -f "$rootfs/tmp/install-deps.sh"
|
|
log_info "Container packages installed"
|
|
return 0
|
|
}
|
|
|
|
# Download and install Nextcloud
|
|
install_nextcloud() {
|
|
local rootfs="$LXC_ROOTFS"
|
|
local nc_dir="$rootfs/var/www/nextcloud"
|
|
|
|
log_info "Downloading Nextcloud ${NEXTCLOUD_VERSION}..."
|
|
|
|
local nc_url="https://download.nextcloud.com/server/releases/nextcloud-${NEXTCLOUD_VERSION}.zip"
|
|
local tmpfile="/tmp/nextcloud.zip"
|
|
|
|
wget -q --show-progress -O "$tmpfile" "$nc_url" || {
|
|
log_error "Failed to download Nextcloud"
|
|
return 1
|
|
}
|
|
|
|
log_info "Extracting Nextcloud..."
|
|
unzip -q "$tmpfile" -d "$rootfs/var/www/" || {
|
|
log_error "Failed to extract Nextcloud"
|
|
rm -f "$tmpfile"
|
|
return 1
|
|
}
|
|
rm -f "$tmpfile"
|
|
|
|
# Set ownership (will be fixed inside container)
|
|
chown -R 33:33 "$nc_dir" 2>/dev/null || true
|
|
|
|
log_info "Nextcloud installed"
|
|
return 0
|
|
}
|
|
|
|
# Create startup script
|
|
create_startup_script() {
|
|
local rootfs="$LXC_ROOTFS"
|
|
|
|
cat > "$rootfs/opt/start-nextcloud.sh" << 'STARTUP'
|
|
#!/bin/bash
|
|
set -e
|
|
|
|
export PATH="/usr/sbin:/usr/bin:/sbin:/bin"
|
|
|
|
# Start MariaDB
|
|
echo "Starting MariaDB..."
|
|
service mariadb start
|
|
sleep 2
|
|
|
|
# Start Redis
|
|
echo "Starting Redis..."
|
|
service redis-server start
|
|
|
|
# Configure PHP-FPM pool
|
|
PHP_VERSION=$(ls /etc/php/ | head -1)
|
|
PHP_FPM_POOL="/etc/php/${PHP_VERSION}/fpm/pool.d/www.conf"
|
|
|
|
# Increase PHP limits
|
|
sed -i "s/upload_max_filesize = .*/upload_max_filesize = ${NEXTCLOUD_UPLOAD_MAX:-512M}/" /etc/php/${PHP_VERSION}/fpm/php.ini
|
|
sed -i "s/post_max_size = .*/post_max_size = ${NEXTCLOUD_UPLOAD_MAX:-512M}/" /etc/php/${PHP_VERSION}/fpm/php.ini
|
|
sed -i "s/memory_limit = .*/memory_limit = 512M/" /etc/php/${PHP_VERSION}/fpm/php.ini
|
|
sed -i "s/max_execution_time = .*/max_execution_time = 3600/" /etc/php/${PHP_VERSION}/fpm/php.ini
|
|
|
|
# Enable opcache
|
|
echo "opcache.enable=1" >> /etc/php/${PHP_VERSION}/fpm/php.ini
|
|
echo "opcache.memory_consumption=128" >> /etc/php/${PHP_VERSION}/fpm/php.ini
|
|
echo "opcache.interned_strings_buffer=16" >> /etc/php/${PHP_VERSION}/fpm/php.ini
|
|
echo "opcache.max_accelerated_files=10000" >> /etc/php/${PHP_VERSION}/fpm/php.ini
|
|
|
|
# Start PHP-FPM
|
|
echo "Starting PHP-FPM..."
|
|
service php${PHP_VERSION}-fpm start
|
|
|
|
# Configure Nginx
|
|
cat > /etc/nginx/sites-available/nextcloud << NGINX
|
|
server {
|
|
listen ${NEXTCLOUD_HTTP_PORT:-8080};
|
|
server_name _;
|
|
|
|
root /var/www/nextcloud;
|
|
index index.php index.html;
|
|
|
|
client_max_body_size 512M;
|
|
fastcgi_buffers 64 4K;
|
|
|
|
gzip on;
|
|
gzip_vary on;
|
|
gzip_comp_level 4;
|
|
gzip_min_length 256;
|
|
gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
|
|
gzip_types application/atom+xml text/javascript application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/wasm application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;
|
|
|
|
add_header Referrer-Policy "no-referrer" always;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
add_header X-Permitted-Cross-Domain-Policies "none" always;
|
|
add_header X-Robots-Tag "noindex, nofollow" always;
|
|
add_header X-XSS-Protection "1; mode=block" always;
|
|
|
|
location = /robots.txt {
|
|
allow all;
|
|
log_not_found off;
|
|
access_log off;
|
|
}
|
|
|
|
location ^~ /.well-known {
|
|
location = /.well-known/carddav { return 301 /remote.php/dav/; }
|
|
location = /.well-known/caldav { return 301 /remote.php/dav/; }
|
|
location /.well-known/acme-challenge { try_files $uri $uri/ =404; }
|
|
location /.well-known/pki-validation { try_files $uri $uri/ =404; }
|
|
return 301 /index.php$request_uri;
|
|
}
|
|
|
|
location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/)(?!.*\.(css|js|svg|png|gif|ico|woff2?)$) { return 404; }
|
|
location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console) { return 404; }
|
|
|
|
# Serve app static files correctly
|
|
location ~ ^/apps/[^/]+/(?:css|js|img|l10n)/.+$ {
|
|
try_files \$uri /index.php\$request_uri;
|
|
add_header Cache-Control "public, max-age=15778463, immutable";
|
|
access_log off;
|
|
}
|
|
|
|
location ~ \.php(?:$|/) {
|
|
rewrite ^/(?!index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|ocs-provider\/.+|.+\/richdocumentscode(_arm64)?\/proxy) /index.php$request_uri;
|
|
|
|
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
|
|
set $path_info $fastcgi_path_info;
|
|
|
|
try_files $fastcgi_script_name =404;
|
|
|
|
include fastcgi_params;
|
|
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
|
fastcgi_param PATH_INFO $path_info;
|
|
|
|
fastcgi_param modHeadersAvailable true;
|
|
fastcgi_param front_controller_active true;
|
|
fastcgi_pass unix:/run/php/php${PHP_VERSION}-fpm.sock;
|
|
|
|
fastcgi_intercept_errors on;
|
|
fastcgi_request_buffering off;
|
|
fastcgi_max_temp_file_size 0;
|
|
}
|
|
|
|
location ~ \.(?:css|js|mjs|svg|gif|png|jpg|ico|wasm|tflite|map|ogg|flac)$ {
|
|
try_files $uri /index.php$request_uri;
|
|
add_header Cache-Control "public, max-age=15778463, immutable";
|
|
access_log off;
|
|
}
|
|
|
|
location ~ \.woff2?$ {
|
|
try_files $uri /index.php$request_uri;
|
|
expires 7d;
|
|
access_log off;
|
|
}
|
|
|
|
location /remote {
|
|
return 301 /remote.php$request_uri;
|
|
}
|
|
|
|
location / {
|
|
rewrite ^ /index.php\$request_uri last;
|
|
}
|
|
}
|
|
NGINX
|
|
|
|
rm -f /etc/nginx/sites-enabled/default
|
|
ln -sf /etc/nginx/sites-available/nextcloud /etc/nginx/sites-enabled/nextcloud
|
|
|
|
# Configure cron for background jobs
|
|
echo "*/5 * * * * www-data php -f /var/www/nextcloud/cron.php" > /etc/cron.d/nextcloud
|
|
chmod 644 /etc/cron.d/nextcloud
|
|
service cron start
|
|
|
|
# Start Nginx (foreground for procd)
|
|
echo "Starting Nginx..."
|
|
exec nginx -g 'daemon off;'
|
|
STARTUP
|
|
chmod +x "$rootfs/opt/start-nextcloud.sh"
|
|
}
|
|
|
|
# Setup database
|
|
setup_database() {
|
|
load_config
|
|
|
|
# Generate password if not set
|
|
if [ -z "$db_password" ]; then
|
|
db_password=$(gen_password)
|
|
uci_set db.password "$db_password"
|
|
fi
|
|
|
|
log_info "Setting up MariaDB database..."
|
|
|
|
# Create setup script
|
|
cat > "$LXC_ROOTFS/tmp/setup-db.sh" << DBSCRIPT
|
|
#!/bin/bash
|
|
service mariadb start
|
|
sleep 2
|
|
|
|
mysql -u root << EOF
|
|
CREATE DATABASE IF NOT EXISTS ${db_name} CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
|
CREATE USER IF NOT EXISTS '${db_user}'@'localhost' IDENTIFIED BY '${db_password}';
|
|
GRANT ALL PRIVILEGES ON ${db_name}.* TO '${db_user}'@'localhost';
|
|
FLUSH PRIVILEGES;
|
|
EOF
|
|
DBSCRIPT
|
|
chmod +x "$LXC_ROOTFS/tmp/setup-db.sh"
|
|
|
|
# Run via chroot
|
|
chroot "$LXC_ROOTFS" /tmp/setup-db.sh || {
|
|
log_error "Failed to setup database"
|
|
return 1
|
|
}
|
|
|
|
rm -f "$LXC_ROOTFS/tmp/setup-db.sh"
|
|
log_info "Database setup complete"
|
|
}
|
|
|
|
# Configure Redis
|
|
setup_redis() {
|
|
load_config
|
|
local rootfs="$LXC_ROOTFS"
|
|
|
|
log_info "Configuring Redis..."
|
|
|
|
# Get memory in MB
|
|
local redis_mb="${redis_memory%M}"
|
|
redis_mb="${redis_mb%m}"
|
|
|
|
# Configure Redis
|
|
cat > "$rootfs/etc/redis/redis.conf" << EOF
|
|
bind 127.0.0.1
|
|
port 6379
|
|
daemonize yes
|
|
pidfile /run/redis/redis-server.pid
|
|
loglevel notice
|
|
logfile /var/log/redis/redis-server.log
|
|
databases 16
|
|
maxmemory ${redis_mb}mb
|
|
maxmemory-policy allkeys-lru
|
|
EOF
|
|
|
|
log_info "Redis configured"
|
|
}
|
|
|
|
# Create LXC config
|
|
lxc_create_config() {
|
|
load_config
|
|
|
|
ensure_dir "$(dirname "$LXC_CONFIG")"
|
|
|
|
# Convert memory limit to bytes
|
|
local mem_bytes
|
|
case "$memory_limit" in
|
|
*G|*g) mem_bytes=$((${memory_limit%[Gg]} * 1024 * 1024 * 1024)) ;;
|
|
*M|*m) mem_bytes=$((${memory_limit%[Mm]} * 1024 * 1024)) ;;
|
|
*K|*k) mem_bytes=$((${memory_limit%[Kk]} * 1024)) ;;
|
|
*) mem_bytes="$memory_limit" ;;
|
|
esac
|
|
|
|
cat > "$LXC_CONFIG" << EOF
|
|
# Nextcloud Platform LXC Configuration
|
|
lxc.uts.name = $LXC_NAME
|
|
lxc.rootfs.path = dir:$LXC_ROOTFS
|
|
lxc.arch = $(uname -m)
|
|
|
|
# Auto-start on boot
|
|
lxc.start.auto = 1
|
|
lxc.start.delay = 5
|
|
|
|
# Network: use host network
|
|
lxc.net.0.type = none
|
|
|
|
# Mount points
|
|
lxc.mount.auto = proc:mixed sys:ro
|
|
lxc.mount.entry = $data_path/data var/www/nextcloud/data none bind,create=dir 0 0
|
|
lxc.mount.entry = $data_path/config var/www/nextcloud/config none bind,create=dir 0 0
|
|
|
|
# Environment
|
|
lxc.environment = NEXTCLOUD_DOMAIN=$domain
|
|
lxc.environment = NEXTCLOUD_HTTP_PORT=$http_port
|
|
lxc.environment = NEXTCLOUD_UPLOAD_MAX=$upload_max
|
|
lxc.environment = NEXTCLOUD_TRUSTED_PROXIES=$trusted_proxies
|
|
|
|
# Memory limit
|
|
lxc.cgroup2.memory.max = $mem_bytes
|
|
|
|
# Security
|
|
lxc.cap.drop = sys_admin sys_module mac_admin mac_override sys_time sys_rawio
|
|
|
|
# Init command
|
|
lxc.init.cmd = /opt/start-nextcloud.sh
|
|
EOF
|
|
|
|
log_info "LXC config created"
|
|
}
|
|
|
|
# Container control
|
|
lxc_running() {
|
|
lxc-info -n "$LXC_NAME" -s 2>/dev/null | grep -q "RUNNING"
|
|
}
|
|
|
|
lxc_exists() {
|
|
[ -f "$LXC_CONFIG" ] && [ -d "$LXC_ROOTFS" ]
|
|
}
|
|
|
|
lxc_stop() {
|
|
if lxc_running; then
|
|
log_info "Stopping Nextcloud container..."
|
|
lxc-stop -n "$LXC_NAME" -k 2>/dev/null || true
|
|
sleep 2
|
|
fi
|
|
}
|
|
|
|
lxc_run() {
|
|
load_config
|
|
lxc_stop
|
|
|
|
if ! lxc_exists; then
|
|
log_error "Container not installed. Run: nextcloudctl install"
|
|
return 1
|
|
fi
|
|
|
|
# Regenerate config in case settings changed
|
|
lxc_create_config
|
|
|
|
log_info "Starting Nextcloud container..."
|
|
exec lxc-start -n "$LXC_NAME" -F -f "$LXC_CONFIG"
|
|
}
|
|
|
|
# Execute command in container
|
|
lxc_exec() {
|
|
if ! lxc_running; then
|
|
log_error "Container not running"
|
|
return 1
|
|
fi
|
|
lxc-attach -n "$LXC_NAME" -- "$@"
|
|
}
|
|
|
|
# Run OCC command
|
|
lxc_occ() {
|
|
if ! lxc_running; then
|
|
log_error "Container not running"
|
|
return 1
|
|
fi
|
|
lxc-attach -n "$LXC_NAME" -- su -s /bin/bash www-data -c "php /var/www/nextcloud/occ $*"
|
|
}
|
|
|
|
# Commands
|
|
cmd_install() {
|
|
require_root
|
|
load_config
|
|
|
|
log_info "Installing Nextcloud Platform..."
|
|
|
|
lxc_check_prereqs || exit 1
|
|
|
|
# Generate admin password if not set
|
|
if [ -z "$admin_password" ]; then
|
|
admin_password=$(gen_password)
|
|
uci_set main.admin_password "$admin_password"
|
|
log_info "Generated admin password: $admin_password"
|
|
fi
|
|
|
|
# Create container
|
|
if ! lxc_exists; then
|
|
lxc_create_rootfs || exit 1
|
|
fi
|
|
|
|
# Install packages
|
|
install_container_packages || exit 1
|
|
|
|
# Download Nextcloud
|
|
if [ ! -f "$LXC_ROOTFS/var/www/nextcloud/occ" ]; then
|
|
install_nextcloud || exit 1
|
|
fi
|
|
|
|
# Setup services
|
|
setup_database || exit 1
|
|
setup_redis || exit 1
|
|
|
|
# Create startup script
|
|
create_startup_script
|
|
|
|
# Create LXC config
|
|
lxc_create_config || exit 1
|
|
|
|
# Enable service
|
|
uci_set main.enabled '1'
|
|
/etc/init.d/nextcloud enable 2>/dev/null || true
|
|
|
|
log_info ""
|
|
log_info "Installation complete!"
|
|
log_info ""
|
|
log_info "Start with: /etc/init.d/nextcloud start"
|
|
log_info "Web interface: http://<router-ip>:$http_port"
|
|
log_info ""
|
|
log_info "After first start, run Nextcloud setup:"
|
|
log_info " nextcloudctl occ maintenance:install \\"
|
|
log_info " --database mysql --database-name $db_name \\"
|
|
log_info " --database-user $db_user --database-pass '$db_password' \\"
|
|
log_info " --admin-user $admin_user --admin-pass '$admin_password'"
|
|
log_info ""
|
|
}
|
|
|
|
cmd_uninstall() {
|
|
require_root
|
|
|
|
log_info "Uninstalling Nextcloud Platform..."
|
|
|
|
# Stop service
|
|
/etc/init.d/nextcloud stop 2>/dev/null || true
|
|
/etc/init.d/nextcloud disable 2>/dev/null || true
|
|
|
|
lxc_stop
|
|
|
|
# Remove container (keep data)
|
|
if [ -d "$LXC_PATH/$LXC_NAME" ]; then
|
|
rm -rf "$LXC_PATH/$LXC_NAME"
|
|
log_info "Container removed"
|
|
fi
|
|
|
|
uci_set main.enabled '0'
|
|
|
|
log_info "Nextcloud Platform uninstalled"
|
|
log_info "Data preserved in: $(uci_get main.data_path)"
|
|
}
|
|
|
|
cmd_update() {
|
|
require_root
|
|
load_config
|
|
|
|
if ! lxc_exists; then
|
|
log_error "Container not installed. Run: nextcloudctl install"
|
|
return 1
|
|
fi
|
|
|
|
log_info "Updating Nextcloud..."
|
|
|
|
# Enable maintenance mode
|
|
if lxc_running; then
|
|
lxc_occ maintenance:mode --on
|
|
fi
|
|
|
|
# Download new Nextcloud
|
|
install_nextcloud || exit 1
|
|
|
|
# Run upgrade
|
|
if lxc_running; then
|
|
lxc_occ upgrade
|
|
lxc_occ maintenance:mode --off
|
|
fi
|
|
|
|
log_info "Update complete"
|
|
}
|
|
|
|
cmd_status() {
|
|
load_config
|
|
|
|
local enabled="$(uci_get main.enabled)"
|
|
local running="false"
|
|
local installed="false"
|
|
local version=""
|
|
local user_count=0
|
|
local disk_used="0"
|
|
|
|
if lxc_exists; then
|
|
installed="true"
|
|
fi
|
|
|
|
if lxc_running; then
|
|
running="true"
|
|
|
|
# Get Nextcloud version
|
|
version=$(lxc_occ -V 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' || echo "")
|
|
|
|
# Get user count
|
|
user_count=$(lxc_occ user:list --output=json 2>/dev/null | jsonfilter -e '@[*]' 2>/dev/null | wc -l || echo 0)
|
|
fi
|
|
|
|
# Get disk usage
|
|
if [ -d "$data_path/data" ]; then
|
|
disk_used=$(du -sh "$data_path/data" 2>/dev/null | awk '{print $1}' || echo "0")
|
|
fi
|
|
|
|
# Get LAN IP
|
|
local lan_ip
|
|
lan_ip=$(uci -q get network.lan.ipaddr || echo "192.168.1.1")
|
|
|
|
cat << EOF
|
|
{
|
|
"enabled": $([ "$enabled" = "1" ] && echo "true" || echo "false"),
|
|
"running": $running,
|
|
"installed": $installed,
|
|
"version": "$version",
|
|
"http_port": $http_port,
|
|
"data_path": "$data_path",
|
|
"memory_limit": "$memory_limit",
|
|
"domain": "$domain",
|
|
"user_count": $user_count,
|
|
"disk_used": "$disk_used",
|
|
"http_url": "http://${lan_ip}:${http_port}",
|
|
"ssl_enabled": $([ "$ssl_enabled" = "1" ] && echo "true" || echo "false"),
|
|
"ssl_domain": "$ssl_domain",
|
|
"redis_enabled": $([ "$redis_enabled" = "1" ] && echo "true" || echo "false"),
|
|
"container_name": "$LXC_NAME"
|
|
}
|
|
EOF
|
|
}
|
|
|
|
cmd_logs() {
|
|
load_config
|
|
|
|
if lxc_running; then
|
|
if [ "$1" = "-f" ]; then
|
|
lxc_exec tail -f /var/log/nginx/error.log /var/www/nextcloud/data/nextcloud.log 2>/dev/null
|
|
else
|
|
echo "=== Nginx Error Log ==="
|
|
lxc_exec tail -50 /var/log/nginx/error.log 2>/dev/null || echo "No logs"
|
|
echo ""
|
|
echo "=== Nextcloud Log ==="
|
|
lxc_exec tail -50 /var/www/nextcloud/data/nextcloud.log 2>/dev/null || echo "No logs"
|
|
fi
|
|
else
|
|
echo "Container not running"
|
|
fi
|
|
}
|
|
|
|
cmd_shell() {
|
|
require_root
|
|
|
|
if ! lxc_running; then
|
|
log_error "Container not running"
|
|
exit 1
|
|
fi
|
|
|
|
lxc-attach -n "$LXC_NAME" -- /bin/bash
|
|
}
|
|
|
|
cmd_occ() {
|
|
require_root
|
|
lxc_occ "$@"
|
|
}
|
|
|
|
cmd_backup() {
|
|
require_root
|
|
load_config
|
|
|
|
local name="${1:-$(date +%Y%m%d-%H%M%S)}"
|
|
local backup_dir="$BACKUP_PATH"
|
|
|
|
ensure_dir "$backup_dir"
|
|
|
|
log_info "Creating backup: $name"
|
|
|
|
# Enable maintenance mode if running
|
|
local was_running=0
|
|
if lxc_running; then
|
|
was_running=1
|
|
lxc_occ maintenance:mode --on
|
|
fi
|
|
|
|
# Dump database
|
|
log_info "Dumping database..."
|
|
if lxc_running; then
|
|
lxc_exec mysqldump -u "$db_user" -p"$db_password" "$db_name" > "$backup_dir/$name-db.sql" || {
|
|
log_error "Database dump failed"
|
|
[ $was_running -eq 1 ] && lxc_occ maintenance:mode --off
|
|
return 1
|
|
}
|
|
fi
|
|
|
|
# Backup data and config
|
|
log_info "Backing up data..."
|
|
tar -czf "$backup_dir/$name-data.tar.gz" \
|
|
-C "$data_path" data config 2>/dev/null || {
|
|
log_error "Data backup failed"
|
|
[ $was_running -eq 1 ] && lxc_occ maintenance:mode --off
|
|
return 1
|
|
}
|
|
|
|
# Disable maintenance mode
|
|
if [ $was_running -eq 1 ]; then
|
|
lxc_occ maintenance:mode --off
|
|
fi
|
|
|
|
local size=$(ls -lh "$backup_dir/$name-data.tar.gz" 2>/dev/null | awk '{print $5}')
|
|
log_info "Backup created: $backup_dir/$name-* ($size)"
|
|
|
|
# Cleanup old backups
|
|
if [ "$backup_keep" -gt 0 ]; then
|
|
ls -t "$backup_dir"/*-db.sql 2>/dev/null | tail -n +$((backup_keep + 1)) | while read f; do
|
|
local base=$(basename "$f" -db.sql)
|
|
rm -f "$backup_dir/$base-db.sql" "$backup_dir/$base-data.tar.gz"
|
|
log_info "Removed old backup: $base"
|
|
done
|
|
fi
|
|
|
|
echo "$name"
|
|
}
|
|
|
|
cmd_restore() {
|
|
require_root
|
|
load_config
|
|
|
|
local name="$1"
|
|
|
|
if [ -z "$name" ]; then
|
|
log_error "Usage: nextcloudctl restore <backup-name>"
|
|
log_error "Available backups:"
|
|
cmd_list_backups
|
|
return 1
|
|
fi
|
|
|
|
local backup_dir="$BACKUP_PATH"
|
|
|
|
if [ ! -f "$backup_dir/$name-db.sql" ]; then
|
|
log_error "Backup not found: $name"
|
|
return 1
|
|
fi
|
|
|
|
log_info "Restoring from backup: $name"
|
|
|
|
# Enable maintenance mode
|
|
local was_running=0
|
|
if lxc_running; then
|
|
was_running=1
|
|
lxc_occ maintenance:mode --on
|
|
fi
|
|
|
|
# Restore database
|
|
log_info "Restoring database..."
|
|
if lxc_running; then
|
|
lxc_exec mysql -u "$db_user" -p"$db_password" "$db_name" < "$backup_dir/$name-db.sql" || {
|
|
log_error "Database restore failed"
|
|
return 1
|
|
}
|
|
fi
|
|
|
|
# Restore data
|
|
log_info "Restoring data..."
|
|
tar -xzf "$backup_dir/$name-data.tar.gz" -C "$data_path" || {
|
|
log_error "Data restore failed"
|
|
return 1
|
|
}
|
|
|
|
# Fix permissions
|
|
if lxc_running; then
|
|
lxc_exec chown -R www-data:www-data /var/www/nextcloud
|
|
lxc_occ maintenance:mode --off
|
|
fi
|
|
|
|
log_info "Restore complete"
|
|
}
|
|
|
|
cmd_list_backups() {
|
|
load_config
|
|
|
|
local backup_dir="$BACKUP_PATH"
|
|
|
|
if [ ! -d "$backup_dir" ]; then
|
|
echo "No backups found"
|
|
return
|
|
fi
|
|
|
|
echo "Available backups:"
|
|
ls -1 "$backup_dir"/*-db.sql 2>/dev/null | while read f; do
|
|
local name=$(basename "$f" -db.sql)
|
|
local data_file="$backup_dir/$name-data.tar.gz"
|
|
local size="N/A"
|
|
local date="N/A"
|
|
|
|
if [ -f "$data_file" ]; then
|
|
size=$(ls -lh "$data_file" | awk '{print $5}')
|
|
fi
|
|
date=$(stat -c '%y' "$f" 2>/dev/null | cut -d. -f1)
|
|
|
|
echo " $name ($size) $date"
|
|
done
|
|
}
|
|
|
|
cmd_ssl_enable() {
|
|
require_root
|
|
load_config
|
|
|
|
local domain="$1"
|
|
|
|
if [ -z "$domain" ]; then
|
|
log_error "Usage: nextcloudctl ssl-enable <domain>"
|
|
return 1
|
|
fi
|
|
|
|
log_info "Enabling SSL for $domain via HAProxy..."
|
|
|
|
# Check if HAProxy is available
|
|
if [ ! -x /usr/sbin/haproxyctl ]; then
|
|
log_error "HAProxy not installed. Install secubox-app-haproxy first."
|
|
return 1
|
|
fi
|
|
|
|
# Add vhost to HAProxy
|
|
local vhost_name="nextcloud_${domain//\./_}"
|
|
|
|
uci add haproxy vhost
|
|
uci set haproxy.@vhost[-1].name="$vhost_name"
|
|
uci set haproxy.@vhost[-1].domain="$domain"
|
|
uci set haproxy.@vhost[-1].backend="nextcloud_backend"
|
|
uci set haproxy.@vhost[-1].ssl='1'
|
|
uci set haproxy.@vhost[-1].acme='1'
|
|
uci set haproxy.@vhost[-1].enabled='1'
|
|
|
|
# Add backend
|
|
uci add haproxy backend
|
|
uci set haproxy.@backend[-1].name="nextcloud_backend"
|
|
uci set haproxy.@backend[-1].mode='http'
|
|
|
|
# Add server
|
|
uci add haproxy server
|
|
uci set haproxy.@server[-1].backend="nextcloud_backend"
|
|
uci set haproxy.@server[-1].address="127.0.0.1"
|
|
uci set haproxy.@server[-1].port="$http_port"
|
|
|
|
uci commit haproxy
|
|
|
|
# Update Nextcloud config
|
|
uci_set ssl.enabled '1'
|
|
uci_set ssl.domain "$domain"
|
|
|
|
# Reload HAProxy
|
|
/etc/init.d/haproxy reload 2>/dev/null || true
|
|
|
|
# Add trusted domain in Nextcloud
|
|
if lxc_running; then
|
|
lxc_occ config:system:set trusted_domains 1 --value="$domain"
|
|
lxc_occ config:system:set overwriteprotocol --value="https"
|
|
fi
|
|
|
|
log_info "SSL enabled for $domain"
|
|
log_info "Access Nextcloud at: https://$domain"
|
|
}
|
|
|
|
cmd_ssl_disable() {
|
|
require_root
|
|
load_config
|
|
|
|
log_info "Disabling SSL..."
|
|
|
|
# Remove HAProxy config (simplified - just disable)
|
|
uci_set ssl.enabled '0'
|
|
|
|
log_info "SSL disabled"
|
|
}
|
|
|
|
cmd_service_run() {
|
|
require_root
|
|
load_config
|
|
|
|
lxc_check_prereqs || exit 1
|
|
lxc_run
|
|
}
|
|
|
|
cmd_service_stop() {
|
|
require_root
|
|
lxc_stop
|
|
}
|
|
|
|
# Main
|
|
case "${1:-}" in
|
|
install) shift; cmd_install "$@" ;;
|
|
uninstall) shift; cmd_uninstall "$@" ;;
|
|
update) shift; cmd_update "$@" ;;
|
|
start) /etc/init.d/nextcloud start ;;
|
|
stop) /etc/init.d/nextcloud stop ;;
|
|
restart) /etc/init.d/nextcloud restart ;;
|
|
status) shift; cmd_status "$@" ;;
|
|
logs) shift; cmd_logs "$@" ;;
|
|
shell) shift; cmd_shell "$@" ;;
|
|
occ) shift; cmd_occ "$@" ;;
|
|
backup) shift; cmd_backup "$@" ;;
|
|
restore) shift; cmd_restore "$@" ;;
|
|
list-backups) shift; cmd_list_backups "$@" ;;
|
|
ssl-enable) shift; cmd_ssl_enable "$@" ;;
|
|
ssl-disable) shift; cmd_ssl_disable "$@" ;;
|
|
service-run) shift; cmd_service_run "$@" ;;
|
|
service-stop) shift; cmd_service_stop "$@" ;;
|
|
*) usage ;;
|
|
esac
|