secubox-openwrt/package/secubox/secubox-app-nextcloud/files/usr/sbin/nextcloudctl
CyberMind-FR 0c55ef6ec1 feat(nextcloud): Enhance integration with WAF, backups, mail, sync URLs
- WAF-safe SSL: Route through mitmproxy_inspector, auto-add routes
- Scheduled backups: setup-backup-cron with hourly/daily/weekly support
- Email/SMTP: setup-mail command for outbound notifications
- CalDAV/CardDAV: connections command shows sync URLs for all clients
- New RPCD methods: get_connections, setup_mail, setup_backup_cron
- ACL updated with new method permissions

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-01 09:07:19 +01:00

1278 lines
33 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 (via WAF)
ssl-disable Remove HAProxy registration
setup-mail <args> Configure outbound email (SMTP)
setup-backup-cron Setup scheduled backup cron job
connections Show CalDAV/CardDAV/WebDAV URLs
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 + WAF..."
# Check if HAProxy is available
if [ ! -x /usr/sbin/haproxyctl ]; then
log_error "HAProxy not installed. Install secubox-app-haproxy first."
return 1
fi
# Use haproxyctl vhost add - routes through mitmproxy_inspector by default
log_info "Adding vhost via haproxyctl..."
if ! /usr/sbin/haproxyctl vhost add "$domain" --acme; then
log_error "Failed to add HAProxy vhost"
return 1
fi
# Add route to mitmproxy for WAF inspection
# This ensures traffic is inspected before reaching Nextcloud
local routes_file="/srv/mitmproxy/haproxy-routes.json"
local routes_file_in="/srv/mitmproxy-in/haproxy-routes.json"
if [ -f "$routes_file" ]; then
log_info "Adding mitmproxy route for WAF inspection..."
# Use jsonfilter to check if route exists, then add if not
local existing
existing=$(jsonfilter -i "$routes_file" -e "@[\"$domain\"]" 2>/dev/null || echo "")
if [ -z "$existing" ]; then
# Add route: domain -> [127.0.0.1, port]
local tmpfile="/tmp/routes-$$.json"
# Read existing routes and add new one
if command -v python3 >/dev/null 2>&1; then
python3 -c "
import json
with open('$routes_file', 'r') as f:
routes = json.load(f)
routes['$domain'] = ['127.0.0.1', $http_port]
with open('$tmpfile', 'w') as f:
json.dump(routes, f, indent=2)
"
mv "$tmpfile" "$routes_file"
# Also update the in-container copy
[ -f "$routes_file_in" ] && cp "$routes_file" "$routes_file_in"
log_info "Added WAF route: $domain -> 127.0.0.1:$http_port"
else
log_info "Python3 not available, manual route config needed"
log_info "Add to $routes_file: \"$domain\": [\"127.0.0.1\", $http_port]"
fi
else
log_info "WAF route already exists for $domain"
fi
else
log_info "mitmproxy routes file not found, creating..."
mkdir -p "$(dirname "$routes_file")"
echo "{\"$domain\": [\"127.0.0.1\", $http_port]}" > "$routes_file"
[ -d "$(dirname "$routes_file_in")" ] && cp "$routes_file" "$routes_file_in"
fi
# Update Nextcloud UCI config
uci_set ssl.enabled '1'
uci_set ssl.domain "$domain"
# Restart mitmproxy to load new routes
if [ -x /etc/init.d/mitmproxy ]; then
log_info "Reloading mitmproxy..."
/etc/init.d/mitmproxy restart 2>/dev/null || true
fi
# Regenerate HAProxy config and reload
log_info "Regenerating HAProxy config..."
/usr/sbin/haproxyctl generate 2>/dev/null || true
/etc/init.d/haproxy reload 2>/dev/null || true
# Add trusted domain in Nextcloud
if lxc_running; then
log_info "Configuring Nextcloud trusted domains..."
lxc_occ config:system:set trusted_domains 1 --value="$domain"
lxc_occ config:system:set overwriteprotocol --value="https"
lxc_occ config:system:set overwrite.cli.url --value="https://$domain"
fi
log_info ""
log_info "SSL enabled for $domain (with WAF inspection)"
log_info "Access Nextcloud at: https://$domain"
log_info ""
log_info "Traffic flow: Client -> HAProxy -> mitmproxy (WAF) -> Nextcloud"
}
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_setup_mail() {
require_root
load_config
local smtp_host="${1:-}"
local smtp_port="${2:-587}"
local smtp_user="${3:-}"
local smtp_pass="${4:-}"
local smtp_from="${5:-}"
if [ -z "$smtp_host" ]; then
cat << 'EOF'
Usage: nextcloudctl setup-mail <smtp_host> [port] [user] [password] [from_address]
Examples:
# Gmail SMTP
nextcloudctl setup-mail smtp.gmail.com 587 user@gmail.com "app-password" user@gmail.com
# Local mailserver (secubox-app-mailserver)
nextcloudctl setup-mail 127.0.0.1 25
# Mailcow/Mailinabox
nextcloudctl setup-mail mail.example.com 587 noreply@example.com "password" noreply@example.com
Note: For Gmail, use an App Password (not your regular password).
EOF
return 1
fi
if ! lxc_running; then
log_error "Nextcloud container not running"
return 1
fi
log_info "Configuring SMTP mail settings..."
# Set mail mode to SMTP
lxc_occ config:system:set mail_smtpmode --value="smtp"
lxc_occ config:system:set mail_smtphost --value="$smtp_host"
lxc_occ config:system:set mail_smtpport --value="$smtp_port"
# Enable TLS for standard ports
if [ "$smtp_port" = "587" ] || [ "$smtp_port" = "465" ]; then
lxc_occ config:system:set mail_smtpsecure --value="tls"
fi
# Set authentication if provided
if [ -n "$smtp_user" ]; then
lxc_occ config:system:set mail_smtpauth --value="1"
lxc_occ config:system:set mail_smtpauthtype --value="LOGIN"
lxc_occ config:system:set mail_smtpname --value="$smtp_user"
fi
if [ -n "$smtp_pass" ]; then
lxc_occ config:system:set mail_smtppassword --value="$smtp_pass"
fi
# Set from address
if [ -n "$smtp_from" ]; then
lxc_occ config:system:set mail_from_address --value="${smtp_from%@*}"
lxc_occ config:system:set mail_domain --value="${smtp_from#*@}"
elif [ -n "$smtp_user" ]; then
lxc_occ config:system:set mail_from_address --value="${smtp_user%@*}"
lxc_occ config:system:set mail_domain --value="${smtp_user#*@}"
fi
log_info "SMTP configured: $smtp_host:$smtp_port"
log_info ""
log_info "Test with: nextcloudctl occ notification:test-push <username>"
}
cmd_setup_backup_cron() {
require_root
load_config
local schedule="$(uci_get backup.schedule)" || schedule="daily"
local cron_file="/etc/cron.d/nextcloud-backup"
log_info "Setting up scheduled backups: $schedule"
case "$schedule" in
hourly)
echo "0 * * * * root /usr/sbin/nextcloudctl backup auto-\$(date +\\%Y\\%m\\%d-\\%H) >/dev/null 2>&1" > "$cron_file"
;;
daily)
echo "0 3 * * * root /usr/sbin/nextcloudctl backup auto-\$(date +\\%Y\\%m\\%d) >/dev/null 2>&1" > "$cron_file"
;;
weekly)
echo "0 3 * * 0 root /usr/sbin/nextcloudctl backup auto-\$(date +\\%Y\\%m\\%d) >/dev/null 2>&1" > "$cron_file"
;;
disabled|none)
rm -f "$cron_file"
log_info "Scheduled backups disabled"
return 0
;;
*)
log_error "Unknown schedule: $schedule (use: hourly, daily, weekly, disabled)"
return 1
;;
esac
chmod 644 "$cron_file"
# Restart cron if running
/etc/init.d/cron restart 2>/dev/null || true
log_info "Backup cron job created: $cron_file"
log_info "Backups will run $schedule and keep last $(uci_get backup.keep) copies"
}
cmd_connections() {
load_config
local lan_ip
lan_ip=$(uci -q get network.lan.ipaddr || echo "192.168.1.1")
local base_url="http://${lan_ip}:${http_port}"
if [ "$ssl_enabled" = "1" ] && [ -n "$ssl_domain" ]; then
base_url="https://${ssl_domain}"
fi
cat << EOF
Nextcloud Connection URLs
=========================
Web Interface:
$base_url
CalDAV (Calendar):
$base_url/remote.php/dav/calendars/<username>/
CardDAV (Contacts):
$base_url/remote.php/dav/addressbooks/users/<username>/contacts/
WebDAV (Files):
$base_url/remote.php/dav/files/<username>/
iOS/macOS Calendar & Contacts:
Server: $base_url
Path: /remote.php/dav
Android (DAVx5):
Base URL: $base_url/remote.php/dav
Login: Your Nextcloud username and password
Thunderbird (TbSync + Provider for CalDAV & CardDAV):
Server URL: $base_url
Desktop Sync Client:
Download from: https://nextcloud.com/install/#install-clients
Server: $base_url
Mobile Apps:
iOS: https://apps.apple.com/app/nextcloud/id1125420102
Android: https://play.google.com/store/apps/details?id=com.nextcloud.client
EOF
}
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 "$@" ;;
setup-mail) shift; cmd_setup_mail "$@" ;;
setup-backup-cron) shift; cmd_setup_backup_cron "$@" ;;
connections) shift; cmd_connections "$@" ;;
service-run) shift; cmd_service_run "$@" ;;
service-stop) shift; cmd_service_stop "$@" ;;
*) usage ;;
esac