feat(gotosocial): Add GoToSocial Fediverse server packages
Add secubox-app-gotosocial and luci-app-gotosocial for running a lightweight ActivityPub social network server in LXC container. Features: - gotosocialctl CLI with install, start, stop, user management - LXC container deployment (ARM64) - HAProxy integration via emancipate command - UCI configuration for instance, container, proxy, federation settings - LuCI web interface with overview, users, and settings tabs - Mesh integration support for auto-federation between SecuBox nodes - Backup/restore functionality Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
92b422e900
commit
f20bb1df6b
29
package/secubox/luci-app-gotosocial/Makefile
Normal file
29
package/secubox/luci-app-gotosocial/Makefile
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
|
LUCI_TITLE:=LuCI app for GoToSocial Fediverse Server
|
||||||
|
LUCI_DEPENDS:=+secubox-app-gotosocial +luci-base
|
||||||
|
|
||||||
|
PKG_NAME:=luci-app-gotosocial
|
||||||
|
PKG_VERSION:=0.1.0
|
||||||
|
PKG_RELEASE:=1
|
||||||
|
|
||||||
|
PKG_MAINTAINER:=SecuBox Team
|
||||||
|
PKG_LICENSE:=MIT
|
||||||
|
|
||||||
|
include $(TOPDIR)/feeds/luci/luci.mk
|
||||||
|
|
||||||
|
define Package/luci-app-gotosocial/install
|
||||||
|
$(INSTALL_DIR) $(1)/usr/libexec/rpcd
|
||||||
|
$(INSTALL_BIN) ./root/usr/libexec/rpcd/luci.gotosocial $(1)/usr/libexec/rpcd/luci.gotosocial
|
||||||
|
|
||||||
|
$(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d
|
||||||
|
$(INSTALL_DATA) ./root/usr/share/rpcd/acl.d/luci-app-gotosocial.json $(1)/usr/share/rpcd/acl.d/luci-app-gotosocial.json
|
||||||
|
|
||||||
|
$(INSTALL_DIR) $(1)/usr/share/luci/menu.d
|
||||||
|
$(INSTALL_DATA) ./root/usr/share/luci/menu.d/luci-app-gotosocial.json $(1)/usr/share/luci/menu.d/luci-app-gotosocial.json
|
||||||
|
|
||||||
|
$(INSTALL_DIR) $(1)/www/luci-static/resources/view/gotosocial
|
||||||
|
$(INSTALL_DATA) ./htdocs/luci-static/resources/view/gotosocial/*.js $(1)/www/luci-static/resources/view/gotosocial/
|
||||||
|
endef
|
||||||
|
|
||||||
|
$(eval $(call BuildPackage,luci-app-gotosocial))
|
||||||
@ -0,0 +1,399 @@
|
|||||||
|
'use strict';
|
||||||
|
'require view';
|
||||||
|
'require rpc';
|
||||||
|
'require ui';
|
||||||
|
'require poll';
|
||||||
|
|
||||||
|
var callStatus = rpc.declare({
|
||||||
|
object: 'luci.gotosocial',
|
||||||
|
method: 'status',
|
||||||
|
expect: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
var callInstall = rpc.declare({
|
||||||
|
object: 'luci.gotosocial',
|
||||||
|
method: 'install',
|
||||||
|
expect: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
var callStart = rpc.declare({
|
||||||
|
object: 'luci.gotosocial',
|
||||||
|
method: 'start',
|
||||||
|
expect: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
var callStop = rpc.declare({
|
||||||
|
object: 'luci.gotosocial',
|
||||||
|
method: 'stop',
|
||||||
|
expect: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
var callRestart = rpc.declare({
|
||||||
|
object: 'luci.gotosocial',
|
||||||
|
method: 'restart',
|
||||||
|
expect: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
var callEmancipate = rpc.declare({
|
||||||
|
object: 'luci.gotosocial',
|
||||||
|
method: 'emancipate',
|
||||||
|
params: ['domain', 'tor', 'dns', 'mesh'],
|
||||||
|
expect: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
var callRevoke = rpc.declare({
|
||||||
|
object: 'luci.gotosocial',
|
||||||
|
method: 'revoke',
|
||||||
|
expect: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
var callBackup = rpc.declare({
|
||||||
|
object: 'luci.gotosocial',
|
||||||
|
method: 'backup',
|
||||||
|
expect: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
var callLogs = rpc.declare({
|
||||||
|
object: 'luci.gotosocial',
|
||||||
|
method: 'logs',
|
||||||
|
params: ['lines'],
|
||||||
|
expect: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
return view.extend({
|
||||||
|
status: null,
|
||||||
|
|
||||||
|
load: function() {
|
||||||
|
return callStatus();
|
||||||
|
},
|
||||||
|
|
||||||
|
pollStatus: function() {
|
||||||
|
return callStatus().then(L.bind(function(status) {
|
||||||
|
this.status = status;
|
||||||
|
this.updateStatusDisplay(status);
|
||||||
|
}, this));
|
||||||
|
},
|
||||||
|
|
||||||
|
updateStatusDisplay: function(status) {
|
||||||
|
var containerEl = document.getElementById('container-status');
|
||||||
|
var serviceEl = document.getElementById('service-status');
|
||||||
|
var versionEl = document.getElementById('gts-version');
|
||||||
|
var hostEl = document.getElementById('gts-host');
|
||||||
|
var exposureEl = document.getElementById('exposure-status');
|
||||||
|
|
||||||
|
if (containerEl) {
|
||||||
|
if (status.container_running) {
|
||||||
|
containerEl.textContent = 'Running';
|
||||||
|
containerEl.className = 'badge success';
|
||||||
|
} else if (status.installed) {
|
||||||
|
containerEl.textContent = 'Stopped';
|
||||||
|
containerEl.className = 'badge warning';
|
||||||
|
} else {
|
||||||
|
containerEl.textContent = 'Not Installed';
|
||||||
|
containerEl.className = 'badge danger';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serviceEl) {
|
||||||
|
if (status.service_running) {
|
||||||
|
serviceEl.textContent = 'Running';
|
||||||
|
serviceEl.className = 'badge success';
|
||||||
|
} else {
|
||||||
|
serviceEl.textContent = 'Stopped';
|
||||||
|
serviceEl.className = 'badge warning';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (versionEl) {
|
||||||
|
versionEl.textContent = status.version || '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hostEl) {
|
||||||
|
hostEl.textContent = status.host || '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exposureEl) {
|
||||||
|
var channels = [];
|
||||||
|
if (status.tor_enabled) channels.push('Tor');
|
||||||
|
if (status.dns_enabled) channels.push('DNS/SSL');
|
||||||
|
if (status.mesh_enabled) channels.push('Mesh');
|
||||||
|
exposureEl.textContent = channels.length > 0 ? channels.join(', ') : 'None';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update button states
|
||||||
|
var installBtn = document.getElementById('btn-install');
|
||||||
|
var startBtn = document.getElementById('btn-start');
|
||||||
|
var stopBtn = document.getElementById('btn-stop');
|
||||||
|
var restartBtn = document.getElementById('btn-restart');
|
||||||
|
|
||||||
|
if (installBtn) installBtn.disabled = status.installed;
|
||||||
|
if (startBtn) startBtn.disabled = !status.installed || status.container_running;
|
||||||
|
if (stopBtn) stopBtn.disabled = !status.container_running;
|
||||||
|
if (restartBtn) restartBtn.disabled = !status.container_running;
|
||||||
|
},
|
||||||
|
|
||||||
|
handleInstall: function() {
|
||||||
|
return ui.showModal(_('Install GoToSocial'), [
|
||||||
|
E('p', _('This will download and install GoToSocial in an LXC container. This may take several minutes.')),
|
||||||
|
E('div', { 'class': 'right' }, [
|
||||||
|
E('button', {
|
||||||
|
'class': 'btn',
|
||||||
|
'click': ui.hideModal
|
||||||
|
}, _('Cancel')),
|
||||||
|
' ',
|
||||||
|
E('button', {
|
||||||
|
'class': 'btn cbi-button-action important',
|
||||||
|
'click': ui.createHandlerFn(this, function() {
|
||||||
|
ui.hideModal();
|
||||||
|
ui.showModal(_('Installing...'), [
|
||||||
|
E('p', { 'class': 'spinning' }, _('Installing GoToSocial, please wait...'))
|
||||||
|
]);
|
||||||
|
return callInstall().then(function(res) {
|
||||||
|
ui.hideModal();
|
||||||
|
if (res.success) {
|
||||||
|
ui.addNotification(null, E('p', _('GoToSocial installed successfully')), 'success');
|
||||||
|
} else {
|
||||||
|
ui.addNotification(null, E('p', res.error || _('Installation failed')), 'error');
|
||||||
|
}
|
||||||
|
return callStatus();
|
||||||
|
}).then(L.bind(function(status) {
|
||||||
|
this.updateStatusDisplay(status);
|
||||||
|
}, this));
|
||||||
|
})
|
||||||
|
}, _('Install'))
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleStart: function() {
|
||||||
|
ui.showModal(_('Starting...'), [
|
||||||
|
E('p', { 'class': 'spinning' }, _('Starting GoToSocial...'))
|
||||||
|
]);
|
||||||
|
return callStart().then(L.bind(function(res) {
|
||||||
|
ui.hideModal();
|
||||||
|
ui.addNotification(null, E('p', res.message || _('GoToSocial started')), 'success');
|
||||||
|
return this.pollStatus();
|
||||||
|
}, this));
|
||||||
|
},
|
||||||
|
|
||||||
|
handleStop: function() {
|
||||||
|
return callStop().then(L.bind(function(res) {
|
||||||
|
ui.addNotification(null, E('p', res.message || _('GoToSocial stopped')), 'info');
|
||||||
|
return this.pollStatus();
|
||||||
|
}, this));
|
||||||
|
},
|
||||||
|
|
||||||
|
handleRestart: function() {
|
||||||
|
ui.showModal(_('Restarting...'), [
|
||||||
|
E('p', { 'class': 'spinning' }, _('Restarting GoToSocial...'))
|
||||||
|
]);
|
||||||
|
return callRestart().then(L.bind(function(res) {
|
||||||
|
ui.hideModal();
|
||||||
|
ui.addNotification(null, E('p', res.message || _('GoToSocial restarted')), 'success');
|
||||||
|
return this.pollStatus();
|
||||||
|
}, this));
|
||||||
|
},
|
||||||
|
|
||||||
|
handleEmancipate: function() {
|
||||||
|
var domain = this.status && this.status.host ? this.status.host : '';
|
||||||
|
|
||||||
|
return ui.showModal(_('Expose Service'), [
|
||||||
|
E('p', _('Configure exposure channels for your Fediverse instance.')),
|
||||||
|
E('div', { 'class': 'cbi-value' }, [
|
||||||
|
E('label', { 'class': 'cbi-value-title' }, _('Domain')),
|
||||||
|
E('div', { 'class': 'cbi-value-field' }, [
|
||||||
|
E('input', { 'type': 'text', 'id': 'emancipate-domain', 'class': 'cbi-input-text', 'value': domain })
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
E('div', { 'class': 'cbi-value' }, [
|
||||||
|
E('label', { 'class': 'cbi-value-title' }, _('Channels')),
|
||||||
|
E('div', { 'class': 'cbi-value-field' }, [
|
||||||
|
E('label', { 'style': 'display:block' }, [
|
||||||
|
E('input', { 'type': 'checkbox', 'id': 'emancipate-tor' }), ' ', _('Tor (.onion)')
|
||||||
|
]),
|
||||||
|
E('label', { 'style': 'display:block' }, [
|
||||||
|
E('input', { 'type': 'checkbox', 'id': 'emancipate-dns', 'checked': true }), ' ', _('DNS/SSL (HTTPS)')
|
||||||
|
]),
|
||||||
|
E('label', { 'style': 'display:block' }, [
|
||||||
|
E('input', { 'type': 'checkbox', 'id': 'emancipate-mesh' }), ' ', _('Mesh Network')
|
||||||
|
])
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
E('div', { 'class': 'right' }, [
|
||||||
|
E('button', { 'class': 'btn', 'click': ui.hideModal }, _('Cancel')),
|
||||||
|
' ',
|
||||||
|
E('button', {
|
||||||
|
'class': 'btn cbi-button-action',
|
||||||
|
'click': ui.createHandlerFn(this, function() {
|
||||||
|
var domain = document.getElementById('emancipate-domain').value;
|
||||||
|
var tor = document.getElementById('emancipate-tor').checked;
|
||||||
|
var dns = document.getElementById('emancipate-dns').checked;
|
||||||
|
var mesh = document.getElementById('emancipate-mesh').checked;
|
||||||
|
|
||||||
|
ui.hideModal();
|
||||||
|
ui.showModal(_('Exposing...'), [
|
||||||
|
E('p', { 'class': 'spinning' }, _('Setting up exposure channels...'))
|
||||||
|
]);
|
||||||
|
|
||||||
|
return callEmancipate(domain, tor, dns, mesh).then(function(res) {
|
||||||
|
ui.hideModal();
|
||||||
|
if (res.success) {
|
||||||
|
ui.addNotification(null, E('p', res.message || _('Service exposed successfully')), 'success');
|
||||||
|
} else {
|
||||||
|
ui.addNotification(null, E('p', res.error || _('Exposure failed')), 'error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}, _('Expose'))
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleRevoke: function() {
|
||||||
|
return ui.showModal(_('Revoke Exposure'), [
|
||||||
|
E('p', _('This will remove all exposure channels for GoToSocial. The service will no longer be accessible externally.')),
|
||||||
|
E('div', { 'class': 'right' }, [
|
||||||
|
E('button', { 'class': 'btn', 'click': ui.hideModal }, _('Cancel')),
|
||||||
|
' ',
|
||||||
|
E('button', {
|
||||||
|
'class': 'btn cbi-button-negative',
|
||||||
|
'click': ui.createHandlerFn(this, function() {
|
||||||
|
ui.hideModal();
|
||||||
|
return callRevoke().then(function(res) {
|
||||||
|
ui.addNotification(null, E('p', res.message || _('Exposure revoked')), 'info');
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}, _('Revoke'))
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleBackup: function() {
|
||||||
|
ui.showModal(_('Creating Backup...'), [
|
||||||
|
E('p', { 'class': 'spinning' }, _('Creating backup...'))
|
||||||
|
]);
|
||||||
|
return callBackup().then(function(res) {
|
||||||
|
ui.hideModal();
|
||||||
|
if (res.success) {
|
||||||
|
ui.addNotification(null, E('p', res.message || _('Backup created')), 'success');
|
||||||
|
} else {
|
||||||
|
ui.addNotification(null, E('p', res.error || _('Backup failed')), 'error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
handleViewLogs: function() {
|
||||||
|
return callLogs(100).then(function(res) {
|
||||||
|
var logs = res.logs || [];
|
||||||
|
ui.showModal(_('GoToSocial Logs'), [
|
||||||
|
E('div', { 'style': 'max-height:400px; overflow-y:auto; font-family:monospace; font-size:12px; background:#111; color:#0f0; padding:10px; white-space:pre-wrap;' },
|
||||||
|
logs.join('\n') || _('No logs available')
|
||||||
|
),
|
||||||
|
E('div', { 'class': 'right', 'style': 'margin-top:10px' }, [
|
||||||
|
E('button', { 'class': 'btn', 'click': ui.hideModal }, _('Close'))
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function(status) {
|
||||||
|
this.status = status;
|
||||||
|
|
||||||
|
var view = E('div', { 'class': 'cbi-map' }, [
|
||||||
|
E('h2', _('GoToSocial Fediverse Server')),
|
||||||
|
E('div', { 'class': 'cbi-map-descr' }, _('Lightweight ActivityPub social network server for the Fediverse.')),
|
||||||
|
|
||||||
|
E('div', { 'class': 'cbi-section' }, [
|
||||||
|
E('h3', _('Status')),
|
||||||
|
E('table', { 'class': 'table' }, [
|
||||||
|
E('tr', { 'class': 'tr' }, [
|
||||||
|
E('td', { 'class': 'td', 'width': '200px' }, _('Container')),
|
||||||
|
E('td', { 'class': 'td' }, E('span', { 'id': 'container-status', 'class': 'badge' }, '-'))
|
||||||
|
]),
|
||||||
|
E('tr', { 'class': 'tr' }, [
|
||||||
|
E('td', { 'class': 'td' }, _('Service')),
|
||||||
|
E('td', { 'class': 'td' }, E('span', { 'id': 'service-status', 'class': 'badge' }, '-'))
|
||||||
|
]),
|
||||||
|
E('tr', { 'class': 'tr' }, [
|
||||||
|
E('td', { 'class': 'td' }, _('Version')),
|
||||||
|
E('td', { 'class': 'td' }, E('span', { 'id': 'gts-version' }, '-'))
|
||||||
|
]),
|
||||||
|
E('tr', { 'class': 'tr' }, [
|
||||||
|
E('td', { 'class': 'td' }, _('Domain')),
|
||||||
|
E('td', { 'class': 'td' }, E('span', { 'id': 'gts-host' }, '-'))
|
||||||
|
]),
|
||||||
|
E('tr', { 'class': 'tr' }, [
|
||||||
|
E('td', { 'class': 'td' }, _('Exposure')),
|
||||||
|
E('td', { 'class': 'td' }, E('span', { 'id': 'exposure-status' }, '-'))
|
||||||
|
])
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
|
||||||
|
E('div', { 'class': 'cbi-section' }, [
|
||||||
|
E('h3', _('Actions')),
|
||||||
|
E('div', { 'class': 'cbi-value' }, [
|
||||||
|
E('button', {
|
||||||
|
'id': 'btn-install',
|
||||||
|
'class': 'btn cbi-button-action',
|
||||||
|
'click': ui.createHandlerFn(this, this.handleInstall)
|
||||||
|
}, _('Install')),
|
||||||
|
' ',
|
||||||
|
E('button', {
|
||||||
|
'id': 'btn-start',
|
||||||
|
'class': 'btn cbi-button-action',
|
||||||
|
'click': ui.createHandlerFn(this, this.handleStart)
|
||||||
|
}, _('Start')),
|
||||||
|
' ',
|
||||||
|
E('button', {
|
||||||
|
'id': 'btn-stop',
|
||||||
|
'class': 'btn cbi-button-neutral',
|
||||||
|
'click': ui.createHandlerFn(this, this.handleStop)
|
||||||
|
}, _('Stop')),
|
||||||
|
' ',
|
||||||
|
E('button', {
|
||||||
|
'id': 'btn-restart',
|
||||||
|
'class': 'btn cbi-button-neutral',
|
||||||
|
'click': ui.createHandlerFn(this, this.handleRestart)
|
||||||
|
}, _('Restart'))
|
||||||
|
]),
|
||||||
|
E('div', { 'class': 'cbi-value', 'style': 'margin-top:10px' }, [
|
||||||
|
E('button', {
|
||||||
|
'class': 'btn cbi-button-action',
|
||||||
|
'click': ui.createHandlerFn(this, this.handleEmancipate)
|
||||||
|
}, _('Expose Service')),
|
||||||
|
' ',
|
||||||
|
E('button', {
|
||||||
|
'class': 'btn cbi-button-neutral',
|
||||||
|
'click': ui.createHandlerFn(this, this.handleRevoke)
|
||||||
|
}, _('Revoke Exposure')),
|
||||||
|
' ',
|
||||||
|
E('button', {
|
||||||
|
'class': 'btn cbi-button-neutral',
|
||||||
|
'click': ui.createHandlerFn(this, this.handleBackup)
|
||||||
|
}, _('Backup')),
|
||||||
|
' ',
|
||||||
|
E('button', {
|
||||||
|
'class': 'btn cbi-button-neutral',
|
||||||
|
'click': ui.createHandlerFn(this, this.handleViewLogs)
|
||||||
|
}, _('View Logs'))
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
|
||||||
|
E('style', {}, `
|
||||||
|
.badge { padding: 2px 8px; border-radius: 3px; font-weight: bold; }
|
||||||
|
.badge.success { background: #4CAF50; color: white; }
|
||||||
|
.badge.warning { background: #FF9800; color: white; }
|
||||||
|
.badge.danger { background: #f44336; color: white; }
|
||||||
|
`)
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.updateStatusDisplay(status);
|
||||||
|
poll.add(L.bind(this.pollStatus, this), 5);
|
||||||
|
|
||||||
|
return view;
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSaveApply: null,
|
||||||
|
handleSave: null,
|
||||||
|
handleReset: null
|
||||||
|
});
|
||||||
@ -0,0 +1,143 @@
|
|||||||
|
'use strict';
|
||||||
|
'require view';
|
||||||
|
'require form';
|
||||||
|
'require rpc';
|
||||||
|
'require uci';
|
||||||
|
'require ui';
|
||||||
|
|
||||||
|
return view.extend({
|
||||||
|
load: function() {
|
||||||
|
return uci.load('gotosocial');
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
var m, s, o;
|
||||||
|
|
||||||
|
m = new form.Map('gotosocial', _('GoToSocial Settings'),
|
||||||
|
_('Configure your Fediverse instance settings.'));
|
||||||
|
|
||||||
|
// Main settings
|
||||||
|
s = m.section(form.NamedSection, 'main', 'gotosocial', _('Instance Settings'));
|
||||||
|
s.addremove = false;
|
||||||
|
|
||||||
|
o = s.option(form.Flag, 'enabled', _('Enabled'),
|
||||||
|
_('Enable GoToSocial service'));
|
||||||
|
o.rmempty = false;
|
||||||
|
|
||||||
|
o = s.option(form.Value, 'host', _('Domain'),
|
||||||
|
_('The domain name for your instance (e.g., social.example.com)'));
|
||||||
|
o.rmempty = false;
|
||||||
|
o.placeholder = 'social.example.com';
|
||||||
|
|
||||||
|
o = s.option(form.Value, 'port', _('Port'),
|
||||||
|
_('Internal port for GoToSocial'));
|
||||||
|
o.datatype = 'port';
|
||||||
|
o.default = '8484';
|
||||||
|
|
||||||
|
o = s.option(form.ListValue, 'protocol', _('Protocol'),
|
||||||
|
_('Protocol for external access'));
|
||||||
|
o.value('https', 'HTTPS');
|
||||||
|
o.value('http', 'HTTP');
|
||||||
|
o.default = 'https';
|
||||||
|
|
||||||
|
o = s.option(form.Value, 'bind_address', _('Bind Address'),
|
||||||
|
_('IP address to listen on'));
|
||||||
|
o.default = '0.0.0.0';
|
||||||
|
|
||||||
|
o = s.option(form.Value, 'instance_name', _('Instance Name'),
|
||||||
|
_('Display name for your instance'));
|
||||||
|
o.placeholder = 'SecuBox Social';
|
||||||
|
|
||||||
|
o = s.option(form.TextValue, 'instance_description', _('Instance Description'),
|
||||||
|
_('Description shown on the instance landing page'));
|
||||||
|
o.rows = 3;
|
||||||
|
|
||||||
|
// Registration settings
|
||||||
|
s = m.section(form.NamedSection, 'main', 'gotosocial', _('Registration'));
|
||||||
|
|
||||||
|
o = s.option(form.Flag, 'accounts_registration_open', _('Open Registration'),
|
||||||
|
_('Allow new users to sign up'));
|
||||||
|
o.default = '0';
|
||||||
|
|
||||||
|
o = s.option(form.Flag, 'accounts_approval_required', _('Require Approval'),
|
||||||
|
_('New registrations require admin approval'));
|
||||||
|
o.default = '1';
|
||||||
|
|
||||||
|
// LXC Container settings
|
||||||
|
s = m.section(form.NamedSection, 'container', 'lxc', _('Container Settings'));
|
||||||
|
s.addremove = false;
|
||||||
|
|
||||||
|
o = s.option(form.Value, 'rootfs_path', _('Container Root'),
|
||||||
|
_('Path to LXC container rootfs'));
|
||||||
|
o.default = '/srv/lxc/gotosocial/rootfs';
|
||||||
|
o.readonly = true;
|
||||||
|
|
||||||
|
o = s.option(form.Value, 'data_path', _('Data Path'),
|
||||||
|
_('Path to persistent data storage'));
|
||||||
|
o.default = '/srv/gotosocial';
|
||||||
|
o.readonly = true;
|
||||||
|
|
||||||
|
o = s.option(form.Value, 'memory_limit', _('Memory Limit'),
|
||||||
|
_('Maximum memory for container'));
|
||||||
|
o.default = '512M';
|
||||||
|
|
||||||
|
o = s.option(form.Value, 'version', _('GoToSocial Version'),
|
||||||
|
_('Version to install'));
|
||||||
|
o.default = '0.17.3';
|
||||||
|
|
||||||
|
// HAProxy integration
|
||||||
|
s = m.section(form.NamedSection, 'proxy', 'haproxy', _('HAProxy Integration'));
|
||||||
|
s.addremove = false;
|
||||||
|
|
||||||
|
o = s.option(form.Flag, 'enabled', _('Enable HAProxy'),
|
||||||
|
_('Route traffic through HAProxy'));
|
||||||
|
o.default = '0';
|
||||||
|
|
||||||
|
o = s.option(form.Value, 'vhost_domain', _('Virtual Host Domain'),
|
||||||
|
_('Domain for HAProxy vhost (usually same as main domain)'));
|
||||||
|
|
||||||
|
o = s.option(form.Flag, 'ssl_enabled', _('Enable SSL'),
|
||||||
|
_('Enable HTTPS via HAProxy'));
|
||||||
|
o.default = '1';
|
||||||
|
|
||||||
|
o = s.option(form.Flag, 'acme_enabled', _('Enable ACME'),
|
||||||
|
_('Automatically provision SSL certificates'));
|
||||||
|
o.default = '1';
|
||||||
|
|
||||||
|
// Federation settings
|
||||||
|
s = m.section(form.NamedSection, 'federation', 'federation', _('Federation'));
|
||||||
|
s.addremove = false;
|
||||||
|
|
||||||
|
o = s.option(form.Flag, 'enabled', _('Enable Federation'),
|
||||||
|
_('Allow communication with other Fediverse instances'));
|
||||||
|
o.default = '1';
|
||||||
|
|
||||||
|
o = s.option(form.Flag, 'auto_approve_followers', _('Auto-Approve Followers'),
|
||||||
|
_('Automatically approve follow requests'));
|
||||||
|
o.default = '0';
|
||||||
|
|
||||||
|
o = s.option(form.DynamicList, 'blocked_domains', _('Blocked Domains'),
|
||||||
|
_('Instances to block from federation'));
|
||||||
|
|
||||||
|
o = s.option(form.DynamicList, 'allowed_domains', _('Allowed Domains'),
|
||||||
|
_('If set, only federate with these instances (allowlist mode)'));
|
||||||
|
|
||||||
|
// Mesh settings
|
||||||
|
s = m.section(form.NamedSection, 'mesh', 'mesh', _('SecuBox Mesh'));
|
||||||
|
s.addremove = false;
|
||||||
|
|
||||||
|
o = s.option(form.Flag, 'auto_federate', _('Auto-Federate with Mesh'),
|
||||||
|
_('Automatically federate with other SecuBox nodes'));
|
||||||
|
o.default = '1';
|
||||||
|
|
||||||
|
o = s.option(form.Flag, 'announce_to_peers', _('Announce to Peers'),
|
||||||
|
_('Publish this instance to mesh network'));
|
||||||
|
o.default = '1';
|
||||||
|
|
||||||
|
o = s.option(form.Flag, 'share_blocklist', _('Share Blocklist'),
|
||||||
|
_('Share and sync blocked domains with mesh peers'));
|
||||||
|
o.default = '1';
|
||||||
|
|
||||||
|
return m.render();
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,258 @@
|
|||||||
|
'use strict';
|
||||||
|
'require view';
|
||||||
|
'require rpc';
|
||||||
|
'require ui';
|
||||||
|
'require poll';
|
||||||
|
|
||||||
|
var callUsers = rpc.declare({
|
||||||
|
object: 'luci.gotosocial',
|
||||||
|
method: 'users',
|
||||||
|
expect: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
var callCreateUser = rpc.declare({
|
||||||
|
object: 'luci.gotosocial',
|
||||||
|
method: 'create_user',
|
||||||
|
params: ['username', 'email', 'password', 'admin'],
|
||||||
|
expect: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
var callDeleteUser = rpc.declare({
|
||||||
|
object: 'luci.gotosocial',
|
||||||
|
method: 'delete_user',
|
||||||
|
params: ['username'],
|
||||||
|
expect: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
var callPromoteUser = rpc.declare({
|
||||||
|
object: 'luci.gotosocial',
|
||||||
|
method: 'promote_user',
|
||||||
|
params: ['username'],
|
||||||
|
expect: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
var callDemoteUser = rpc.declare({
|
||||||
|
object: 'luci.gotosocial',
|
||||||
|
method: 'demote_user',
|
||||||
|
params: ['username'],
|
||||||
|
expect: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
return view.extend({
|
||||||
|
users: [],
|
||||||
|
|
||||||
|
load: function() {
|
||||||
|
return callUsers();
|
||||||
|
},
|
||||||
|
|
||||||
|
pollUsers: function() {
|
||||||
|
return callUsers().then(L.bind(function(res) {
|
||||||
|
this.users = res.users || [];
|
||||||
|
this.updateUserTable();
|
||||||
|
}, this));
|
||||||
|
},
|
||||||
|
|
||||||
|
updateUserTable: function() {
|
||||||
|
var tbody = document.getElementById('users-tbody');
|
||||||
|
if (!tbody) return;
|
||||||
|
|
||||||
|
while (tbody.firstChild) {
|
||||||
|
tbody.removeChild(tbody.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.users.length === 0) {
|
||||||
|
tbody.appendChild(E('tr', { 'class': 'tr' }, [
|
||||||
|
E('td', { 'class': 'td', 'colspan': '5', 'style': 'text-align:center' }, _('No users found'))
|
||||||
|
]));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.users.forEach(L.bind(function(user) {
|
||||||
|
var row = E('tr', { 'class': 'tr' }, [
|
||||||
|
E('td', { 'class': 'td' }, user.username || '-'),
|
||||||
|
E('td', { 'class': 'td' }, user.email || '-'),
|
||||||
|
E('td', { 'class': 'td' }, user.admin ?
|
||||||
|
E('span', { 'class': 'badge success' }, _('Admin')) :
|
||||||
|
E('span', { 'class': 'badge' }, _('User'))
|
||||||
|
),
|
||||||
|
E('td', { 'class': 'td' }, user.confirmed ?
|
||||||
|
E('span', { 'class': 'badge success' }, _('Confirmed')) :
|
||||||
|
E('span', { 'class': 'badge warning' }, _('Pending'))
|
||||||
|
),
|
||||||
|
E('td', { 'class': 'td' }, [
|
||||||
|
user.admin ?
|
||||||
|
E('button', {
|
||||||
|
'class': 'btn cbi-button-neutral',
|
||||||
|
'click': ui.createHandlerFn(this, this.handleDemote, user.username)
|
||||||
|
}, _('Demote')) :
|
||||||
|
E('button', {
|
||||||
|
'class': 'btn cbi-button-action',
|
||||||
|
'click': ui.createHandlerFn(this, this.handlePromote, user.username)
|
||||||
|
}, _('Promote')),
|
||||||
|
' ',
|
||||||
|
E('button', {
|
||||||
|
'class': 'btn cbi-button-negative',
|
||||||
|
'click': ui.createHandlerFn(this, this.handleDelete, user.username)
|
||||||
|
}, _('Delete'))
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
tbody.appendChild(row);
|
||||||
|
}, this));
|
||||||
|
},
|
||||||
|
|
||||||
|
handleCreate: function() {
|
||||||
|
return ui.showModal(_('Create User'), [
|
||||||
|
E('div', { 'class': 'cbi-value' }, [
|
||||||
|
E('label', { 'class': 'cbi-value-title' }, _('Username')),
|
||||||
|
E('div', { 'class': 'cbi-value-field' }, [
|
||||||
|
E('input', { 'type': 'text', 'id': 'new-username', 'class': 'cbi-input-text', 'placeholder': 'johndoe' })
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
E('div', { 'class': 'cbi-value' }, [
|
||||||
|
E('label', { 'class': 'cbi-value-title' }, _('Email')),
|
||||||
|
E('div', { 'class': 'cbi-value-field' }, [
|
||||||
|
E('input', { 'type': 'email', 'id': 'new-email', 'class': 'cbi-input-text', 'placeholder': 'john@example.com' })
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
E('div', { 'class': 'cbi-value' }, [
|
||||||
|
E('label', { 'class': 'cbi-value-title' }, _('Password')),
|
||||||
|
E('div', { 'class': 'cbi-value-field' }, [
|
||||||
|
E('input', { 'type': 'password', 'id': 'new-password', 'class': 'cbi-input-text' })
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
E('div', { 'class': 'cbi-value' }, [
|
||||||
|
E('label', { 'class': 'cbi-value-title' }, _('Admin')),
|
||||||
|
E('div', { 'class': 'cbi-value-field' }, [
|
||||||
|
E('input', { 'type': 'checkbox', 'id': 'new-admin' }),
|
||||||
|
' ', _('Grant admin privileges')
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
E('div', { 'class': 'right' }, [
|
||||||
|
E('button', { 'class': 'btn', 'click': ui.hideModal }, _('Cancel')),
|
||||||
|
' ',
|
||||||
|
E('button', {
|
||||||
|
'class': 'btn cbi-button-action',
|
||||||
|
'click': ui.createHandlerFn(this, function() {
|
||||||
|
var username = document.getElementById('new-username').value;
|
||||||
|
var email = document.getElementById('new-email').value;
|
||||||
|
var password = document.getElementById('new-password').value;
|
||||||
|
var admin = document.getElementById('new-admin').checked;
|
||||||
|
|
||||||
|
if (!username || !email || !password) {
|
||||||
|
ui.addNotification(null, E('p', _('All fields are required')), 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.hideModal();
|
||||||
|
ui.showModal(_('Creating User...'), [
|
||||||
|
E('p', { 'class': 'spinning' }, _('Creating user...'))
|
||||||
|
]);
|
||||||
|
|
||||||
|
return callCreateUser(username, email, password, admin).then(L.bind(function(res) {
|
||||||
|
ui.hideModal();
|
||||||
|
if (res.success) {
|
||||||
|
ui.addNotification(null, E('p', res.message || _('User created successfully')), 'success');
|
||||||
|
return this.pollUsers();
|
||||||
|
} else {
|
||||||
|
ui.addNotification(null, E('p', res.error || _('Failed to create user')), 'error');
|
||||||
|
}
|
||||||
|
}, this));
|
||||||
|
})
|
||||||
|
}, _('Create'))
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleDelete: function(username) {
|
||||||
|
return ui.showModal(_('Delete User'), [
|
||||||
|
E('p', _('Are you sure you want to delete user "%s"?').format(username)),
|
||||||
|
E('p', { 'class': 'alert-message warning' }, _('This action cannot be undone. All posts and data will be lost.')),
|
||||||
|
E('div', { 'class': 'right' }, [
|
||||||
|
E('button', { 'class': 'btn', 'click': ui.hideModal }, _('Cancel')),
|
||||||
|
' ',
|
||||||
|
E('button', {
|
||||||
|
'class': 'btn cbi-button-negative',
|
||||||
|
'click': ui.createHandlerFn(this, function() {
|
||||||
|
ui.hideModal();
|
||||||
|
return callDeleteUser(username).then(L.bind(function(res) {
|
||||||
|
if (res.success) {
|
||||||
|
ui.addNotification(null, E('p', res.message || _('User deleted')), 'success');
|
||||||
|
return this.pollUsers();
|
||||||
|
} else {
|
||||||
|
ui.addNotification(null, E('p', res.error || _('Failed to delete user')), 'error');
|
||||||
|
}
|
||||||
|
}, this));
|
||||||
|
})
|
||||||
|
}, _('Delete'))
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
|
||||||
|
handlePromote: function(username) {
|
||||||
|
return callPromoteUser(username).then(L.bind(function(res) {
|
||||||
|
if (res.success) {
|
||||||
|
ui.addNotification(null, E('p', res.message || _('User promoted')), 'success');
|
||||||
|
return this.pollUsers();
|
||||||
|
} else {
|
||||||
|
ui.addNotification(null, E('p', res.error || _('Failed to promote user')), 'error');
|
||||||
|
}
|
||||||
|
}, this));
|
||||||
|
},
|
||||||
|
|
||||||
|
handleDemote: function(username) {
|
||||||
|
return callDemoteUser(username).then(L.bind(function(res) {
|
||||||
|
if (res.success) {
|
||||||
|
ui.addNotification(null, E('p', res.message || _('User demoted')), 'success');
|
||||||
|
return this.pollUsers();
|
||||||
|
} else {
|
||||||
|
ui.addNotification(null, E('p', res.error || _('Failed to demote user')), 'error');
|
||||||
|
}
|
||||||
|
}, this));
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function(data) {
|
||||||
|
this.users = data.users || [];
|
||||||
|
|
||||||
|
var view = E('div', { 'class': 'cbi-map' }, [
|
||||||
|
E('h2', _('GoToSocial Users')),
|
||||||
|
E('div', { 'class': 'cbi-map-descr' }, _('Manage user accounts for your Fediverse instance.')),
|
||||||
|
|
||||||
|
E('div', { 'class': 'cbi-section' }, [
|
||||||
|
E('div', { 'style': 'margin-bottom:10px' }, [
|
||||||
|
E('button', {
|
||||||
|
'class': 'btn cbi-button-action',
|
||||||
|
'click': ui.createHandlerFn(this, this.handleCreate)
|
||||||
|
}, _('Create User'))
|
||||||
|
]),
|
||||||
|
|
||||||
|
E('table', { 'class': 'table' }, [
|
||||||
|
E('thead', {}, [
|
||||||
|
E('tr', { 'class': 'tr' }, [
|
||||||
|
E('th', { 'class': 'th' }, _('Username')),
|
||||||
|
E('th', { 'class': 'th' }, _('Email')),
|
||||||
|
E('th', { 'class': 'th' }, _('Role')),
|
||||||
|
E('th', { 'class': 'th' }, _('Status')),
|
||||||
|
E('th', { 'class': 'th' }, _('Actions'))
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
E('tbody', { 'id': 'users-tbody' })
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
|
||||||
|
E('style', {}, `
|
||||||
|
.badge { padding: 2px 8px; border-radius: 3px; background: #666; color: white; }
|
||||||
|
.badge.success { background: #4CAF50; }
|
||||||
|
.badge.warning { background: #FF9800; }
|
||||||
|
`)
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.updateUserTable();
|
||||||
|
poll.add(L.bind(this.pollUsers, this), 10);
|
||||||
|
|
||||||
|
return view;
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSaveApply: null,
|
||||||
|
handleSave: null,
|
||||||
|
handleReset: null
|
||||||
|
});
|
||||||
@ -0,0 +1,264 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
. /lib/functions.sh
|
||||||
|
. /usr/share/libubox/jshn.sh
|
||||||
|
|
||||||
|
GOTOSOCIALCTL="/usr/sbin/gotosocialctl"
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
list)
|
||||||
|
echo '{"status":{},"install":{},"start":{},"stop":{},"restart":{},"users":{},"create_user":{"username":"str","email":"str","password":"str","admin":"bool"},"delete_user":{"username":"str"},"promote_user":{"username":"str"},"demote_user":{"username":"str"},"get_config":{},"save_config":{"host":"str","port":"int","protocol":"str","instance_name":"str","instance_description":"str","accounts_registration_open":"bool","accounts_approval_required":"bool"},"emancipate":{"domain":"str","tor":"bool","dns":"bool","mesh":"bool"},"revoke":{},"backup":{},"logs":{"lines":"int"}}'
|
||||||
|
;;
|
||||||
|
call)
|
||||||
|
case "$2" in
|
||||||
|
status)
|
||||||
|
$GOTOSOCIALCTL status
|
||||||
|
;;
|
||||||
|
install)
|
||||||
|
result=$($GOTOSOCIALCTL install 2>&1)
|
||||||
|
json_init
|
||||||
|
if echo "$result" | grep -q "successfully\|already installed"; then
|
||||||
|
json_add_boolean "success" 1
|
||||||
|
json_add_string "message" "$result"
|
||||||
|
else
|
||||||
|
json_add_boolean "success" 0
|
||||||
|
json_add_string "error" "$result"
|
||||||
|
fi
|
||||||
|
json_dump
|
||||||
|
;;
|
||||||
|
start)
|
||||||
|
result=$($GOTOSOCIALCTL start 2>&1)
|
||||||
|
json_init
|
||||||
|
json_add_boolean "success" 1
|
||||||
|
json_add_string "message" "$result"
|
||||||
|
json_dump
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
result=$($GOTOSOCIALCTL stop 2>&1)
|
||||||
|
json_init
|
||||||
|
json_add_boolean "success" 1
|
||||||
|
json_add_string "message" "$result"
|
||||||
|
json_dump
|
||||||
|
;;
|
||||||
|
restart)
|
||||||
|
$GOTOSOCIALCTL stop 2>/dev/null
|
||||||
|
sleep 2
|
||||||
|
result=$($GOTOSOCIALCTL start 2>&1)
|
||||||
|
json_init
|
||||||
|
json_add_boolean "success" 1
|
||||||
|
json_add_string "message" "$result"
|
||||||
|
json_dump
|
||||||
|
;;
|
||||||
|
users)
|
||||||
|
$GOTOSOCIALCTL users
|
||||||
|
;;
|
||||||
|
create_user)
|
||||||
|
read input
|
||||||
|
json_load "$input"
|
||||||
|
json_get_var username username
|
||||||
|
json_get_var email email
|
||||||
|
json_get_var password password
|
||||||
|
json_get_var admin admin
|
||||||
|
|
||||||
|
if [ -z "$username" ] || [ -z "$email" ] || [ -z "$password" ]; then
|
||||||
|
json_init
|
||||||
|
json_add_boolean "success" 0
|
||||||
|
json_add_string "error" "Missing required fields: username, email, password"
|
||||||
|
json_dump
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
admin_flag=""
|
||||||
|
[ "$admin" = "1" ] || [ "$admin" = "true" ] && admin_flag="--admin"
|
||||||
|
|
||||||
|
result=$($GOTOSOCIALCTL user create "$username" "$email" "$password" $admin_flag 2>&1)
|
||||||
|
json_init
|
||||||
|
if echo "$result" | grep -qi "success\|created"; then
|
||||||
|
json_add_boolean "success" 1
|
||||||
|
json_add_string "message" "User $username created successfully"
|
||||||
|
else
|
||||||
|
json_add_boolean "success" 0
|
||||||
|
json_add_string "error" "$result"
|
||||||
|
fi
|
||||||
|
json_dump
|
||||||
|
;;
|
||||||
|
delete_user)
|
||||||
|
read input
|
||||||
|
json_load "$input"
|
||||||
|
json_get_var username username
|
||||||
|
|
||||||
|
if [ -z "$username" ]; then
|
||||||
|
json_init
|
||||||
|
json_add_boolean "success" 0
|
||||||
|
json_add_string "error" "Username required"
|
||||||
|
json_dump
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
result=$($GOTOSOCIALCTL user delete "$username" 2>&1)
|
||||||
|
json_init
|
||||||
|
json_add_boolean "success" 1
|
||||||
|
json_add_string "message" "User $username deleted"
|
||||||
|
json_dump
|
||||||
|
;;
|
||||||
|
promote_user)
|
||||||
|
read input
|
||||||
|
json_load "$input"
|
||||||
|
json_get_var username username
|
||||||
|
|
||||||
|
result=$($GOTOSOCIALCTL user promote "$username" 2>&1)
|
||||||
|
json_init
|
||||||
|
json_add_boolean "success" 1
|
||||||
|
json_add_string "message" "User $username promoted to admin"
|
||||||
|
json_dump
|
||||||
|
;;
|
||||||
|
demote_user)
|
||||||
|
read input
|
||||||
|
json_load "$input"
|
||||||
|
json_get_var username username
|
||||||
|
|
||||||
|
result=$($GOTOSOCIALCTL user demote "$username" 2>&1)
|
||||||
|
json_init
|
||||||
|
json_add_boolean "success" 1
|
||||||
|
json_add_string "message" "User $username demoted from admin"
|
||||||
|
json_dump
|
||||||
|
;;
|
||||||
|
get_config)
|
||||||
|
json_init
|
||||||
|
|
||||||
|
config_load gotosocial
|
||||||
|
|
||||||
|
# Main config
|
||||||
|
config_get enabled main enabled '0'
|
||||||
|
config_get host main host 'social.example.com'
|
||||||
|
config_get port main port '8484'
|
||||||
|
config_get protocol main protocol 'https'
|
||||||
|
config_get bind_address main bind_address '0.0.0.0'
|
||||||
|
config_get instance_name main instance_name 'SecuBox Social'
|
||||||
|
config_get instance_description main instance_description ''
|
||||||
|
config_get accounts_registration_open main accounts_registration_open '0'
|
||||||
|
config_get accounts_approval_required main accounts_approval_required '1'
|
||||||
|
|
||||||
|
json_add_string "enabled" "$enabled"
|
||||||
|
json_add_string "host" "$host"
|
||||||
|
json_add_string "port" "$port"
|
||||||
|
json_add_string "protocol" "$protocol"
|
||||||
|
json_add_string "bind_address" "$bind_address"
|
||||||
|
json_add_string "instance_name" "$instance_name"
|
||||||
|
json_add_string "instance_description" "$instance_description"
|
||||||
|
json_add_string "accounts_registration_open" "$accounts_registration_open"
|
||||||
|
json_add_string "accounts_approval_required" "$accounts_approval_required"
|
||||||
|
|
||||||
|
# LXC config
|
||||||
|
config_get rootfs_path container rootfs_path '/srv/lxc/gotosocial/rootfs'
|
||||||
|
config_get data_path container data_path '/srv/gotosocial'
|
||||||
|
config_get memory_limit container memory_limit '512M'
|
||||||
|
config_get version container version '0.17.3'
|
||||||
|
|
||||||
|
json_add_string "rootfs_path" "$rootfs_path"
|
||||||
|
json_add_string "data_path" "$data_path"
|
||||||
|
json_add_string "memory_limit" "$memory_limit"
|
||||||
|
json_add_string "version" "$version"
|
||||||
|
|
||||||
|
# HAProxy config
|
||||||
|
config_get proxy_enabled proxy enabled '0'
|
||||||
|
config_get vhost_domain proxy vhost_domain ''
|
||||||
|
config_get ssl_enabled proxy ssl_enabled '1'
|
||||||
|
config_get acme_enabled proxy acme_enabled '1'
|
||||||
|
|
||||||
|
json_add_string "proxy_enabled" "$proxy_enabled"
|
||||||
|
json_add_string "vhost_domain" "$vhost_domain"
|
||||||
|
json_add_string "ssl_enabled" "$ssl_enabled"
|
||||||
|
json_add_string "acme_enabled" "$acme_enabled"
|
||||||
|
|
||||||
|
# Federation config
|
||||||
|
config_get federation_enabled federation enabled '1'
|
||||||
|
config_get auto_approve federation auto_approve_followers '0'
|
||||||
|
|
||||||
|
json_add_string "federation_enabled" "$federation_enabled"
|
||||||
|
json_add_string "auto_approve_followers" "$auto_approve"
|
||||||
|
|
||||||
|
json_dump
|
||||||
|
;;
|
||||||
|
save_config)
|
||||||
|
read input
|
||||||
|
json_load "$input"
|
||||||
|
|
||||||
|
json_get_var host host
|
||||||
|
json_get_var port port
|
||||||
|
json_get_var protocol protocol
|
||||||
|
json_get_var instance_name instance_name
|
||||||
|
json_get_var instance_description instance_description
|
||||||
|
json_get_var accounts_registration_open accounts_registration_open
|
||||||
|
json_get_var accounts_approval_required accounts_approval_required
|
||||||
|
|
||||||
|
[ -n "$host" ] && uci set gotosocial.main.host="$host"
|
||||||
|
[ -n "$port" ] && uci set gotosocial.main.port="$port"
|
||||||
|
[ -n "$protocol" ] && uci set gotosocial.main.protocol="$protocol"
|
||||||
|
[ -n "$instance_name" ] && uci set gotosocial.main.instance_name="$instance_name"
|
||||||
|
[ -n "$instance_description" ] && uci set gotosocial.main.instance_description="$instance_description"
|
||||||
|
[ -n "$accounts_registration_open" ] && uci set gotosocial.main.accounts_registration_open="$accounts_registration_open"
|
||||||
|
[ -n "$accounts_approval_required" ] && uci set gotosocial.main.accounts_approval_required="$accounts_approval_required"
|
||||||
|
|
||||||
|
uci commit gotosocial
|
||||||
|
|
||||||
|
json_init
|
||||||
|
json_add_boolean "success" 1
|
||||||
|
json_add_string "message" "Configuration saved"
|
||||||
|
json_dump
|
||||||
|
;;
|
||||||
|
emancipate)
|
||||||
|
read input
|
||||||
|
json_load "$input"
|
||||||
|
json_get_var domain domain
|
||||||
|
json_get_var tor tor
|
||||||
|
json_get_var dns dns
|
||||||
|
json_get_var mesh mesh
|
||||||
|
|
||||||
|
args=""
|
||||||
|
[ "$tor" = "1" ] || [ "$tor" = "true" ] && args="$args --tor"
|
||||||
|
[ "$dns" = "1" ] || [ "$dns" = "true" ] && args="$args --dns"
|
||||||
|
[ "$mesh" = "1" ] || [ "$mesh" = "true" ] && args="$args --mesh"
|
||||||
|
[ -z "$args" ] && args="--all"
|
||||||
|
|
||||||
|
result=$($GOTOSOCIALCTL emancipate "$domain" $args 2>&1)
|
||||||
|
json_init
|
||||||
|
if echo "$result" | grep -qi "success\|complete\|enabled"; then
|
||||||
|
json_add_boolean "success" 1
|
||||||
|
json_add_string "message" "$result"
|
||||||
|
else
|
||||||
|
json_add_boolean "success" 0
|
||||||
|
json_add_string "error" "$result"
|
||||||
|
fi
|
||||||
|
json_dump
|
||||||
|
;;
|
||||||
|
revoke)
|
||||||
|
result=$($GOTOSOCIALCTL revoke 2>&1)
|
||||||
|
json_init
|
||||||
|
json_add_boolean "success" 1
|
||||||
|
json_add_string "message" "Exposure revoked"
|
||||||
|
json_dump
|
||||||
|
;;
|
||||||
|
backup)
|
||||||
|
result=$($GOTOSOCIALCTL backup 2>&1)
|
||||||
|
json_init
|
||||||
|
if echo "$result" | grep -qi "backup\|success"; then
|
||||||
|
json_add_boolean "success" 1
|
||||||
|
json_add_string "message" "$result"
|
||||||
|
else
|
||||||
|
json_add_boolean "success" 0
|
||||||
|
json_add_string "error" "$result"
|
||||||
|
fi
|
||||||
|
json_dump
|
||||||
|
;;
|
||||||
|
logs)
|
||||||
|
read input
|
||||||
|
json_load "$input"
|
||||||
|
json_get_var lines lines
|
||||||
|
[ -z "$lines" ] && lines=50
|
||||||
|
|
||||||
|
$GOTOSOCIALCTL logs "$lines"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
esac
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"admin/services/gotosocial": {
|
||||||
|
"title": "GoToSocial",
|
||||||
|
"order": 60,
|
||||||
|
"action": {
|
||||||
|
"type": "firstchild"
|
||||||
|
},
|
||||||
|
"depends": {
|
||||||
|
"acl": ["luci-app-gotosocial"],
|
||||||
|
"uci": {"gotosocial": true}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"admin/services/gotosocial/overview": {
|
||||||
|
"title": "Overview",
|
||||||
|
"order": 1,
|
||||||
|
"action": {
|
||||||
|
"type": "view",
|
||||||
|
"path": "gotosocial/overview"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"admin/services/gotosocial/users": {
|
||||||
|
"title": "Users",
|
||||||
|
"order": 2,
|
||||||
|
"action": {
|
||||||
|
"type": "view",
|
||||||
|
"path": "gotosocial/users"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"admin/services/gotosocial/settings": {
|
||||||
|
"title": "Settings",
|
||||||
|
"order": 3,
|
||||||
|
"action": {
|
||||||
|
"type": "view",
|
||||||
|
"path": "gotosocial/settings"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"luci-app-gotosocial": {
|
||||||
|
"description": "Grant access to GoToSocial Fediverse Server",
|
||||||
|
"read": {
|
||||||
|
"ubus": {
|
||||||
|
"luci.gotosocial": ["status", "users", "get_config", "logs"]
|
||||||
|
},
|
||||||
|
"uci": ["gotosocial"]
|
||||||
|
},
|
||||||
|
"write": {
|
||||||
|
"ubus": {
|
||||||
|
"luci.gotosocial": ["install", "start", "stop", "restart", "create_user", "delete_user", "promote_user", "demote_user", "save_config", "emancipate", "revoke", "backup"]
|
||||||
|
},
|
||||||
|
"uci": ["gotosocial"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
39
package/secubox/secubox-app-gotosocial/Makefile
Normal file
39
package/secubox/secubox-app-gotosocial/Makefile
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
|
PKG_NAME:=secubox-app-gotosocial
|
||||||
|
PKG_VERSION:=0.1.0
|
||||||
|
PKG_RELEASE:=1
|
||||||
|
|
||||||
|
PKG_MAINTAINER:=SecuBox Team
|
||||||
|
PKG_LICENSE:=MIT
|
||||||
|
|
||||||
|
include $(INCLUDE_DIR)/package.mk
|
||||||
|
|
||||||
|
define Package/secubox-app-gotosocial
|
||||||
|
SECTION:=secubox
|
||||||
|
CATEGORY:=SecuBox
|
||||||
|
TITLE:=GoToSocial Fediverse Server
|
||||||
|
DEPENDS:=+lxc +lxc-attach +wget +jq +openssl-util
|
||||||
|
PKGARCH:=all
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/secubox-app-gotosocial/description
|
||||||
|
Lightweight ActivityPub social network server for SecuBox.
|
||||||
|
Provides a self-hosted Fediverse instance with LuCI management.
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/secubox-app-gotosocial/install
|
||||||
|
$(INSTALL_DIR) $(1)/etc/config
|
||||||
|
$(INSTALL_CONF) ./files/etc/config/gotosocial $(1)/etc/config/gotosocial
|
||||||
|
|
||||||
|
$(INSTALL_DIR) $(1)/etc/init.d
|
||||||
|
$(INSTALL_BIN) ./files/etc/init.d/gotosocial $(1)/etc/init.d/gotosocial
|
||||||
|
|
||||||
|
$(INSTALL_DIR) $(1)/usr/sbin
|
||||||
|
$(INSTALL_BIN) ./files/usr/sbin/gotosocialctl $(1)/usr/sbin/gotosocialctl
|
||||||
|
|
||||||
|
$(INSTALL_DIR) $(1)/usr/share/gotosocial
|
||||||
|
$(INSTALL_DATA) ./files/usr/share/gotosocial/config.yaml.template $(1)/usr/share/gotosocial/
|
||||||
|
endef
|
||||||
|
|
||||||
|
$(eval $(call BuildPackage,secubox-app-gotosocial))
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
config gotosocial 'main'
|
||||||
|
option enabled '0'
|
||||||
|
option host 'social.example.com'
|
||||||
|
option port '8484'
|
||||||
|
option protocol 'https'
|
||||||
|
option bind_address '0.0.0.0'
|
||||||
|
option db_type 'sqlite'
|
||||||
|
option db_path '/data/gotosocial.db'
|
||||||
|
option storage_path '/data/storage'
|
||||||
|
option letsencrypt_enabled '0'
|
||||||
|
option letsencrypt_email ''
|
||||||
|
option instance_name 'SecuBox Social'
|
||||||
|
option instance_description 'A SecuBox Fediverse instance'
|
||||||
|
option accounts_registration_open '0'
|
||||||
|
option accounts_approval_required '1'
|
||||||
|
|
||||||
|
config lxc 'container'
|
||||||
|
option rootfs_path '/srv/lxc/gotosocial/rootfs'
|
||||||
|
option data_path '/srv/gotosocial'
|
||||||
|
option memory_limit '512M'
|
||||||
|
option version '0.17.3'
|
||||||
|
|
||||||
|
config haproxy 'proxy'
|
||||||
|
option enabled '0'
|
||||||
|
option backend_name 'gotosocial'
|
||||||
|
option vhost_domain ''
|
||||||
|
option ssl_enabled '1'
|
||||||
|
option acme_enabled '1'
|
||||||
|
|
||||||
|
config federation 'federation'
|
||||||
|
option enabled '1'
|
||||||
|
option auto_approve_followers '0'
|
||||||
|
option blocked_domains ''
|
||||||
|
option allowed_domains ''
|
||||||
|
|
||||||
|
config mesh 'mesh'
|
||||||
|
option auto_federate '1'
|
||||||
|
option announce_to_peers '1'
|
||||||
|
option share_blocklist '1'
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
#!/bin/sh /etc/rc.common
|
||||||
|
|
||||||
|
START=95
|
||||||
|
STOP=10
|
||||||
|
USE_PROCD=1
|
||||||
|
|
||||||
|
PROG=/usr/sbin/gotosocialctl
|
||||||
|
|
||||||
|
start_service() {
|
||||||
|
local enabled
|
||||||
|
config_load gotosocial
|
||||||
|
config_get enabled main enabled '0'
|
||||||
|
|
||||||
|
[ "$enabled" = "1" ] || return 0
|
||||||
|
|
||||||
|
$PROG start
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_service() {
|
||||||
|
$PROG stop
|
||||||
|
}
|
||||||
|
|
||||||
|
reload_service() {
|
||||||
|
$PROG reload
|
||||||
|
}
|
||||||
|
|
||||||
|
service_triggers() {
|
||||||
|
procd_add_reload_trigger "gotosocial"
|
||||||
|
}
|
||||||
|
|
||||||
|
status() {
|
||||||
|
$PROG status
|
||||||
|
}
|
||||||
@ -0,0 +1,778 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# GoToSocial Controller for SecuBox
|
||||||
|
# Manages GoToSocial LXC container and configuration
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
VERSION="0.1.0"
|
||||||
|
GTS_VERSION="0.17.3"
|
||||||
|
LXC_NAME="gotosocial"
|
||||||
|
LXC_PATH="/srv/lxc/gotosocial"
|
||||||
|
DATA_PATH="/srv/gotosocial"
|
||||||
|
CONFIG_FILE="/etc/config/gotosocial"
|
||||||
|
GTS_BINARY_URL="https://github.com/superseriousbusiness/gotosocial/releases/download/v${GTS_VERSION}/gotosocial_${GTS_VERSION}_linux_arm64.tar.gz"
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
log_info() { logger -t gotosocial -p daemon.info "$1"; echo "[INFO] $1"; }
|
||||||
|
log_error() { logger -t gotosocial -p daemon.err "$1"; echo "[ERROR] $1" >&2; }
|
||||||
|
log_warn() { logger -t gotosocial -p daemon.warn "$1"; echo "[WARN] $1"; }
|
||||||
|
|
||||||
|
# UCI helpers
|
||||||
|
get_config() {
|
||||||
|
local section="$1"
|
||||||
|
local option="$2"
|
||||||
|
local default="$3"
|
||||||
|
uci -q get "gotosocial.${section}.${option}" || echo "$default"
|
||||||
|
}
|
||||||
|
|
||||||
|
set_config() {
|
||||||
|
uci set "gotosocial.$1.$2=$3"
|
||||||
|
uci commit gotosocial
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if container exists
|
||||||
|
container_exists() {
|
||||||
|
[ -d "$LXC_PATH/rootfs" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if container is running
|
||||||
|
container_running() {
|
||||||
|
lxc-info -n "$LXC_NAME" 2>/dev/null | grep -q "RUNNING"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Download GoToSocial binary
|
||||||
|
download_binary() {
|
||||||
|
local version="${1:-$GTS_VERSION}"
|
||||||
|
local url="https://github.com/superseriousbusiness/gotosocial/releases/download/v${version}/gotosocial_${version}_linux_arm64.tar.gz"
|
||||||
|
local tmp_dir="/tmp/gotosocial_install"
|
||||||
|
|
||||||
|
log_info "Downloading GoToSocial v${version}..."
|
||||||
|
|
||||||
|
mkdir -p "$tmp_dir"
|
||||||
|
cd "$tmp_dir"
|
||||||
|
|
||||||
|
wget -q -O gotosocial.tar.gz "$url" || {
|
||||||
|
log_error "Failed to download GoToSocial"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
tar -xzf gotosocial.tar.gz
|
||||||
|
|
||||||
|
mkdir -p "$LXC_PATH/rootfs/opt/gotosocial"
|
||||||
|
cp gotosocial "$LXC_PATH/rootfs/opt/gotosocial/"
|
||||||
|
chmod +x "$LXC_PATH/rootfs/opt/gotosocial/gotosocial"
|
||||||
|
|
||||||
|
# Copy web assets
|
||||||
|
[ -d "web" ] && cp -r web "$LXC_PATH/rootfs/opt/gotosocial/"
|
||||||
|
|
||||||
|
rm -rf "$tmp_dir"
|
||||||
|
log_info "GoToSocial binary installed"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create minimal rootfs
|
||||||
|
create_rootfs() {
|
||||||
|
local rootfs="$LXC_PATH/rootfs"
|
||||||
|
|
||||||
|
log_info "Creating minimal rootfs..."
|
||||||
|
|
||||||
|
mkdir -p "$rootfs"/{opt/gotosocial,data,etc,proc,sys,dev,tmp,run}
|
||||||
|
|
||||||
|
# Create basic filesystem structure
|
||||||
|
mkdir -p "$rootfs/etc/ssl/certs"
|
||||||
|
|
||||||
|
# Copy SSL certificates from host
|
||||||
|
cp /etc/ssl/certs/ca-certificates.crt "$rootfs/etc/ssl/certs/" 2>/dev/null || \
|
||||||
|
cat /etc/ssl/certs/*.pem > "$rootfs/etc/ssl/certs/ca-certificates.crt" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Create passwd/group for GoToSocial
|
||||||
|
echo "root:x:0:0:root:/root:/bin/sh" > "$rootfs/etc/passwd"
|
||||||
|
echo "gotosocial:x:1000:1000:GoToSocial:/data:/bin/false" >> "$rootfs/etc/passwd"
|
||||||
|
echo "root:x:0:" > "$rootfs/etc/group"
|
||||||
|
echo "gotosocial:x:1000:" >> "$rootfs/etc/group"
|
||||||
|
|
||||||
|
# Create resolv.conf
|
||||||
|
cp /etc/resolv.conf "$rootfs/etc/"
|
||||||
|
|
||||||
|
# Create hosts file
|
||||||
|
cat > "$rootfs/etc/hosts" <<EOF
|
||||||
|
127.0.0.1 localhost
|
||||||
|
::1 localhost
|
||||||
|
EOF
|
||||||
|
|
||||||
|
log_info "Rootfs created"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Generate LXC config
|
||||||
|
create_lxc_config() {
|
||||||
|
local host=$(get_config main host "social.local")
|
||||||
|
local port=$(get_config main port "8484")
|
||||||
|
local data_path=$(get_config container data_path "$DATA_PATH")
|
||||||
|
|
||||||
|
log_info "Creating LXC configuration..."
|
||||||
|
|
||||||
|
mkdir -p "$LXC_PATH"
|
||||||
|
|
||||||
|
cat > "$LXC_PATH/config" <<EOF
|
||||||
|
# GoToSocial LXC Configuration
|
||||||
|
lxc.uts.name = $LXC_NAME
|
||||||
|
lxc.rootfs.path = dir:$LXC_PATH/rootfs
|
||||||
|
lxc.arch = aarch64
|
||||||
|
|
||||||
|
# Network: use host network
|
||||||
|
lxc.net.0.type = none
|
||||||
|
|
||||||
|
# Mount points
|
||||||
|
lxc.mount.auto = proc:mixed sys:ro
|
||||||
|
lxc.mount.entry = $data_path data none bind,create=dir 0 0
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
lxc.environment = GTS_HOST=$host
|
||||||
|
lxc.environment = GTS_PORT=$port
|
||||||
|
lxc.environment = GTS_DB_TYPE=sqlite
|
||||||
|
lxc.environment = GTS_DB_ADDRESS=/data/gotosocial.db
|
||||||
|
lxc.environment = GTS_STORAGE_LOCAL_BASE_PATH=/data/storage
|
||||||
|
lxc.environment = GTS_LETSENCRYPT_ENABLED=false
|
||||||
|
lxc.environment = HOME=/data
|
||||||
|
|
||||||
|
# Security
|
||||||
|
lxc.cap.drop = sys_admin sys_module mac_admin mac_override sys_time sys_rawio
|
||||||
|
|
||||||
|
# Init command
|
||||||
|
lxc.init.cmd = /opt/gotosocial/gotosocial server
|
||||||
|
EOF
|
||||||
|
|
||||||
|
log_info "LXC config created"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Generate GoToSocial config
|
||||||
|
generate_config() {
|
||||||
|
local host=$(get_config main host "social.local")
|
||||||
|
local port=$(get_config main port "8484")
|
||||||
|
local protocol=$(get_config main protocol "https")
|
||||||
|
local bind=$(get_config main bind_address "0.0.0.0")
|
||||||
|
local instance_name=$(get_config main instance_name "SecuBox Social")
|
||||||
|
local instance_desc=$(get_config main instance_description "A SecuBox Fediverse instance")
|
||||||
|
local reg_open=$(get_config main accounts_registration_open "false")
|
||||||
|
local approval=$(get_config main accounts_approval_required "true")
|
||||||
|
local data_path=$(get_config container data_path "$DATA_PATH")
|
||||||
|
|
||||||
|
mkdir -p "$data_path"
|
||||||
|
|
||||||
|
cat > "$data_path/config.yaml" <<EOF
|
||||||
|
# GoToSocial Configuration
|
||||||
|
# Generated by SecuBox gotosocialctl
|
||||||
|
|
||||||
|
host: "$host"
|
||||||
|
account-domain: "$host"
|
||||||
|
protocol: "$protocol"
|
||||||
|
bind-address: "$bind"
|
||||||
|
port: $port
|
||||||
|
|
||||||
|
db-type: "sqlite"
|
||||||
|
db-address: "/data/gotosocial.db"
|
||||||
|
|
||||||
|
storage-backend: "local"
|
||||||
|
storage-local-base-path: "/data/storage"
|
||||||
|
|
||||||
|
instance-expose-public-timeline: true
|
||||||
|
instance-expose-suspended: false
|
||||||
|
instance-expose-suspended-web: false
|
||||||
|
|
||||||
|
accounts-registration-open: $reg_open
|
||||||
|
accounts-approval-required: $approval
|
||||||
|
accounts-reason-required: true
|
||||||
|
|
||||||
|
media-image-max-size: 10485760
|
||||||
|
media-video-max-size: 41943040
|
||||||
|
media-description-min-chars: 0
|
||||||
|
media-description-max-chars: 500
|
||||||
|
media-remote-cache-days: 30
|
||||||
|
|
||||||
|
statuses-max-chars: 5000
|
||||||
|
statuses-cw-max-chars: 100
|
||||||
|
statuses-poll-max-options: 6
|
||||||
|
statuses-poll-option-max-chars: 50
|
||||||
|
statuses-media-max-files: 6
|
||||||
|
|
||||||
|
letsencrypt-enabled: false
|
||||||
|
|
||||||
|
oidc-enabled: false
|
||||||
|
|
||||||
|
smtp-host: ""
|
||||||
|
smtp-port: 0
|
||||||
|
|
||||||
|
syslog-enabled: false
|
||||||
|
syslog-protocol: "udp"
|
||||||
|
syslog-address: "localhost:514"
|
||||||
|
|
||||||
|
log-level: "info"
|
||||||
|
log-db-queries: false
|
||||||
|
|
||||||
|
advanced-cookies-samesite: "lax"
|
||||||
|
advanced-rate-limit-requests: 300
|
||||||
|
advanced-throttling-multiplier: 8
|
||||||
|
|
||||||
|
cache:
|
||||||
|
gts:
|
||||||
|
account-max-size: 2000
|
||||||
|
account-ttl: "30m"
|
||||||
|
account-sweep-freq: "1m"
|
||||||
|
status-max-size: 2000
|
||||||
|
status-ttl: "30m"
|
||||||
|
status-sweep-freq: "1m"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Create storage directories
|
||||||
|
mkdir -p "$data_path/storage"
|
||||||
|
|
||||||
|
log_info "Configuration generated at $data_path/config.yaml"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Install GoToSocial
|
||||||
|
cmd_install() {
|
||||||
|
local version="${1:-$GTS_VERSION}"
|
||||||
|
|
||||||
|
log_info "Installing GoToSocial v${version}..."
|
||||||
|
|
||||||
|
# Check dependencies
|
||||||
|
command -v lxc-start >/dev/null || {
|
||||||
|
log_error "LXC not installed. Install lxc package first."
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create directories
|
||||||
|
mkdir -p "$LXC_PATH" "$DATA_PATH"
|
||||||
|
|
||||||
|
# Create rootfs
|
||||||
|
create_rootfs
|
||||||
|
|
||||||
|
# Download binary
|
||||||
|
download_binary "$version"
|
||||||
|
|
||||||
|
# Create LXC config
|
||||||
|
create_lxc_config
|
||||||
|
|
||||||
|
# Generate GoToSocial config
|
||||||
|
generate_config
|
||||||
|
|
||||||
|
log_info "GoToSocial installed successfully"
|
||||||
|
log_info "Run 'gotosocialctl start' to start the service"
|
||||||
|
log_info "Then create a user with 'gotosocialctl user create <username> <email>'"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Uninstall
|
||||||
|
cmd_uninstall() {
|
||||||
|
local keep_data="$1"
|
||||||
|
|
||||||
|
log_info "Uninstalling GoToSocial..."
|
||||||
|
|
||||||
|
# Stop container if running
|
||||||
|
container_running && cmd_stop
|
||||||
|
|
||||||
|
# Remove container
|
||||||
|
rm -rf "$LXC_PATH"
|
||||||
|
|
||||||
|
# Remove data unless --keep-data
|
||||||
|
if [ "$keep_data" != "--keep-data" ]; then
|
||||||
|
rm -rf "$DATA_PATH"
|
||||||
|
log_info "Data removed"
|
||||||
|
else
|
||||||
|
log_info "Data preserved at $DATA_PATH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "GoToSocial uninstalled"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Start container
|
||||||
|
cmd_start() {
|
||||||
|
if ! container_exists; then
|
||||||
|
log_error "GoToSocial not installed. Run 'gotosocialctl install' first."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if container_running; then
|
||||||
|
log_info "GoToSocial is already running"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Regenerate config in case settings changed
|
||||||
|
create_lxc_config
|
||||||
|
generate_config
|
||||||
|
|
||||||
|
log_info "Starting GoToSocial container..."
|
||||||
|
|
||||||
|
lxc-start -n "$LXC_NAME" -d -P "$(dirname $LXC_PATH)" || {
|
||||||
|
log_error "Failed to start container"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
if container_running; then
|
||||||
|
log_info "GoToSocial started"
|
||||||
|
local port=$(get_config main port "8484")
|
||||||
|
log_info "Web interface available at http://localhost:$port"
|
||||||
|
else
|
||||||
|
log_error "Container failed to start"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Stop container
|
||||||
|
cmd_stop() {
|
||||||
|
if ! container_running; then
|
||||||
|
log_info "GoToSocial is not running"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Stopping GoToSocial..."
|
||||||
|
lxc-stop -n "$LXC_NAME" -P "$(dirname $LXC_PATH)" || true
|
||||||
|
log_info "GoToSocial stopped"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Restart
|
||||||
|
cmd_restart() {
|
||||||
|
cmd_stop
|
||||||
|
sleep 1
|
||||||
|
cmd_start
|
||||||
|
}
|
||||||
|
|
||||||
|
# Reload config
|
||||||
|
cmd_reload() {
|
||||||
|
log_info "Reloading configuration..."
|
||||||
|
generate_config
|
||||||
|
cmd_restart
|
||||||
|
}
|
||||||
|
|
||||||
|
# Status (JSON output for RPCD)
|
||||||
|
cmd_status() {
|
||||||
|
local installed="false"
|
||||||
|
local container_state="false"
|
||||||
|
local service_state="false"
|
||||||
|
local host=$(get_config main host "social.example.com")
|
||||||
|
local port=$(get_config main port "8484")
|
||||||
|
local version=$(get_config container version "$GTS_VERSION")
|
||||||
|
local tor_enabled=$(get_config federation tor_enabled "0")
|
||||||
|
local dns_enabled=$(get_config proxy enabled "0")
|
||||||
|
local mesh_enabled=$(get_config mesh announce_to_peers "0")
|
||||||
|
|
||||||
|
container_exists && installed="true"
|
||||||
|
container_running && container_state="true"
|
||||||
|
|
||||||
|
# Check if API responds
|
||||||
|
if [ "$container_state" = "true" ]; then
|
||||||
|
curl -s --connect-timeout 2 "http://127.0.0.1:$port/api/v1/instance" >/dev/null 2>&1 && service_state="true"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
{
|
||||||
|
"installed": $installed,
|
||||||
|
"container_running": $container_state,
|
||||||
|
"service_running": $service_state,
|
||||||
|
"host": "$host",
|
||||||
|
"port": "$port",
|
||||||
|
"version": "$version",
|
||||||
|
"tor_enabled": $([ "$tor_enabled" = "1" ] && echo "true" || echo "false"),
|
||||||
|
"dns_enabled": $([ "$dns_enabled" = "1" ] && echo "true" || echo "false"),
|
||||||
|
"mesh_enabled": $([ "$mesh_enabled" = "1" ] && echo "true" || echo "false")
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Status (human readable)
|
||||||
|
cmd_status_human() {
|
||||||
|
if container_running; then
|
||||||
|
echo "GoToSocial: running"
|
||||||
|
lxc-info -n "$LXC_NAME" -P "$(dirname $LXC_PATH)" 2>/dev/null | grep -E "State|PID|CPU|Memory"
|
||||||
|
|
||||||
|
local port=$(get_config main port "8484")
|
||||||
|
local host=$(get_config main host "localhost")
|
||||||
|
echo "Host: $host"
|
||||||
|
echo "Port: $port"
|
||||||
|
|
||||||
|
# Check if web interface responds
|
||||||
|
if curl -s --connect-timeout 2 "http://127.0.0.1:$port/api/v1/instance" >/dev/null 2>&1; then
|
||||||
|
echo "API: responding"
|
||||||
|
else
|
||||||
|
echo "API: not responding (may still be starting)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "GoToSocial: stopped"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create user
|
||||||
|
cmd_user_create() {
|
||||||
|
local username="$1"
|
||||||
|
local email="$2"
|
||||||
|
local admin="${3:-false}"
|
||||||
|
|
||||||
|
[ -z "$username" ] || [ -z "$email" ] && {
|
||||||
|
echo "Usage: gotosocialctl user create <username> <email> [--admin]"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
[ "$3" = "--admin" ] && admin="true"
|
||||||
|
|
||||||
|
if ! container_running; then
|
||||||
|
log_error "GoToSocial is not running"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Creating user $username..."
|
||||||
|
|
||||||
|
# Generate random password
|
||||||
|
local password=$(openssl rand -base64 12)
|
||||||
|
|
||||||
|
lxc-attach -n "$LXC_NAME" -P "$(dirname $LXC_PATH)" -- \
|
||||||
|
/opt/gotosocial/gotosocial admin account create \
|
||||||
|
--username "$username" \
|
||||||
|
--email "$email" \
|
||||||
|
--password "$password" \
|
||||||
|
--config /data/config.yaml
|
||||||
|
|
||||||
|
if [ "$admin" = "true" ]; then
|
||||||
|
lxc-attach -n "$LXC_NAME" -P "$(dirname $LXC_PATH)" -- \
|
||||||
|
/opt/gotosocial/gotosocial admin account promote \
|
||||||
|
--username "$username" \
|
||||||
|
--config /data/config.yaml
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "User created successfully!"
|
||||||
|
echo "Username: $username"
|
||||||
|
echo "Email: $email"
|
||||||
|
echo "Password: $password"
|
||||||
|
echo ""
|
||||||
|
echo "Please change this password after first login."
|
||||||
|
}
|
||||||
|
|
||||||
|
# List users (JSON output for RPCD)
|
||||||
|
cmd_users() {
|
||||||
|
local db_path="$DATA_PATH/gotosocial.db"
|
||||||
|
local users="[]"
|
||||||
|
|
||||||
|
if [ -f "$db_path" ] && command -v sqlite3 >/dev/null; then
|
||||||
|
users=$(sqlite3 -json "$db_path" "SELECT username, created_at as created,
|
||||||
|
CASE WHEN suspended_at IS NULL THEN 0 ELSE 1 END as suspended,
|
||||||
|
CASE WHEN confirmed_at IS NULL THEN 0 ELSE 1 END as confirmed
|
||||||
|
FROM accounts WHERE domain IS NULL OR domain = '';" 2>/dev/null || echo "[]")
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "{\"users\":$users}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# List users (human readable)
|
||||||
|
cmd_user_list() {
|
||||||
|
if ! container_running; then
|
||||||
|
log_error "GoToSocial is not running"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local port=$(get_config main port "8484")
|
||||||
|
|
||||||
|
# Use API to list accounts (requires admin token)
|
||||||
|
# For now, check the database directly
|
||||||
|
local db_path="$DATA_PATH/gotosocial.db"
|
||||||
|
|
||||||
|
if [ -f "$db_path" ] && command -v sqlite3 >/dev/null; then
|
||||||
|
sqlite3 "$db_path" "SELECT username, created_at, suspended_at FROM accounts WHERE domain IS NULL OR domain = '';" 2>/dev/null || {
|
||||||
|
echo "Unable to query database directly. Use the web interface."
|
||||||
|
}
|
||||||
|
else
|
||||||
|
echo "Use the web interface to manage users."
|
||||||
|
echo "URL: https://$(get_config main host)/admin"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Confirm user email
|
||||||
|
cmd_user_confirm() {
|
||||||
|
local username="$1"
|
||||||
|
|
||||||
|
[ -z "$username" ] && {
|
||||||
|
echo "Usage: gotosocialctl user confirm <username>"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! container_running; then
|
||||||
|
log_error "GoToSocial is not running"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
lxc-attach -n "$LXC_NAME" -P "$(dirname $LXC_PATH)" -- \
|
||||||
|
/opt/gotosocial/gotosocial admin account confirm \
|
||||||
|
--username "$username" \
|
||||||
|
--config /data/config.yaml
|
||||||
|
|
||||||
|
log_info "User $username confirmed"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Emancipate - expose via HAProxy
|
||||||
|
cmd_emancipate() {
|
||||||
|
local domain="$1"
|
||||||
|
|
||||||
|
[ -z "$domain" ] && domain=$(get_config main host)
|
||||||
|
[ -z "$domain" ] || [ "$domain" = "social.example.com" ] && {
|
||||||
|
echo "Usage: gotosocialctl emancipate <domain>"
|
||||||
|
echo "Example: gotosocialctl emancipate social.mysite.com"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
local port=$(get_config main port "8484")
|
||||||
|
local lan_ip=$(uci -q get network.lan.ipaddr || echo "192.168.255.1")
|
||||||
|
|
||||||
|
log_info "Exposing GoToSocial at $domain..."
|
||||||
|
|
||||||
|
# Update config
|
||||||
|
set_config main host "$domain"
|
||||||
|
set_config proxy enabled "1"
|
||||||
|
set_config proxy vhost_domain "$domain"
|
||||||
|
|
||||||
|
# Create HAProxy backend
|
||||||
|
uci set haproxy.gotosocial=backend
|
||||||
|
uci set haproxy.gotosocial.name='gotosocial'
|
||||||
|
uci set haproxy.gotosocial.mode='http'
|
||||||
|
uci set haproxy.gotosocial.balance='roundrobin'
|
||||||
|
uci set haproxy.gotosocial.enabled='1'
|
||||||
|
|
||||||
|
uci set haproxy.gotosocial_srv=server
|
||||||
|
uci set haproxy.gotosocial_srv.backend='gotosocial'
|
||||||
|
uci set haproxy.gotosocial_srv.name='gotosocial'
|
||||||
|
uci set haproxy.gotosocial_srv.address="$lan_ip"
|
||||||
|
uci set haproxy.gotosocial_srv.port="$port"
|
||||||
|
uci set haproxy.gotosocial_srv.weight='100'
|
||||||
|
uci set haproxy.gotosocial_srv.check='1'
|
||||||
|
uci set haproxy.gotosocial_srv.enabled='1'
|
||||||
|
|
||||||
|
# Create vhost
|
||||||
|
local vhost_name=$(echo "$domain" | tr '.-' '_')
|
||||||
|
uci set haproxy.${vhost_name}=vhost
|
||||||
|
uci set haproxy.${vhost_name}.domain="$domain"
|
||||||
|
uci set haproxy.${vhost_name}.backend='gotosocial'
|
||||||
|
uci set haproxy.${vhost_name}.ssl='1'
|
||||||
|
uci set haproxy.${vhost_name}.ssl_redirect='1'
|
||||||
|
uci set haproxy.${vhost_name}.acme='1'
|
||||||
|
uci set haproxy.${vhost_name}.enabled='1'
|
||||||
|
|
||||||
|
uci commit haproxy
|
||||||
|
uci commit gotosocial
|
||||||
|
|
||||||
|
# Regenerate HAProxy config
|
||||||
|
if command -v haproxyctl >/dev/null; then
|
||||||
|
haproxyctl generate
|
||||||
|
/etc/init.d/haproxy reload
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Regenerate GoToSocial config with new domain
|
||||||
|
generate_config
|
||||||
|
|
||||||
|
# Restart to apply new config
|
||||||
|
container_running && cmd_restart
|
||||||
|
|
||||||
|
log_info "GoToSocial exposed at https://$domain"
|
||||||
|
log_info "SSL certificate will be provisioned automatically"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Backup
|
||||||
|
cmd_backup() {
|
||||||
|
local backup_path="${1:-/tmp/gotosocial-backup-$(date +%Y%m%d-%H%M%S).tar.gz}"
|
||||||
|
|
||||||
|
log_info "Creating backup..."
|
||||||
|
|
||||||
|
# Stop container for consistent backup
|
||||||
|
local was_running=false
|
||||||
|
if container_running; then
|
||||||
|
was_running=true
|
||||||
|
cmd_stop
|
||||||
|
fi
|
||||||
|
|
||||||
|
tar -czf "$backup_path" -C "$DATA_PATH" . 2>/dev/null || {
|
||||||
|
log_error "Backup failed"
|
||||||
|
[ "$was_running" = "true" ] && cmd_start
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
[ "$was_running" = "true" ] && cmd_start
|
||||||
|
|
||||||
|
log_info "Backup created: $backup_path"
|
||||||
|
ls -lh "$backup_path"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Restore
|
||||||
|
cmd_restore() {
|
||||||
|
local backup_path="$1"
|
||||||
|
|
||||||
|
[ -z "$backup_path" ] || [ ! -f "$backup_path" ] && {
|
||||||
|
echo "Usage: gotosocialctl restore <backup-file>"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
log_info "Restoring from $backup_path..."
|
||||||
|
|
||||||
|
# Stop container
|
||||||
|
container_running && cmd_stop
|
||||||
|
|
||||||
|
# Clear existing data
|
||||||
|
rm -rf "$DATA_PATH"/*
|
||||||
|
|
||||||
|
# Extract backup
|
||||||
|
tar -xzf "$backup_path" -C "$DATA_PATH" || {
|
||||||
|
log_error "Restore failed"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
log_info "Restore complete"
|
||||||
|
cmd_start
|
||||||
|
}
|
||||||
|
|
||||||
|
# Federation commands
|
||||||
|
cmd_federation_list() {
|
||||||
|
local port=$(get_config main port "8484")
|
||||||
|
|
||||||
|
curl -s "http://127.0.0.1:$port/api/v1/instance/peers" 2>/dev/null | jq -r '.[]' 2>/dev/null || {
|
||||||
|
echo "Unable to fetch federation list. Is GoToSocial running?"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Show logs (JSON output)
|
||||||
|
cmd_logs() {
|
||||||
|
local lines="${1:-50}"
|
||||||
|
local logs
|
||||||
|
|
||||||
|
logs=$(logread -e gotosocial 2>/dev/null | tail -n "$lines" | jq -R -s 'split("\n") | map(select(length > 0))' 2>/dev/null || echo "[]")
|
||||||
|
|
||||||
|
echo "{\"logs\":$logs}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Show help
|
||||||
|
cmd_help() {
|
||||||
|
cat <<EOF
|
||||||
|
GoToSocial Controller for SecuBox v$VERSION
|
||||||
|
|
||||||
|
Usage: gotosocialctl <command> [options]
|
||||||
|
|
||||||
|
Installation:
|
||||||
|
install [version] Install GoToSocial (default: v$GTS_VERSION)
|
||||||
|
uninstall [--keep-data] Remove GoToSocial
|
||||||
|
update [version] Update to new version
|
||||||
|
|
||||||
|
Service:
|
||||||
|
start Start GoToSocial
|
||||||
|
stop Stop GoToSocial
|
||||||
|
restart Restart GoToSocial
|
||||||
|
reload Reload configuration
|
||||||
|
status Show status
|
||||||
|
|
||||||
|
User Management:
|
||||||
|
user create <user> <email> [--admin] Create user
|
||||||
|
user list List users
|
||||||
|
user confirm <user> Confirm user email
|
||||||
|
|
||||||
|
Exposure:
|
||||||
|
emancipate <domain> Expose via HAProxy + SSL
|
||||||
|
|
||||||
|
Backup:
|
||||||
|
backup [path] Backup data
|
||||||
|
restore <path> Restore from backup
|
||||||
|
|
||||||
|
Federation:
|
||||||
|
federation list List federated instances
|
||||||
|
|
||||||
|
Other:
|
||||||
|
help Show this help
|
||||||
|
version Show version
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
gotosocialctl install
|
||||||
|
gotosocialctl start
|
||||||
|
gotosocialctl user create alice alice@example.com --admin
|
||||||
|
gotosocialctl emancipate social.mysite.com
|
||||||
|
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main
|
||||||
|
case "$1" in
|
||||||
|
install)
|
||||||
|
cmd_install "$2"
|
||||||
|
;;
|
||||||
|
uninstall)
|
||||||
|
cmd_uninstall "$2"
|
||||||
|
;;
|
||||||
|
update)
|
||||||
|
cmd_stop
|
||||||
|
download_binary "${2:-$GTS_VERSION}"
|
||||||
|
cmd_start
|
||||||
|
;;
|
||||||
|
start)
|
||||||
|
cmd_start
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
cmd_stop
|
||||||
|
;;
|
||||||
|
restart)
|
||||||
|
cmd_restart
|
||||||
|
;;
|
||||||
|
reload)
|
||||||
|
cmd_reload
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
cmd_status
|
||||||
|
;;
|
||||||
|
status-human)
|
||||||
|
cmd_status_human
|
||||||
|
;;
|
||||||
|
users)
|
||||||
|
cmd_users
|
||||||
|
;;
|
||||||
|
logs)
|
||||||
|
cmd_logs "$2"
|
||||||
|
;;
|
||||||
|
user)
|
||||||
|
case "$2" in
|
||||||
|
create)
|
||||||
|
cmd_user_create "$3" "$4" "$5"
|
||||||
|
;;
|
||||||
|
list)
|
||||||
|
cmd_user_list
|
||||||
|
;;
|
||||||
|
confirm)
|
||||||
|
cmd_user_confirm "$3"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: gotosocialctl user {create|list|confirm}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
emancipate)
|
||||||
|
cmd_emancipate "$2"
|
||||||
|
;;
|
||||||
|
backup)
|
||||||
|
cmd_backup "$2"
|
||||||
|
;;
|
||||||
|
restore)
|
||||||
|
cmd_restore "$2"
|
||||||
|
;;
|
||||||
|
federation)
|
||||||
|
case "$2" in
|
||||||
|
list)
|
||||||
|
cmd_federation_list
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: gotosocialctl federation {list}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
version)
|
||||||
|
echo "gotosocialctl v$VERSION (GoToSocial v$GTS_VERSION)"
|
||||||
|
;;
|
||||||
|
help|--help|-h|"")
|
||||||
|
cmd_help
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown command: $1"
|
||||||
|
cmd_help
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
# GoToSocial Configuration Template
|
||||||
|
# This file is used as a reference. Actual config is generated by gotosocialctl.
|
||||||
|
|
||||||
|
host: "${GTS_HOST}"
|
||||||
|
account-domain: "${GTS_HOST}"
|
||||||
|
protocol: "${GTS_PROTOCOL}"
|
||||||
|
bind-address: "${GTS_BIND_ADDRESS}"
|
||||||
|
port: ${GTS_PORT}
|
||||||
|
|
||||||
|
db-type: "sqlite"
|
||||||
|
db-address: "/data/gotosocial.db"
|
||||||
|
|
||||||
|
storage-backend: "local"
|
||||||
|
storage-local-base-path: "/data/storage"
|
||||||
|
|
||||||
|
instance-expose-public-timeline: true
|
||||||
|
instance-expose-suspended: false
|
||||||
|
|
||||||
|
accounts-registration-open: false
|
||||||
|
accounts-approval-required: true
|
||||||
|
accounts-reason-required: true
|
||||||
|
|
||||||
|
media-image-max-size: 10485760
|
||||||
|
media-video-max-size: 41943040
|
||||||
|
|
||||||
|
statuses-max-chars: 5000
|
||||||
|
statuses-cw-max-chars: 100
|
||||||
|
statuses-poll-max-options: 6
|
||||||
|
statuses-poll-option-max-chars: 50
|
||||||
|
statuses-media-max-files: 6
|
||||||
|
|
||||||
|
letsencrypt-enabled: false
|
||||||
|
|
||||||
|
log-level: "info"
|
||||||
Loading…
Reference in New Issue
Block a user