secubox-deb/packages/secubox-nextcloud/sbin/nextcloudctl
CyberMind e31b01cbc5
Merge pull request #411 from CyberMind-FR/feature/410-secubox-user-provisioning-push-master-us
SecuBox user provisioning: push master users into Nextcloud & PhotoPrism (#410)
2026-05-29 09:00:02 +02:00

894 lines
29 KiB
Bash
Executable File

#!/bin/bash
# SecuBox Nextcloud Controller
# File sync in Debian LXC for Debian
# Three-fold architecture: Components, Status, Access
VERSION="1.4.0"
CONFIG_FILE="/etc/secubox/nextcloud.toml"
# Shared photo library — also mounted by secubox-photoprism as its originals.
# Phone → Nextcloud "PhotoLibrary" external storage → here → PhotoPrism (#397).
SHARED_PHOTOS="${SECUBOX_SHARED_PHOTOS:-/data/shared/photos}"
# v2.11.1 modernization: /data/lxc is the canonical LXC path on SecuBox
# boards (matches authelia/grafana/etc). Override via SECUBOX_LXC_PATH for tests.
LXC_PATH="${SECUBOX_LXC_PATH:-/data/lxc}"
DATA_PATH="${SECUBOX_NEXTCLOUD_DATA:-/data/volumes/nextcloud}"
CONTAINER="${SECUBOX_LXC_NAME:-nextcloud}"
LXC_BRIDGE="${SECUBOX_LXC_BRIDGE:-br-lxc}"
LXC_IP="${SECUBOX_LXC_IP:-10.100.0.21}"
LXC_GW="${SECUBOX_LXC_GW:-10.100.0.1}"
NC_VERSION="32.0.10"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log() { echo -e "${GREEN}[NEXTCLOUD]${NC} $1"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
error() { echo -e "${RED}[ERROR]${NC} $1" >&2; }
# ============================================================================
# Configuration (TOML parsing)
# ============================================================================
config_get() {
local key="$1" default="$2"
[ -f "$CONFIG_FILE" ] && grep "^${key} *=" "$CONFIG_FILE" 2>/dev/null | head -1 | cut -d= -f2- | tr -d ' "' || echo "$default"
}
HTTP_PORT=$(config_get "http_port" "9080")
DOMAIN=$(config_get "domain" "cloud.local")
ADMIN_USER=$(config_get "admin_user" "ncadmin")
DB_TYPE=$(config_get "db_type" "sqlite")
require_root() {
[ "$(id -u)" -eq 0 ] || { error "Root required"; exit 1; }
}
lxc_running() { lxc-info -n "$CONTAINER" -P "$LXC_PATH" 2>/dev/null | grep -q "State:.*RUNNING"; }
lxc_exists() { [ -d "$LXC_PATH/$CONTAINER/rootfs" ]; }
lxc_attach() {
local cmd="$1"
lxc-attach -n "$CONTAINER" -P "$LXC_PATH" -- sh -c "$cmd"
}
# ============================================================================
# Debian Bootstrap
# ============================================================================
bootstrap_debian() {
require_root
log "Bootstrapping Debian bookworm..."
mkdir -p "$LXC_PATH/$CONTAINER/rootfs"
if [ ! -f "$LXC_PATH/$CONTAINER/rootfs/bin/bash" ]; then
debootstrap --variant=minbase --include=systemd,systemd-sysv,dbus,ca-certificates,curl,gnupg \
bookworm "$LXC_PATH/$CONTAINER/rootfs" http://deb.debian.org/debian
fi
# Configure apt sources
cat > "$LXC_PATH/$CONTAINER/rootfs/etc/apt/sources.list" << 'EOF'
deb http://deb.debian.org/debian bookworm main contrib
deb http://deb.debian.org/debian bookworm-updates main contrib
deb http://security.debian.org/debian-security bookworm-security main contrib
EOF
# Set hostname
echo "nextcloud" > "$LXC_PATH/$CONTAINER/rootfs/etc/hostname"
# DNS. A static resolv.conf alone is fragile: if systemd-resolved is active
# in the container it hijacks /etc/resolv.conf to its 127.0.0.53 stub, which
# has no upstream → DNS silently dies (no appstore, no updates). Give
# systemd-resolved an explicit upstream too. See docs/FAQ-LXC-DNS.md.
cat > "$LXC_PATH/$CONTAINER/rootfs/etc/resolv.conf" << 'EOF'
nameserver 8.8.8.8
nameserver 1.1.1.1
EOF
mkdir -p "$LXC_PATH/$CONTAINER/rootfs/etc/systemd/resolved.conf.d"
cat > "$LXC_PATH/$CONTAINER/rootfs/etc/systemd/resolved.conf.d/secubox-dns.conf" << 'EOF'
[Resolve]
DNS=1.1.1.1 9.9.9.9
FallbackDNS=8.8.8.8
EOF
log "Debian base system installed"
}
# ============================================================================
# LXC Configuration
# ============================================================================
create_lxc_config() {
mkdir -p "$LXC_PATH/$CONTAINER"
cat > "$LXC_PATH/$CONTAINER/config" << EOF
lxc.uts.name = nextcloud
lxc.rootfs.path = dir:${LXC_PATH}/${CONTAINER}/rootfs
lxc.include = /usr/share/lxc/config/common.conf
lxc.apparmor.profile = generated
# Bridged networking on br-lxc (10.100.0.0/24). Pinned to 10.100.0.21 to
# avoid the legacy collision with secubox-authelia at 10.100.0.20 (see #280).
lxc.net.0.type = veth
lxc.net.0.link = ${LXC_BRIDGE:-br-lxc}
lxc.net.0.flags = up
lxc.net.0.name = eth0
lxc.net.0.ipv4.address = ${LXC_IP:-10.100.0.21}/24
lxc.net.0.ipv4.gateway = ${LXC_GW:-10.100.0.1}
# Unprivileged uid mapping
lxc.idmap = u 0 100000 65536
lxc.idmap = g 0 100000 65536
lxc.cgroup2.memory.max = 2G
lxc.init.cmd = /sbin/init
lxc.start.auto = 1
# Bind mounts for persistent data (host paths must be owned by mapped uid 100000)
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
# Shared photo library, mounted OUTSIDE the data dir and exposed via the
# "PhotoLibrary" external storage (nextcloudctl link-photos). Shared with
# secubox-photoprism originals (#397/#398).
lxc.mount.entry = ${SHARED_PHOTOS} media/photos none bind,create=dir 0 0
EOF
# The shared dir must be writable by www-data (uid 33 → 100033) and
# readable by PhotoPrism; 0777 owned by the LXC root uid on a single box.
install -d -m 0777 "$SHARED_PHOTOS" 2>/dev/null || true
chown 100000:100000 "$SHARED_PHOTOS" 2>/dev/null || true
log "LXC config created (veth on ${LXC_BRIDGE:-br-lxc}, ip ${LXC_IP:-10.100.0.21})"
}
create_install_init() {
# For Debian we use systemd, no special init needed
log "Using systemd init"
}
# ============================================================================
# Install Nextcloud
# ============================================================================
install_nextcloud() {
require_root
log "Installing Nextcloud in LXC..."
if ! lxc_running; then
error "Container not running"
return 1
fi
# Update and install packages
log "Installing PHP and dependencies..."
lxc_attach "apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
nginx \
php-fpm php-gd php-curl php-zip php-xml php-mbstring php-intl \
php-bcmath php-gmp php-imagick php-apcu php-redis \
php-sqlite3 php-mysql php-pgsql \
mariadb-server redis-server \
unzip wget sudo"
# Create www-data directories
lxc_attach "mkdir -p /var/www/nextcloud /var/www/nextcloud/data /var/www/nextcloud/config"
# Download and extract Nextcloud
log "Downloading Nextcloud ${NC_VERSION}..."
lxc_attach "cd /tmp && \
wget -q https://download.nextcloud.com/server/releases/nextcloud-${NC_VERSION}.zip && \
unzip -q nextcloud-${NC_VERSION}.zip"
# Copy files (separate from unzip to avoid bind mount issues)
log "Installing Nextcloud files..."
lxc_attach "cp -r /tmp/nextcloud/* /var/www/nextcloud/ && \
rm -rf /tmp/nextcloud /tmp/nextcloud-${NC_VERSION}.zip"
# Set permissions
lxc_attach "chown -R www-data:www-data /var/www/nextcloud"
# Add hostname to /etc/hosts
lxc_attach "echo '127.0.0.1 nextcloud' >> /etc/hosts"
# Configure PHP-FPM
configure_php
# Configure Nginx
configure_nginx
# Configure Redis
configure_redis
# Fix Redis systemd unit for LXC (namespace issues)
lxc_attach "cat > /etc/systemd/system/redis-server.service << 'REDISUNIT'
[Unit]
Description=Redis Server
After=network.target
[Service]
Type=simple
User=redis
Group=redis
ExecStart=/usr/bin/redis-server /etc/redis/redis.conf
Restart=always
[Install]
WantedBy=multi-user.target
REDISUNIT"
# Enable and start services
lxc_attach "systemctl daemon-reload"
lxc_attach "systemctl enable nginx php8.2-fpm redis-server"
lxc_attach "systemctl start nginx php8.2-fpm redis-server"
log "Nextcloud installed successfully!"
}
configure_php() {
log "Configuring PHP..."
lxc_attach "cat > /etc/php/8.2/fpm/pool.d/nextcloud.conf << 'PHPCONF'
[nextcloud]
user = www-data
group = www-data
listen = /run/php/nextcloud.sock
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 20
pm.start_servers = 5
pm.min_spare_servers = 3
pm.max_spare_servers = 10
env[HOSTNAME] = \$HOSTNAME
env[PATH] = /usr/local/bin:/usr/bin:/bin
env[TMP] = /tmp
env[TMPDIR] = /tmp
env[TEMP] = /tmp
php_admin_value[upload_max_filesize] = 16G
php_admin_value[post_max_size] = 16G
php_admin_value[memory_limit] = 512M
php_admin_value[max_execution_time] = 3600
php_admin_value[max_input_time] = 3600
php_admin_value[output_buffering] = 0
PHPCONF"
# PHP settings
lxc_attach "cat > /etc/php/8.2/fpm/conf.d/99-nextcloud.ini << 'PHPINI'
opcache.enable=1
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.memory_consumption=128
opcache.save_comments=1
opcache.revalidate_freq=1
PHPINI"
}
configure_nginx() {
log "Configuring Nginx..."
lxc_attach "cat > /etc/nginx/sites-available/nextcloud << 'NGINXCONF'
server {
listen ${HTTP_PORT};
server_name ${DOMAIN} localhost;
root /var/www/nextcloud;
index index.php index.html;
client_max_body_size 16G;
client_body_timeout 3600s;
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 { return 301 /index.php\$uri; }
try_files \$uri \$uri/ =404;
}
location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:\$|/) { return 404; }
location ~ ^/(?:\\.|autotest|occ|issue|indie|db_|console) { return 404; }
location ~ \\.php(?:\$|/) {
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 HTTPS off;
fastcgi_param modHeadersAvailable true;
fastcgi_param front_controller_active true;
fastcgi_pass unix:/run/php/nextcloud.sock;
fastcgi_intercept_errors on;
fastcgi_request_buffering off;
fastcgi_read_timeout 3600;
}
location ~ \\.(?:css|js|mjs|svg|gif|png|jpg|jpeg|ico|wasm|tflite|map|html|ttf|bcmap|mp4|webm|ogg)\$ {
try_files \$uri /index.php\$request_uri;
expires 6M;
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 / {
try_files \$uri \$uri/ /index.php\$request_uri;
}
}
NGINXCONF"
lxc_attach "rm -f /etc/nginx/sites-enabled/default"
lxc_attach "ln -sf /etc/nginx/sites-available/nextcloud /etc/nginx/sites-enabled/"
}
configure_redis() {
log "Configuring Redis..."
lxc_attach "sed -i 's/^# unixsocket /unixsocket /' /etc/redis/redis.conf"
lxc_attach "sed -i 's/^# unixsocketperm 700/unixsocketperm 770/' /etc/redis/redis.conf"
lxc_attach "usermod -a -G redis www-data"
}
# ============================================================================
# Nextcloud OCC wrapper
# ============================================================================
occ() {
if ! lxc_running; then
error "Container not running"
return 1
fi
lxc_attach "sudo -u www-data php /var/www/nextcloud/occ $*"
}
# ============================================================================
# Setup wizard
# ============================================================================
cmd_setup() {
local admin_pass="${1:-$(openssl rand -base64 12)}"
if ! lxc_running; then
error "Container not running. Run: nextcloudctl start"
return 1
fi
log "Running Nextcloud setup..."
# Install with SQLite (simple setup)
lxc_attach "sudo -u www-data php /var/www/nextcloud/occ maintenance:install \
--database sqlite \
--admin-user '$ADMIN_USER' \
--admin-pass '$admin_pass' \
--data-dir /var/www/nextcloud/data"
# Configure trusted domains
lxc_attach "sudo -u www-data php /var/www/nextcloud/occ config:system:set trusted_domains 0 --value='localhost'"
lxc_attach "sudo -u www-data php /var/www/nextcloud/occ config:system:set trusted_domains 1 --value='$DOMAIN'"
lxc_attach "sudo -u www-data php /var/www/nextcloud/occ config:system:set trusted_domains 2 --value='127.0.0.1'"
# Configure Redis
lxc_attach "sudo -u www-data php /var/www/nextcloud/occ config:system:set memcache.local --value='\\OC\\Memcache\\APCu'"
lxc_attach "sudo -u www-data php /var/www/nextcloud/occ config:system:set memcache.distributed --value='\\OC\\Memcache\\Redis'"
lxc_attach "sudo -u www-data php /var/www/nextcloud/occ config:system:set memcache.locking --value='\\OC\\Memcache\\Redis'"
lxc_attach "sudo -u www-data php /var/www/nextcloud/occ config:system:set redis host --value='/var/run/redis/redis-server.sock'"
lxc_attach "sudo -u www-data php /var/www/nextcloud/occ config:system:set redis port --value='0' --type=integer"
# Background jobs
lxc_attach "sudo -u www-data php /var/www/nextcloud/occ background:cron"
# Add cron job
lxc_attach "echo '*/5 * * * * www-data php -f /var/www/nextcloud/cron.php' > /etc/cron.d/nextcloud"
# Photo integration (idempotent) — phone → PhotoLibrary → PhotoPrism.
link_photos
log "Nextcloud setup complete!"
log ""
log "Admin credentials:"
log " Username: $ADMIN_USER"
log " Password: $admin_pass"
log ""
log "Access: http://localhost:${HTTP_PORT}"
}
# ============================================================================
# Photo integration (PhotoLibrary external storage → /media/photos)
# ============================================================================
link_photos() {
# Expose the shared photo dir (bind-mounted at /media/photos, OUTSIDE the
# NC data dir) as a Local external storage so the mobile client can sync
# into it and PhotoPrism indexes the same files. Idempotent.
if ! lxc_running; then
error "Container not running. Run: nextcloudctl start"
return 1
fi
log "Wiring 'PhotoLibrary' external storage → /media/photos ..."
lxc_attach "sudo -u www-data php /var/www/nextcloud/occ app:enable files_external" >/dev/null 2>&1 || true
if lxc_attach "sudo -u www-data php /var/www/nextcloud/occ files_external:list" 2>/dev/null | grep -q PhotoLibrary; then
log "PhotoLibrary already configured."
return 0
fi
local mid
mid=$(lxc_attach "sudo -u www-data php /var/www/nextcloud/occ files_external:create 'PhotoLibrary' local null::null -c datadir=/media/photos" 2>/dev/null | grep -oE '[0-9]+' | head -1)
if [ -n "$mid" ]; then
lxc_attach "sudo -u www-data php /var/www/nextcloud/occ files_external:option $mid filesystem_check_changes 1" >/dev/null 2>&1 || true
log "PhotoLibrary created (mount id $mid). Point the phone NC client's auto-upload here."
else
warn "Could not create PhotoLibrary mount (is files_external enabled?)."
fi
}
# ============================================================================
# User Management
# ============================================================================
user_add() {
local username="$1"
local password="$2"
local display_name="${3:-$username}"
if [ -z "$username" ] || [ -z "$password" ]; then
echo "Usage: nextcloudctl user add <username> <password> [display_name]"
return 1
fi
log "Creating user: $username"
lxc_attach "export OC_PASS='$password' && sudo -u www-data php /var/www/nextcloud/occ user:add --password-from-env --display-name='$display_name' '$username'"
}
user_del() {
local username="$1"
[ -z "$username" ] && { echo "Usage: nextcloudctl user del <username>"; return 1; }
log "Deleting user: $username"
occ user:delete "$username"
}
user_list() {
occ user:list --output=json
}
# Provisioning push entry point (#410): called by secubox-user-sync. Password
# arrives via $SECUBOX_USER_PASSWORD (never argv). Idempotent: creates the user
# or resets the password, then ensures a per-user photo folder + a Photos
# external storage scoped to that user only.
user_provision() {
local username="$1"
local display_name="${2:-$username}"
local password="${SECUBOX_USER_PASSWORD:-}"
[ -z "$username" ] && { echo "Usage: SECUBOX_USER_PASSWORD=... nextcloudctl user-provision <username> [display]"; return 1; }
[ -z "$password" ] && { error "SECUBOX_USER_PASSWORD not set"; return 1; }
if ! lxc_running; then error "Container not running."; return 1; fi
# Password travels via stdin → OC_PASS (export + sudo --preserve-env, since
# sudo otherwise strips it); username/display passed as argv, not embedded —
# safe for special characters in either field.
if occ user:list --output=json 2>/dev/null | grep -q "\"$username\""; then
log "Resetting password for existing Nextcloud user: $username"
printf '%s' "$password" | lxc-attach -n "$CONTAINER" -P "$LXC_PATH" -- sh -c \
'IFS= read -r OC_PASS; export OC_PASS; sudo --preserve-env=OC_PASS -u www-data php /var/www/nextcloud/occ user:resetpassword --password-from-env "$1"' _ "$username"
else
log "Creating Nextcloud user: $username"
printf '%s' "$password" | lxc-attach -n "$CONTAINER" -P "$LXC_PATH" -- sh -c \
'IFS= read -r OC_PASS; export OC_PASS; sudo --preserve-env=OC_PASS -u www-data php /var/www/nextcloud/occ user:add --password-from-env --display-name="$1" "$2"' _ "$display_name" "$username"
fi
# Per-user photo folder: /data/shared/photos/<user> (host) = /media/photos/<user> (container).
mkdir -p "$SHARED_PHOTOS/$username" && chmod 0777 "$SHARED_PHOTOS/$username"
lxc_attach "sudo -u www-data php /var/www/nextcloud/occ app:enable files_external" >/dev/null 2>&1 || true
if occ files_external:list 2>/dev/null | grep -q "Photos-$username"; then
log "Per-user Photos storage already present for $username"
else
local mid
mid=$(lxc_attach "sudo -u www-data php /var/www/nextcloud/occ files_external:create 'Photos-$username' local null::null -c datadir=/media/photos/$username" 2>/dev/null | grep -oE '[0-9]+' | head -1)
if [ -n "$mid" ]; then
lxc_attach "sudo -u www-data php /var/www/nextcloud/occ files_external:applicable $mid --add-user '$username'" >/dev/null 2>&1 || true
lxc_attach "sudo -u www-data php /var/www/nextcloud/occ files_external:option $mid filesystem_check_changes 1" >/dev/null 2>&1 || true
log "Per-user Photos storage for $username (mount $mid → /media/photos/$username)"
else
warn "Could not create per-user Photos storage for $username"
fi
fi
log "Provisioned Nextcloud user: $username"
}
# ============================================================================
# App Management
# ============================================================================
app_install() {
local app="$1"
[ -z "$app" ] && { echo "Usage: nextcloudctl app install <app_name>"; return 1; }
log "Installing app: $app"
occ app:install "$app"
}
app_list() {
occ app:list --output=json
}
# ============================================================================
# Backup / Restore
# ============================================================================
cmd_backup() {
local name="${1:-$(date +%Y%m%d_%H%M%S)}"
local backup_file="$DATA_PATH/backups/nextcloud_$name.tar.gz"
log "Creating backup: $backup_file"
mkdir -p "$DATA_PATH/backups"
# Enable maintenance mode
occ maintenance:mode --on 2>/dev/null
tar -czf "$backup_file" \
-C "$DATA_PATH" \
data config \
2>/dev/null
# Disable maintenance mode
occ maintenance:mode --off 2>/dev/null
log "Backup created: $backup_file"
echo "$backup_file"
}
cmd_restore() {
local backup_file="$1"
if [ -z "$backup_file" ] || [ ! -f "$backup_file" ]; then
echo "Usage: nextcloudctl restore <backup_file>"
echo ""
echo "Available backups:"
ls -la "$DATA_PATH/backups/"*.tar.gz 2>/dev/null || echo " No backups found"
return 1
fi
warn "This will overwrite current Nextcloud data!"
read -p "Continue? (yes/no): " confirm
[ "$confirm" != "yes" ] && { echo "Aborted"; return 1; }
# Enable maintenance mode
occ maintenance:mode --on 2>/dev/null
log "Restoring from: $backup_file"
tar -xzf "$backup_file" -C "$DATA_PATH"
# Fix permissions
lxc_attach "chown -R www-data:www-data /var/www/nextcloud/data /var/www/nextcloud/config"
# Disable maintenance mode
occ maintenance:mode --off 2>/dev/null
log "Restore complete!"
}
# ============================================================================
# Commands
# ============================================================================
cmd_install() {
require_root
log "Installing Nextcloud LXC..."
mkdir -p "$DATA_PATH"/{data,config,backups}
chown -R 33:33 "$DATA_PATH" # www-data UID
if ! lxc_exists; then
bootstrap_debian
fi
create_lxc_config
# Start container
log "Starting container..."
lxc-start -n "$CONTAINER" -P "$LXC_PATH" -d
sleep 10 # Wait for systemd to initialize
if lxc_running; then
install_nextcloud
log "Nextcloud installed!"
log "Run 'nextcloudctl setup' to complete configuration"
else
error "Failed to start container"
return 1
fi
}
cmd_uninstall() {
require_root
warn "This will remove Nextcloud container. Data in $DATA_PATH will be preserved."
read -p "Continue? (yes/no): " confirm
[ "$confirm" != "yes" ] && { echo "Aborted"; return 1; }
log "Stopping container..."
lxc-stop -n "$CONTAINER" -P "$LXC_PATH" 2>/dev/null
log "Removing container..."
rm -rf "$LXC_PATH/$CONTAINER"
log "Nextcloud removed. Data preserved in $DATA_PATH"
}
cmd_start() {
require_root
if lxc_running; then
log "Nextcloud already running"
return 0
fi
if ! lxc_exists; then
error "Nextcloud not installed. Run: nextcloudctl install"
return 1
fi
log "Starting Nextcloud..."
lxc-start -n "$CONTAINER" -P "$LXC_PATH" -d
sleep 5
if lxc_running; then
log "Nextcloud started at http://localhost:${HTTP_PORT}"
else
error "Failed to start"
return 1
fi
}
cmd_stop() {
require_root
if ! lxc_running; then
log "Nextcloud not running"
return 0
fi
log "Stopping Nextcloud..."
lxc-stop -n "$CONTAINER" -P "$LXC_PATH"
log "Stopped"
}
cmd_restart() {
cmd_stop
sleep 2
cmd_start
}
cmd_status() {
echo ""
echo "Nextcloud File Sync v${VERSION}"
echo "=============================="
echo ""
echo "Container: $CONTAINER"
if lxc_running; then
echo -e "Status: ${GREEN}Running${NC}"
echo "HTTP: http://localhost:${HTTP_PORT}"
# Storage info
local storage=$(du -sh "$DATA_PATH/data" 2>/dev/null | cut -f1)
echo "Storage: ${storage:-0K}"
# User count
local users=$(occ user:list 2>/dev/null | wc -l)
echo "Users: $users"
else
echo -e "Status: ${YELLOW}Stopped${NC}"
fi
echo ""
}
cmd_logs() {
local lines="${1:-50}"
if lxc_running; then
lxc_attach "tail -n $lines /var/log/nginx/error.log"
else
error "Container not running"
fi
}
cmd_shell() {
if ! lxc_running; then
error "Container not running"
return 1
fi
lxc_attach "/bin/bash"
}
# ============================================================================
# Three-fold Architecture
# ============================================================================
cmd_components() {
local nc_version=""
local nc_installed=false
local php_version=""
local user_count=0
if lxc_running; then
nc_version=$(occ --version 2>/dev/null | grep -oP 'Nextcloud \K[0-9.]+' || echo "unknown")
[ -n "$nc_version" ] && nc_installed=true
php_version=$(lxc_attach "php -v 2>/dev/null | head -1 | grep -oP 'PHP \K[0-9.]+'" || echo "")
user_count=$(occ user:list 2>/dev/null | wc -l)
fi
cat << EOF
{
"components": [
{
"name": "Nextcloud Container",
"type": "lxc",
"container": "$CONTAINER",
"description": "Nextcloud file sync server",
"installed": $(lxc_exists && echo true || echo false),
"running": $(lxc_running && echo true || echo false),
"version": "$nc_version"
},
{
"name": "PHP-FPM",
"type": "service",
"description": "PHP FastCGI Process Manager",
"version": "$php_version"
},
{
"name": "Nginx",
"type": "service",
"port": $HTTP_PORT,
"description": "Web server"
},
{
"name": "Redis",
"type": "service",
"description": "Cache server"
},
{
"name": "User Data",
"type": "data",
"path": "$DATA_PATH/data",
"user_count": $user_count
}
]
}
EOF
}
cmd_access() {
cat << EOF
{
"domain": "$DOMAIN",
"http": {
"port": $HTTP_PORT,
"url": "http://localhost:$HTTP_PORT",
"public_url": "https://$DOMAIN"
},
"webdav": {
"url": "http://localhost:$HTTP_PORT/remote.php/dav/files/USERNAME/"
},
"caldav": {
"url": "http://localhost:$HTTP_PORT/remote.php/dav/calendars/USERNAME/"
},
"carddav": {
"url": "http://localhost:$HTTP_PORT/remote.php/dav/addressbooks/users/USERNAME/"
},
"admin_panel": "/nextcloud/"
}
EOF
}
# ============================================================================
# Main
# ============================================================================
show_help() {
cat << EOF
SecuBox Nextcloud Controller v${VERSION}
Three-fold architecture: Components, Status, Access
Usage: nextcloudctl <command> [options]
Information (Three-fold):
components List system components (JSON)
status Show status
access Show connection info (JSON)
Setup:
install Install Nextcloud LXC
setup [password] Run first-time setup wizard
uninstall Remove Nextcloud container
Service:
start Start Nextcloud
stop Stop Nextcloud
restart Restart Nextcloud
Users:
user add <user> <pass> [name] Add user
user del <user> Delete user
user list List users (JSON)
Apps:
app install <name> Install app
app list List apps (JSON)
Maintenance:
link-photos Wire the 'PhotoLibrary' external storage (→ PhotoPrism)
occ <command> Run Nextcloud occ command
backup [name] Create backup
restore <file> Restore from backup
logs [lines] Show error logs
shell Open container shell
Examples:
nextcloudctl install
nextcloudctl setup mypassword
nextcloudctl user add alice secret123 "Alice Smith"
nextcloudctl app install calendar
nextcloudctl occ status
EOF
}
case "${1:-}" in
components) cmd_components ;;
status) cmd_status ;;
access) cmd_access ;;
install) cmd_install ;;
setup) shift; cmd_setup "$@" ;;
uninstall) cmd_uninstall ;;
start) cmd_start ;;
stop) cmd_stop ;;
restart) cmd_restart ;;
user)
case "${2:-}" in
add) shift 2; user_add "$@" ;;
del|delete) shift 2; user_del "$@" ;;
list) user_list ;;
*) echo "Usage: nextcloudctl user {add|del|list}"; exit 1 ;;
esac
;;
app)
case "${2:-}" in
install) shift 2; app_install "$@" ;;
list) app_list ;;
*) echo "Usage: nextcloudctl app {install|list}"; exit 1 ;;
esac
;;
link-photos) link_photos ;;
user-provision) shift; user_provision "$@" ;;
occ) shift; occ "$@" ;;
backup) shift; cmd_backup "$@" ;;
restore) shift; cmd_restore "$@" ;;
logs) shift; cmd_logs "$@" ;;
shell) cmd_shell ;;
-h|--help|help) show_help ;;
"") show_help ;;
*) error "Unknown command: $1"; show_help; exit 1 ;;
esac