Skip to content

Commit dad3e1d

Browse files
committed
Adds support to run processes as a user/group, defined
with PUID and PGID environment variables - Detects if image is run with a user in docker command and fails if so - Adds s6 prepare scripts for adding a 'npmuser' - Split up and refactor the s6 prepare scripts - Runs nginx and backend node as 'npmuser' - Changes ownership of files required at startup
1 parent 82d9452 commit dad3e1d

File tree

21 files changed

+266
-152
lines changed

21 files changed

+266
-152
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ services:
7070
- ./letsencrypt:/etc/letsencrypt
7171
```
7272
73+
This is the bare minimum configuration required. See the [documentation](https://nginxproxymanager.com/setup/) for more.
74+
7375
3. Bring up your stack by running
7476
7577
```bash

backend/internal/certificate.js

+6
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ const internalCertificate = {
4646

4747
const cmd = certbotCommand + ' renew --non-interactive --quiet ' +
4848
'--config "' + letsencryptConfig + '" ' +
49+
'--work-dir "/tmp/letsencrypt-lib" ' +
50+
'--logs-dir "/tmp/letsencrypt-log" ' +
4951
'--preferred-challenges "dns,http" ' +
5052
'--disable-hook-validation ' +
5153
(letsencryptStaging ? '--staging' : '');
@@ -833,6 +835,8 @@ const internalCertificate = {
833835

834836
const cmd = certbotCommand + ' certonly ' +
835837
'--config "' + letsencryptConfig + '" ' +
838+
'--work-dir "/tmp/letsencrypt-lib" ' +
839+
'--logs-dir "/tmp/letsencrypt-log" ' +
836840
'--cert-name "npm-' + certificate.id + '" ' +
837841
'--agree-tos ' +
838842
'--authenticator webroot ' +
@@ -878,6 +882,8 @@ const internalCertificate = {
878882

879883
let mainCmd = certbotCommand + ' certonly ' +
880884
'--config "' + letsencryptConfig + '" ' +
885+
'--work-dir "/tmp/letsencrypt-lib" ' +
886+
'--logs-dir "/tmp/letsencrypt-log" ' +
881887
'--cert-name "npm-' + certificate.id + '" ' +
882888
'--agree-tos ' +
883889
'--email "' + certificate.meta.letsencrypt_email + '" ' +

docker/docker-compose.dev.yml

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ services:
1414
networks:
1515
- nginx_proxy_manager
1616
environment:
17+
PUID: 1000
18+
PGID: 1000
1719
NODE_ENV: "development"
1820
FORCE_COLOR: 1
1921
DEVELOPMENT: "true"

docker/rootfs/bin/common.sh

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/bin/bash
2+
3+
set -e
4+
5+
CYAN='\E[1;36m'
6+
BLUE='\E[1;34m'
7+
YELLOW='\E[1;33m'
8+
RED='\E[1;31m'
9+
RESET='\E[0m'
10+
export CYAN BLUE YELLOW RED RESET
11+
12+
log_info () {
13+
echo -e "${BLUE}${CYAN}$1${RESET}"
14+
}
15+
16+
log_error () {
17+
echo -e "${RED}$1${RESET}"
18+
}
19+
20+
# The `run` file will only execute 1 line so this helps keep things
21+
# logically separated
22+
23+
log_fatal () {
24+
echo -e "${RED}--------------------------------------${RESET}"
25+
echo -e "${RED}ERROR: $1${RESET}"
26+
echo -e "${RED}--------------------------------------${RESET}"
27+
/run/s6/basedir/bin/halt
28+
exit 1
29+
}

docker/rootfs/bin/handle-ipv6-setting

-46
This file was deleted.

docker/rootfs/etc/nginx/nginx.conf

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# run nginx in foreground
22
daemon off;
3-
4-
user root;
3+
pid /run/nginx/nginx.pid;
54

65
# Set number of worker processes automatically based on number of CPU cores.
76
worker_processes auto;
@@ -57,7 +56,7 @@ http {
5756
}
5857

5958
# Real IP Determination
60-
59+
6160
# Local subnets:
6261
set_real_ip_from 10.0.0.0/8;
6362
set_real_ip_from 172.16.0.0/12; # Includes Docker subnet

docker/rootfs/etc/s6-overlay/s6-rc.d/backend/run

+7-4
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,20 @@
33

44
set -e
55

6-
echo "❯ Starting backend ..."
6+
. /bin/common.sh
7+
8+
log_info 'Starting backend ...'
9+
710
if [ "$DEVELOPMENT" == "true" ]; then
811
cd /app || exit 1
912
# If yarn install fails: add --verbose --network-concurrency 1
10-
yarn install
11-
node --max_old_space_size=250 --abort_on_uncaught_exception node_modules/nodemon/bin/nodemon.js
13+
s6-setuidgid npmuser yarn install
14+
exec s6-setuidgid npmuser node --max_old_space_size=250 --abort_on_uncaught_exception node_modules/nodemon/bin/nodemon.js
1215
else
1316
cd /app || exit 1
1417
while :
1518
do
16-
node --abort_on_uncaught_exception --max_old_space_size=250 index.js
19+
s6-setuidgid npmuser node --abort_on_uncaught_exception --max_old_space_size=250 index.js
1720
sleep 1
1821
done
1922
fi

docker/rootfs/etc/s6-overlay/s6-rc.d/frontend/run

+8-2
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,16 @@ set -e
66
# This service is DEVELOPMENT only.
77

88
if [ "$DEVELOPMENT" == "true" ]; then
9+
. /bin/common.sh
910
cd /app/frontend || exit 1
11+
log_info 'Starting frontend ...'
12+
HOME=/tmp/npmuserhome
13+
export HOME
14+
mkdir -p /app/frontend/dist
15+
chown -R npmuser:npmuser /app/frontend/dist
1016
# If yarn install fails: add --verbose --network-concurrency 1
11-
yarn install
12-
yarn watch
17+
s6-setuidgid npmuser yarn install
18+
exec s6-setuidgid npmuser yarn watch
1319
else
1420
exit 0
1521
fi

docker/rootfs/etc/s6-overlay/s6-rc.d/nginx/run

+5-2
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,8 @@
33

44
set -e
55

6-
echo "❯ Starting nginx ..."
7-
exec nginx
6+
. /bin/common.sh
7+
8+
log_info 'Starting nginx ...'
9+
10+
exec s6-setuidgid npmuser nginx
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/command/with-contenv bash
2+
# shellcheck shell=bash
3+
4+
set -e
5+
6+
. /bin/common.sh
7+
8+
if [ "$(id -u)" != "0" ]; then
9+
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."
10+
fi
11+
12+
. /etc/s6-overlay/s6-rc.d/prepare/10-npmuser.sh
13+
. /etc/s6-overlay/s6-rc.d/prepare/20-paths.sh
14+
. /etc/s6-overlay/s6-rc.d/prepare/30-ownership.sh
15+
. /etc/s6-overlay/s6-rc.d/prepare/40-dynamic.sh
16+
. /etc/s6-overlay/s6-rc.d/prepare/50-ipv6.sh
17+
. /etc/s6-overlay/s6-rc.d/prepare/60-secrets.sh
18+
. /etc/s6-overlay/s6-rc.d/prepare/90-banner.sh
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/command/with-contenv bash
2+
# shellcheck shell=bash
3+
4+
set -e
5+
6+
PUID=${PUID:-911}
7+
PGID=${PGID:-911}
8+
9+
# Add npmuser user
10+
log_info 'Creating npmuser ...'
11+
12+
groupmod -g 1000 users || exit 1
13+
useradd -u "${PUID}" -U -d /data -s /bin/false npmuser || exit 1
14+
usermod -G users npmuser || exit 1
15+
groupmod -o -g "$PGID" npmuser || exit 1
16+
# Home for npmuser
17+
mkdir -p /tmp/npmuserhome
18+
chown -R npmuser:npmuser /tmp/npmuserhome
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#!/command/with-contenv bash
2+
# shellcheck shell=bash
3+
4+
set -e
5+
6+
log_info 'Checking paths ...'
7+
8+
# Ensure /data is mounted
9+
if [ ! -d '/data' ]; then
10+
log_fatal '/data is not mounted! Check your docker configuration.'
11+
fi
12+
# Ensure /etc/letsencrypt is mounted
13+
if [ ! -d '/etc/letsencrypt' ]; then
14+
log_fatal '/etc/letsencrypt is not mounted! Check your docker configuration.'
15+
fi
16+
17+
# Create required folders
18+
mkdir -p \
19+
/data/nginx \
20+
/data/custom_ssl \
21+
/data/logs \
22+
/data/access \
23+
/data/nginx/default_host \
24+
/data/nginx/default_www \
25+
/data/nginx/proxy_host \
26+
/data/nginx/redirection_host \
27+
/data/nginx/stream \
28+
/data/nginx/dead_host \
29+
/data/nginx/temp \
30+
/data/letsencrypt-acme-challenge \
31+
/run/nginx \
32+
/tmp/nginx/body \
33+
/var/log/nginx \
34+
/var/lib/nginx/cache/public \
35+
/var/lib/nginx/cache/private \
36+
/var/cache/nginx/proxy_temp
37+
38+
touch /var/log/nginx/error.log || true
39+
chmod 777 /var/log/nginx/error.log || true
40+
chmod -R 777 /var/cache/nginx || true
41+
chmod 644 /etc/logrotate.d/nginx-proxy-manager
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/command/with-contenv bash
2+
# shellcheck shell=bash
3+
4+
set -e
5+
6+
log_info 'Setting ownership ...'
7+
8+
# root
9+
chown root /tmp/nginx
10+
11+
# npmuser
12+
chown -R npmuser:npmuser \
13+
/data \
14+
/etc/letsencrypt \
15+
/etc/nginx \
16+
/run/nginx \
17+
/tmp/nginx \
18+
/var/cache/nginx \
19+
/var/lib/logrotate \
20+
/var/lib/nginx \
21+
/var/log/nginx
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/command/with-contenv bash
2+
# shellcheck shell=bash
3+
4+
set -e
5+
6+
log_info 'Dynamic resolvers ...'
7+
8+
DISABLE_IPV6=$(echo "${DISABLE_IPV6:-}" | tr '[:upper:]' '[:lower:]')
9+
10+
# Dynamically generate resolvers file, if resolver is IPv6, enclose in `[]`
11+
# thanks @tfmm
12+
if [ "$DISABLE_IPV6" == "true" ] || [ "$DISABLE_IPV6" == "on" ] || [ "$DISABLE_IPV6" == "1" ] || [ "$DISABLE_IPV6" == "yes" ];
13+
then
14+
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
15+
else
16+
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
17+
fi
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#!/bin/bash
2+
3+
# This command reads the `DISABLE_IPV6` env var and will either enable
4+
# or disable ipv6 in all nginx configs based on this setting.
5+
6+
log_info 'IPv6 ...'
7+
8+
# Lowercase
9+
DISABLE_IPV6=$(echo "${DISABLE_IPV6:-}" | tr '[:upper:]' '[:lower:]')
10+
11+
process_folder () {
12+
FILES=$(find "$1" -type f -name "*.conf")
13+
SED_REGEX=
14+
15+
if [ "$DISABLE_IPV6" == "true" ] || [ "$DISABLE_IPV6" == "on" ] || [ "$DISABLE_IPV6" == "1" ] || [ "$DISABLE_IPV6" == "yes" ]; then
16+
# IPV6 is disabled
17+
echo "Disabling IPV6 in hosts in: $1"
18+
SED_REGEX='s/^([^#]*)listen \[::\]/\1#listen [::]/g'
19+
else
20+
# IPV6 is enabled
21+
echo "Enabling IPV6 in hosts in: $1"
22+
SED_REGEX='s/^(\s*)#listen \[::\]/\1listen [::]/g'
23+
fi
24+
25+
for FILE in $FILES
26+
do
27+
echo "- ${FILE}"
28+
sed -E -i "$SED_REGEX" "$FILE"
29+
done
30+
31+
# ensure the files are still owned by the npmuser
32+
chown -R npmuser:npmuser "$1"
33+
}
34+
35+
process_folder /etc/nginx/conf.d
36+
process_folder /data/nginx
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#!/command/with-contenv bash
2+
# shellcheck shell=bash
3+
4+
set -e
5+
6+
# in s6, environmental variables are written as text files for s6 to monitor
7+
# search through full-path filenames for files ending in "__FILE"
8+
log_info 'Docker secrets ...'
9+
10+
for FILENAME in $(find /var/run/s6/container_environment/ | grep "__FILE$"); do
11+
echo "[secret-init] Evaluating ${FILENAME##*/} ..."
12+
13+
# set SECRETFILE to the contents of the full-path textfile
14+
SECRETFILE=$(cat "${FILENAME}")
15+
# if SECRETFILE exists / is not null
16+
if [[ -f "${SECRETFILE}" ]]; then
17+
# strip the appended "__FILE" from environmental variable name ...
18+
STRIPFILE=$(echo "${FILENAME}" | sed "s/__FILE//g")
19+
# echo "[secret-init] Set STRIPFILE to ${STRIPFILE}" # DEBUG - rm for prod!
20+
21+
# ... and set value to contents of secretfile
22+
# since s6 uses text files, this is effectively "export ..."
23+
printf $(cat "${SECRETFILE}") > "${STRIPFILE}"
24+
# echo "[secret-init] Set ${STRIPFILE##*/} to $(cat ${STRIPFILE})" # DEBUG - rm for prod!"
25+
echo "Success: ${STRIPFILE##*/} set from ${FILENAME##*/}"
26+
27+
else
28+
echo "Cannot find secret in ${FILENAME}"
29+
fi
30+
done

0 commit comments

Comments
 (0)