feat(nextcloud): Enhance LXC package with storage stats and backup management

- 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>
This commit is contained in:
CyberMind-FR 2026-02-17 14:41:04 +01:00
parent 92c3a4df46
commit 4d193c5e48
5 changed files with 309 additions and 28 deletions

View File

@ -91,6 +91,25 @@ var callLogs = rpc.declare({
expect: {}
});
var callGetStorage = rpc.declare({
object: 'luci.nextcloud',
method: 'get_storage',
expect: {}
});
var callDeleteBackup = rpc.declare({
object: 'luci.nextcloud',
method: 'delete_backup',
params: ['name'],
expect: {}
});
var callUninstall = rpc.declare({
object: 'luci.nextcloud',
method: 'uninstall',
expect: {}
});
var callListUsers = rpc.declare({
object: 'luci.nextcloud',
method: 'list_users',
@ -134,6 +153,7 @@ return view.extend({
config: {},
backups: [],
users: [],
storage: {},
currentTab: 'overview',
load: function() {
@ -141,7 +161,8 @@ return view.extend({
callStatus(),
callGetConfig(),
callListBackups().catch(function() { return { backups: [] }; }),
callListUsers().catch(function() { return { users: [] }; })
callListUsers().catch(function() { return { users: [] }; }),
callGetStorage().catch(function() { return {}; })
]);
},
@ -151,6 +172,7 @@ return view.extend({
this.config = data[1] || {};
this.backups = (data[2] || {}).backups || [];
this.users = (data[3] || {}).users || [];
this.storage = data[4] || {};
// Not installed - show install view
if (!this.status.installed) {
@ -161,6 +183,7 @@ return view.extend({
var tabs = [
{ id: 'overview', label: 'Overview', icon: '🎛️' },
{ id: 'users', label: 'Users', icon: '👥' },
{ id: 'storage', label: 'Storage', icon: '💿' },
{ id: 'backups', label: 'Backups', icon: '💾' },
{ id: 'ssl', label: 'SSL', icon: '🔒' },
{ id: 'logs', label: 'Logs', icon: '📜' }
@ -215,6 +238,7 @@ return view.extend({
renderTabContent: function() {
switch (this.currentTab) {
case 'users': return this.renderUsersTab();
case 'storage': return this.renderStorageTab();
case 'backups': return this.renderBackupsTab();
case 'ssl': return this.renderSSLTab();
case 'logs': return this.renderLogsTab();
@ -480,6 +504,63 @@ return view.extend({
});
},
// ========================================================================
// Storage Tab
// ========================================================================
renderStorageTab: function() {
var self = this;
var st = this.storage;
return E('div', {}, [
// Storage Stats
E('div', { 'class': 'kiss-grid kiss-grid-4', 'style': 'margin-bottom:24px;' }, [
KissTheme.stat(st.data_size || '0', 'User Data', 'var(--kiss-cyan)'),
KissTheme.stat(st.backup_size || '0', 'Backups', 'var(--kiss-purple)'),
KissTheme.stat(st.disk_free || '0', 'Disk Free', 'var(--kiss-green)'),
KissTheme.stat((st.disk_used_percent || 0) + '%', 'Disk Used', st.disk_used_percent > 80 ? 'var(--kiss-red)' : 'var(--kiss-blue)')
]),
// Disk Usage Bar
KissTheme.card([
E('span', {}, '💿 Disk Usage')
], E('div', {}, [
E('div', { 'style': 'display:flex;justify-content:space-between;margin-bottom:8px;font-size:13px;' }, [
E('span', {}, 'Used: ' + (st.disk_used_percent || 0) + '%'),
E('span', { 'style': 'color:var(--kiss-muted);' }, st.disk_free + ' free of ' + st.disk_total)
]),
E('div', { 'style': 'height:24px;background:var(--kiss-bg2);border-radius:12px;overflow:hidden;' }, [
E('div', {
'style': 'height:100%;width:' + (st.disk_used_percent || 0) + '%;background:linear-gradient(90deg, var(--kiss-cyan), var(--kiss-blue));transition:width 0.3s;'
})
])
])),
// Storage Details
KissTheme.card([
E('span', {}, '📊 Storage Breakdown')
], E('div', { 'style': 'display:flex;flex-direction:column;gap:12px;' }, [
this.storageRow('📁 User Data', st.data_size || '0', 'var(--kiss-cyan)'),
this.storageRow('💾 Backups', st.backup_size || '0', 'var(--kiss-purple)'),
this.storageRow('📦 Total Nextcloud', st.total_size || '0', 'var(--kiss-blue)')
])),
// Data Path Info
KissTheme.card([
E('span', {}, ' Data Location')
], E('div', { 'style': 'font-family:monospace;padding:12px;background:var(--kiss-bg2);border-radius:6px;' },
this.status.data_path || '/srv/nextcloud'
))
]);
},
storageRow: function(label, size, color) {
return E('div', { 'style': 'display:flex;justify-content:space-between;align-items:center;padding:10px;background:var(--kiss-bg2);border-radius:6px;' }, [
E('span', { 'style': 'display:flex;align-items:center;gap:8px;' }, label),
E('span', { 'style': 'font-weight:600;color:' + color + ';' }, size)
]);
},
// ========================================================================
// Backups Tab
// ========================================================================
@ -531,19 +612,27 @@ return view.extend({
E('th', {}, 'Name'),
E('th', {}, 'Size'),
E('th', {}, 'Date'),
E('th', {}, 'Actions')
E('th', { 'style': 'width:150px;' }, 'Actions')
])),
E('tbody', {}, this.backups.map(function(b) {
return E('tr', {}, [
E('td', { 'style': 'font-family:monospace;' }, b.name),
E('td', {}, b.size || '-'),
E('td', {}, fmtRelative(b.timestamp)),
E('td', {}, E('button', {
'class': 'kiss-btn kiss-btn-blue',
'style': 'padding:4px 10px;font-size:11px;',
'data-name': b.name,
'click': function(ev) { self.handleRestore(ev.currentTarget.dataset.name); }
}, '⬇️ Restore'))
E('td', { 'style': 'display:flex;gap:6px;' }, [
E('button', {
'class': 'kiss-btn kiss-btn-blue',
'style': 'padding:4px 10px;font-size:11px;',
'data-name': b.name,
'click': function(ev) { self.handleRestore(ev.currentTarget.dataset.name); }
}, '⬇️ Restore'),
E('button', {
'class': 'kiss-btn kiss-btn-red',
'style': 'padding:4px 10px;font-size:11px;',
'data-name': b.name,
'click': function(ev) { self.handleDeleteBackup(ev.currentTarget.dataset.name); }
}, '🗑️')
])
]);
}))
]);
@ -810,6 +899,30 @@ return view.extend({
});
},
handleDeleteBackup: function(name) {
var self = this;
if (!confirm('Delete backup "' + name + '"? This cannot be undone.')) {
return;
}
ui.showModal('Deleting Backup', [
E('p', { 'class': 'spinning' }, 'Deleting ' + name + '...')
]);
callDeleteBackup(name).then(function(r) {
ui.hideModal();
if (r.success) {
ui.addNotification(null, E('p', 'Backup deleted: ' + name), 'info');
self.refreshBackups();
} else {
ui.addNotification(null, E('p', 'Failed: ' + (r.error || 'Unknown')), 'error');
}
}).catch(function(e) {
ui.hideModal();
ui.addNotification(null, E('p', 'Error: ' + e.message), 'error');
});
},
refreshBackups: function() {
var self = this;
callListBackups().then(function(data) {
@ -839,11 +952,13 @@ return view.extend({
return Promise.all([
callStatus(),
callListBackups().catch(function() { return { backups: [] }; }),
callListUsers().catch(function() { return { users: [] }; })
callListUsers().catch(function() { return { users: [] }; }),
callGetStorage().catch(function() { return {}; })
]).then(function(data) {
self.status = data[0] || {};
self.backups = (data[1] || {}).backups || [];
self.users = (data[2] || {}).users || [];
self.storage = data[3] || {};
// Update tab content
var tabContent = document.getElementById('tab-content');

View File

@ -370,6 +370,74 @@ reset_password() {
fi
}
# Uninstall Nextcloud
do_uninstall() {
if command -v nextcloudctl >/dev/null 2>&1; then
nextcloudctl uninstall >/tmp/nextcloud-uninstall.log 2>&1
echo '{"success": true, "message": "Nextcloud uninstalled (data preserved)"}'
else
echo '{"success": false, "error": "nextcloudctl not found"}'
fi
}
# Get storage stats
get_storage() {
local data_path=$(uci_get main.data_path)
data_path="${data_path:-/srv/nextcloud}"
local total_size="0"
local data_size="0"
local backup_size="0"
if [ -d "$data_path" ]; then
total_size=$(du -sh "$data_path" 2>/dev/null | awk '{print $1}' || echo "0")
fi
if [ -d "$data_path/data" ]; then
data_size=$(du -sh "$data_path/data" 2>/dev/null | awk '{print $1}' || echo "0")
fi
if [ -d "$data_path/backups" ]; then
backup_size=$(du -sh "$data_path/backups" 2>/dev/null | awk '{print $1}' || echo "0")
fi
# Get disk free space
local disk_free=$(df -h "$data_path" 2>/dev/null | tail -1 | awk '{print $4}' || echo "0")
local disk_total=$(df -h "$data_path" 2>/dev/null | tail -1 | awk '{print $2}' || echo "0")
local disk_used_pct=$(df "$data_path" 2>/dev/null | tail -1 | awk '{print $5}' | tr -d '%' || echo "0")
cat <<EOF
{
"total_size": "$total_size",
"data_size": "$data_size",
"backup_size": "$backup_size",
"disk_free": "$disk_free",
"disk_total": "$disk_total",
"disk_used_percent": $disk_used_pct
}
EOF
}
# Delete a backup
delete_backup() {
local input
read -r input
local name=$(echo "$input" | jsonfilter -e '@.name' 2>/dev/null)
if [ -z "$name" ]; then
echo '{"success": false, "error": "No backup name specified"}'
return
fi
local data_path=$(uci_get main.data_path)
local backup_dir="${data_path:-/srv/nextcloud}/backups"
if [ -f "$backup_dir/$name-db.sql" ]; then
rm -f "$backup_dir/$name-db.sql" "$backup_dir/$name-data.tar.gz"
echo '{"success": true, "message": "Backup deleted: '"$name"'"}'
else
echo '{"success": false, "error": "Backup not found"}'
fi
}
# RPCD list method
list_methods() {
cat <<'EOF'
@ -378,6 +446,7 @@ list_methods() {
"get_config": {},
"save_config": {"http_port": "string", "data_path": "string", "domain": "string", "admin_user": "string", "memory_limit": "string", "upload_max": "string"},
"install": {},
"uninstall": {},
"start": {},
"stop": {},
"restart": {},
@ -385,12 +454,14 @@ list_methods() {
"backup": {"name": "string"},
"restore": {"name": "string"},
"list_backups": {},
"delete_backup": {"name": "string"},
"ssl_enable": {"domain": "string"},
"ssl_disable": {},
"occ": {"command": "string"},
"logs": {},
"list_users": {},
"reset_password": {"uid": "string", "password": "string"}
"reset_password": {"uid": "string", "password": "string"},
"get_storage": {}
}
EOF
}
@ -406,6 +477,7 @@ case "$1" in
get_config) get_config ;;
save_config) save_config ;;
install) do_install ;;
uninstall) do_uninstall ;;
start) do_start ;;
stop) do_stop ;;
restart) do_restart ;;
@ -413,12 +485,14 @@ case "$1" in
backup) do_backup ;;
restore) do_restore ;;
list_backups) list_backups ;;
delete_backup) delete_backup ;;
ssl_enable) do_ssl_enable ;;
ssl_disable) do_ssl_disable ;;
occ) do_occ ;;
logs) get_logs ;;
list_users) list_users ;;
reset_password) reset_password ;;
get_storage) get_storage ;;
*) echo '{"error": "Unknown method"}' ;;
esac
;;

View File

@ -3,7 +3,7 @@
"description": "Grant access to Nextcloud LXC app",
"read": {
"ubus": {
"luci.nextcloud": ["status", "get_config", "list_backups", "logs", "list_users"]
"luci.nextcloud": ["status", "get_config", "list_backups", "logs", "list_users", "get_storage"]
},
"uci": ["nextcloud"]
},
@ -11,6 +11,7 @@
"ubus": {
"luci.nextcloud": [
"install",
"uninstall",
"start",
"stop",
"restart",
@ -18,6 +19,7 @@
"save_config",
"backup",
"restore",
"delete_backup",
"ssl_enable",
"ssl_disable",
"occ",

View File

@ -1,11 +1,24 @@
# SecuBox Nextcloud
Self-hosted file sync and share platform running in Docker on OpenWrt. Provides calendar, contacts, collaborative editing, and file management.
Self-hosted file sync and collaboration platform running in a Debian LXC container on OpenWrt. Features MariaDB database, Redis caching, and Nginx web server.
## Installation
```bash
opkg install secubox-app-nextcloud
opkg install secubox-app-nextcloud luci-app-nextcloud
```
## Quick Start
```bash
# Install Nextcloud (creates LXC container)
nextcloudctl install
# Start service
/etc/init.d/nextcloud start
# Access web interface
# http://router-ip:8080
```
## Configuration
@ -15,21 +28,55 @@ UCI config file: `/etc/config/nextcloud`
```bash
uci set nextcloud.main.enabled='1'
uci set nextcloud.main.domain='cloud.example.com'
uci set nextcloud.main.port='8080'
uci set nextcloud.main.http_port='8080'
uci set nextcloud.main.admin_user='admin'
uci set nextcloud.main.data_dir='/srv/nextcloud/data'
uci set nextcloud.main.memory_limit='1G'
uci set nextcloud.main.upload_max='512M'
uci commit nextcloud
```
## Usage
## CLI Commands
```bash
nextcloudctl start # Start Nextcloud container
nextcloudctl stop # Stop Nextcloud container
nextcloudctl status # Show service status
nextcloudctl update # Pull latest container image
nextcloudctl occ <cmd> # Run Nextcloud occ command
nextcloudctl logs # View container logs
nextcloudctl install # Create Debian LXC, install Nextcloud stack
nextcloudctl uninstall # Remove container (preserves data)
nextcloudctl update # Update Nextcloud to latest version
nextcloudctl start # Start Nextcloud service
nextcloudctl stop # Stop Nextcloud service
nextcloudctl restart # Restart Nextcloud service
nextcloudctl status # Show service status (JSON)
nextcloudctl logs [-f] # Show container logs
nextcloudctl shell # Open shell in container
nextcloudctl occ <cmd> # Run Nextcloud OCC command
nextcloudctl backup [name] # Create backup of data and database
nextcloudctl restore <name> # Restore from backup
nextcloudctl list-backups # List available backups
nextcloudctl ssl-enable <domain> # Register with HAProxy for SSL
nextcloudctl ssl-disable # Remove HAProxy registration
```
## Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ OpenWrt Host │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ LXC: nextcloud (Debian 12) │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ Nginx │ │Nextcloud│ │ MariaDB │ │ Redis │ │ │
│ │ │ :8080 │→ │ PHP-FPM │→ │ :3306 │ │ :6379 │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
│ │ ↓ │ │
│ │ /srv/nextcloud (bind mount) │ │
│ └───────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ HAProxy (optional SSL termination) │ │
│ │ cloud.example.com:443 → nextcloud:8080 │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
## Features
@ -37,13 +84,42 @@ nextcloudctl logs # View container logs
- File sync and share with web, desktop, and mobile clients
- Calendar and contacts (CalDAV/CardDAV)
- Collaborative document editing
- Docker-based deployment with persistent storage
- End-to-end encryption support
- Debian LXC container with PHP 8.2
- MariaDB database with optimized settings
- Redis caching for improved performance
- Nginx with Nextcloud-optimized config
- HAProxy integration for SSL/HTTPS
- Automated backup and restore
- Memory limit via cgroups
- Auto-start on boot
## Data Locations
```
/srv/nextcloud/
├── data/ # Nextcloud user data
├── config/ # Nextcloud config.php
└── backups/ # Automated backups
```
## SSL with HAProxy
```bash
# Enable HTTPS via HAProxy with Let's Encrypt
nextcloudctl ssl-enable cloud.example.com
# Access via HTTPS
https://cloud.example.com
```
## Dependencies
- `dockerd`
- `docker`
- `containerd`
- `lxc` - Container runtime
- `lxc-common` - LXC utilities
- `tar`, `wget-ssl`, `unzip`, `xz` - Archive tools
- `jsonfilter` - JSON parsing
- `openssl-util` - SSL utilities
## License

View File

@ -6,7 +6,7 @@
CONFIG="nextcloud"
LXC_NAME="nextcloud"
NEXTCLOUD_VERSION="30.0.4"
NEXTCLOUD_VERSION="31.0.5"
# Paths
LXC_PATH="/srv/lxc"
@ -397,9 +397,16 @@ server {
return 301 /index.php$request_uri;
}
location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/) { return 404; }
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;
@ -543,6 +550,10 @@ 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
@ -557,6 +568,9 @@ 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