Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5447aa7c80 | |||
| 933918ea27 | |||
| cbbcdc5df1 | |||
| 4dfa7b132d | |||
| fb5bfaa2bd | |||
| 20e206fa43 | |||
| 467fa59023 |
@@ -2087,3 +2087,7 @@
|
||||
* ovh: add sydney region
|
||||
* s3: makes multi-part copies in parallel
|
||||
|
||||
[5.6.1]
|
||||
* Blocklists are now stored in a text file instead of json
|
||||
* regenerate nginx configs
|
||||
|
||||
|
||||
@@ -29,9 +29,9 @@ anyone to effortlessly host web applications on their server on their own terms.
|
||||
* Trivially migrate to another server keeping your apps and data (for example, switch your
|
||||
infrastructure provider or move to a bigger server).
|
||||
|
||||
* Comprehensive [REST API](https://cloudron.io/documentation/developer/api/).
|
||||
* Comprehensive [REST API](https://docs.cloudron.io/api/).
|
||||
|
||||
* [CLI](https://cloudron.io/documentation/cli/) to configure apps.
|
||||
* [CLI](https://docs.cloudron.io/custom-apps/cli/) to configure apps.
|
||||
|
||||
* Alerts, audit logs, graphs, dns management ... and much more
|
||||
|
||||
@@ -41,7 +41,7 @@ Try our demo at https://my.demo.cloudron.io (username: cloudron password: cloudr
|
||||
|
||||
## Installing
|
||||
|
||||
[Install script](https://cloudron.io/documentation/installation/) - [Pricing](https://cloudron.io/pricing.html)
|
||||
[Install script](https://docs.cloudron.io/installation/) - [Pricing](https://cloudron.io/pricing.html)
|
||||
|
||||
**Note:** This repo is a small part of what gets installed on your server - there is
|
||||
the dashboard, database addons, graph container, base image etc. Cloudron also relies
|
||||
@@ -50,6 +50,6 @@ clone this repo and npm install and expect something to work.
|
||||
|
||||
## Support
|
||||
|
||||
* [Documentation](https://cloudron.io/documentation/)
|
||||
* [Documentation](https://docs.cloudron.io/)
|
||||
* [Forum](https://forum.cloudron.io/)
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ timedatectl set-ntp 1
|
||||
timedatectl set-timezone UTC
|
||||
|
||||
echo "==> Adding sshd configuration warning"
|
||||
sed -e '/Port 22/ i # NOTE: Cloudron only supports moving SSH to port 202. See https://cloudron.io/documentation/security/#securing-ssh-access' -i /etc/ssh/sshd_config
|
||||
sed -e '/Port 22/ i # NOTE: Cloudron only supports moving SSH to port 202. See https://docs.cloudron.io/security/#securing-ssh-access' -i /etc/ssh/sshd_config
|
||||
|
||||
# https://bugs.launchpad.net/ubuntu/+source/base-files/+bug/1701068
|
||||
echo "==> Disabling motd news"
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
|
||||
const OLD_FIREWALL_CONFIG_JSON = '/home/yellowtent/boxdata/firewall-config.json';
|
||||
const PORTS_FILE = '/home/yellowtent/boxdata/firewall/ports.json';
|
||||
const BLOCKLIST_FILE = '/home/yellowtent/boxdata/firewall/blocklist.txt';
|
||||
|
||||
const fs = require('fs');
|
||||
|
||||
exports.up = function (db, callback) {
|
||||
if (!fs.existsSync(OLD_FIREWALL_CONFIG_JSON)) return callback();
|
||||
|
||||
try {
|
||||
const dataJson = fs.readFileSync(OLD_FIREWALL_CONFIG_JSON, 'utf8');
|
||||
const data = JSON.parse(dataJson);
|
||||
fs.writeFileSync(BLOCKLIST_FILE, data.blocklist.join('\n') + '\n', 'utf8');
|
||||
fs.writeFileSync(PORTS_FILE, JSON.stringify({ allowed_tcp_ports: data.allowed_tcp_ports }, null, 4), 'utf8');
|
||||
fs.unlinkSync(OLD_FIREWALL_CONFIG_JSON);
|
||||
} catch (error) {
|
||||
console.log('Error migrating old firewall config', error);
|
||||
}
|
||||
|
||||
callback();
|
||||
};
|
||||
|
||||
exports.down = function (db, callback) {
|
||||
callback();
|
||||
};
|
||||
Generated
+12
-5
@@ -2438,9 +2438,9 @@
|
||||
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
|
||||
},
|
||||
"ipaddr.js": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz",
|
||||
"integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA=="
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.0.tgz",
|
||||
"integrity": "sha512-S54H9mIj0rbxRIyrDMEuuER86LdlgUg9FSeZ8duQb6CUG2iRrA36MYVQBSprTF/ZeAwvyQ5mDGuNvIPM0BIl3w=="
|
||||
},
|
||||
"is-arguments": {
|
||||
"version": "1.0.4",
|
||||
@@ -3779,6 +3779,13 @@
|
||||
"requires": {
|
||||
"forwarded": "~0.1.2",
|
||||
"ipaddr.js": "1.9.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ipaddr.js": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz",
|
||||
"integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"proxy-middleware": {
|
||||
@@ -4635,8 +4642,8 @@
|
||||
"resolved": "https://registry.npmjs.org/prettyjson/-/prettyjson-1.2.1.tgz",
|
||||
"integrity": "sha1-/P+rQdGcq0365eV15kJGYZsS0ok=",
|
||||
"requires": {
|
||||
"colors": "1.2.1",
|
||||
"minimist": "1.2.0"
|
||||
"colors": "^1.1.2",
|
||||
"minimist": "^1.2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"ejs": "^2.6.1",
|
||||
"ejs-cli": "^2.2.0",
|
||||
"express": "^4.17.1",
|
||||
"ipaddr.js": "^2.0.0",
|
||||
"js-yaml": "^3.14.0",
|
||||
"json": "^9.0.6",
|
||||
"ldapjs": "^1.0.2",
|
||||
|
||||
@@ -53,7 +53,7 @@ eval set -- "${args}"
|
||||
|
||||
while true; do
|
||||
case "$1" in
|
||||
--help) echo "See https://cloudron.io/documentation/installation/ on how to install Cloudron"; exit 0;;
|
||||
--help) echo "See https://docs.cloudron.io/installation/ on how to install Cloudron"; exit 0;;
|
||||
--provider) provider="$2"; shift 2;;
|
||||
--version) requestedVersion="$2"; shift 2;;
|
||||
--env)
|
||||
|
||||
@@ -57,7 +57,7 @@ if [[ "`df --output="avail" / | sed -n 2p`" -lt "10240" ]]; then
|
||||
echo ""
|
||||
df -h
|
||||
echo ""
|
||||
echo "To recover from a full disk, follow the guide at https://cloudron.io/documentation/troubleshooting/#recovery-after-disk-full"
|
||||
echo "To recover from a full disk, follow the guide at https://docs.cloudron.io/troubleshooting/#recovery-after-disk-full"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ mkdir -p "${PLATFORM_DATA_DIR}/logs/backup" \
|
||||
mkdir -p "${PLATFORM_DATA_DIR}/update"
|
||||
|
||||
mkdir -p "${BOX_DATA_DIR}/appicons"
|
||||
mkdir -p "${BOX_DATA_DIR}/firewall"
|
||||
mkdir -p "${BOX_DATA_DIR}/profileicons"
|
||||
mkdir -p "${BOX_DATA_DIR}/certs"
|
||||
mkdir -p "${BOX_DATA_DIR}/acme" # acme keys
|
||||
|
||||
@@ -20,8 +20,8 @@ iptables -t filter -A CLOUDRON -m state --state RELATED,ESTABLISHED -j ACCEPT
|
||||
iptables -t filter -A CLOUDRON -p tcp -m tcp -m multiport --dports 22,25,80,202,443 -j ACCEPT # 202 is the alternate ssh port
|
||||
|
||||
# whitelist any user ports
|
||||
user_firewall_json="/home/yellowtent/boxdata/firewall-config.json"
|
||||
if allowed_tcp_ports=$(node -e "console.log(JSON.parse(fs.readFileSync('${user_firewall_json}', 'utf8')).allowed_tcp_ports.join(','))" 2>/dev/null); then
|
||||
ports_json="/home/yellowtent/boxdata/firewall/ports.json"
|
||||
if allowed_tcp_ports=$(node -e "console.log(JSON.parse(fs.readFileSync('${ports_json}', 'utf8')).allowed_tcp_ports.join(','))" 2>/dev/null); then
|
||||
[[ -n "${allowed_tcp_ports}" ]] && iptables -A CLOUDRON -p tcp -m tcp -m multiport --dports "${allowed_tcp_ports}" -j ACCEPT
|
||||
fi
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@ if [[ -z "$(ls -A /home/yellowtent/boxdata/mail/dkim)" ]]; then
|
||||
printf "\t\t\t-------------------\n"
|
||||
|
||||
printf '\n\e[1;32m%-6s\e[m\n\n' "Visit https://${ip} on your browser and accept the self-signed certificate to finish setup."
|
||||
printf "Cloudron overview - https://cloudron.io/documentation/ \n"
|
||||
printf "Cloudron setup - https://cloudron.io/documentation/installation/#setup \n"
|
||||
printf "Cloudron overview - https://docs.cloudron.io/ \n"
|
||||
printf "Cloudron setup - https://docs.cloudron.io/installation/#setup \n"
|
||||
else
|
||||
printf "\t\t\tNOTE TO CLOUDRON ADMINS\n"
|
||||
printf "\t\t\t-----------------------\n"
|
||||
@@ -23,7 +23,7 @@ else
|
||||
printf "Cloudron relies on and may break your installation. Ubuntu security updates\n"
|
||||
printf "are automatically installed on this server every night.\n"
|
||||
printf "\n"
|
||||
printf "Read more at https://cloudron.io/documentation/security/#os-updates\n"
|
||||
printf "Read more at https://docs.cloudron.io/security/#os-updates\n"
|
||||
fi
|
||||
|
||||
printf "\nFor help and more information, visit https://forum.cloudron.io\n\n"
|
||||
|
||||
+2
-2
@@ -1523,9 +1523,9 @@ function checkConfiguration(callback) {
|
||||
|
||||
let message = '';
|
||||
if (backupConfig.provider === 'noop') {
|
||||
message = 'Cloudron backups are disabled. Please ensure this server is backed up using alternate means. See https://cloudron.io/documentation/backups/#storage-providers for more information.';
|
||||
message = 'Cloudron backups are disabled. Please ensure this server is backed up using alternate means. See https://docs.cloudron.io/backups/#storage-providers for more information.';
|
||||
} else if (backupConfig.provider === 'filesystem' && !backupConfig.externalDisk) {
|
||||
message = 'Cloudron backups are currently on the same disk as the Cloudron server instance. This is dangerous and can lead to complete data loss if the disk fails. See https://cloudron.io/documentation/backups/#storage-providers for storing backups in an external location.';
|
||||
message = 'Cloudron backups are currently on the same disk as the Cloudron server instance. This is dangerous and can lead to complete data loss if the disk fails. See https://docs.cloudron.io/backups/#storage-providers for storing backups in an external location.';
|
||||
}
|
||||
|
||||
callback(null, message);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
exports = module.exports = {
|
||||
// a version change recreates all containers with latest docker config
|
||||
'version': '48.17.0',
|
||||
'version': '48.17.1',
|
||||
|
||||
'baseImages': [
|
||||
{ repo: 'cloudron/base', tag: 'cloudron/base:2.0.0@sha256:f9fea80513aa7c92fe2e7bf3978b54c8ac5222f47a9a32a7f8833edf0eb5a4f4' }
|
||||
|
||||
+1
-1
@@ -550,7 +550,7 @@ function checkConfiguration(callback) {
|
||||
markdownMessage += '\n\n';
|
||||
});
|
||||
|
||||
if (markdownMessage) markdownMessage += 'Email Status is checked every 30 minutes.\n See the [troubleshooting docs](https://cloudron.io/documentation/troubleshooting/#mail-dns) for more information.\n';
|
||||
if (markdownMessage) markdownMessage += 'Email Status is checked every 30 minutes.\n See the [troubleshooting docs](https://docs.cloudron.io/troubleshooting/#mail-dns) for more information.\n';
|
||||
|
||||
callback(null, markdownMessage); // empty message means all status checks succeeded
|
||||
});
|
||||
|
||||
@@ -7,8 +7,8 @@ The application '<%= title %>' installed at <%= appFqdn %> is not responding.
|
||||
This is most likely a problem in the application.
|
||||
|
||||
To resolve this, you can try the following:
|
||||
* Restart the app by opening the app's web terminal - https://cloudron.io/documentation/apps/#web-terminal
|
||||
* Restore the app to the latest backup - https://cloudron.io/documentation/backups/#restoring-an-app
|
||||
* Restart the app by opening the app's web terminal - https://docs.cloudron.io/apps/#web-terminal
|
||||
* Restore the app to the latest backup - https://docs.cloudron.io/backups/#restoring-an-app
|
||||
* Contact us via <%= supportEmail %> or https://forum.cloudron.io
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Dear <%= cloudronName %> Admin,
|
||||
|
||||
Cloudron failed to create a complete backup. Please see https://cloudron.io/documentation/troubleshooting/#backups
|
||||
Cloudron failed to create a complete backup. Please see https://docs.cloudron.io/troubleshooting/#backups
|
||||
for troubleshooting.
|
||||
|
||||
Logs for this failure are available at <%= logUrl %>
|
||||
|
||||
@@ -8,7 +8,7 @@ The Cloudron will attempt to renew the certificate every 12 hours
|
||||
until the certificate expires (at which point it will switch to
|
||||
using the fallback certificate).
|
||||
|
||||
See https://cloudron.io/documentation/troubleshooting/#certificates to
|
||||
See https://docs.cloudron.io/troubleshooting/#certificates to
|
||||
double check if your server is configured correctly to obtain certificates
|
||||
via Let's Encrypt.
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ Dear <%= cloudronName %> Admin,
|
||||
|
||||
If this message appears repeatedly, give the app more memory.
|
||||
|
||||
* To increase an app's memory limit - https://cloudron.io/documentation/apps/#memory-limit
|
||||
* To increase a service's memory limit - https://cloudron.io/documentation/troubleshooting/#services
|
||||
* To increase an app's memory limit - https://docs.cloudron.io/apps/#memory-limit
|
||||
* To increase a service's memory limit - https://docs.cloudron.io/troubleshooting/#services
|
||||
|
||||
Out of memory event:
|
||||
|
||||
|
||||
+21
-14
@@ -7,6 +7,7 @@ exports = module.exports = {
|
||||
|
||||
const assert = require('assert'),
|
||||
BoxError = require('./boxerror.js'),
|
||||
ipaddr = require('ipaddr.js'),
|
||||
path = require('path'),
|
||||
paths = require('./paths.js'),
|
||||
safe = require('safetydance'),
|
||||
@@ -19,27 +20,33 @@ const SET_BLOCKLIST_CMD = path.join(__dirname, 'scripts/setblocklist.sh');
|
||||
function getBlocklist(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
const data = safe.fs.readFileSync(paths.FIREWALL_CONFIG_FILE, 'utf8');
|
||||
const config = safe.JSON.parse(data);
|
||||
const blocklist = config && config.blocklist ? config.blocklist : [];
|
||||
|
||||
callback(null, blocklist);
|
||||
const data = safe.fs.readFileSync(paths.FIREWALL_BLOCKLIST_FILE, 'utf8');
|
||||
callback(null, data);
|
||||
}
|
||||
|
||||
function setBlocklist(blocklist, callback) {
|
||||
assert(Array.isArray(blocklist));
|
||||
function setBlocklist(blocklist, auditSource, callback) {
|
||||
assert.strictEqual(typeof blocklist, 'string');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
if (!blocklist.every(x => validator.isIP(x) || validator.isIPRange(x))) return callback(new BoxError(BoxError.BAD_FIELD, 'blocklist must contain IP or IP range'));
|
||||
const parsedIp = ipaddr.process(auditSource.ip);
|
||||
|
||||
for (const line of blocklist.split('\n')) {
|
||||
if (!line || line.startsWith('#')) continue;
|
||||
const rangeOrIP = line.trim();
|
||||
if (!validator.isIP(rangeOrIP) && !validator.isIPRange(rangeOrIP)) return callback(new BoxError(BoxError.BAD_FIELD, `${rangeOrIP} is not a valid IP or range`));
|
||||
|
||||
if (rangeOrIP.indexOf('/') === -1) {
|
||||
if (auditSource.ip === rangeOrIP) return callback(new BoxError(BoxError.BAD_FIELD, `${rangeOrIP} includes client IP. Cannot block yourself`));
|
||||
} else {
|
||||
const parsedRange = ipaddr.parseCIDR(rangeOrIP);
|
||||
if (parsedIp.match(parsedRange)) return callback(new BoxError(BoxError.BAD_FIELD, `${rangeOrIP} includes client IP. Cannot block yourself`));
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.isDemo()) return callback(new BoxError(BoxError.CONFLICT, 'Not allowed in demo mode'));
|
||||
|
||||
const data = safe.fs.readFileSync(paths.FIREWALL_CONFIG_FILE, 'utf8');
|
||||
const config = safe.JSON.parse(data) || {};
|
||||
|
||||
config.blocklist = blocklist;
|
||||
|
||||
if (!safe.fs.writeFileSync(paths.FIREWALL_CONFIG_FILE, JSON.stringify(config, null, 4), 'utf8')) return callback(new BoxError(BoxError.FS_ERROR, safe.error.message));
|
||||
if (!safe.fs.writeFileSync(paths.FIREWALL_BLOCKLIST_FILE, blocklist + '\n', 'utf8')) return callback(new BoxError(BoxError.FS_ERROR, safe.error.message));
|
||||
|
||||
shell.sudo('setBlocklist', [ SET_BLOCKLIST_CMD ], {}, function (error) {
|
||||
if (error) return callback(new BoxError(BoxError.IPTABLES_ERROR, `Error setting blocklist: ${error.message}`));
|
||||
|
||||
@@ -156,11 +156,11 @@ function oomEvent(eventId, app, addon, containerId, event, callback) {
|
||||
if (app) {
|
||||
program = `App ${app.fqdn}`;
|
||||
title = `The application at ${app.fqdn} ran out of memory.`;
|
||||
message = 'The application has been restarted automatically. If you see this notification often, consider increasing the [memory limit](https://cloudron.io/documentation/apps/#memory-limit)';
|
||||
message = 'The application has been restarted automatically. If you see this notification often, consider increasing the [memory limit](https://docs.cloudron.io/apps/#memory-limit)';
|
||||
} else if (addon) {
|
||||
program = `${addon.name} service`;
|
||||
title = `The ${addon.name} service ran out of memory`;
|
||||
message = 'The service has been restarted automatically. If you see this notification often, consider increasing the [memory limit](https://cloudron.io/documentation/troubleshooting/#services)';
|
||||
message = 'The service has been restarted automatically. If you see this notification often, consider increasing the [memory limit](https://docs.cloudron.io/troubleshooting/#services)';
|
||||
} else { // this never happens currently
|
||||
program = `Container ${containerId}`;
|
||||
title = `The container ${containerId} ran out of memory`;
|
||||
|
||||
@@ -46,6 +46,7 @@ exports = module.exports = {
|
||||
CLOUDRON_AVATAR_FILE: path.join(baseDir(), 'boxdata/avatar.png'),
|
||||
UPDATE_CHECKER_FILE: path.join(baseDir(), 'boxdata/updatechecker.json'),
|
||||
ADDON_TURN_SECRET_FILE: path.join(baseDir(), 'boxdata/addon-turn-secret'),
|
||||
FIREWALL_BLOCKLIST_FILE: path.join(baseDir(), 'boxdata/firewall/blocklist.txt'),
|
||||
FIREWALL_CONFIG_FILE: path.join(baseDir(), 'boxdata/firewall-config.json'),
|
||||
|
||||
LOG_DIR: path.join(baseDir(), 'platformdata/logs'),
|
||||
|
||||
@@ -6,6 +6,7 @@ exports = module.exports = {
|
||||
};
|
||||
|
||||
var assert = require('assert'),
|
||||
auditSource = require('../auditsource.js'),
|
||||
BoxError = require('../boxerror.js'),
|
||||
HttpError = require('connect-lastmile').HttpError,
|
||||
HttpSuccess = require('connect-lastmile').HttpSuccess,
|
||||
@@ -22,10 +23,11 @@ function getBlocklist(req, res, next) {
|
||||
function setBlocklist(req, res, next) {
|
||||
assert.strictEqual(typeof req.body, 'object');
|
||||
|
||||
if (!Array.isArray(req.body.blocklist)) return next(new HttpError(400, 'blocklist is required'));
|
||||
if (!req.body.blocklist.every(x => typeof x === 'string')) return next(new HttpError(400, 'blocklist must be array of strings'));
|
||||
if (typeof req.body.blocklist !== 'string') return next(new HttpError(400, 'blocklist must be a string'));
|
||||
|
||||
network.setBlocklist(req.body.blocklist, function (error) {
|
||||
req.clearTimeout(); // can take a while if there is a lot of network ranges
|
||||
|
||||
network.setBlocklist(req.body.blocklist, auditSource.fromRequest(req), function (error) {
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(200, {}));
|
||||
|
||||
@@ -14,10 +14,13 @@ fi
|
||||
|
||||
ipset flush cloudron_blocklist
|
||||
|
||||
user_firewall_json="/home/yellowtent/boxdata/firewall-config.json"
|
||||
if blocklist=$(node -e "console.log(JSON.parse(fs.readFileSync('${user_firewall_json}', 'utf8')).blocklist.join(' '))" 2>/dev/null); then
|
||||
user_firewall_json="/home/yellowtent/boxdata/firewall/blocklist.txt"
|
||||
|
||||
for ip in ${blocklist}; do
|
||||
ipset add cloudron_blocklist "${ip}"
|
||||
done
|
||||
if [[ -f "${user_firewall_json}" ]]; then
|
||||
# without the -n block, any last line without a new line won't be read it!
|
||||
while read -r line || [[ -n "$line" ]]; do
|
||||
[[ -z "${line}" ]] && continue # ignore empty lines
|
||||
[[ "$line" =~ ^#.*$ ]] && continue # ignore lines starting with #
|
||||
ipset add -! cloudron_blocklist "${line}" # the -! ignore duplicates
|
||||
done < "${user_firewall_json}"
|
||||
fi
|
||||
|
||||
+2
-2
@@ -182,8 +182,8 @@ let gDefaults = (function () {
|
||||
remoteSupport: true,
|
||||
ticketFormBody:
|
||||
'Use this form to open support tickets. You can also write directly to [support@cloudron.io](mailto:support@cloudron.io).\n\n'
|
||||
+ '* [Knowledge Base & App Docs](https://cloudron.io/documentation/apps/?support_view)\n'
|
||||
+ '* [Custom App Packaging & API](https://cloudron.io/developer/packaging/?support_view)\n'
|
||||
+ '* [Knowledge Base & App Docs](https://docs.cloudron.io/apps/?support_view)\n'
|
||||
+ '* [Custom App Packaging & API](https://docs.cloudron.io/custom-apps/tutorial/?support_view)\n'
|
||||
+ '* [Forum](https://forum.cloudron.io/)\n\n',
|
||||
submitTickets: true
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user