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:
parent
92c3a4df46
commit
4d193c5e48
@ -91,6 +91,25 @@ var callLogs = rpc.declare({
|
|||||||
expect: {}
|
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({
|
var callListUsers = rpc.declare({
|
||||||
object: 'luci.nextcloud',
|
object: 'luci.nextcloud',
|
||||||
method: 'list_users',
|
method: 'list_users',
|
||||||
@ -134,6 +153,7 @@ return view.extend({
|
|||||||
config: {},
|
config: {},
|
||||||
backups: [],
|
backups: [],
|
||||||
users: [],
|
users: [],
|
||||||
|
storage: {},
|
||||||
currentTab: 'overview',
|
currentTab: 'overview',
|
||||||
|
|
||||||
load: function() {
|
load: function() {
|
||||||
@ -141,7 +161,8 @@ return view.extend({
|
|||||||
callStatus(),
|
callStatus(),
|
||||||
callGetConfig(),
|
callGetConfig(),
|
||||||
callListBackups().catch(function() { return { backups: [] }; }),
|
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.config = data[1] || {};
|
||||||
this.backups = (data[2] || {}).backups || [];
|
this.backups = (data[2] || {}).backups || [];
|
||||||
this.users = (data[3] || {}).users || [];
|
this.users = (data[3] || {}).users || [];
|
||||||
|
this.storage = data[4] || {};
|
||||||
|
|
||||||
// Not installed - show install view
|
// Not installed - show install view
|
||||||
if (!this.status.installed) {
|
if (!this.status.installed) {
|
||||||
@ -161,6 +183,7 @@ return view.extend({
|
|||||||
var tabs = [
|
var tabs = [
|
||||||
{ id: 'overview', label: 'Overview', icon: '🎛️' },
|
{ id: 'overview', label: 'Overview', icon: '🎛️' },
|
||||||
{ id: 'users', label: 'Users', icon: '👥' },
|
{ id: 'users', label: 'Users', icon: '👥' },
|
||||||
|
{ id: 'storage', label: 'Storage', icon: '💿' },
|
||||||
{ id: 'backups', label: 'Backups', icon: '💾' },
|
{ id: 'backups', label: 'Backups', icon: '💾' },
|
||||||
{ id: 'ssl', label: 'SSL', icon: '🔒' },
|
{ id: 'ssl', label: 'SSL', icon: '🔒' },
|
||||||
{ id: 'logs', label: 'Logs', icon: '📜' }
|
{ id: 'logs', label: 'Logs', icon: '📜' }
|
||||||
@ -215,6 +238,7 @@ return view.extend({
|
|||||||
renderTabContent: function() {
|
renderTabContent: function() {
|
||||||
switch (this.currentTab) {
|
switch (this.currentTab) {
|
||||||
case 'users': return this.renderUsersTab();
|
case 'users': return this.renderUsersTab();
|
||||||
|
case 'storage': return this.renderStorageTab();
|
||||||
case 'backups': return this.renderBackupsTab();
|
case 'backups': return this.renderBackupsTab();
|
||||||
case 'ssl': return this.renderSSLTab();
|
case 'ssl': return this.renderSSLTab();
|
||||||
case 'logs': return this.renderLogsTab();
|
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
|
// Backups Tab
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
@ -531,19 +612,27 @@ return view.extend({
|
|||||||
E('th', {}, 'Name'),
|
E('th', {}, 'Name'),
|
||||||
E('th', {}, 'Size'),
|
E('th', {}, 'Size'),
|
||||||
E('th', {}, 'Date'),
|
E('th', {}, 'Date'),
|
||||||
E('th', {}, 'Actions')
|
E('th', { 'style': 'width:150px;' }, 'Actions')
|
||||||
])),
|
])),
|
||||||
E('tbody', {}, this.backups.map(function(b) {
|
E('tbody', {}, this.backups.map(function(b) {
|
||||||
return E('tr', {}, [
|
return E('tr', {}, [
|
||||||
E('td', { 'style': 'font-family:monospace;' }, b.name),
|
E('td', { 'style': 'font-family:monospace;' }, b.name),
|
||||||
E('td', {}, b.size || '-'),
|
E('td', {}, b.size || '-'),
|
||||||
E('td', {}, fmtRelative(b.timestamp)),
|
E('td', {}, fmtRelative(b.timestamp)),
|
||||||
E('td', {}, E('button', {
|
E('td', { 'style': 'display:flex;gap:6px;' }, [
|
||||||
'class': 'kiss-btn kiss-btn-blue',
|
E('button', {
|
||||||
'style': 'padding:4px 10px;font-size:11px;',
|
'class': 'kiss-btn kiss-btn-blue',
|
||||||
'data-name': b.name,
|
'style': 'padding:4px 10px;font-size:11px;',
|
||||||
'click': function(ev) { self.handleRestore(ev.currentTarget.dataset.name); }
|
'data-name': b.name,
|
||||||
}, '⬇️ Restore'))
|
'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() {
|
refreshBackups: function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
callListBackups().then(function(data) {
|
callListBackups().then(function(data) {
|
||||||
@ -839,11 +952,13 @@ return view.extend({
|
|||||||
return Promise.all([
|
return Promise.all([
|
||||||
callStatus(),
|
callStatus(),
|
||||||
callListBackups().catch(function() { return { backups: [] }; }),
|
callListBackups().catch(function() { return { backups: [] }; }),
|
||||||
callListUsers().catch(function() { return { users: [] }; })
|
callListUsers().catch(function() { return { users: [] }; }),
|
||||||
|
callGetStorage().catch(function() { return {}; })
|
||||||
]).then(function(data) {
|
]).then(function(data) {
|
||||||
self.status = data[0] || {};
|
self.status = data[0] || {};
|
||||||
self.backups = (data[1] || {}).backups || [];
|
self.backups = (data[1] || {}).backups || [];
|
||||||
self.users = (data[2] || {}).users || [];
|
self.users = (data[2] || {}).users || [];
|
||||||
|
self.storage = data[3] || {};
|
||||||
|
|
||||||
// Update tab content
|
// Update tab content
|
||||||
var tabContent = document.getElementById('tab-content');
|
var tabContent = document.getElementById('tab-content');
|
||||||
|
|||||||
@ -370,6 +370,74 @@ reset_password() {
|
|||||||
fi
|
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
|
# RPCD list method
|
||||||
list_methods() {
|
list_methods() {
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
@ -378,6 +446,7 @@ list_methods() {
|
|||||||
"get_config": {},
|
"get_config": {},
|
||||||
"save_config": {"http_port": "string", "data_path": "string", "domain": "string", "admin_user": "string", "memory_limit": "string", "upload_max": "string"},
|
"save_config": {"http_port": "string", "data_path": "string", "domain": "string", "admin_user": "string", "memory_limit": "string", "upload_max": "string"},
|
||||||
"install": {},
|
"install": {},
|
||||||
|
"uninstall": {},
|
||||||
"start": {},
|
"start": {},
|
||||||
"stop": {},
|
"stop": {},
|
||||||
"restart": {},
|
"restart": {},
|
||||||
@ -385,12 +454,14 @@ list_methods() {
|
|||||||
"backup": {"name": "string"},
|
"backup": {"name": "string"},
|
||||||
"restore": {"name": "string"},
|
"restore": {"name": "string"},
|
||||||
"list_backups": {},
|
"list_backups": {},
|
||||||
|
"delete_backup": {"name": "string"},
|
||||||
"ssl_enable": {"domain": "string"},
|
"ssl_enable": {"domain": "string"},
|
||||||
"ssl_disable": {},
|
"ssl_disable": {},
|
||||||
"occ": {"command": "string"},
|
"occ": {"command": "string"},
|
||||||
"logs": {},
|
"logs": {},
|
||||||
"list_users": {},
|
"list_users": {},
|
||||||
"reset_password": {"uid": "string", "password": "string"}
|
"reset_password": {"uid": "string", "password": "string"},
|
||||||
|
"get_storage": {}
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
@ -406,6 +477,7 @@ case "$1" in
|
|||||||
get_config) get_config ;;
|
get_config) get_config ;;
|
||||||
save_config) save_config ;;
|
save_config) save_config ;;
|
||||||
install) do_install ;;
|
install) do_install ;;
|
||||||
|
uninstall) do_uninstall ;;
|
||||||
start) do_start ;;
|
start) do_start ;;
|
||||||
stop) do_stop ;;
|
stop) do_stop ;;
|
||||||
restart) do_restart ;;
|
restart) do_restart ;;
|
||||||
@ -413,12 +485,14 @@ case "$1" in
|
|||||||
backup) do_backup ;;
|
backup) do_backup ;;
|
||||||
restore) do_restore ;;
|
restore) do_restore ;;
|
||||||
list_backups) list_backups ;;
|
list_backups) list_backups ;;
|
||||||
|
delete_backup) delete_backup ;;
|
||||||
ssl_enable) do_ssl_enable ;;
|
ssl_enable) do_ssl_enable ;;
|
||||||
ssl_disable) do_ssl_disable ;;
|
ssl_disable) do_ssl_disable ;;
|
||||||
occ) do_occ ;;
|
occ) do_occ ;;
|
||||||
logs) get_logs ;;
|
logs) get_logs ;;
|
||||||
list_users) list_users ;;
|
list_users) list_users ;;
|
||||||
reset_password) reset_password ;;
|
reset_password) reset_password ;;
|
||||||
|
get_storage) get_storage ;;
|
||||||
*) echo '{"error": "Unknown method"}' ;;
|
*) echo '{"error": "Unknown method"}' ;;
|
||||||
esac
|
esac
|
||||||
;;
|
;;
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
"description": "Grant access to Nextcloud LXC app",
|
"description": "Grant access to Nextcloud LXC app",
|
||||||
"read": {
|
"read": {
|
||||||
"ubus": {
|
"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"]
|
"uci": ["nextcloud"]
|
||||||
},
|
},
|
||||||
@ -11,6 +11,7 @@
|
|||||||
"ubus": {
|
"ubus": {
|
||||||
"luci.nextcloud": [
|
"luci.nextcloud": [
|
||||||
"install",
|
"install",
|
||||||
|
"uninstall",
|
||||||
"start",
|
"start",
|
||||||
"stop",
|
"stop",
|
||||||
"restart",
|
"restart",
|
||||||
@ -18,6 +19,7 @@
|
|||||||
"save_config",
|
"save_config",
|
||||||
"backup",
|
"backup",
|
||||||
"restore",
|
"restore",
|
||||||
|
"delete_backup",
|
||||||
"ssl_enable",
|
"ssl_enable",
|
||||||
"ssl_disable",
|
"ssl_disable",
|
||||||
"occ",
|
"occ",
|
||||||
|
|||||||
@ -1,11 +1,24 @@
|
|||||||
# SecuBox Nextcloud
|
# 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
|
## Installation
|
||||||
|
|
||||||
```bash
|
```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
|
## Configuration
|
||||||
@ -15,21 +28,55 @@ UCI config file: `/etc/config/nextcloud`
|
|||||||
```bash
|
```bash
|
||||||
uci set nextcloud.main.enabled='1'
|
uci set nextcloud.main.enabled='1'
|
||||||
uci set nextcloud.main.domain='cloud.example.com'
|
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.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
|
uci commit nextcloud
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## CLI Commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
nextcloudctl start # Start Nextcloud container
|
nextcloudctl install # Create Debian LXC, install Nextcloud stack
|
||||||
nextcloudctl stop # Stop Nextcloud container
|
nextcloudctl uninstall # Remove container (preserves data)
|
||||||
nextcloudctl status # Show service status
|
nextcloudctl update # Update Nextcloud to latest version
|
||||||
nextcloudctl update # Pull latest container image
|
nextcloudctl start # Start Nextcloud service
|
||||||
nextcloudctl occ <cmd> # Run Nextcloud occ command
|
nextcloudctl stop # Stop Nextcloud service
|
||||||
nextcloudctl logs # View container logs
|
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
|
## Features
|
||||||
@ -37,13 +84,42 @@ nextcloudctl logs # View container logs
|
|||||||
- File sync and share with web, desktop, and mobile clients
|
- File sync and share with web, desktop, and mobile clients
|
||||||
- Calendar and contacts (CalDAV/CardDAV)
|
- Calendar and contacts (CalDAV/CardDAV)
|
||||||
- Collaborative document editing
|
- 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
|
## Dependencies
|
||||||
|
|
||||||
- `dockerd`
|
- `lxc` - Container runtime
|
||||||
- `docker`
|
- `lxc-common` - LXC utilities
|
||||||
- `containerd`
|
- `tar`, `wget-ssl`, `unzip`, `xz` - Archive tools
|
||||||
|
- `jsonfilter` - JSON parsing
|
||||||
|
- `openssl-util` - SSL utilities
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
CONFIG="nextcloud"
|
CONFIG="nextcloud"
|
||||||
LXC_NAME="nextcloud"
|
LXC_NAME="nextcloud"
|
||||||
NEXTCLOUD_VERSION="30.0.4"
|
NEXTCLOUD_VERSION="31.0.5"
|
||||||
|
|
||||||
# Paths
|
# Paths
|
||||||
LXC_PATH="/srv/lxc"
|
LXC_PATH="/srv/lxc"
|
||||||
@ -397,9 +397,16 @@ server {
|
|||||||
return 301 /index.php$request_uri;
|
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; }
|
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(?:$|/) {
|
location ~ \.php(?:$|/) {
|
||||||
rewrite ^/(?!index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|ocs-provider\/.+|.+\/richdocumentscode(_arm64)?\/proxy) /index.php$request_uri;
|
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.rootfs.path = dir:$LXC_ROOTFS
|
||||||
lxc.arch = $(uname -m)
|
lxc.arch = $(uname -m)
|
||||||
|
|
||||||
|
# Auto-start on boot
|
||||||
|
lxc.start.auto = 1
|
||||||
|
lxc.start.delay = 5
|
||||||
|
|
||||||
# Network: use host network
|
# Network: use host network
|
||||||
lxc.net.0.type = none
|
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_UPLOAD_MAX=$upload_max
|
||||||
lxc.environment = NEXTCLOUD_TRUSTED_PROXIES=$trusted_proxies
|
lxc.environment = NEXTCLOUD_TRUSTED_PROXIES=$trusted_proxies
|
||||||
|
|
||||||
|
# Memory limit
|
||||||
|
lxc.cgroup2.memory.max = $mem_bytes
|
||||||
|
|
||||||
# Security
|
# Security
|
||||||
lxc.cap.drop = sys_admin sys_module mac_admin mac_override sys_time sys_rawio
|
lxc.cap.drop = sys_admin sys_module mac_admin mac_override sys_time sys_rawio
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user