mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-07-29 15:37:03 +00:00
894 lines
29 KiB
Bash
Executable File
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
|