ocmaster-rebuild

This commit is contained in:
Nick Craig
2023-05-03 09:00:28 -04:00
parent 469ecadbbb
commit b3e228285d
30 changed files with 549 additions and 2 deletions

184
backend/lib/config.js Normal file
View File

@@ -0,0 +1,184 @@
const fs = require('fs');
const NodeRSA = require('node-rsa');
const logger = require('../logger').global;
const keysFile = '/data/keys.json';
let instance = null;
// 1. Load from config file first (not recommended anymore)
// 2. Use config env variables next
const configure = () => {
const filename = (process.env.NODE_CONFIG_DIR || './config') + '/' + (process.env.NODE_ENV || 'default') + '.json';
if (fs.existsSync(filename)) {
let configData;
try {
configData = require(filename);
} catch (err) {
// do nothing
}
if (configData && configData.database) {
logger.info(`Using configuration from file: ${filename}`);
instance = configData;
instance.keys = getKeys();
return;
}
}
const envMysqlHost = process.env.DB_MYSQL_HOST || null;
const envMysqlUser = process.env.DB_MYSQL_USER || null;
const envMysqlName = process.env.DB_MYSQL_NAME || null;
if (envMysqlHost && envMysqlUser && envMysqlName) {
// we have enough mysql creds to go with mysql
logger.info('Using MySQL configuration');
instance = {
database: {
engine: 'mysql',
host: envMysqlHost,
port: process.env.DB_MYSQL_PORT || 3306,
user: envMysqlUser,
password: process.env.DB_MYSQL_PASSWORD,
name: envMysqlName,
},
keys: getKeys(),
};
return;
}
const envSqliteFile = process.env.DB_SQLITE_FILE || '/data/database.sqlite';
logger.info(`Using Sqlite: ${envSqliteFile}`);
instance = {
database: {
engine: 'knex-native',
knex: {
client: 'sqlite3',
connection: {
filename: envSqliteFile
},
useNullAsDefault: true
}
},
keys: getKeys(),
};
};
const getKeys = () => {
// Get keys from file
if (!fs.existsSync(keysFile)) {
generateKeys();
} else if (process.env.DEBUG) {
logger.info('Keys file exists OK');
}
try {
return require(keysFile);
} catch (err) {
logger.error('Could not read JWT key pair from config file: ' + keysFile, err);
process.exit(1);
}
};
const generateKeys = () => {
logger.info('Creating a new JWT key pair...');
// Now create the keys and save them in the config.
const key = new NodeRSA({ b: 2048 });
key.generateKeyPair();
const keys = {
key: key.exportKey('private').toString(),
pub: key.exportKey('public').toString(),
};
// Write keys config
try {
fs.writeFileSync(keysFile, JSON.stringify(keys, null, 2));
} catch (err) {
logger.error('Could not write JWT key pair to config file: ' + keysFile + ': ' . err.message);
process.exit(1);
}
logger.info('Wrote JWT key pair to config file: ' + keysFile);
};
module.exports = {
/**
*
* @param {string} key ie: 'database' or 'database.engine'
* @returns {boolean}
*/
has: function(key) {
instance === null && configure();
const keys = key.split('.');
let level = instance;
let has = true;
keys.forEach((keyItem) =>{
if (typeof level[keyItem] === 'undefined') {
has = false;
} else {
level = level[keyItem];
}
});
return has;
},
/**
* Gets a specific key from the top level
*
* @param {string} key
* @returns {*}
*/
get: function (key) {
instance === null && configure();
if (key && typeof instance[key] !== 'undefined') {
return instance[key];
}
return instance;
},
/**
* Is this a sqlite configuration?
*
* @returns {boolean}
*/
isSqlite: function () {
instance === null && configure();
return instance.database.knex && instance.database.knex.client === 'sqlite3';
},
/**
* Are we running in debug mdoe?
*
* @returns {boolean}
*/
debug: function () {
return !!process.env.DEBUG;
},
/**
* Returns a public key
*
* @returns {string}
*/
getPublicKey: function () {
instance === null && configure();
return instance.keys.pub;
},
/**
* Returns a private key
*
* @returns {string}
*/
getPrivateKey: function () {
instance === null && configure();
return instance.keys.key;
},
/**
* @returns {boolean}
*/
useLetsencryptStaging: function () {
return !!process.env.LE_STAGING;
}
};

View File

@@ -0,0 +1,25 @@
{% if access_list_id > 0 %}
{% if access_list.items.length > 0 %}
# Authorization
auth_basic "Authorization required";
auth_basic_user_file /data/access/{{ access_list_id }};
{% if access_list.pass_auth == 0 %}
proxy_set_header Authorization "";
{% endif %}
{% endif %}
# Access Rules: {{ access_list.clients | size }} total
{% for client in access_list.clients %}
{{client | nginxAccessRule}}
{% endfor %}
deny all;
# Access checks must...
{% if access_list.satisfy_any == 1 %}
satisfy any;
{% else %}
satisfy all;
{% endif %}
{% endif %}

View File

@@ -0,0 +1,42 @@
#!/bin/bash
set -e
CYAN='\E[1;36m'
BLUE='\E[1;34m'
YELLOW='\E[1;33m'
RED='\E[1;31m'
RESET='\E[0m'
export CYAN BLUE YELLOW RED RESET
PUID=${PUID:-0}
PGID=${PGID:-0}
if [[ "$PUID" -ne '0' ]] && [ "$PGID" = '0' ]; then
# set group id to same as user id,
# the user probably forgot to specify the group id and
# it would be rediculous to intentionally use the root group
# for a non-root user
PGID=$PUID
fi
export PUID PGID
log_info () {
echo -e "${BLUE} ${CYAN}$1${RESET}"
}
log_error () {
echo -e "${RED} $1${RESET}"
}
# The `run` file will only execute 1 line so this helps keep things
# logically separated
log_fatal () {
echo -e "${RED}--------------------------------------${RESET}"
echo -e "${RED}ERROR: $1${RESET}"
echo -e "${RED}--------------------------------------${RESET}"
/run/s6/basedir/bin/halt
exit 1
}

View File

@@ -0,0 +1,21 @@
#!/command/with-contenv bash
# shellcheck shell=bash
set -e
. /bin/common.sh
cd /app || exit 1
log_info 'Starting backend ...'
if [ "${DEVELOPMENT:-}" = 'true' ]; then
s6-setuidgid npmuser yarn install
exec s6-setuidgid npmuser bash -c 'export HOME=/tmp/npmuserhome;node --max_old_space_size=250 --abort_on_uncaught_exception node_modules/nodemon/bin/nodemon.js'
else
while :
do
s6-setuidgid npmuser bash -c 'export HOME=/tmp/npmuserhome;node --abort_on_uncaught_exception --max_old_space_size=250 index.js'
sleep 1
done
fi

View File

@@ -0,0 +1 @@
longrun

View File

@@ -0,0 +1,21 @@
#!/command/with-contenv bash
# shellcheck shell=bash
set -e
# This service is DEVELOPMENT only.
if [ "$DEVELOPMENT" = 'true' ]; then
. /bin/common.sh
cd /app/frontend || exit 1
HOME=/tmp/npmuserhome
export HOME
mkdir -p /app/frontend/dist
chown -R "$PUID:$PGID" /app/frontend/dist
log_info 'Starting frontend ...'
s6-setuidgid npmuser yarn install
exec s6-setuidgid npmuser yarn watch
else
exit 0
fi

View File

@@ -0,0 +1 @@
longrun

View File

@@ -0,0 +1,9 @@
#!/command/with-contenv bash
# shellcheck shell=bash
set -e
. /bin/common.sh
log_info 'Starting nginx ...'
exec s6-setuidgid npmuser nginx

View File

@@ -0,0 +1 @@
longrun

View File

@@ -0,0 +1,18 @@
#!/command/with-contenv bash
# shellcheck shell=bash
set -e
. /bin/common.sh
if [ "$(id -u)" != "0" ]; then
log_fatal "This docker container must be run as root, do not specify a user.\nYou can specify PUID and PGID env vars to run processes as that user and group after initialization."
fi
. /etc/s6-overlay/s6-rc.d/prepare/10-npmuser.sh
. /etc/s6-overlay/s6-rc.d/prepare/20-paths.sh
. /etc/s6-overlay/s6-rc.d/prepare/30-ownership.sh
. /etc/s6-overlay/s6-rc.d/prepare/40-dynamic.sh
. /etc/s6-overlay/s6-rc.d/prepare/50-ipv6.sh
. /etc/s6-overlay/s6-rc.d/prepare/60-secrets.sh
. /etc/s6-overlay/s6-rc.d/prepare/90-banner.sh

View File

@@ -0,0 +1,20 @@
#!/command/with-contenv bash
# shellcheck shell=bash
set -e
log_info 'Configuring npmuser ...'
if id -u npmuser; then
# user already exists
usermod -u "$PUID" npmuser || exit 1
else
# Add npmuser user
useradd -o -u "$PUID" -U -d /tmp/npmuserhome -s /bin/false npmuser || exit 1
fi
usermod -G "$PGID" npmuser || exit 1
groupmod -o -g "$PGID" npmuser || exit 1
# Home for npmuser
mkdir -p /tmp/npmuserhome
chown -R "$PUID:$PGID" /tmp/npmuserhome

View File

@@ -0,0 +1,41 @@
#!/command/with-contenv bash
# shellcheck shell=bash
set -e
log_info 'Checking paths ...'
# Ensure /data is mounted
if [ ! -d '/data' ]; then
log_fatal '/data is not mounted! Check your docker configuration.'
fi
# Ensure /etc/letsencrypt is mounted
if [ ! -d '/etc/letsencrypt' ]; then
log_fatal '/etc/letsencrypt is not mounted! Check your docker configuration.'
fi
# Create required folders
mkdir -p \
/data/nginx \
/data/custom_ssl \
/data/logs \
/data/access \
/data/nginx/default_host \
/data/nginx/default_www \
/data/nginx/proxy_host \
/data/nginx/redirection_host \
/data/nginx/stream \
/data/nginx/dead_host \
/data/nginx/temp \
/data/letsencrypt-acme-challenge \
/run/nginx \
/tmp/nginx/body \
/var/log/nginx \
/var/lib/nginx/cache/public \
/var/lib/nginx/cache/private \
/var/cache/nginx/proxy_temp
touch /var/log/nginx/error.log || true
chmod 777 /var/log/nginx/error.log || true
chmod -R 777 /var/cache/nginx || true
chmod 644 /etc/logrotate.d/nginx-proxy-manager

View File

@@ -0,0 +1,24 @@
#!/command/with-contenv bash
# shellcheck shell=bash
set -e
log_info 'Setting ownership ...'
# root
chown root /tmp/nginx
# npmuser
chown -R "$PUID:$PGID" /data \
/etc/letsencrypt \
/run/nginx \
/tmp/nginx \
/var/cache/nginx \
/var/lib/logrotate \
/var/lib/nginx \
/var/log/nginx
# Don't chown entire /etc/nginx folder as this causes crashes on some systems
chown -R "$PUID:$PGID" /etc/nginx/nginx \
/etc/nginx/nginx.conf \
/etc/nginx/conf.d

View File

@@ -0,0 +1,17 @@
#!/command/with-contenv bash
# shellcheck shell=bash
set -e
log_info 'Dynamic resolvers ...'
DISABLE_IPV6=$(echo "${DISABLE_IPV6:-}" | tr '[:upper:]' '[:lower:]')
# Dynamically generate resolvers file, if resolver is IPv6, enclose in `[]`
# thanks @tfmm
if [ "$DISABLE_IPV6" == "true" ] || [ "$DISABLE_IPV6" == "on" ] || [ "$DISABLE_IPV6" == "1" ] || [ "$DISABLE_IPV6" == "yes" ];
then
echo resolver "$(awk 'BEGIN{ORS=" "} $1=="nameserver" { sub(/%.*$/,"",$2); print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf) ipv6=off valid=10s;" > /etc/nginx/conf.d/include/resolvers.conf
else
echo resolver "$(awk 'BEGIN{ORS=" "} $1=="nameserver" { sub(/%.*$/,"",$2); print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf) valid=10s;" > /etc/nginx/conf.d/include/resolvers.conf
fi

View File

@@ -0,0 +1,36 @@
#!/bin/bash
# This command reads the `DISABLE_IPV6` env var and will either enable
# or disable ipv6 in all nginx configs based on this setting.
log_info 'IPv6 ...'
# Lowercase
DISABLE_IPV6=$(echo "${DISABLE_IPV6:-}" | tr '[:upper:]' '[:lower:]')
process_folder () {
FILES=$(find "$1" -type f -name "*.conf")
SED_REGEX=
if [ "$DISABLE_IPV6" == "true" ] || [ "$DISABLE_IPV6" == "on" ] || [ "$DISABLE_IPV6" == "1" ] || [ "$DISABLE_IPV6" == "yes" ]; then
# IPV6 is disabled
echo "Disabling IPV6 in hosts in: $1"
SED_REGEX='s/^([^#]*)listen \[::\]/\1#listen [::]/g'
else
# IPV6 is enabled
echo "Enabling IPV6 in hosts in: $1"
SED_REGEX='s/^(\s*)#listen \[::\]/\1listen [::]/g'
fi
for FILE in $FILES
do
echo "- ${FILE}"
sed -E -i "$SED_REGEX" "$FILE"
done
# ensure the files are still owned by the npmuser
chown -R "$PUID:$PGID" "$1"
}
process_folder /etc/nginx/conf.d
process_folder /data/nginx

View File

@@ -0,0 +1,30 @@
#!/command/with-contenv bash
# shellcheck shell=bash
set -e
# in s6, environmental variables are written as text files for s6 to monitor
# search through full-path filenames for files ending in "__FILE"
log_info 'Docker secrets ...'
for FILENAME in $(find /var/run/s6/container_environment/ | grep "__FILE$"); do
echo "[secret-init] Evaluating ${FILENAME##*/} ..."
# set SECRETFILE to the contents of the full-path textfile
SECRETFILE=$(cat "${FILENAME}")
# if SECRETFILE exists / is not null
if [[ -f "${SECRETFILE}" ]]; then
# strip the appended "__FILE" from environmental variable name ...
STRIPFILE=$(echo "${FILENAME}" | sed "s/__FILE//g")
# echo "[secret-init] Set STRIPFILE to ${STRIPFILE}" # DEBUG - rm for prod!
# ... and set value to contents of secretfile
# since s6 uses text files, this is effectively "export ..."
printf $(cat "${SECRETFILE}") > "${STRIPFILE}"
# echo "[secret-init] Set ${STRIPFILE##*/} to $(cat ${STRIPFILE})" # DEBUG - rm for prod!"
echo "Success: ${STRIPFILE##*/} set from ${FILENAME##*/}"
else
echo "Cannot find secret in ${FILENAME}"
fi
done

View File

@@ -0,0 +1,17 @@
#!/command/with-contenv bash
# shellcheck shell=bash
set -e
echo "
-------------------------------------
_ _ ____ __ __
| \ | | _ \| \/ |
| \| | |_) | |\/| |
| |\ | __/| | | |
|_| \_|_| |_| |_|
-------------------------------------
User ID: $PUID
Group ID: $PGID
-------------------------------------
"

View File

@@ -0,0 +1 @@
oneshot

View File

@@ -0,0 +1,2 @@
# shellcheck shell=bash
/etc/s6-overlay/s6-rc.d/prepare/00-all.sh

View File

@@ -1 +0,0 @@
/bin/true

38
docker/scripts/install-s6 Normal file
View File

@@ -0,0 +1,38 @@
#!/bin/bash -e
# Note: This script is designed to be run inside a Docker Build for a container
CYAN='\E[1;36m'
YELLOW='\E[1;33m'
BLUE='\E[1;34m'
GREEN='\E[1;32m'
RESET='\E[0m'
S6_OVERLAY_VERSION=3.1.4.1
TARGETPLATFORM=${1:unspecified}
# Determine the correct binary file for the architecture given
case $TARGETPLATFORM in
linux/arm64)
S6_ARCH=aarch64
;;
linux/arm/v7)
S6_ARCH=armhf
;;
*)
S6_ARCH=x86_64
;;
esac
echo -e "${BLUE} ${CYAN}Installing S6-overlay v${S6_OVERLAY_VERSION} for ${YELLOW}${TARGETPLATFORM} (${S6_ARCH})${RESET}"
curl -L -o '/tmp/s6-overlay-noarch.tar.xz' "https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz"
curl -L -o "/tmp/s6-overlay-${S6_ARCH}.tar.xz" "https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-${S6_ARCH}.tar.xz"
tar -C / -Jxpf '/tmp/s6-overlay-noarch.tar.xz'
tar -C / -Jxpf "/tmp/s6-overlay-${S6_ARCH}.tar.xz"
rm -rf "/tmp/s6-overlay-${S6_ARCH}.tar.xz"
echo -e "${BLUE} ${GREEN}S6-overlay install Complete${RESET}"

View File

@@ -1 +0,0 @@
../../README.md