Compare commits
300 Commits
v2.9.21
...
63d06da8a8
Author | SHA1 | Date | |
---|---|---|---|
63d06da8a8 | |||
b5a0d74654 | |||
99cce7e2b0 | |||
120d50e5c0 | |||
5454fd61b3 | |||
b33012705b | |||
e948b60194 | |||
7913c9a07d | |||
d1c23b6286 | |||
c7e2946dbf | |||
8936402229 | |||
001c77e686 | |||
5578e825b1 | |||
c93656a7a1 | |||
50aeae234f | |||
a5c06c1a34 | |||
51414ced3a | |||
5e35e538af | |||
13fec42d1f | |||
b4560d7dde | |||
6f9eed8a61 | |||
d66e4e03e6 | |||
1d19c29bb0 | |||
e20a11de4a | |||
d3a654b546 | |||
bed387ebd4 | |||
6ac9a82279 | |||
ef23e796ec | |||
3754a569ba | |||
b383f46656 | |||
3ce477d350 | |||
516b4d991c | |||
12d77e3ab6 | |||
8d80af3a26 | |||
1f45e6a5e9 | |||
dcb9628c36 | |||
029b184398 | |||
2422587530 | |||
4ee940d3dc | |||
47dddc548b | |||
256a667e2c | |||
79cd0c5294 | |||
09a03edfd7 | |||
35f0fe745d | |||
f1e433714e | |||
035eaed0a4 | |||
4b100a384d | |||
c5c5fa0a5a | |||
280bac8b43 | |||
02aefa50cd | |||
4d91cfc397 | |||
79a453f2fe | |||
c62c09569d | |||
09bcf4010c | |||
6aeade6c98 | |||
8655b7d2db | |||
2d929dffa8 | |||
52eaa042d8 | |||
b35aa50b88 | |||
c575a706b5 | |||
587b97c2d3 | |||
317003beda | |||
5a761236c5 | |||
b135527347 | |||
abca9cc89c | |||
6721923601 | |||
a88f77c1a5 | |||
a5b21d0306 | |||
8eab8d71f2 | |||
d06572bb5f | |||
d40f9e06fc | |||
69ec017a53 | |||
fa67f257ef | |||
0dcd648c9d | |||
c989a282e3 | |||
5aff969c04 | |||
bfbf7519ec | |||
bf36c7966a | |||
63cd9ba08f | |||
e3d4882c3d | |||
3e1b73143e | |||
10ece3548d | |||
0503a6af75 | |||
55d765e785 | |||
1fb9a75a33 | |||
9c2e838d61 | |||
c55e47aacf | |||
40d81d6e44 | |||
1c84eaac02 | |||
577954ef8c | |||
f0c75641d8 | |||
e42e2acf12 | |||
eaa11fe460 | |||
5b53825ccb | |||
a94660120f | |||
39f4836485 | |||
aec30207da | |||
209c1b3334 | |||
58138fbac4 | |||
da820db4e1 | |||
47b868bfc6 | |||
89a405f60c | |||
0353051436 | |||
a3630a6286 | |||
10d9760242 | |||
c722eb1cea | |||
0472abacd2 | |||
a2e85ceed8 | |||
cddd6fb985 | |||
db23c9a52f | |||
8646cb5a19 | |||
fe0c04610f | |||
9f16dae2ff | |||
00264bcfb2 | |||
834fb1a361 | |||
1be87f48c1 | |||
9c54d1b718 | |||
f7d1c490b3 | |||
fe4bd9fed6 | |||
58ef9a688e | |||
d19ebf5925 | |||
96fc6a20bb | |||
e69684919c | |||
be39253a6f | |||
30772a48bd | |||
33c867895c | |||
a7fe687bae | |||
4028120f55 | |||
d1119ec63f | |||
4c906283df | |||
8ec0c76f51 | |||
c70f65d349 | |||
883a272b0a | |||
6aee2bbcba | |||
025fc9776b | |||
b699f05f47 | |||
f7c87f63bd | |||
e4ef095254 | |||
09d5e2c94f | |||
459b7a2223 | |||
9c813bcce3 | |||
b8596ac01c | |||
082c4e1008 | |||
2273eae6ee | |||
997e9d431b | |||
b3564b6d4b | |||
4e27cdabc4 | |||
965873adc5 | |||
5de95a8c90 | |||
fa557d8159 | |||
bc8211a6a9 | |||
1c498f84ad | |||
ea6e9757e3 | |||
1308ae42c2 | |||
7be548575b | |||
c6aab8d4e6 | |||
da55e93183 | |||
af475ab5d4 | |||
7d85463dae | |||
13d4f98fdb | |||
388fff84f2 | |||
49a765516c | |||
27bc8c4e33 | |||
881a067aff | |||
1975e4a151 | |||
4704bd6a38 | |||
ca56e0483f | |||
3b8cb86d72 | |||
5165de4a91 | |||
1ab3575c68 | |||
ccf9cce825 | |||
3ad2188f78 | |||
33dbffb974 | |||
289e438c59 | |||
e08a4d4490 | |||
d1d1819677 | |||
4e0768d56c | |||
3666364418 | |||
9052502a17 | |||
b608d3392d | |||
edb81ecce0 | |||
e24181936f | |||
940d06cac9 | |||
134902d127 | |||
2df4620d05 | |||
f41b1069ae | |||
004a93fbc3 | |||
2d9f04edcd | |||
53dbe258a5 | |||
e4ba22f0f8 | |||
3197de41de | |||
0f7be7987b | |||
853c48dff6 | |||
410c3484ab | |||
44e9f377f9 | |||
0f3b76f607 | |||
f426e64569 | |||
4867db078c | |||
6b565e628f | |||
881d70502b | |||
62e4edddf0 | |||
4b9c02cc0c | |||
5af834e40b | |||
6f8db95249 | |||
fe93cb3474 | |||
fa851b61da | |||
3333a32612 | |||
9a79fce498 | |||
b1180f5077 | |||
5454352fe5 | |||
aee93a2f6f | |||
f38cb5b500 | |||
f1b7156c89 | |||
98465cf1b0 | |||
137e865b66 | |||
e740fb4064 | |||
f91f0ee8db | |||
1c9f751512 | |||
a602bdd514 | |||
f7b2be68cc | |||
ab4586fc6b | |||
a984a68065 | |||
52875fca6e | |||
63b50fcd95 | |||
5ab4aea03f | |||
d73135378e | |||
e19d685cb6 | |||
c8caaa56d9 | |||
11a98f4c12 | |||
4a85d4ac4e | |||
3138ba46ce | |||
cdd0b2e6d3 | |||
f458730d87 | |||
d20873dcbb | |||
d1e9407e4d | |||
63ee69f432 | |||
f39e527680 | |||
2dd4434ceb | |||
81054631f9 | |||
53d61bd626 | |||
847e879b3f | |||
4c59400731 | |||
824c837a38 | |||
2a06384a4a | |||
05307aa253 | |||
3d2406ac3d | |||
0127dc7f03 | |||
4349d42636 | |||
4b6f9d9419 | |||
c3f019c911 | |||
ecf0290203 | |||
4f41fe0c95 | |||
c3735fdbbb | |||
c432c34fb3 | |||
a1245bc161 | |||
db4ab1d548 | |||
86ddd9c83c | |||
67208e43cc | |||
ddf80302c6 | |||
5f2576946d | |||
9fe07fa6c3 | |||
d9b9af543e | |||
eb2e2e0478 | |||
9225d5d442 | |||
308a7149ed | |||
8a4a7d0caf | |||
5d03ede100 | |||
4a86bb42cc | |||
dad8561ea1 | |||
56a92e5c0e | |||
9d672f5813 | |||
d5ed70dbb6 | |||
c197e66d62 | |||
91cf3c8873 | |||
7f5e0414ac | |||
d179887c15 | |||
35abb4d7ae | |||
61b290e220 | |||
e1bcef6e5c | |||
81f51f9e2d | |||
661953db25 | |||
065c2dac42 | |||
2926844cbe | |||
c1960f3793 | |||
11a29a8b67 | |||
c40e48e678 | |||
124cb18e17 | |||
5ac9dc0758 | |||
9a799d51ce | |||
77eb618758 | |||
79fedfcea4 | |||
8fdb8ac853 | |||
4fdc80be01 | |||
f8e6c8d018 | |||
c3469de61b | |||
ea61b15a40 | |||
60175e6d8c | |||
2a07445005 | |||
dad3e1da7c | |||
82d9452001 |
21
.github/workflows/stale.yml
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
name: 'Close stale issues and PRs'
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '30 1 * * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v9
|
||||||
|
with:
|
||||||
|
stale-issue-label: 'stale'
|
||||||
|
stale-pr-label: 'stale'
|
||||||
|
stale-issue-message: 'Issue is now considered stale. If you want to keep it open, please comment :+1:'
|
||||||
|
stale-pr-message: 'PR is now considered stale. If you want to keep it open, please comment :+1:'
|
||||||
|
close-issue-message: 'Issue was closed due to inactivity.'
|
||||||
|
close-pr-message: 'PR was closed due to inactivity.'
|
||||||
|
days-before-stale: 182
|
||||||
|
days-before-close: 365
|
||||||
|
operations-per-run: 50
|
4
.gitignore
vendored
@ -3,3 +3,7 @@
|
|||||||
._*
|
._*
|
||||||
.vscode
|
.vscode
|
||||||
certbot-help.txt
|
certbot-help.txt
|
||||||
|
test/node_modules
|
||||||
|
*/node_modules
|
||||||
|
docker/dev/dnsrouter-config.json.tmp
|
||||||
|
docker/dev/resolv.conf
|
||||||
|
224
Jenkinsfile
vendored
@ -14,16 +14,12 @@ pipeline {
|
|||||||
ansiColor('xterm')
|
ansiColor('xterm')
|
||||||
}
|
}
|
||||||
environment {
|
environment {
|
||||||
IMAGE = "nginx-proxy-manager"
|
IMAGE = 'nginx-proxy-manager'
|
||||||
BUILD_VERSION = getVersion()
|
BUILD_VERSION = getVersion()
|
||||||
MAJOR_VERSION = "2"
|
MAJOR_VERSION = '2'
|
||||||
BRANCH_LOWER = "${BRANCH_NAME.toLowerCase().replaceAll('/', '-')}"
|
BRANCH_LOWER = "${BRANCH_NAME.toLowerCase().replaceAll('\\\\', '-').replaceAll('/', '-').replaceAll('\\.', '-')}"
|
||||||
COMPOSE_PROJECT_NAME = "npm_${BRANCH_LOWER}_${BUILD_NUMBER}"
|
BUILDX_NAME = "npm_${BRANCH_LOWER}_${BUILD_NUMBER}"
|
||||||
COMPOSE_FILE = 'docker/docker-compose.ci.yml'
|
|
||||||
COMPOSE_INTERACTIVE_NO_CLI = 1
|
COMPOSE_INTERACTIVE_NO_CLI = 1
|
||||||
BUILDX_NAME = "${COMPOSE_PROJECT_NAME}"
|
|
||||||
DOCS_BUCKET = 'jc21-npm-site'
|
|
||||||
DOCS_CDN = 'EN1G6DEWZUTDT'
|
|
||||||
}
|
}
|
||||||
stages {
|
stages {
|
||||||
stage('Environment') {
|
stage('Environment') {
|
||||||
@ -62,99 +58,96 @@ pipeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stage('Build and Test') {
|
stage('Builds') {
|
||||||
steps {
|
parallel {
|
||||||
script {
|
stage('Project') {
|
||||||
// Frontend and Backend
|
steps {
|
||||||
def shStatusCode = sh(label: 'Checking and Building', returnStatus: true, script: '''
|
script {
|
||||||
set -e
|
// Frontend and Backend
|
||||||
./scripts/ci/frontend-build > ${WORKSPACE}/tmp-sh-build 2>&1
|
def shStatusCode = sh(label: 'Checking and Building', returnStatus: true, script: '''
|
||||||
./scripts/ci/test-and-build > ${WORKSPACE}/tmp-sh-build 2>&1
|
set -e
|
||||||
''')
|
./scripts/ci/frontend-build > ${WORKSPACE}/tmp-sh-build 2>&1
|
||||||
shOutput = readFile "${env.WORKSPACE}/tmp-sh-build"
|
./scripts/ci/test-and-build > ${WORKSPACE}/tmp-sh-build 2>&1
|
||||||
if (shStatusCode != 0) {
|
''')
|
||||||
error "${shOutput}"
|
shOutput = readFile "${env.WORKSPACE}/tmp-sh-build"
|
||||||
|
if (shStatusCode != 0) {
|
||||||
|
error "${shOutput}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
always {
|
||||||
|
sh 'rm -f ${WORKSPACE}/tmp-sh-build'
|
||||||
|
}
|
||||||
|
failure {
|
||||||
|
npmGithubPrComment("CI Error:\n\n```\n${shOutput}\n```", true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Docs') {
|
||||||
|
steps {
|
||||||
|
dir(path: 'docs') {
|
||||||
|
sh 'yarn install'
|
||||||
|
sh 'yarn build'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
post {
|
|
||||||
always {
|
|
||||||
sh 'rm -f ${WORKSPACE}/tmp-sh-build'
|
|
||||||
}
|
|
||||||
failure {
|
|
||||||
npmGithubPrComment("CI Error:\n\n```\n${shOutput}\n```", true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
stage('Integration Tests Sqlite') {
|
stage('Test Sqlite') {
|
||||||
steps {
|
environment {
|
||||||
// Bring up a stack
|
COMPOSE_PROJECT_NAME = "npm_${BRANCH_LOWER}_${BUILD_NUMBER}_sqlite"
|
||||||
sh 'docker-compose up -d fullstack-sqlite'
|
COMPOSE_FILE = 'docker/docker-compose.ci.yml:docker/docker-compose.ci.sqlite.yml'
|
||||||
sh './scripts/wait-healthy $(docker-compose ps -q fullstack-sqlite) 120'
|
|
||||||
|
|
||||||
// Run tests
|
|
||||||
sh 'rm -rf test/results'
|
|
||||||
sh 'docker-compose up cypress-sqlite'
|
|
||||||
// Get results
|
|
||||||
sh 'docker cp -L "$(docker-compose ps -q cypress-sqlite):/test/results" test/'
|
|
||||||
}
|
}
|
||||||
post {
|
|
||||||
always {
|
|
||||||
// Dumps to analyze later
|
|
||||||
sh 'mkdir -p debug'
|
|
||||||
sh 'docker-compose logs fullstack-sqlite | gzip > debug/docker_fullstack_sqlite.log.gz'
|
|
||||||
sh 'docker-compose logs db | gzip > debug/docker_db.log.gz'
|
|
||||||
// Cypress videos and screenshot artifacts
|
|
||||||
dir(path: 'test/results') {
|
|
||||||
archiveArtifacts allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml'
|
|
||||||
}
|
|
||||||
junit 'test/results/junit/*'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Integration Tests Mysql') {
|
|
||||||
steps {
|
|
||||||
// Bring up a stack
|
|
||||||
sh 'docker-compose up -d fullstack-mysql'
|
|
||||||
sh './scripts/wait-healthy $(docker-compose ps -q fullstack-mysql) 120'
|
|
||||||
|
|
||||||
// Run tests
|
|
||||||
sh 'rm -rf test/results'
|
|
||||||
sh 'docker-compose up cypress-mysql'
|
|
||||||
// Get results
|
|
||||||
sh 'docker cp -L "$(docker-compose ps -q cypress-mysql):/test/results" test/'
|
|
||||||
}
|
|
||||||
post {
|
|
||||||
always {
|
|
||||||
// Dumps to analyze later
|
|
||||||
sh 'mkdir -p debug'
|
|
||||||
sh 'docker-compose logs fullstack-mysql | gzip > debug/docker_fullstack_mysql.log.gz'
|
|
||||||
sh 'docker-compose logs db | gzip > debug/docker_db.log.gz'
|
|
||||||
// Cypress videos and screenshot artifacts
|
|
||||||
dir(path: 'test/results') {
|
|
||||||
archiveArtifacts allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml'
|
|
||||||
}
|
|
||||||
junit 'test/results/junit/*'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Docs') {
|
|
||||||
when {
|
when {
|
||||||
not {
|
not {
|
||||||
equals expected: 'UNSTABLE', actual: currentBuild.result
|
equals expected: 'UNSTABLE', actual: currentBuild.result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
steps {
|
steps {
|
||||||
dir(path: 'docs') {
|
sh 'rm -rf ./test/results/junit/*'
|
||||||
sh 'yarn install'
|
sh './scripts/ci/fulltest-cypress'
|
||||||
sh 'yarn build'
|
}
|
||||||
|
post {
|
||||||
|
always {
|
||||||
|
// Dumps to analyze later
|
||||||
|
sh 'mkdir -p debug/sqlite'
|
||||||
|
sh 'docker logs $(docker-compose ps --all -q fullstack) > debug/sqlite/docker_fullstack.log 2>&1'
|
||||||
|
sh 'docker logs $(docker-compose ps --all -q stepca) > debug/sqlite/docker_stepca.log 2>&1'
|
||||||
|
sh 'docker logs $(docker-compose ps --all -q pdns) > debug/sqlite/docker_pdns.log 2>&1'
|
||||||
|
sh 'docker logs $(docker-compose ps --all -q pdns-db) > debug/sqlite/docker_pdns-db.log 2>&1'
|
||||||
|
sh 'docker logs $(docker-compose ps --all -q dnsrouter) > debug/sqlite/docker_dnsrouter.log 2>&1'
|
||||||
|
junit 'test/results/junit/*'
|
||||||
|
sh 'docker-compose down --remove-orphans --volumes -t 30 || true'
|
||||||
}
|
}
|
||||||
|
}
|
||||||
dir(path: 'docs/.vuepress/dist') {
|
}
|
||||||
sh 'tar -czf ../../docs.tgz *'
|
stage('Test Mysql') {
|
||||||
|
environment {
|
||||||
|
COMPOSE_PROJECT_NAME = "npm_${BRANCH_LOWER}_${BUILD_NUMBER}_mysql"
|
||||||
|
COMPOSE_FILE = 'docker/docker-compose.ci.yml:docker/docker-compose.ci.mysql.yml'
|
||||||
|
}
|
||||||
|
when {
|
||||||
|
not {
|
||||||
|
equals expected: 'UNSTABLE', actual: currentBuild.result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'rm -rf ./test/results/junit/*'
|
||||||
|
sh './scripts/ci/fulltest-cypress'
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
always {
|
||||||
|
// Dumps to analyze later
|
||||||
|
sh 'mkdir -p debug/mysql'
|
||||||
|
sh 'docker logs $(docker-compose ps --all -q fullstack) > debug/mysql/docker_fullstack.log 2>&1'
|
||||||
|
sh 'docker logs $(docker-compose ps --all -q stepca) > debug/mysql/docker_stepca.log 2>&1'
|
||||||
|
sh 'docker logs $(docker-compose ps --all -q pdns) > debug/mysql/docker_pdns.log 2>&1'
|
||||||
|
sh 'docker logs $(docker-compose ps --all -q pdns-db) > debug/mysql/docker_pdns-db.log 2>&1'
|
||||||
|
sh 'docker logs $(docker-compose ps --all -q dnsrouter) > debug/mysql/docker_dnsrouter.log 2>&1'
|
||||||
|
junit 'test/results/junit/*'
|
||||||
|
sh 'docker-compose down --remove-orphans --volumes -t 30 || true'
|
||||||
}
|
}
|
||||||
|
|
||||||
archiveArtifacts(artifacts: 'docs/docs.tgz', allowEmptyArchive: false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stage('MultiArch Build') {
|
stage('MultiArch Build') {
|
||||||
@ -170,52 +163,55 @@ pipeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stage('Docs Deploy') {
|
stage('Docs / Comment') {
|
||||||
when {
|
parallel {
|
||||||
allOf {
|
stage('Docs Job') {
|
||||||
branch 'master'
|
when {
|
||||||
not {
|
allOf {
|
||||||
equals expected: 'UNSTABLE', actual: currentBuild.result
|
branch pattern: "^(develop|master)\$", comparator: "REGEXP"
|
||||||
|
not {
|
||||||
|
equals expected: 'UNSTABLE', actual: currentBuild.result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
build wait: false, job: 'nginx-proxy-manager-docs', parameters: [string(name: 'docs_branch', value: "$BRANCH_NAME")]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
stage('PR Comment') {
|
||||||
steps {
|
when {
|
||||||
npmDocsRelease("$DOCS_BUCKET", "$DOCS_CDN")
|
allOf {
|
||||||
}
|
changeRequest()
|
||||||
}
|
not {
|
||||||
stage('PR Comment') {
|
equals expected: 'UNSTABLE', actual: currentBuild.result
|
||||||
when {
|
}
|
||||||
allOf {
|
}
|
||||||
changeRequest()
|
}
|
||||||
not {
|
steps {
|
||||||
equals expected: 'UNSTABLE', actual: currentBuild.result
|
script {
|
||||||
|
npmGithubPrComment("Docker Image for build ${BUILD_NUMBER} is available on [DockerHub](https://cloud.docker.com/repository/docker/jc21/${IMAGE}) as `jc21/${IMAGE}:github-${BRANCH_LOWER}`\n\n**Note:** ensure you backup your NPM instance before testing this PR image! Especially if this PR contains database changes.", true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
script {
|
|
||||||
npmGithubPrComment("Docker Image for build ${BUILD_NUMBER} is available on [DockerHub](https://cloud.docker.com/repository/docker/jc21/${IMAGE}) as `jc21/${IMAGE}:github-${BRANCH_LOWER}`\n\n**Note:** ensure you backup your NPM instance before testing this PR image! Especially if this PR contains database changes.", true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
post {
|
post {
|
||||||
always {
|
always {
|
||||||
sh 'docker-compose down --remove-orphans --volumes -t 30'
|
|
||||||
sh 'echo Reverting ownership'
|
sh 'echo Reverting ownership'
|
||||||
sh 'docker run --rm -v $(pwd):/data jc21/ci-tools chown -R $(id -u):$(id -g) /data'
|
sh 'docker run --rm -v "$(pwd):/data" jc21/ci-tools chown -R "$(id -u):$(id -g)" /data'
|
||||||
}
|
}
|
||||||
success {
|
success {
|
||||||
juxtapose event: 'success'
|
juxtapose event: 'success'
|
||||||
sh 'figlet "SUCCESS"'
|
sh 'figlet "SUCCESS"'
|
||||||
}
|
}
|
||||||
failure {
|
failure {
|
||||||
archiveArtifacts(artifacts: 'debug/**.*', allowEmptyArchive: true)
|
archiveArtifacts(artifacts: 'debug/**/*.*', allowEmptyArchive: true)
|
||||||
juxtapose event: 'failure'
|
juxtapose event: 'failure'
|
||||||
sh 'figlet "FAILURE"'
|
sh 'figlet "FAILURE"'
|
||||||
}
|
}
|
||||||
unstable {
|
unstable {
|
||||||
archiveArtifacts(artifacts: 'debug/**.*', allowEmptyArchive: true)
|
archiveArtifacts(artifacts: 'debug/**/*.*', allowEmptyArchive: true)
|
||||||
juxtapose event: 'unstable'
|
juxtapose event: 'unstable'
|
||||||
sh 'figlet "UNSTABLE"'
|
sh 'figlet "UNSTABLE"'
|
||||||
}
|
}
|
||||||
|
25
README.md
@ -1,7 +1,7 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://nginxproxymanager.com/github.png">
|
<img src="https://nginxproxymanager.com/github.png">
|
||||||
<br><br>
|
<br><br>
|
||||||
<img src="https://img.shields.io/badge/version-2.9.19-green.svg?style=for-the-badge">
|
<img src="https://img.shields.io/badge/version-2.11.3-green.svg?style=for-the-badge">
|
||||||
<a href="https://hub.docker.com/repository/docker/jc21/nginx-proxy-manager">
|
<a href="https://hub.docker.com/repository/docker/jc21/nginx-proxy-manager">
|
||||||
<img src="https://img.shields.io/docker/stars/jc21/nginx-proxy-manager.svg?style=for-the-badge">
|
<img src="https://img.shields.io/docker/stars/jc21/nginx-proxy-manager.svg?style=for-the-badge">
|
||||||
</a>
|
</a>
|
||||||
@ -19,7 +19,7 @@ running at home or otherwise, including free SSL, without having to know too muc
|
|||||||
|
|
||||||
## Project Goal
|
## Project Goal
|
||||||
|
|
||||||
I created this project to fill a personal need to provide users with a easy way to accomplish reverse
|
I created this project to fill a personal need to provide users with an easy way to accomplish reverse
|
||||||
proxying hosts with SSL termination and it had to be so easy that a monkey could do it. This goal hasn't changed.
|
proxying hosts with SSL termination and it had to be so easy that a monkey could do it. This goal hasn't changed.
|
||||||
While there might be advanced options they are optional and the project should be as simple as possible
|
While there might be advanced options they are optional and the project should be as simple as possible
|
||||||
so that the barrier for entry here is low.
|
so that the barrier for entry here is low.
|
||||||
@ -56,10 +56,9 @@ I won't go in to too much detail here but here are the basics for someone new to
|
|||||||
2. Create a docker-compose.yml file similar to this:
|
2. Create a docker-compose.yml file similar to this:
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
version: '3'
|
|
||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
image: 'jc21/nginx-proxy-manager:latest'
|
image: 'docker.io/jc21/nginx-proxy-manager:latest'
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- '80:80'
|
- '80:80'
|
||||||
@ -70,6 +69,8 @@ services:
|
|||||||
- ./letsencrypt:/etc/letsencrypt
|
- ./letsencrypt:/etc/letsencrypt
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This is the bare minimum configuration required. See the [documentation](https://nginxproxymanager.com/setup/) for more.
|
||||||
|
|
||||||
3. Bring up your stack by running
|
3. Bring up your stack by running
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@ -96,7 +97,18 @@ Password: changeme
|
|||||||
Immediately after logging in with this default user you will be asked to modify your details and change your password.
|
Immediately after logging in with this default user you will be asked to modify your details and change your password.
|
||||||
|
|
||||||
|
|
||||||
## Contributors
|
## Contributing
|
||||||
|
|
||||||
|
All are welcome to create pull requests for this project, against the `develop` branch. Official releases are created from the `master` branch.
|
||||||
|
|
||||||
|
CI is used in this project. All PR's must pass before being considered. After passing,
|
||||||
|
docker builds for PR's are available on dockerhub for manual verifications.
|
||||||
|
|
||||||
|
Documentation within the `develop` branch is available for preview at
|
||||||
|
[https://develop.nginxproxymanager.com](https://develop.nginxproxymanager.com)
|
||||||
|
|
||||||
|
|
||||||
|
### Contributors
|
||||||
|
|
||||||
Special thanks to [all of our contributors](https://github.com/NginxProxyManager/nginx-proxy-manager/graphs/contributors).
|
Special thanks to [all of our contributors](https://github.com/NginxProxyManager/nginx-proxy-manager/graphs/contributors).
|
||||||
|
|
||||||
@ -105,5 +117,4 @@ Special thanks to [all of our contributors](https://github.com/NginxProxyManager
|
|||||||
|
|
||||||
1. [Found a bug?](https://github.com/NginxProxyManager/nginx-proxy-manager/issues)
|
1. [Found a bug?](https://github.com/NginxProxyManager/nginx-proxy-manager/issues)
|
||||||
2. [Discussions](https://github.com/NginxProxyManager/nginx-proxy-manager/discussions)
|
2. [Discussions](https://github.com/NginxProxyManager/nginx-proxy-manager/discussions)
|
||||||
3. [Development Gitter](https://gitter.im/nginx-proxy-manager/community)
|
3. [Reddit](https://reddit.com/r/nginxproxymanager)
|
||||||
4. [Reddit](https://reddit.com/r/nginxproxymanager)
|
|
||||||
|
@ -2,6 +2,7 @@ const express = require('express');
|
|||||||
const bodyParser = require('body-parser');
|
const bodyParser = require('body-parser');
|
||||||
const fileUpload = require('express-fileupload');
|
const fileUpload = require('express-fileupload');
|
||||||
const compression = require('compression');
|
const compression = require('compression');
|
||||||
|
const config = require('./lib/config');
|
||||||
const log = require('./logger').express;
|
const log = require('./logger').express;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -24,7 +25,7 @@ app.enable('trust proxy', ['loopback', 'linklocal', 'uniquelocal']);
|
|||||||
app.enable('strict routing');
|
app.enable('strict routing');
|
||||||
|
|
||||||
// pretty print JSON when not live
|
// pretty print JSON when not live
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (config.debug()) {
|
||||||
app.set('json spaces', 2);
|
app.set('json spaces', 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,7 +66,7 @@ app.use(function (err, req, res, next) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'development' || (req.baseUrl + req.path).includes('nginx/certificates')) {
|
if (config.debug() || (req.baseUrl + req.path).includes('nginx/certificates')) {
|
||||||
payload.debug = {
|
payload.debug = {
|
||||||
stack: typeof err.stack !== 'undefined' && err.stack ? err.stack.split('\n') : null,
|
stack: typeof err.stack !== 'undefined' && err.stack ? err.stack.split('\n') : null,
|
||||||
previous: err.previous
|
previous: err.previous
|
||||||
@ -74,7 +75,7 @@ app.use(function (err, req, res, next) {
|
|||||||
|
|
||||||
// Not every error is worth logging - but this is good for now until it gets annoying.
|
// Not every error is worth logging - but this is good for now until it gets annoying.
|
||||||
if (typeof err.stack !== 'undefined' && err.stack) {
|
if (typeof err.stack !== 'undefined' && err.stack) {
|
||||||
if (process.env.NODE_ENV === 'development' || process.env.DEBUG) {
|
if (config.debug()) {
|
||||||
log.debug(err.stack);
|
log.debug(err.stack);
|
||||||
} else if (typeof err.public == 'undefined' || !err.public) {
|
} else if (typeof err.public == 'undefined' || !err.public) {
|
||||||
log.warn(err.message);
|
log.warn(err.message);
|
||||||
|
@ -1,33 +1,27 @@
|
|||||||
const config = require('config');
|
const config = require('./lib/config');
|
||||||
|
|
||||||
if (!config.has('database')) {
|
if (!config.has('database')) {
|
||||||
throw new Error('Database config does not exist! Please read the instructions: https://github.com/jc21/nginx-proxy-manager/blob/master/doc/INSTALL.md');
|
throw new Error('Database config does not exist! Please read the instructions: https://nginxproxymanager.com/setup/');
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateDbConfig() {
|
function generateDbConfig() {
|
||||||
if (config.database.engine === 'knex-native') {
|
const cfg = config.get('database');
|
||||||
return config.database.knex;
|
if (cfg.engine === 'knex-native') {
|
||||||
} else
|
return cfg.knex;
|
||||||
return {
|
}
|
||||||
client: config.database.engine,
|
return {
|
||||||
connection: {
|
client: cfg.engine,
|
||||||
host: config.database.host,
|
connection: {
|
||||||
user: config.database.user,
|
host: cfg.host,
|
||||||
password: config.database.password,
|
user: cfg.user,
|
||||||
database: config.database.name,
|
password: cfg.password,
|
||||||
port: config.database.port
|
database: cfg.name,
|
||||||
},
|
port: cfg.port
|
||||||
migrations: {
|
},
|
||||||
tableName: 'migrations'
|
migrations: {
|
||||||
}
|
tableName: 'migrations'
|
||||||
};
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module.exports = require('knex')(generateDbConfig());
|
||||||
let data = generateDbConfig();
|
|
||||||
|
|
||||||
if (typeof config.database.version !== 'undefined') {
|
|
||||||
data.version = config.database.version;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = require('knex')(data);
|
|
||||||
|
@ -40,6 +40,210 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/nginx/proxy-hosts": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "getProxyHosts",
|
||||||
|
"summary": "Get all proxy hosts",
|
||||||
|
"tags": ["Proxy Hosts"],
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": ["users"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "query",
|
||||||
|
"name": "expand",
|
||||||
|
"description": "Expansions",
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["access_list", "owner", "certificate"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "200 response",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"examples": {
|
||||||
|
"default": {
|
||||||
|
"value": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"created_on": "2023-03-30T01:12:23.000Z",
|
||||||
|
"modified_on": "2023-03-30T02:15:40.000Z",
|
||||||
|
"owner_user_id": 1,
|
||||||
|
"domain_names": ["aasdasdad"],
|
||||||
|
"forward_host": "asdasd",
|
||||||
|
"forward_port": 80,
|
||||||
|
"access_list_id": 0,
|
||||||
|
"certificate_id": 0,
|
||||||
|
"ssl_forced": 0,
|
||||||
|
"caching_enabled": 0,
|
||||||
|
"block_exploits": 0,
|
||||||
|
"advanced_config": "sdfsdfsdf",
|
||||||
|
"meta": {
|
||||||
|
"letsencrypt_agree": false,
|
||||||
|
"dns_challenge": false,
|
||||||
|
"nginx_online": false,
|
||||||
|
"nginx_err": "Command failed: /usr/sbin/nginx -t -g \"error_log off;\"\nnginx: [emerg] unknown directive \"sdfsdfsdf\" in /data/nginx/proxy_host/1.conf:37\nnginx: configuration file /etc/nginx/nginx.conf test failed\n"
|
||||||
|
},
|
||||||
|
"allow_websocket_upgrade": 0,
|
||||||
|
"http2_support": 0,
|
||||||
|
"forward_scheme": "http",
|
||||||
|
"enabled": 1,
|
||||||
|
"locations": [],
|
||||||
|
"hsts_enabled": 0,
|
||||||
|
"hsts_subdomains": 0,
|
||||||
|
"owner": {
|
||||||
|
"id": 1,
|
||||||
|
"created_on": "2023-03-30T01:11:50.000Z",
|
||||||
|
"modified_on": "2023-03-30T01:11:50.000Z",
|
||||||
|
"is_deleted": 0,
|
||||||
|
"is_disabled": 0,
|
||||||
|
"email": "admin@example.com",
|
||||||
|
"name": "Administrator",
|
||||||
|
"nickname": "Admin",
|
||||||
|
"avatar": "",
|
||||||
|
"roles": ["admin"]
|
||||||
|
},
|
||||||
|
"access_list": null,
|
||||||
|
"certificate": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"created_on": "2023-03-30T02:11:49.000Z",
|
||||||
|
"modified_on": "2023-03-30T02:11:49.000Z",
|
||||||
|
"owner_user_id": 1,
|
||||||
|
"domain_names": ["test.example.com"],
|
||||||
|
"forward_host": "1.1.1.1",
|
||||||
|
"forward_port": 80,
|
||||||
|
"access_list_id": 0,
|
||||||
|
"certificate_id": 0,
|
||||||
|
"ssl_forced": 0,
|
||||||
|
"caching_enabled": 0,
|
||||||
|
"block_exploits": 0,
|
||||||
|
"advanced_config": "",
|
||||||
|
"meta": {
|
||||||
|
"letsencrypt_agree": false,
|
||||||
|
"dns_challenge": false,
|
||||||
|
"nginx_online": true,
|
||||||
|
"nginx_err": null
|
||||||
|
},
|
||||||
|
"allow_websocket_upgrade": 0,
|
||||||
|
"http2_support": 0,
|
||||||
|
"forward_scheme": "http",
|
||||||
|
"enabled": 1,
|
||||||
|
"locations": [],
|
||||||
|
"hsts_enabled": 0,
|
||||||
|
"hsts_subdomains": 0,
|
||||||
|
"owner": {
|
||||||
|
"id": 1,
|
||||||
|
"created_on": "2023-03-30T01:11:50.000Z",
|
||||||
|
"modified_on": "2023-03-30T01:11:50.000Z",
|
||||||
|
"is_deleted": 0,
|
||||||
|
"is_disabled": 0,
|
||||||
|
"email": "admin@example.com",
|
||||||
|
"name": "Administrator",
|
||||||
|
"nickname": "Admin",
|
||||||
|
"avatar": "",
|
||||||
|
"roles": ["admin"]
|
||||||
|
},
|
||||||
|
"access_list": null,
|
||||||
|
"certificate": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProxyHostsList"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"operationId": "createProxyHost",
|
||||||
|
"summary": "Create a Proxy Host",
|
||||||
|
"tags": ["Proxy Hosts"],
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": ["users"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "body",
|
||||||
|
"name": "proxyhost",
|
||||||
|
"description": "Proxy Host Payload",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProxyHostObject"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "201 response",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"examples": {
|
||||||
|
"default": {
|
||||||
|
"value": {
|
||||||
|
"id": 3,
|
||||||
|
"created_on": "2023-03-30T02:31:27.000Z",
|
||||||
|
"modified_on": "2023-03-30T02:31:27.000Z",
|
||||||
|
"owner_user_id": 1,
|
||||||
|
"domain_names": ["test2.example.com"],
|
||||||
|
"forward_host": "1.1.1.1",
|
||||||
|
"forward_port": 80,
|
||||||
|
"access_list_id": 0,
|
||||||
|
"certificate_id": 0,
|
||||||
|
"ssl_forced": 0,
|
||||||
|
"caching_enabled": 0,
|
||||||
|
"block_exploits": 0,
|
||||||
|
"advanced_config": "",
|
||||||
|
"meta": {
|
||||||
|
"letsencrypt_agree": false,
|
||||||
|
"dns_challenge": false
|
||||||
|
},
|
||||||
|
"allow_websocket_upgrade": 0,
|
||||||
|
"http2_support": 0,
|
||||||
|
"forward_scheme": "http",
|
||||||
|
"enabled": 1,
|
||||||
|
"locations": [],
|
||||||
|
"hsts_enabled": 0,
|
||||||
|
"hsts_subdomains": 0,
|
||||||
|
"certificate": null,
|
||||||
|
"owner": {
|
||||||
|
"id": 1,
|
||||||
|
"created_on": "2023-03-30T01:11:50.000Z",
|
||||||
|
"modified_on": "2023-03-30T01:11:50.000Z",
|
||||||
|
"is_deleted": 0,
|
||||||
|
"is_disabled": 0,
|
||||||
|
"email": "admin@example.com",
|
||||||
|
"name": "Administrator",
|
||||||
|
"nickname": "Admin",
|
||||||
|
"avatar": "",
|
||||||
|
"roles": ["admin"]
|
||||||
|
},
|
||||||
|
"access_list": null,
|
||||||
|
"use_default_location": true,
|
||||||
|
"ipv6": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProxyHostObject"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/schema": {
|
"/schema": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "schema",
|
"operationId": "schema",
|
||||||
@ -55,14 +259,10 @@
|
|||||||
"get": {
|
"get": {
|
||||||
"operationId": "refreshToken",
|
"operationId": "refreshToken",
|
||||||
"summary": "Refresh your access token",
|
"summary": "Refresh your access token",
|
||||||
"tags": [
|
"tags": ["Tokens"],
|
||||||
"Tokens"
|
|
||||||
],
|
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": [
|
"BearerAuth": ["tokens"]
|
||||||
"tokens"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -104,19 +304,14 @@
|
|||||||
"scope": {
|
"scope": {
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": ["user"]
|
||||||
"user"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"secret": {
|
"secret": {
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": ["identity", "secret"],
|
||||||
"identity",
|
|
||||||
"secret"
|
|
||||||
],
|
|
||||||
"type": "object"
|
"type": "object"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -144,23 +339,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"summary": "Request a new access token from credentials",
|
"summary": "Request a new access token from credentials",
|
||||||
"tags": [
|
"tags": ["Tokens"]
|
||||||
"Tokens"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/settings": {
|
"/settings": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "getSettings",
|
"operationId": "getSettings",
|
||||||
"summary": "Get all settings",
|
"summary": "Get all settings",
|
||||||
"tags": [
|
"tags": ["Settings"],
|
||||||
"Settings"
|
|
||||||
],
|
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": [
|
"BearerAuth": ["settings"]
|
||||||
"settings"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -194,14 +383,10 @@
|
|||||||
"get": {
|
"get": {
|
||||||
"operationId": "getSetting",
|
"operationId": "getSetting",
|
||||||
"summary": "Get a setting",
|
"summary": "Get a setting",
|
||||||
"tags": [
|
"tags": ["Settings"],
|
||||||
"Settings"
|
|
||||||
],
|
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": [
|
"BearerAuth": ["settings"]
|
||||||
"settings"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@ -244,14 +429,10 @@
|
|||||||
"put": {
|
"put": {
|
||||||
"operationId": "updateSetting",
|
"operationId": "updateSetting",
|
||||||
"summary": "Update a setting",
|
"summary": "Update a setting",
|
||||||
"tags": [
|
"tags": ["Settings"],
|
||||||
"Settings"
|
|
||||||
],
|
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": [
|
"BearerAuth": ["settings"]
|
||||||
"settings"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@ -305,14 +486,10 @@
|
|||||||
"get": {
|
"get": {
|
||||||
"operationId": "getUsers",
|
"operationId": "getUsers",
|
||||||
"summary": "Get all users",
|
"summary": "Get all users",
|
||||||
"tags": [
|
"tags": ["Users"],
|
||||||
"Users"
|
|
||||||
],
|
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": [
|
"BearerAuth": ["users"]
|
||||||
"users"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@ -322,9 +499,7 @@
|
|||||||
"description": "Expansions",
|
"description": "Expansions",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": ["permissions"]
|
||||||
"permissions"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -345,9 +520,7 @@
|
|||||||
"name": "Jamie Curnow",
|
"name": "Jamie Curnow",
|
||||||
"nickname": "James",
|
"nickname": "James",
|
||||||
"avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm",
|
"avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm",
|
||||||
"roles": [
|
"roles": ["admin"]
|
||||||
"admin"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -362,9 +535,7 @@
|
|||||||
"name": "Jamie Curnow",
|
"name": "Jamie Curnow",
|
||||||
"nickname": "James",
|
"nickname": "James",
|
||||||
"avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm",
|
"avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm",
|
||||||
"roles": [
|
"roles": ["admin"],
|
||||||
"admin"
|
|
||||||
],
|
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"visibility": "all",
|
"visibility": "all",
|
||||||
"proxy_hosts": "manage",
|
"proxy_hosts": "manage",
|
||||||
@ -389,14 +560,10 @@
|
|||||||
"post": {
|
"post": {
|
||||||
"operationId": "createUser",
|
"operationId": "createUser",
|
||||||
"summary": "Create a User",
|
"summary": "Create a User",
|
||||||
"tags": [
|
"tags": ["Users"],
|
||||||
"Users"
|
|
||||||
],
|
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": [
|
"BearerAuth": ["users"]
|
||||||
"users"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@ -426,9 +593,7 @@
|
|||||||
"name": "Jamie Curnow",
|
"name": "Jamie Curnow",
|
||||||
"nickname": "James",
|
"nickname": "James",
|
||||||
"avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm",
|
"avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm",
|
||||||
"roles": [
|
"roles": ["admin"],
|
||||||
"admin"
|
|
||||||
],
|
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"visibility": "all",
|
"visibility": "all",
|
||||||
"proxy_hosts": "manage",
|
"proxy_hosts": "manage",
|
||||||
@ -454,14 +619,10 @@
|
|||||||
"get": {
|
"get": {
|
||||||
"operationId": "getUser",
|
"operationId": "getUser",
|
||||||
"summary": "Get a user",
|
"summary": "Get a user",
|
||||||
"tags": [
|
"tags": ["Users"],
|
||||||
"Users"
|
|
||||||
],
|
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": [
|
"BearerAuth": ["users"]
|
||||||
"users"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@ -501,9 +662,7 @@
|
|||||||
"name": "Jamie Curnow",
|
"name": "Jamie Curnow",
|
||||||
"nickname": "James",
|
"nickname": "James",
|
||||||
"avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm",
|
"avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm",
|
||||||
"roles": [
|
"roles": ["admin"]
|
||||||
"admin"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -518,14 +677,10 @@
|
|||||||
"put": {
|
"put": {
|
||||||
"operationId": "updateUser",
|
"operationId": "updateUser",
|
||||||
"summary": "Update a User",
|
"summary": "Update a User",
|
||||||
"tags": [
|
"tags": ["Users"],
|
||||||
"Users"
|
|
||||||
],
|
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": [
|
"BearerAuth": ["users"]
|
||||||
"users"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@ -574,9 +729,7 @@
|
|||||||
"name": "Jamie Curnow",
|
"name": "Jamie Curnow",
|
||||||
"nickname": "James",
|
"nickname": "James",
|
||||||
"avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm",
|
"avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm",
|
||||||
"roles": [
|
"roles": ["admin"]
|
||||||
"admin"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -591,14 +744,10 @@
|
|||||||
"delete": {
|
"delete": {
|
||||||
"operationId": "deleteUser",
|
"operationId": "deleteUser",
|
||||||
"summary": "Delete a User",
|
"summary": "Delete a User",
|
||||||
"tags": [
|
"tags": ["Users"],
|
||||||
"Users"
|
|
||||||
],
|
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": [
|
"BearerAuth": ["users"]
|
||||||
"users"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@ -637,14 +786,10 @@
|
|||||||
"put": {
|
"put": {
|
||||||
"operationId": "updateUserAuth",
|
"operationId": "updateUserAuth",
|
||||||
"summary": "Update a User's Authentication",
|
"summary": "Update a User's Authentication",
|
||||||
"tags": [
|
"tags": ["Users"],
|
||||||
"Users"
|
|
||||||
],
|
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": [
|
"BearerAuth": ["users"]
|
||||||
"users"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@ -700,14 +845,10 @@
|
|||||||
"put": {
|
"put": {
|
||||||
"operationId": "updateUserPermissions",
|
"operationId": "updateUserPermissions",
|
||||||
"summary": "Update a User's Permissions",
|
"summary": "Update a User's Permissions",
|
||||||
"tags": [
|
"tags": ["Users"],
|
||||||
"Users"
|
|
||||||
],
|
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": [
|
"BearerAuth": ["users"]
|
||||||
"users"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@ -755,14 +896,10 @@
|
|||||||
"put": {
|
"put": {
|
||||||
"operationId": "loginAsUser",
|
"operationId": "loginAsUser",
|
||||||
"summary": "Login as this user",
|
"summary": "Login as this user",
|
||||||
"tags": [
|
"tags": ["Users"],
|
||||||
"Users"
|
|
||||||
],
|
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": [
|
"BearerAuth": ["users"]
|
||||||
"users"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@ -797,9 +934,7 @@
|
|||||||
"name": "Jamie Curnow",
|
"name": "Jamie Curnow",
|
||||||
"nickname": "James",
|
"nickname": "James",
|
||||||
"avatar": "//www.gravatar.com/avatar/3c8d73f45fd8763f827b964c76e6032a?default=mm",
|
"avatar": "//www.gravatar.com/avatar/3c8d73f45fd8763f827b964c76e6032a?default=mm",
|
||||||
"roles": [
|
"roles": ["admin"]
|
||||||
"admin"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -807,11 +942,7 @@
|
|||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "Login object",
|
"description": "Login object",
|
||||||
"required": [
|
"required": ["expires", "token", "user"],
|
||||||
"expires",
|
|
||||||
"token",
|
|
||||||
"user"
|
|
||||||
],
|
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"expires": {
|
"expires": {
|
||||||
@ -840,14 +971,10 @@
|
|||||||
"get": {
|
"get": {
|
||||||
"operationId": "reportsHosts",
|
"operationId": "reportsHosts",
|
||||||
"summary": "Report on Host Statistics",
|
"summary": "Report on Host Statistics",
|
||||||
"tags": [
|
"tags": ["Reports"],
|
||||||
"Reports"
|
|
||||||
],
|
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": [
|
"BearerAuth": ["reports"]
|
||||||
"reports"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -878,14 +1005,10 @@
|
|||||||
"get": {
|
"get": {
|
||||||
"operationId": "getAuditLog",
|
"operationId": "getAuditLog",
|
||||||
"summary": "Get Audit Log",
|
"summary": "Get Audit Log",
|
||||||
"tags": [
|
"tags": ["Audit Log"],
|
||||||
"Audit Log"
|
|
||||||
],
|
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": [
|
"BearerAuth": ["audit-log"]
|
||||||
"audit-log"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -925,10 +1048,7 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "Health object",
|
"description": "Health object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"required": [
|
"required": ["status", "version"],
|
||||||
"status",
|
|
||||||
"version"
|
|
||||||
],
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"status": {
|
"status": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@ -944,11 +1064,7 @@
|
|||||||
"revision": 0
|
"revision": 0
|
||||||
},
|
},
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"required": [
|
"required": ["major", "minor", "revision"],
|
||||||
"major",
|
|
||||||
"minor",
|
|
||||||
"revision"
|
|
||||||
],
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"major": {
|
"major": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
@ -969,10 +1085,7 @@
|
|||||||
"TokenObject": {
|
"TokenObject": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "Token object",
|
"description": "Token object",
|
||||||
"required": [
|
"required": ["expires", "token"],
|
||||||
"expires",
|
|
||||||
"token"
|
|
||||||
],
|
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"expires": {
|
"expires": {
|
||||||
@ -988,16 +1101,147 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ProxyHostObject": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Proxy Host object",
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"created_on",
|
||||||
|
"modified_on",
|
||||||
|
"owner_user_id",
|
||||||
|
"domain_names",
|
||||||
|
"forward_host",
|
||||||
|
"forward_port",
|
||||||
|
"access_list_id",
|
||||||
|
"certificate_id",
|
||||||
|
"ssl_forced",
|
||||||
|
"caching_enabled",
|
||||||
|
"block_exploits",
|
||||||
|
"advanced_config",
|
||||||
|
"meta",
|
||||||
|
"allow_websocket_upgrade",
|
||||||
|
"http2_support",
|
||||||
|
"forward_scheme",
|
||||||
|
"enabled",
|
||||||
|
"locations",
|
||||||
|
"hsts_enabled",
|
||||||
|
"hsts_subdomains",
|
||||||
|
"certificate",
|
||||||
|
"use_default_location",
|
||||||
|
"ipv6"
|
||||||
|
],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Proxy Host ID",
|
||||||
|
"minimum": 1,
|
||||||
|
"example": 1
|
||||||
|
},
|
||||||
|
"created_on": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Created Date",
|
||||||
|
"example": "2020-01-30T09:36:08.000Z"
|
||||||
|
},
|
||||||
|
"modified_on": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Modified Date",
|
||||||
|
"example": "2020-01-30T09:41:04.000Z"
|
||||||
|
},
|
||||||
|
"owner_user_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1,
|
||||||
|
"example": 1
|
||||||
|
},
|
||||||
|
"domain_names": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"forward_host": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"forward_port": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1
|
||||||
|
},
|
||||||
|
"access_list_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"certificate_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"ssl_forced": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"caching_enabled": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"block_exploits": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"advanced_config": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"allow_websocket_upgrade": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"http2_support": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"forward_scheme": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"locations": {
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"hsts_enabled": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"hsts_subdomains": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"certificate": {
|
||||||
|
"type": "object",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"owner": {
|
||||||
|
"type": "object",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"access_list": {
|
||||||
|
"type": "object",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"use_default_location": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"ipv6": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ProxyHostsList": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Proxyn Hosts list",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/ProxyHostObject"
|
||||||
|
}
|
||||||
|
},
|
||||||
"SettingObject": {
|
"SettingObject": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "Setting object",
|
"description": "Setting object",
|
||||||
"required": [
|
"required": ["id", "name", "description", "value", "meta"],
|
||||||
"id",
|
|
||||||
"name",
|
|
||||||
"description",
|
|
||||||
"value",
|
|
||||||
"meta"
|
|
||||||
],
|
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"id": {
|
||||||
@ -1057,17 +1301,7 @@
|
|||||||
"UserObject": {
|
"UserObject": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "User object",
|
"description": "User object",
|
||||||
"required": [
|
"required": ["id", "created_on", "modified_on", "is_disabled", "email", "name", "nickname", "avatar", "roles"],
|
||||||
"id",
|
|
||||||
"created_on",
|
|
||||||
"modified_on",
|
|
||||||
"is_disabled",
|
|
||||||
"email",
|
|
||||||
"name",
|
|
||||||
"nickname",
|
|
||||||
"avatar",
|
|
||||||
"roles"
|
|
||||||
],
|
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"id": {
|
||||||
@ -1117,9 +1351,7 @@
|
|||||||
},
|
},
|
||||||
"roles": {
|
"roles": {
|
||||||
"description": "Roles applied",
|
"description": "Roles applied",
|
||||||
"example": [
|
"example": ["admin"],
|
||||||
"admin"
|
|
||||||
],
|
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@ -1137,10 +1369,7 @@
|
|||||||
"AuthObject": {
|
"AuthObject": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "Authentication Object",
|
"description": "Authentication Object",
|
||||||
"required": [
|
"required": ["type", "secret"],
|
||||||
"type",
|
|
||||||
"secret"
|
|
||||||
],
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@ -1167,64 +1396,37 @@
|
|||||||
"visibility": {
|
"visibility": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Visibility Type",
|
"description": "Visibility Type",
|
||||||
"enum": [
|
"enum": ["all", "user"]
|
||||||
"all",
|
|
||||||
"user"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"access_lists": {
|
"access_lists": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Access Lists Permissions",
|
"description": "Access Lists Permissions",
|
||||||
"enum": [
|
"enum": ["hidden", "view", "manage"]
|
||||||
"hidden",
|
|
||||||
"view",
|
|
||||||
"manage"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"dead_hosts": {
|
"dead_hosts": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "404 Hosts Permissions",
|
"description": "404 Hosts Permissions",
|
||||||
"enum": [
|
"enum": ["hidden", "view", "manage"]
|
||||||
"hidden",
|
|
||||||
"view",
|
|
||||||
"manage"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"proxy_hosts": {
|
"proxy_hosts": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Proxy Hosts Permissions",
|
"description": "Proxy Hosts Permissions",
|
||||||
"enum": [
|
"enum": ["hidden", "view", "manage"]
|
||||||
"hidden",
|
|
||||||
"view",
|
|
||||||
"manage"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"redirection_hosts": {
|
"redirection_hosts": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Redirection Permissions",
|
"description": "Redirection Permissions",
|
||||||
"enum": [
|
"enum": ["hidden", "view", "manage"]
|
||||||
"hidden",
|
|
||||||
"view",
|
|
||||||
"manage"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"streams": {
|
"streams": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Streams Permissions",
|
"description": "Streams Permissions",
|
||||||
"enum": [
|
"enum": ["hidden", "view", "manage"]
|
||||||
"hidden",
|
|
||||||
"view",
|
|
||||||
"manage"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"certificates": {
|
"certificates": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Certificates Permissions",
|
"description": "Certificates Permissions",
|
||||||
"enum": [
|
"enum": ["hidden", "view", "manage"]
|
||||||
"hidden",
|
|
||||||
"view",
|
|
||||||
"manage"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1251,4 +1453,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,6 @@
|
|||||||
const logger = require('./logger').global;
|
const logger = require('./logger').global;
|
||||||
|
|
||||||
async function appStart () {
|
async function appStart () {
|
||||||
// Create config file db settings if environment variables have been set
|
|
||||||
await createDbConfigFromEnvironment();
|
|
||||||
|
|
||||||
const migrate = require('./migrate');
|
const migrate = require('./migrate');
|
||||||
const setup = require('./setup');
|
const setup = require('./setup');
|
||||||
const app = require('./app');
|
const app = require('./app');
|
||||||
@ -42,90 +39,6 @@ async function appStart () {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createDbConfigFromEnvironment() {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const envMysqlHost = process.env.DB_MYSQL_HOST || null;
|
|
||||||
const envMysqlPort = process.env.DB_MYSQL_PORT || null;
|
|
||||||
const envMysqlUser = process.env.DB_MYSQL_USER || null;
|
|
||||||
const envMysqlName = process.env.DB_MYSQL_NAME || null;
|
|
||||||
let envSqliteFile = process.env.DB_SQLITE_FILE || null;
|
|
||||||
|
|
||||||
const fs = require('fs');
|
|
||||||
const filename = (process.env.NODE_CONFIG_DIR || './config') + '/' + (process.env.NODE_ENV || 'default') + '.json';
|
|
||||||
let configData = {};
|
|
||||||
|
|
||||||
try {
|
|
||||||
configData = require(filename);
|
|
||||||
} catch (err) {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
if (configData.database && configData.database.engine && !configData.database.fromEnv) {
|
|
||||||
logger.info('Manual db configuration already exists, skipping config creation from environment variables');
|
|
||||||
resolve();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((!envMysqlHost || !envMysqlPort || !envMysqlUser || !envMysqlName) && !envSqliteFile){
|
|
||||||
envSqliteFile = '/data/database.sqlite';
|
|
||||||
logger.info(`No valid environment variables for database provided, using default SQLite file '${envSqliteFile}'`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (envMysqlHost && envMysqlPort && envMysqlUser && envMysqlName) {
|
|
||||||
const newConfig = {
|
|
||||||
fromEnv: true,
|
|
||||||
engine: 'mysql',
|
|
||||||
host: envMysqlHost,
|
|
||||||
port: envMysqlPort,
|
|
||||||
user: envMysqlUser,
|
|
||||||
password: process.env.DB_MYSQL_PASSWORD,
|
|
||||||
name: envMysqlName,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (JSON.stringify(configData.database) === JSON.stringify(newConfig)) {
|
|
||||||
// Config is unchanged, skip overwrite
|
|
||||||
resolve();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info('Generating MySQL knex configuration from environment variables');
|
|
||||||
configData.database = newConfig;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
const newConfig = {
|
|
||||||
fromEnv: true,
|
|
||||||
engine: 'knex-native',
|
|
||||||
knex: {
|
|
||||||
client: 'sqlite3',
|
|
||||||
connection: {
|
|
||||||
filename: envSqliteFile
|
|
||||||
},
|
|
||||||
useNullAsDefault: true
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (JSON.stringify(configData.database) === JSON.stringify(newConfig)) {
|
|
||||||
// Config is unchanged, skip overwrite
|
|
||||||
resolve();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info('Generating SQLite knex configuration');
|
|
||||||
configData.database = newConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write config
|
|
||||||
fs.writeFile(filename, JSON.stringify(configData, null, 2), (err) => {
|
|
||||||
if (err) {
|
|
||||||
logger.error('Could not write db config to config file: ' + filename);
|
|
||||||
reject(err);
|
|
||||||
} else {
|
|
||||||
logger.debug('Wrote db configuration to config file: ' + filename);
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
appStart();
|
appStart();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -204,7 +204,6 @@ const internalAccessList = {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(internalNginx.reload)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// Add to audit log
|
// Add to audit log
|
||||||
return internalAuditLog.add(access, {
|
return internalAuditLog.add(access, {
|
||||||
@ -227,7 +226,7 @@ const internalAccessList = {
|
|||||||
if (row.proxy_host_count) {
|
if (row.proxy_host_count) {
|
||||||
return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts);
|
return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts);
|
||||||
}
|
}
|
||||||
})
|
}).then(internalNginx.reload)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return internalAccessList.maskItems(row);
|
return internalAccessList.maskItems(row);
|
||||||
});
|
});
|
||||||
|
@ -1,22 +1,26 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const https = require('https');
|
const https = require('https');
|
||||||
const tempWrite = require('temp-write');
|
const tempWrite = require('temp-write');
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
const logger = require('../logger').ssl;
|
const logger = require('../logger').ssl;
|
||||||
const error = require('../lib/error');
|
const config = require('../lib/config');
|
||||||
const utils = require('../lib/utils');
|
const error = require('../lib/error');
|
||||||
const certificateModel = require('../models/certificate');
|
const utils = require('../lib/utils');
|
||||||
const dnsPlugins = require('../global/certbot-dns-plugins');
|
const certificateModel = require('../models/certificate');
|
||||||
const internalAuditLog = require('./audit-log');
|
const tokenModel = require('../models/token');
|
||||||
const internalNginx = require('./nginx');
|
const dnsPlugins = require('../global/certbot-dns-plugins.json');
|
||||||
const internalHost = require('./host');
|
const internalAuditLog = require('./audit-log');
|
||||||
const letsencryptStaging = process.env.NODE_ENV !== 'production';
|
const internalNginx = require('./nginx');
|
||||||
|
const internalHost = require('./host');
|
||||||
|
const certbot = require('../lib/certbot');
|
||||||
|
const archiver = require('archiver');
|
||||||
|
const path = require('path');
|
||||||
|
const { isArray } = require('lodash');
|
||||||
|
|
||||||
|
const letsencryptStaging = config.useLetsencryptStaging();
|
||||||
const letsencryptConfig = '/etc/letsencrypt.ini';
|
const letsencryptConfig = '/etc/letsencrypt.ini';
|
||||||
const certbotCommand = 'certbot';
|
const certbotCommand = 'certbot';
|
||||||
const archiver = require('archiver');
|
|
||||||
const path = require('path');
|
|
||||||
const { isArray } = require('lodash');
|
|
||||||
|
|
||||||
function omissions() {
|
function omissions() {
|
||||||
return ['is_deleted'];
|
return ['is_deleted'];
|
||||||
@ -24,10 +28,11 @@ function omissions() {
|
|||||||
|
|
||||||
const internalCertificate = {
|
const internalCertificate = {
|
||||||
|
|
||||||
allowedSslFiles: ['certificate', 'certificate_key', 'intermediate_certificate'],
|
allowedSslFiles: ['certificate', 'certificate_key', 'intermediate_certificate'],
|
||||||
intervalTimeout: 1000 * 60 * 60, // 1 hour
|
intervalTimeout: 1000 * 60 * 60, // 1 hour
|
||||||
interval: null,
|
interval: null,
|
||||||
intervalProcessing: false,
|
intervalProcessing: false,
|
||||||
|
renewBeforeExpirationBy: [30, 'days'],
|
||||||
|
|
||||||
initTimer: () => {
|
initTimer: () => {
|
||||||
logger.info('Let\'s Encrypt Renewal Timer initialized');
|
logger.info('Let\'s Encrypt Renewal Timer initialized');
|
||||||
@ -42,60 +47,51 @@ const internalCertificate = {
|
|||||||
processExpiringHosts: () => {
|
processExpiringHosts: () => {
|
||||||
if (!internalCertificate.intervalProcessing) {
|
if (!internalCertificate.intervalProcessing) {
|
||||||
internalCertificate.intervalProcessing = true;
|
internalCertificate.intervalProcessing = true;
|
||||||
logger.info('Renewing SSL certs close to expiry...');
|
logger.info('Renewing SSL certs expiring within ' + internalCertificate.renewBeforeExpirationBy[0] + ' ' + internalCertificate.renewBeforeExpirationBy[1] + ' ...');
|
||||||
|
|
||||||
const cmd = certbotCommand + ' renew --non-interactive --quiet ' +
|
const expirationThreshold = moment().add(internalCertificate.renewBeforeExpirationBy[0], internalCertificate.renewBeforeExpirationBy[1]).format('YYYY-MM-DD HH:mm:ss');
|
||||||
'--config "' + letsencryptConfig + '" ' +
|
|
||||||
'--preferred-challenges "dns,http" ' +
|
|
||||||
'--disable-hook-validation ' +
|
|
||||||
(letsencryptStaging ? '--staging' : '');
|
|
||||||
|
|
||||||
return utils.exec(cmd)
|
// Fetch all the letsencrypt certs from the db that will expire within the configured threshold
|
||||||
.then((result) => {
|
certificateModel
|
||||||
if (result) {
|
.query()
|
||||||
logger.info('Renew Result: ' + result);
|
.where('is_deleted', 0)
|
||||||
|
.andWhere('provider', 'letsencrypt')
|
||||||
|
.andWhere('expires_on', '<', expirationThreshold)
|
||||||
|
.then((certificates) => {
|
||||||
|
if (!certificates || !certificates.length) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return internalNginx.reload()
|
/**
|
||||||
.then(() => {
|
* Renews must be run sequentially or we'll get an error 'Another
|
||||||
logger.info('Renew Complete');
|
* instance of Certbot is already running.'
|
||||||
return result;
|
*/
|
||||||
});
|
let sequence = Promise.resolve();
|
||||||
})
|
|
||||||
.then(() => {
|
certificates.forEach(function (certificate) {
|
||||||
// Now go and fetch all the letsencrypt certs from the db and query the files and update expiry times
|
sequence = sequence.then(() =>
|
||||||
return certificateModel
|
internalCertificate
|
||||||
.query()
|
.renew(
|
||||||
.where('is_deleted', 0)
|
{
|
||||||
.andWhere('provider', 'letsencrypt')
|
can: () =>
|
||||||
.then((certificates) => {
|
Promise.resolve({
|
||||||
if (certificates && certificates.length) {
|
permission_visibility: 'all',
|
||||||
let promises = [];
|
}),
|
||||||
|
token: new tokenModel(),
|
||||||
certificates.map(function (certificate) {
|
},
|
||||||
promises.push(
|
{ id: certificate.id },
|
||||||
internalCertificate.getCertificateInfoFromFile('/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem')
|
)
|
||||||
.then((cert_info) => {
|
.catch((err) => {
|
||||||
return certificateModel
|
// Don't want to stop the train here, just log the error
|
||||||
.query()
|
logger.error(err.message);
|
||||||
.where('id', certificate.id)
|
}),
|
||||||
.andWhere('provider', 'letsencrypt')
|
);
|
||||||
.patch({
|
});
|
||||||
expires_on: moment(cert_info.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss')
|
|
||||||
});
|
return sequence;
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
// Don't want to stop the train here, just log the error
|
|
||||||
logger.error(err.message);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return Promise.all(promises);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
logger.info('Completed SSL cert renew process');
|
||||||
internalCertificate.intervalProcessing = false;
|
internalCertificate.intervalProcessing = false;
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
@ -833,6 +829,8 @@ const internalCertificate = {
|
|||||||
|
|
||||||
const cmd = certbotCommand + ' certonly ' +
|
const cmd = certbotCommand + ' certonly ' +
|
||||||
'--config "' + letsencryptConfig + '" ' +
|
'--config "' + letsencryptConfig + '" ' +
|
||||||
|
'--work-dir "/tmp/letsencrypt-lib" ' +
|
||||||
|
'--logs-dir "/tmp/letsencrypt-log" ' +
|
||||||
'--cert-name "npm-' + certificate.id + '" ' +
|
'--cert-name "npm-' + certificate.id + '" ' +
|
||||||
'--agree-tos ' +
|
'--agree-tos ' +
|
||||||
'--authenticator webroot ' +
|
'--authenticator webroot ' +
|
||||||
@ -852,45 +850,40 @@ const internalCertificate = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Object} certificate the certificate row
|
* @param {Object} certificate the certificate row
|
||||||
* @param {String} dns_provider the dns provider name (key used in `certbot-dns-plugins.js`)
|
* @param {String} dns_provider the dns provider name (key used in `certbot-dns-plugins.json`)
|
||||||
* @param {String | null} credentials the content of this providers credentials file
|
* @param {String | null} credentials the content of this providers credentials file
|
||||||
* @param {String} propagation_seconds the cloudflare api token
|
* @param {String} propagation_seconds
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
requestLetsEncryptSslWithDnsChallenge: (certificate) => {
|
requestLetsEncryptSslWithDnsChallenge: async (certificate) => {
|
||||||
const dns_plugin = dnsPlugins[certificate.meta.dns_provider];
|
await certbot.installPlugin(certificate.meta.dns_provider);
|
||||||
|
const dnsPlugin = dnsPlugins[certificate.meta.dns_provider];
|
||||||
if (!dns_plugin) {
|
logger.info(`Requesting Let'sEncrypt certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`);
|
||||||
throw Error(`Unknown DNS provider '${certificate.meta.dns_provider}'`);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(`Requesting Let'sEncrypt certificates via ${dns_plugin.display_name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`);
|
|
||||||
|
|
||||||
const credentialsLocation = '/etc/letsencrypt/credentials/credentials-' + certificate.id;
|
const credentialsLocation = '/etc/letsencrypt/credentials/credentials-' + certificate.id;
|
||||||
// Escape single quotes and backslashes
|
fs.mkdirSync('/etc/letsencrypt/credentials', { recursive: true });
|
||||||
const escapedCredentials = certificate.meta.dns_provider_credentials.replaceAll('\'', '\\\'').replaceAll('\\', '\\\\');
|
fs.writeFileSync(credentialsLocation, certificate.meta.dns_provider_credentials, {mode: 0o600});
|
||||||
const credentialsCmd = 'mkdir -p /etc/letsencrypt/credentials 2> /dev/null; echo \'' + escapedCredentials + '\' > \'' + credentialsLocation + '\' && chmod 600 \'' + credentialsLocation + '\'';
|
|
||||||
// we call `. /opt/certbot/bin/activate` (`.` is alternative to `source` in dash) to access certbot venv
|
|
||||||
let prepareCmd = '. /opt/certbot/bin/activate && pip install ' + dns_plugin.package_name + (dns_plugin.version_requirement || '') + ' ' + dns_plugin.dependencies + ' && deactivate';
|
|
||||||
|
|
||||||
// Whether the plugin has a --<name>-credentials argument
|
// Whether the plugin has a --<name>-credentials argument
|
||||||
const hasConfigArg = certificate.meta.dns_provider !== 'route53';
|
const hasConfigArg = certificate.meta.dns_provider !== 'route53';
|
||||||
|
|
||||||
let mainCmd = certbotCommand + ' certonly ' +
|
let mainCmd = certbotCommand + ' certonly ' +
|
||||||
'--config "' + letsencryptConfig + '" ' +
|
'--config "' + letsencryptConfig + '" ' +
|
||||||
|
'--work-dir "/tmp/letsencrypt-lib" ' +
|
||||||
|
'--logs-dir "/tmp/letsencrypt-log" ' +
|
||||||
'--cert-name "npm-' + certificate.id + '" ' +
|
'--cert-name "npm-' + certificate.id + '" ' +
|
||||||
'--agree-tos ' +
|
'--agree-tos ' +
|
||||||
'--email "' + certificate.meta.letsencrypt_email + '" ' +
|
'--email "' + certificate.meta.letsencrypt_email + '" ' +
|
||||||
'--domains "' + certificate.domain_names.join(',') + '" ' +
|
'--domains "' + certificate.domain_names.join(',') + '" ' +
|
||||||
'--authenticator ' + dns_plugin.full_plugin_name + ' ' +
|
'--authenticator ' + dnsPlugin.full_plugin_name + ' ' +
|
||||||
(
|
(
|
||||||
hasConfigArg
|
hasConfigArg
|
||||||
? '--' + dns_plugin.full_plugin_name + '-credentials "' + credentialsLocation + '"'
|
? '--' + dnsPlugin.full_plugin_name + '-credentials "' + credentialsLocation + '"'
|
||||||
: ''
|
: ''
|
||||||
) +
|
) +
|
||||||
(
|
(
|
||||||
certificate.meta.propagation_seconds !== undefined
|
certificate.meta.propagation_seconds !== undefined
|
||||||
? ' --' + dns_plugin.full_plugin_name + '-propagation-seconds ' + certificate.meta.propagation_seconds
|
? ' --' + dnsPlugin.full_plugin_name + '-propagation-seconds ' + certificate.meta.propagation_seconds
|
||||||
: ''
|
: ''
|
||||||
) +
|
) +
|
||||||
(letsencryptStaging ? ' --staging' : '');
|
(letsencryptStaging ? ' --staging' : '');
|
||||||
@ -900,24 +893,21 @@ const internalCertificate = {
|
|||||||
mainCmd = 'AWS_CONFIG_FILE=\'' + credentialsLocation + '\' ' + mainCmd;
|
mainCmd = 'AWS_CONFIG_FILE=\'' + credentialsLocation + '\' ' + mainCmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info('Command:', `${credentialsCmd} && ${prepareCmd} && ${mainCmd}`);
|
if (certificate.meta.dns_provider === 'duckdns') {
|
||||||
|
mainCmd = mainCmd + ' --dns-duckdns-no-txt-restore';
|
||||||
|
}
|
||||||
|
|
||||||
return utils.exec(credentialsCmd)
|
logger.info('Command:', mainCmd);
|
||||||
.then(() => {
|
|
||||||
return utils.exec(prepareCmd)
|
try {
|
||||||
.then(() => {
|
const result = await utils.exec(mainCmd);
|
||||||
return utils.exec(mainCmd)
|
logger.info(result);
|
||||||
.then(async (result) => {
|
return result;
|
||||||
logger.info(result);
|
} catch (err) {
|
||||||
return result;
|
// Don't fail if file does not exist, so no need for action in the callback
|
||||||
});
|
fs.unlink(credentialsLocation, () => {});
|
||||||
});
|
throw err;
|
||||||
}).catch(async (err) => {
|
}
|
||||||
// Don't fail if file does not exist
|
|
||||||
const delete_credentialsCmd = `rm -f '${credentialsLocation}' || true`;
|
|
||||||
await utils.exec(delete_credentialsCmd);
|
|
||||||
throw err;
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
@ -974,6 +964,8 @@ const internalCertificate = {
|
|||||||
|
|
||||||
const cmd = certbotCommand + ' renew --force-renewal ' +
|
const cmd = certbotCommand + ' renew --force-renewal ' +
|
||||||
'--config "' + letsencryptConfig + '" ' +
|
'--config "' + letsencryptConfig + '" ' +
|
||||||
|
'--work-dir "/tmp/letsencrypt-lib" ' +
|
||||||
|
'--logs-dir "/tmp/letsencrypt-log" ' +
|
||||||
'--cert-name "npm-' + certificate.id + '" ' +
|
'--cert-name "npm-' + certificate.id + '" ' +
|
||||||
'--preferred-challenges "dns,http" ' +
|
'--preferred-challenges "dns,http" ' +
|
||||||
'--no-random-sleep-on-renew ' +
|
'--no-random-sleep-on-renew ' +
|
||||||
@ -994,16 +986,18 @@ const internalCertificate = {
|
|||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
renewLetsEncryptSslWithDnsChallenge: (certificate) => {
|
renewLetsEncryptSslWithDnsChallenge: (certificate) => {
|
||||||
const dns_plugin = dnsPlugins[certificate.meta.dns_provider];
|
const dnsPlugin = dnsPlugins[certificate.meta.dns_provider];
|
||||||
|
|
||||||
if (!dns_plugin) {
|
if (!dnsPlugin) {
|
||||||
throw Error(`Unknown DNS provider '${certificate.meta.dns_provider}'`);
|
throw Error(`Unknown DNS provider '${certificate.meta.dns_provider}'`);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(`Renewing Let'sEncrypt certificates via ${dns_plugin.display_name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`);
|
logger.info(`Renewing Let'sEncrypt certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`);
|
||||||
|
|
||||||
let mainCmd = certbotCommand + ' renew ' +
|
let mainCmd = certbotCommand + ' renew --force-renewal ' +
|
||||||
'--config "' + letsencryptConfig + '" ' +
|
'--config "' + letsencryptConfig + '" ' +
|
||||||
|
'--work-dir "/tmp/letsencrypt-lib" ' +
|
||||||
|
'--logs-dir "/tmp/letsencrypt-log" ' +
|
||||||
'--cert-name "npm-' + certificate.id + '" ' +
|
'--cert-name "npm-' + certificate.id + '" ' +
|
||||||
'--disable-hook-validation ' +
|
'--disable-hook-validation ' +
|
||||||
'--no-random-sleep-on-renew ' +
|
'--no-random-sleep-on-renew ' +
|
||||||
@ -1034,6 +1028,8 @@ const internalCertificate = {
|
|||||||
|
|
||||||
const mainCmd = certbotCommand + ' revoke ' +
|
const mainCmd = certbotCommand + ' revoke ' +
|
||||||
'--config "' + letsencryptConfig + '" ' +
|
'--config "' + letsencryptConfig + '" ' +
|
||||||
|
'--work-dir "/tmp/letsencrypt-lib" ' +
|
||||||
|
'--logs-dir "/tmp/letsencrypt-log" ' +
|
||||||
'--cert-path "/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem" ' +
|
'--cert-path "/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem" ' +
|
||||||
'--delete-after-revoke ' +
|
'--delete-after-revoke ' +
|
||||||
(letsencryptStaging ? '--staging' : '');
|
(letsencryptStaging ? '--staging' : '');
|
||||||
@ -1151,6 +1147,7 @@ const internalCertificate = {
|
|||||||
const options = {
|
const options = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
|
'User-Agent': 'Mozilla/5.0',
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
'Content-Length': Buffer.byteLength(formBody)
|
'Content-Length': Buffer.byteLength(formBody)
|
||||||
}
|
}
|
||||||
@ -1163,12 +1160,22 @@ const internalCertificate = {
|
|||||||
|
|
||||||
res.on('data', (chunk) => responseBody = responseBody + chunk);
|
res.on('data', (chunk) => responseBody = responseBody + chunk);
|
||||||
res.on('end', function () {
|
res.on('end', function () {
|
||||||
const parsedBody = JSON.parse(responseBody + '');
|
try {
|
||||||
if (res.statusCode !== 200) {
|
const parsedBody = JSON.parse(responseBody + '');
|
||||||
logger.warn(`Failed to test HTTP challenge for domain ${domain}`, res);
|
if (res.statusCode !== 200) {
|
||||||
|
logger.warn(`Failed to test HTTP challenge for domain ${domain} because HTTP status code ${res.statusCode} was returned: ${parsedBody.message}`);
|
||||||
|
resolve(undefined);
|
||||||
|
} else {
|
||||||
|
resolve(parsedBody);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (res.statusCode !== 200) {
|
||||||
|
logger.warn(`Failed to test HTTP challenge for domain ${domain} because HTTP status code ${res.statusCode} was returned`);
|
||||||
|
} else {
|
||||||
|
logger.warn(`Failed to test HTTP challenge for domain ${domain} because response failed to be parsed: ${err.message}`);
|
||||||
|
}
|
||||||
resolve(undefined);
|
resolve(undefined);
|
||||||
}
|
}
|
||||||
resolve(parsedBody);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1182,6 +1189,9 @@ const internalCertificate = {
|
|||||||
if (!result) {
|
if (!result) {
|
||||||
// Some error occurred while trying to get the data
|
// Some error occurred while trying to get the data
|
||||||
return 'failed';
|
return 'failed';
|
||||||
|
} else if (result.error) {
|
||||||
|
logger.info(`HTTP challenge test failed for domain ${domain} because error was returned: ${result.error.msg}`);
|
||||||
|
return `other:${result.error.msg}`;
|
||||||
} else if (`${result.responsecode}` === '200' && result.htmlresponse === 'Success') {
|
} else if (`${result.responsecode}` === '200' && result.htmlresponse === 'Success') {
|
||||||
// Server exists and has responded with the correct data
|
// Server exists and has responded with the correct data
|
||||||
return 'ok';
|
return 'ok';
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const logger = require('../logger').nginx;
|
const logger = require('../logger').nginx;
|
||||||
const utils = require('../lib/utils');
|
const config = require('../lib/config');
|
||||||
const error = require('../lib/error');
|
const utils = require('../lib/utils');
|
||||||
const debug_mode = process.env.NODE_ENV !== 'production' || !!process.env.DEBUG;
|
const error = require('../lib/error');
|
||||||
|
|
||||||
const internalNginx = {
|
const internalNginx = {
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ const internalNginx = {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (debug_mode) {
|
if (config.debug()) {
|
||||||
logger.error('Nginx test failed:', valid_lines.join('\n'));
|
logger.error('Nginx test failed:', valid_lines.join('\n'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,7 +101,7 @@ const internalNginx = {
|
|||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
test: () => {
|
test: () => {
|
||||||
if (debug_mode) {
|
if (config.debug()) {
|
||||||
logger.info('Testing Nginx configuration');
|
logger.info('Testing Nginx configuration');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,7 +184,7 @@ const internalNginx = {
|
|||||||
generateConfig: (host_type, host) => {
|
generateConfig: (host_type, host) => {
|
||||||
const nice_host_type = internalNginx.getFileFriendlyHostType(host_type);
|
const nice_host_type = internalNginx.getFileFriendlyHostType(host_type);
|
||||||
|
|
||||||
if (debug_mode) {
|
if (config.debug()) {
|
||||||
logger.info('Generating ' + nice_host_type + ' Config:', JSON.stringify(host, null, 2));
|
logger.info('Generating ' + nice_host_type + ' Config:', JSON.stringify(host, null, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,7 +239,7 @@ const internalNginx = {
|
|||||||
.then((config_text) => {
|
.then((config_text) => {
|
||||||
fs.writeFileSync(filename, config_text, {encoding: 'utf8'});
|
fs.writeFileSync(filename, config_text, {encoding: 'utf8'});
|
||||||
|
|
||||||
if (debug_mode) {
|
if (config.debug()) {
|
||||||
logger.success('Wrote config:', filename, config_text);
|
logger.success('Wrote config:', filename, config_text);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,7 +249,7 @@ const internalNginx = {
|
|||||||
resolve(true);
|
resolve(true);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if (debug_mode) {
|
if (config.debug()) {
|
||||||
logger.warn('Could not write ' + filename + ':', err.message);
|
logger.warn('Could not write ' + filename + ':', err.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,7 +268,7 @@ const internalNginx = {
|
|||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
generateLetsEncryptRequestConfig: (certificate) => {
|
generateLetsEncryptRequestConfig: (certificate) => {
|
||||||
if (debug_mode) {
|
if (config.debug()) {
|
||||||
logger.info('Generating LetsEncrypt Request Config:', certificate);
|
logger.info('Generating LetsEncrypt Request Config:', certificate);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,14 +292,14 @@ const internalNginx = {
|
|||||||
.then((config_text) => {
|
.then((config_text) => {
|
||||||
fs.writeFileSync(filename, config_text, {encoding: 'utf8'});
|
fs.writeFileSync(filename, config_text, {encoding: 'utf8'});
|
||||||
|
|
||||||
if (debug_mode) {
|
if (config.debug()) {
|
||||||
logger.success('Wrote config:', filename, config_text);
|
logger.success('Wrote config:', filename, config_text);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(true);
|
resolve(true);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if (debug_mode) {
|
if (config.debug()) {
|
||||||
logger.warn('Could not write ' + filename + ':', err.message);
|
logger.warn('Could not write ' + filename + ':', err.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -416,8 +416,8 @@ const internalNginx = {
|
|||||||
* @param {string} config
|
* @param {string} config
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
advancedConfigHasDefaultLocation: function (config) {
|
advancedConfigHasDefaultLocation: function (cfg) {
|
||||||
return !!config.match(/^(?:.*;)?\s*?location\s*?\/\s*?{/im);
|
return !!cfg.match(/^(?:.*;)?\s*?location\s*?\/\s*?{/im);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -225,7 +225,7 @@ const internalProxyHost = {
|
|||||||
.query()
|
.query()
|
||||||
.where('is_deleted', 0)
|
.where('is_deleted', 0)
|
||||||
.andWhere('id', data.id)
|
.andWhere('id', data.id)
|
||||||
.allowGraph('[owner,access_list,access_list.[clients,items],certificate]')
|
.allowGraph('[owner,access_list.[clients,items],certificate]')
|
||||||
.first();
|
.first();
|
||||||
|
|
||||||
if (access_data.permission_visibility !== 'all') {
|
if (access_data.permission_visibility !== 'all') {
|
||||||
|
78
backend/lib/certbot.js
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
const dnsPlugins = require('../global/certbot-dns-plugins.json');
|
||||||
|
const utils = require('./utils');
|
||||||
|
const error = require('./error');
|
||||||
|
const logger = require('../logger').certbot;
|
||||||
|
const batchflow = require('batchflow');
|
||||||
|
|
||||||
|
const CERTBOT_VERSION_REPLACEMENT = '$(certbot --version | grep -Eo \'[0-9](\\.[0-9]+)+\')';
|
||||||
|
|
||||||
|
const certbot = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {array} pluginKeys
|
||||||
|
*/
|
||||||
|
installPlugins: async function (pluginKeys) {
|
||||||
|
let hasErrors = false;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (pluginKeys.length === 0) {
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
batchflow(pluginKeys).sequential()
|
||||||
|
.each((i, pluginKey, next) => {
|
||||||
|
certbot.installPlugin(pluginKey)
|
||||||
|
.then(() => {
|
||||||
|
next();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
hasErrors = true;
|
||||||
|
next(err);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.error((err) => {
|
||||||
|
logger.error(err.message);
|
||||||
|
})
|
||||||
|
.end(() => {
|
||||||
|
if (hasErrors) {
|
||||||
|
reject(new error.CommandError('Some plugins failed to install. Please check the logs above', 1));
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs a cerbot plugin given the key for the object from
|
||||||
|
* ../global/certbot-dns-plugins.json
|
||||||
|
*
|
||||||
|
* @param {string} pluginKey
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
installPlugin: async function (pluginKey) {
|
||||||
|
if (typeof dnsPlugins[pluginKey] === 'undefined') {
|
||||||
|
// throw Error(`Certbot plugin ${pluginKey} not found`);
|
||||||
|
throw new error.ItemNotFoundError(pluginKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
const plugin = dnsPlugins[pluginKey];
|
||||||
|
logger.start(`Installing ${pluginKey}...`);
|
||||||
|
|
||||||
|
plugin.version = plugin.version.replace(/{{certbot-version}}/g, CERTBOT_VERSION_REPLACEMENT);
|
||||||
|
plugin.dependencies = plugin.dependencies.replace(/{{certbot-version}}/g, CERTBOT_VERSION_REPLACEMENT);
|
||||||
|
|
||||||
|
const cmd = '. /opt/certbot/bin/activate && pip install --no-cache-dir ' + plugin.dependencies + ' ' + plugin.package_name + plugin.version + ' ' + ' && deactivate';
|
||||||
|
return utils.exec(cmd)
|
||||||
|
.then((result) => {
|
||||||
|
logger.complete(`Installed ${pluginKey}`);
|
||||||
|
return result;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = certbot;
|
184
backend/lib/config.js
Normal 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;
|
||||||
|
}
|
||||||
|
};
|
@ -82,7 +82,16 @@ module.exports = {
|
|||||||
this.message = message;
|
this.message = message;
|
||||||
this.public = false;
|
this.public = false;
|
||||||
this.status = 400;
|
this.status = 400;
|
||||||
}
|
},
|
||||||
|
|
||||||
|
CommandError: function (stdErr, code, previous) {
|
||||||
|
Error.captureStackTrace(this, this.constructor);
|
||||||
|
this.name = this.constructor.name;
|
||||||
|
this.previous = previous;
|
||||||
|
this.message = stdErr;
|
||||||
|
this.code = code;
|
||||||
|
this.public = false;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
_.forEach(module.exports, function (error) {
|
_.forEach(module.exports, function (error) {
|
||||||
|
@ -3,23 +3,27 @@ const exec = require('child_process').exec;
|
|||||||
const execFile = require('child_process').execFile;
|
const execFile = require('child_process').execFile;
|
||||||
const { Liquid } = require('liquidjs');
|
const { Liquid } = require('liquidjs');
|
||||||
const logger = require('../logger').global;
|
const logger = require('../logger').global;
|
||||||
|
const error = require('./error');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
||||||
/**
|
exec: async function(cmd, options = {}) {
|
||||||
* @param {String} cmd
|
logger.debug('CMD:', cmd);
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
const { stdout, stderr } = await new Promise((resolve, reject) => {
|
||||||
exec: function (cmd) {
|
const child = exec(cmd, options, (isError, stdout, stderr) => {
|
||||||
return new Promise((resolve, reject) => {
|
if (isError) {
|
||||||
exec(cmd, function (err, stdout, /*stderr*/) {
|
reject(new error.CommandError(stderr, isError));
|
||||||
if (err && typeof err === 'object') {
|
|
||||||
reject(err);
|
|
||||||
} else {
|
} else {
|
||||||
resolve(stdout.trim());
|
resolve({ stdout, stderr });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
child.on('error', (e) => {
|
||||||
|
reject(new error.CommandError(stderr, 1, e));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
return stdout;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -28,7 +32,8 @@ module.exports = {
|
|||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
execFile: function (cmd, args) {
|
execFile: function (cmd, args) {
|
||||||
logger.debug('CMD: ' + cmd + ' ' + (args ? args.join(' ') : ''));
|
// logger.debug('CMD: ' + cmd + ' ' + (args ? args.join(' ') : ''));
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
execFile(cmd, args, function (err, stdout, /*stderr*/) {
|
execFile(cmd, args, function (err, stdout, /*stderr*/) {
|
||||||
if (err && typeof err === 'object') {
|
if (err && typeof err === 'object') {
|
||||||
|
@ -5,7 +5,7 @@ const definitions = require('../../schema/definitions.json');
|
|||||||
RegExp.prototype.toJSON = RegExp.prototype.toString;
|
RegExp.prototype.toJSON = RegExp.prototype.toString;
|
||||||
|
|
||||||
const ajv = require('ajv')({
|
const ajv = require('ajv')({
|
||||||
verbose: true, //process.env.NODE_ENV === 'development',
|
verbose: true,
|
||||||
allErrors: true,
|
allErrors: true,
|
||||||
format: 'full', // strict regexes for format checks
|
format: 'full', // strict regexes for format checks
|
||||||
coerceTypes: true,
|
coerceTypes: true,
|
||||||
|
@ -7,6 +7,7 @@ module.exports = {
|
|||||||
access: new Signale({scope: 'Access '}),
|
access: new Signale({scope: 'Access '}),
|
||||||
nginx: new Signale({scope: 'Nginx '}),
|
nginx: new Signale({scope: 'Nginx '}),
|
||||||
ssl: new Signale({scope: 'SSL '}),
|
ssl: new Signale({scope: 'SSL '}),
|
||||||
|
certbot: new Signale({scope: 'Certbot '}),
|
||||||
import: new Signale({scope: 'Importer '}),
|
import: new Signale({scope: 'Importer '}),
|
||||||
setup: new Signale({scope: 'Setup '}),
|
setup: new Signale({scope: 'Setup '}),
|
||||||
ip_ranges: new Signale({scope: 'IP Ranges'})
|
ip_ranges: new Signale({scope: 'IP Ranges'})
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
const db = require('../db');
|
const db = require('../db');
|
||||||
const config = require('config');
|
const config = require('../lib/config');
|
||||||
const Model = require('objection').Model;
|
const Model = require('objection').Model;
|
||||||
|
|
||||||
Model.knex(db);
|
Model.knex(db);
|
||||||
|
|
||||||
module.exports = function () {
|
module.exports = function () {
|
||||||
if (config.database.knex && config.database.knex.client === 'sqlite3') {
|
if (config.isSqlite()) {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
return Model.raw("datetime('now','localtime')");
|
return Model.raw("datetime('now','localtime')");
|
||||||
}
|
}
|
||||||
|
@ -6,44 +6,36 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const jwt = require('jsonwebtoken');
|
const jwt = require('jsonwebtoken');
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
|
const config = require('../lib/config');
|
||||||
const error = require('../lib/error');
|
const error = require('../lib/error');
|
||||||
|
const logger = require('../logger').global;
|
||||||
const ALGO = 'RS256';
|
const ALGO = 'RS256';
|
||||||
|
|
||||||
let public_key = null;
|
|
||||||
let private_key = null;
|
|
||||||
|
|
||||||
function checkJWTKeyPair() {
|
|
||||||
if (!public_key || !private_key) {
|
|
||||||
let config = require('config');
|
|
||||||
public_key = config.get('jwt.pub');
|
|
||||||
private_key = config.get('jwt.key');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = function () {
|
module.exports = function () {
|
||||||
|
|
||||||
let token_data = {};
|
let token_data = {};
|
||||||
|
|
||||||
let self = {
|
const self = {
|
||||||
/**
|
/**
|
||||||
* @param {Object} payload
|
* @param {Object} payload
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
create: (payload) => {
|
create: (payload) => {
|
||||||
|
if (!config.getPrivateKey()) {
|
||||||
|
logger.error('Private key is empty!');
|
||||||
|
}
|
||||||
// sign with RSA SHA256
|
// sign with RSA SHA256
|
||||||
let options = {
|
const options = {
|
||||||
algorithm: ALGO,
|
algorithm: ALGO,
|
||||||
expiresIn: payload.expiresIn || '1d'
|
expiresIn: payload.expiresIn || '1d'
|
||||||
};
|
};
|
||||||
|
|
||||||
payload.jti = crypto.randomBytes(12)
|
payload.jti = crypto.randomBytes(12)
|
||||||
.toString('base64')
|
.toString('base64')
|
||||||
.substr(-8);
|
.substring(-8);
|
||||||
|
|
||||||
checkJWTKeyPair();
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
jwt.sign(payload, private_key, options, (err, token) => {
|
jwt.sign(payload, config.getPrivateKey(), options, (err, token) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
} else {
|
} else {
|
||||||
@ -62,13 +54,15 @@ module.exports = function () {
|
|||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
load: function (token) {
|
load: function (token) {
|
||||||
|
if (!config.getPublicKey()) {
|
||||||
|
logger.error('Public key is empty!');
|
||||||
|
}
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
checkJWTKeyPair();
|
|
||||||
try {
|
try {
|
||||||
if (!token || token === null || token === 'null') {
|
if (!token || token === null || token === 'null') {
|
||||||
reject(new error.AuthError('Empty token'));
|
reject(new error.AuthError('Empty token'));
|
||||||
} else {
|
} else {
|
||||||
jwt.verify(token, public_key, {ignoreExpiration: false, algorithms: [ALGO]}, (err, result) => {
|
jwt.verify(token, config.getPublicKey(), {ignoreExpiration: false, algorithms: [ALGO]}, (err, result) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
|
||||||
if (err.name === 'TokenExpiredError') {
|
if (err.name === 'TokenExpiredError') {
|
||||||
@ -132,7 +126,7 @@ module.exports = function () {
|
|||||||
* @returns {Integer}
|
* @returns {Integer}
|
||||||
*/
|
*/
|
||||||
getUserId: (default_value) => {
|
getUserId: (default_value) => {
|
||||||
let attrs = self.get('attrs');
|
const attrs = self.get('attrs');
|
||||||
if (attrs && typeof attrs.id !== 'undefined' && attrs.id) {
|
if (attrs && typeof attrs.id !== 'undefined' && attrs.id) {
|
||||||
return attrs.id;
|
return attrs.id;
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,7 @@
|
|||||||
"bcrypt": "^5.0.0",
|
"bcrypt": "^5.0.0",
|
||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.0",
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"config": "^3.3.1",
|
"express": "^4.19.2",
|
||||||
"express": "^4.17.3",
|
|
||||||
"express-fileupload": "^1.1.9",
|
"express-fileupload": "^1.1.9",
|
||||||
"gravatar": "^1.8.0",
|
"gravatar": "^1.8.0",
|
||||||
"json-schema-ref-parser": "^8.0.0",
|
"json-schema-ref-parser": "^8.0.0",
|
||||||
@ -22,7 +21,6 @@
|
|||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"mysql": "^2.18.1",
|
"mysql": "^2.18.1",
|
||||||
"node-rsa": "^1.0.8",
|
"node-rsa": "^1.0.8",
|
||||||
"nodemon": "^2.0.2",
|
|
||||||
"objection": "3.0.1",
|
"objection": "3.0.1",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"signale": "1.4.0",
|
"signale": "1.4.0",
|
||||||
@ -36,8 +34,9 @@
|
|||||||
"author": "Jamie Curnow <jc@jc21.com>",
|
"author": "Jamie Curnow <jc@jc21.com>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^8.36.0",
|
||||||
"eslint-plugin-align-assignments": "^1.1.2",
|
"eslint-plugin-align-assignments": "^1.1.2",
|
||||||
|
"nodemon": "^2.0.2",
|
||||||
"prettier": "^2.0.4"
|
"prettier": "^2.0.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -172,7 +172,7 @@
|
|||||||
"description": "Domain Names separated by a comma",
|
"description": "Domain Names separated by a comma",
|
||||||
"example": "*.jc21.com,blog.jc21.com",
|
"example": "*.jc21.com,blog.jc21.com",
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"maxItems": 15,
|
"maxItems": 100,
|
||||||
"uniqueItems": true,
|
"uniqueItems": true,
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
49
backend/scripts/install-certbot-plugins
Executable file
@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/node
|
||||||
|
|
||||||
|
// Usage:
|
||||||
|
// Install all plugins defined in `certbot-dns-plugins.json`:
|
||||||
|
// ./install-certbot-plugins
|
||||||
|
// Install one or more specific plugins:
|
||||||
|
// ./install-certbot-plugins route53 cloudflare
|
||||||
|
//
|
||||||
|
// Usage with a running docker container:
|
||||||
|
// docker exec npm_core /command/s6-setuidgid 1000:1000 bash -c "/app/scripts/install-certbot-plugins"
|
||||||
|
//
|
||||||
|
|
||||||
|
const dnsPlugins = require('../global/certbot-dns-plugins.json');
|
||||||
|
const certbot = require('../lib/certbot');
|
||||||
|
const logger = require('../logger').certbot;
|
||||||
|
const batchflow = require('batchflow');
|
||||||
|
|
||||||
|
let hasErrors = false;
|
||||||
|
let failingPlugins = [];
|
||||||
|
|
||||||
|
let pluginKeys = Object.keys(dnsPlugins);
|
||||||
|
if (process.argv.length > 2) {
|
||||||
|
pluginKeys = process.argv.slice(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
batchflow(pluginKeys).sequential()
|
||||||
|
.each((i, pluginKey, next) => {
|
||||||
|
certbot.installPlugin(pluginKey)
|
||||||
|
.then(() => {
|
||||||
|
next();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
hasErrors = true;
|
||||||
|
failingPlugins.push(pluginKey);
|
||||||
|
next(err);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.error((err) => {
|
||||||
|
logger.error(err.message);
|
||||||
|
})
|
||||||
|
.end(() => {
|
||||||
|
if (hasErrors) {
|
||||||
|
logger.error('Some plugins failed to install. Please check the logs above. Failing plugins: ' + '\n - ' + failingPlugins.join('\n - '));
|
||||||
|
process.exit(1);
|
||||||
|
} else {
|
||||||
|
logger.complete('Plugins installed successfully');
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
});
|
110
backend/setup.js
@ -1,6 +1,4 @@
|
|||||||
const fs = require('fs');
|
const config = require('./lib/config');
|
||||||
const NodeRSA = require('node-rsa');
|
|
||||||
const config = require('config');
|
|
||||||
const logger = require('./logger').setup;
|
const logger = require('./logger').setup;
|
||||||
const certificateModel = require('./models/certificate');
|
const certificateModel = require('./models/certificate');
|
||||||
const userModel = require('./models/user');
|
const userModel = require('./models/user');
|
||||||
@ -8,64 +6,7 @@ const userPermissionModel = require('./models/user_permission');
|
|||||||
const utils = require('./lib/utils');
|
const utils = require('./lib/utils');
|
||||||
const authModel = require('./models/auth');
|
const authModel = require('./models/auth');
|
||||||
const settingModel = require('./models/setting');
|
const settingModel = require('./models/setting');
|
||||||
const dns_plugins = require('./global/certbot-dns-plugins');
|
const certbot = require('./lib/certbot');
|
||||||
const debug_mode = process.env.NODE_ENV !== 'production' || !!process.env.DEBUG;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new JWT RSA Keypair if not alread set on the config
|
|
||||||
*
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
const setupJwt = () => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
// Now go and check if the jwt gpg keys have been created and if not, create them
|
|
||||||
if (!config.has('jwt') || !config.has('jwt.key') || !config.has('jwt.pub')) {
|
|
||||||
logger.info('Creating a new JWT key pair...');
|
|
||||||
|
|
||||||
// jwt keys are not configured properly
|
|
||||||
const filename = config.util.getEnv('NODE_CONFIG_DIR') + '/' + (config.util.getEnv('NODE_ENV') || 'default') + '.json';
|
|
||||||
let config_data = {};
|
|
||||||
|
|
||||||
try {
|
|
||||||
config_data = require(filename);
|
|
||||||
} catch (err) {
|
|
||||||
// do nothing
|
|
||||||
if (debug_mode) {
|
|
||||||
logger.debug(filename + ' config file could not be required');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now create the keys and save them in the config.
|
|
||||||
let key = new NodeRSA({ b: 2048 });
|
|
||||||
key.generateKeyPair();
|
|
||||||
|
|
||||||
config_data.jwt = {
|
|
||||||
key: key.exportKey('private').toString(),
|
|
||||||
pub: key.exportKey('public').toString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Write config
|
|
||||||
fs.writeFile(filename, JSON.stringify(config_data, null, 2), (err) => {
|
|
||||||
if (err) {
|
|
||||||
logger.error('Could not write JWT key pair to config file: ' + filename);
|
|
||||||
reject(err);
|
|
||||||
} else {
|
|
||||||
logger.info('Wrote JWT key pair to config file: ' + filename);
|
|
||||||
delete require.cache[require.resolve('config')];
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// JWT key pair exists
|
|
||||||
if (debug_mode) {
|
|
||||||
logger.debug('JWT Keypair already exists');
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a default admin users if one doesn't already exist in the database
|
* Creates a default admin users if one doesn't already exist in the database
|
||||||
*
|
*
|
||||||
@ -80,11 +21,14 @@ const setupDefaultUser = () => {
|
|||||||
.then((row) => {
|
.then((row) => {
|
||||||
if (!row.count) {
|
if (!row.count) {
|
||||||
// Create a new user and set password
|
// Create a new user and set password
|
||||||
logger.info('Creating a new user: admin@example.com with password: changeme');
|
let email = process.env.INITIAL_ADMIN_EMAIL || 'admin@example.com';
|
||||||
|
let password = process.env.INITIAL_ADMIN_PASSWORD || 'changeme';
|
||||||
|
|
||||||
|
logger.info('Creating a new user: ' + email + ' with password: ' + password);
|
||||||
|
|
||||||
let data = {
|
let data = {
|
||||||
is_deleted: 0,
|
is_deleted: 0,
|
||||||
email: 'admin@example.com',
|
email: email,
|
||||||
name: 'Administrator',
|
name: 'Administrator',
|
||||||
nickname: 'Admin',
|
nickname: 'Admin',
|
||||||
avatar: '',
|
avatar: '',
|
||||||
@ -100,7 +44,7 @@ const setupDefaultUser = () => {
|
|||||||
.insert({
|
.insert({
|
||||||
user_id: user.id,
|
user_id: user.id,
|
||||||
type: 'password',
|
type: 'password',
|
||||||
secret: 'changeme',
|
secret: password,
|
||||||
meta: {},
|
meta: {},
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@ -119,8 +63,8 @@ const setupDefaultUser = () => {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
logger.info('Initial admin setup completed');
|
logger.info('Initial admin setup completed');
|
||||||
});
|
});
|
||||||
} else if (debug_mode) {
|
} else if (config.debug()) {
|
||||||
logger.debug('Admin user setup not required');
|
logger.info('Admin user setup not required');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -151,8 +95,8 @@ const setupDefaultSettings = () => {
|
|||||||
logger.info('Default settings added');
|
logger.info('Default settings added');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (debug_mode) {
|
if (config.debug()) {
|
||||||
logger.debug('Default setting setup not required');
|
logger.info('Default setting setup not required');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -174,10 +118,9 @@ const setupCertbotPlugins = () => {
|
|||||||
|
|
||||||
certificates.map(function (certificate) {
|
certificates.map(function (certificate) {
|
||||||
if (certificate.meta && certificate.meta.dns_challenge === true) {
|
if (certificate.meta && certificate.meta.dns_challenge === true) {
|
||||||
const dns_plugin = dns_plugins[certificate.meta.dns_provider];
|
if (plugins.indexOf(certificate.meta.dns_provider) === -1) {
|
||||||
|
plugins.push(certificate.meta.dns_provider);
|
||||||
const packages_to_install = `${dns_plugin.package_name}${dns_plugin.version_requirement || ''} ${dns_plugin.dependencies}`;
|
}
|
||||||
if (plugins.indexOf(packages_to_install) === -1) plugins.push(packages_to_install);
|
|
||||||
|
|
||||||
// Make sure credentials file exists
|
// Make sure credentials file exists
|
||||||
const credentials_loc = '/etc/letsencrypt/credentials/credentials-' + certificate.id;
|
const credentials_loc = '/etc/letsencrypt/credentials/credentials-' + certificate.id;
|
||||||
@ -188,17 +131,15 @@ const setupCertbotPlugins = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (plugins.length) {
|
return certbot.installPlugins(plugins)
|
||||||
const install_cmd = '. /opt/certbot/bin/activate && pip install ' + plugins.join(' ') + ' && deactivate';
|
.then(() => {
|
||||||
promises.push(utils.exec(install_cmd));
|
if (promises.length) {
|
||||||
}
|
return Promise.all(promises)
|
||||||
|
.then(() => {
|
||||||
if (promises.length) {
|
logger.info('Added Certbot plugins ' + plugins.join(', '));
|
||||||
return Promise.all(promises)
|
});
|
||||||
.then(() => {
|
}
|
||||||
logger.info('Added Certbot plugins ' + plugins.join(', '));
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -225,8 +166,7 @@ const setupLogrotation = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
module.exports = function () {
|
module.exports = function () {
|
||||||
return setupJwt()
|
return setupDefaultUser()
|
||||||
.then(setupDefaultUser)
|
|
||||||
.then(setupDefaultSettings)
|
.then(setupDefaultSettings)
|
||||||
.then(setupCertbotPlugins)
|
.then(setupCertbotPlugins)
|
||||||
.then(setupLogrotation);
|
.then(setupLogrotation);
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% if ssl_forced == 1 or ssl_forced == true %}
|
{% if ssl_forced == 1 or ssl_forced == true %}
|
||||||
{% if hsts_enabled == 1 or hsts_enabled == true %}
|
{% if hsts_enabled == 1 or hsts_enabled == true %}
|
||||||
# HSTS (ngx_http_headers_module is required) (63072000 seconds = 2 years)
|
# HSTS (ngx_http_headers_module is required) (63072000 seconds = 2 years)
|
||||||
add_header Strict-Transport-Security "max-age=63072000;{% if hsts_subdomains == 1 or hsts_subdomains == true -%} includeSubDomains;{% endif %} preload" always;
|
add_header Strict-Transport-Security $hsts_header always;
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
3
backend/templates/_hsts_map.conf
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
map $scheme $hsts_header {
|
||||||
|
https "max-age=63072000;{% if hsts_subdomains == 1 or hsts_subdomains == true -%} includeSubDomains;{% endif %} preload";
|
||||||
|
}
|
@ -5,9 +5,9 @@
|
|||||||
#listen [::]:80;
|
#listen [::]:80;
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if certificate -%}
|
{% if certificate -%}
|
||||||
listen 443 ssl{% if http2_support %} http2{% endif %};
|
listen 443 ssl{% if http2_support == 1 or http2_support == true %} http2{% endif %};
|
||||||
{% if ipv6 -%}
|
{% if ipv6 -%}
|
||||||
listen [::]:443 ssl{% if http2_support %} http2{% endif %};
|
listen [::]:443 ssl{% if http2_support == 1 or http2_support == true %} http2{% endif %};
|
||||||
{% else -%}
|
{% else -%}
|
||||||
#listen [::]:443;
|
#listen [::]:443;
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
location {{ path }} {
|
location {{ path }} {
|
||||||
|
{{ advanced_config }}
|
||||||
|
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Forwarded-Scheme $scheme;
|
proxy_set_header X-Forwarded-Scheme $scheme;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
@ -17,8 +19,5 @@
|
|||||||
proxy_set_header Connection $http_connection;
|
proxy_set_header Connection $http_connection;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
{{ advanced_config }}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
{% include "_header_comment.conf" %}
|
{% include "_header_comment.conf" %}
|
||||||
|
|
||||||
{% if enabled %}
|
{% if enabled %}
|
||||||
|
|
||||||
|
{% include "_hsts_map.conf" %}
|
||||||
|
|
||||||
server {
|
server {
|
||||||
{% include "_listen.conf" %}
|
{% include "_listen.conf" %}
|
||||||
{% include "_certificates.conf" %}
|
{% include "_certificates.conf" %}
|
||||||
|
@ -24,6 +24,12 @@ server {
|
|||||||
}
|
}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{%- if value == "444" %}
|
||||||
|
location / {
|
||||||
|
return 444;
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{%- if value == "redirect" %}
|
{%- if value == "redirect" %}
|
||||||
location / {
|
location / {
|
||||||
return 301 {{ meta.redirect }};
|
return 301 {{ meta.redirect }};
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
{% include "_header_comment.conf" %}
|
{% include "_header_comment.conf" %}
|
||||||
|
|
||||||
{% if enabled %}
|
{% if enabled %}
|
||||||
|
|
||||||
|
{% include "_hsts_map.conf" %}
|
||||||
|
|
||||||
server {
|
server {
|
||||||
set $forward_scheme {{ forward_scheme }};
|
set $forward_scheme {{ forward_scheme }};
|
||||||
set $server "{{ forward_host }}";
|
set $server "{{ forward_host }}";
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
{% include "_header_comment.conf" %}
|
{% include "_header_comment.conf" %}
|
||||||
|
|
||||||
{% if enabled %}
|
{% if enabled %}
|
||||||
|
|
||||||
|
{% include "_hsts_map.conf" %}
|
||||||
|
|
||||||
server {
|
server {
|
||||||
{% include "_listen.conf" %}
|
{% include "_listen.conf" %}
|
||||||
{% include "_certificates.conf" %}
|
{% include "_certificates.conf" %}
|
||||||
|
1078
backend/yarn.lock
@ -3,20 +3,25 @@
|
|||||||
|
|
||||||
# This file assumes that the frontend has been built using ./scripts/frontend-build
|
# This file assumes that the frontend has been built using ./scripts/frontend-build
|
||||||
|
|
||||||
FROM jc21/nginx-full:certbot-node
|
FROM nginxproxymanager/nginx-full:certbot-node
|
||||||
|
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
ARG BUILD_VERSION
|
ARG BUILD_VERSION
|
||||||
ARG BUILD_COMMIT
|
ARG BUILD_COMMIT
|
||||||
ARG BUILD_DATE
|
ARG BUILD_DATE
|
||||||
|
|
||||||
|
# See: https://github.com/just-containers/s6-overlay/blob/master/README.md
|
||||||
ENV SUPPRESS_NO_CONFIG_WARNING=1 \
|
ENV SUPPRESS_NO_CONFIG_WARNING=1 \
|
||||||
S6_FIX_ATTRS_HIDDEN=1 \
|
|
||||||
S6_BEHAVIOUR_IF_STAGE2_FAILS=1 \
|
S6_BEHAVIOUR_IF_STAGE2_FAILS=1 \
|
||||||
|
S6_CMD_WAIT_FOR_SERVICES_MAXTIME=0 \
|
||||||
|
S6_FIX_ATTRS_HIDDEN=1 \
|
||||||
|
S6_KILL_FINISH_MAXTIME=10000 \
|
||||||
|
S6_VERBOSITY=1 \
|
||||||
NODE_ENV=production \
|
NODE_ENV=production \
|
||||||
NPM_BUILD_VERSION="${BUILD_VERSION}" \
|
NPM_BUILD_VERSION="${BUILD_VERSION}" \
|
||||||
NPM_BUILD_COMMIT="${BUILD_COMMIT}" \
|
NPM_BUILD_COMMIT="${BUILD_COMMIT}" \
|
||||||
NPM_BUILD_DATE="${BUILD_DATE}"
|
NPM_BUILD_DATE="${BUILD_DATE}" \
|
||||||
|
NODE_OPTIONS="--openssl-legacy-provider"
|
||||||
|
|
||||||
RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \
|
RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \
|
||||||
&& apt-get update \
|
&& apt-get update \
|
||||||
@ -35,23 +40,17 @@ COPY frontend/dist /app/frontend
|
|||||||
COPY global /app/global
|
COPY global /app/global
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
RUN yarn install
|
RUN yarn install \
|
||||||
|
&& yarn cache clean
|
||||||
|
|
||||||
# add late to limit cache-busting by modifications
|
# add late to limit cache-busting by modifications
|
||||||
COPY docker/rootfs /
|
COPY docker/rootfs /
|
||||||
|
|
||||||
# Remove frontend service not required for prod, dev nginx config as well
|
# Remove frontend service not required for prod, dev nginx config as well
|
||||||
RUN rm -rf /etc/services.d/frontend /etc/nginx/conf.d/dev.conf
|
RUN rm -rf /etc/s6-overlay/s6-rc.d/user/contents.d/frontend /etc/nginx/conf.d/dev.conf \
|
||||||
|
&& chmod 644 /etc/logrotate.d/nginx-proxy-manager
|
||||||
|
|
||||||
# Change permission of logrotate config file
|
VOLUME [ "/data" ]
|
||||||
RUN chmod 644 /etc/logrotate.d/nginx-proxy-manager
|
|
||||||
|
|
||||||
# fix for pip installs
|
|
||||||
# https://github.com/NginxProxyManager/nginx-proxy-manager/issues/1769
|
|
||||||
RUN pip uninstall --yes setuptools \
|
|
||||||
&& pip install "setuptools==58.0.0"
|
|
||||||
|
|
||||||
VOLUME [ "/data", "/etc/letsencrypt" ]
|
|
||||||
ENTRYPOINT [ "/init" ]
|
ENTRYPOINT [ "/init" ]
|
||||||
|
|
||||||
LABEL org.label-schema.schema-version="1.0" \
|
LABEL org.label-schema.schema-version="1.0" \
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
FROM jc21/nginx-full:certbot-node
|
FROM nginxproxymanager/nginx-full:certbot-node
|
||||||
LABEL maintainer="Jamie Curnow <jc@jc21.com>"
|
LABEL maintainer="Jamie Curnow <jc@jc21.com>"
|
||||||
|
|
||||||
ENV S6_LOGGING=0 \
|
# See: https://github.com/just-containers/s6-overlay/blob/master/README.md
|
||||||
SUPPRESS_NO_CONFIG_WARNING=1 \
|
ENV SUPPRESS_NO_CONFIG_WARNING=1 \
|
||||||
S6_FIX_ATTRS_HIDDEN=1
|
S6_BEHAVIOUR_IF_STAGE2_FAILS=1 \
|
||||||
|
S6_CMD_WAIT_FOR_SERVICES_MAXTIME=0 \
|
||||||
|
S6_FIX_ATTRS_HIDDEN=1 \
|
||||||
|
S6_KILL_FINISH_MAXTIME=10000 \
|
||||||
|
S6_VERBOSITY=2 \
|
||||||
|
NODE_OPTIONS="--openssl-legacy-provider"
|
||||||
|
|
||||||
RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \
|
RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \
|
||||||
&& apt-get update \
|
&& apt-get update \
|
||||||
|
28
docker/dev/dnsrouter-config.json
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"format": "nice",
|
||||||
|
"level": "debug"
|
||||||
|
},
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"host": "0.0.0.0",
|
||||||
|
"port": 53,
|
||||||
|
"upstreams": [
|
||||||
|
{
|
||||||
|
"regex": "website[0-9]+.example\\.com",
|
||||||
|
"upstream": "127.0.0.11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regex": ".*\\.example\\.com",
|
||||||
|
"upstream": "1.1.1.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regex": "local",
|
||||||
|
"nxdomain": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"internal": null,
|
||||||
|
"default_upstream": "127.0.0.11"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
7
docker/dev/letsencrypt.ini
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
text = True
|
||||||
|
non-interactive = True
|
||||||
|
webroot-path = /data/letsencrypt-acme-challenge
|
||||||
|
key-type = ecdsa
|
||||||
|
elliptic-curve = secp384r1
|
||||||
|
preferred-chain = ISRG Root X1
|
||||||
|
server =
|
255
docker/dev/pdns-db.sql
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
How this was generated:
|
||||||
|
1. bring up an empty pdns stack
|
||||||
|
2. use api to create a zone ...
|
||||||
|
|
||||||
|
curl -X POST \
|
||||||
|
'http://npm.dev:8081/api/v1/servers/localhost/zones' \
|
||||||
|
--header 'X-API-Key: npm' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--data-raw '{
|
||||||
|
"name": "example.com.",
|
||||||
|
"kind": "Native",
|
||||||
|
"masters": [],
|
||||||
|
"nameservers": [
|
||||||
|
"ns1.pdns.",
|
||||||
|
"ns2.pdns."
|
||||||
|
]
|
||||||
|
}'
|
||||||
|
|
||||||
|
3. Dump sql:
|
||||||
|
|
||||||
|
docker exec -ti npm.pdns.db mysqldump -u pdns -p pdns
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
|
||||||
|
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||||
|
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||||
|
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
||||||
|
/*!40101 SET NAMES utf8mb4 */;
|
||||||
|
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
|
||||||
|
/*!40103 SET TIME_ZONE='+00:00' */;
|
||||||
|
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
|
||||||
|
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
||||||
|
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
||||||
|
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `comments`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `comments`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `comments` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`domain_id` int(11) NOT NULL,
|
||||||
|
`name` varchar(255) NOT NULL,
|
||||||
|
`type` varchar(10) NOT NULL,
|
||||||
|
`modified_at` int(11) NOT NULL,
|
||||||
|
`account` varchar(40) CHARACTER SET utf8mb3 DEFAULT NULL,
|
||||||
|
`comment` text CHARACTER SET utf8mb3 NOT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `comments_name_type_idx` (`name`,`type`),
|
||||||
|
KEY `comments_order_idx` (`domain_id`,`modified_at`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Dumping data for table `comments`
|
||||||
|
--
|
||||||
|
|
||||||
|
LOCK TABLES `comments` WRITE;
|
||||||
|
/*!40000 ALTER TABLE `comments` DISABLE KEYS */;
|
||||||
|
/*!40000 ALTER TABLE `comments` ENABLE KEYS */;
|
||||||
|
UNLOCK TABLES;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `cryptokeys`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `cryptokeys`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `cryptokeys` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`domain_id` int(11) NOT NULL,
|
||||||
|
`flags` int(11) NOT NULL,
|
||||||
|
`active` tinyint(1) DEFAULT NULL,
|
||||||
|
`published` tinyint(1) DEFAULT 1,
|
||||||
|
`content` text DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `domainidindex` (`domain_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Dumping data for table `cryptokeys`
|
||||||
|
--
|
||||||
|
|
||||||
|
LOCK TABLES `cryptokeys` WRITE;
|
||||||
|
/*!40000 ALTER TABLE `cryptokeys` DISABLE KEYS */;
|
||||||
|
/*!40000 ALTER TABLE `cryptokeys` ENABLE KEYS */;
|
||||||
|
UNLOCK TABLES;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `domainmetadata`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `domainmetadata`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `domainmetadata` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`domain_id` int(11) NOT NULL,
|
||||||
|
`kind` varchar(32) DEFAULT NULL,
|
||||||
|
`content` text DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `domainmetadata_idx` (`domain_id`,`kind`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Dumping data for table `domainmetadata`
|
||||||
|
--
|
||||||
|
|
||||||
|
LOCK TABLES `domainmetadata` WRITE;
|
||||||
|
/*!40000 ALTER TABLE `domainmetadata` DISABLE KEYS */;
|
||||||
|
INSERT INTO `domainmetadata` VALUES
|
||||||
|
(1,1,'SOA-EDIT-API','DEFAULT');
|
||||||
|
/*!40000 ALTER TABLE `domainmetadata` ENABLE KEYS */;
|
||||||
|
UNLOCK TABLES;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `domains`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `domains`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `domains` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`name` varchar(255) NOT NULL,
|
||||||
|
`master` varchar(128) DEFAULT NULL,
|
||||||
|
`last_check` int(11) DEFAULT NULL,
|
||||||
|
`type` varchar(8) NOT NULL,
|
||||||
|
`notified_serial` int(10) unsigned DEFAULT NULL,
|
||||||
|
`account` varchar(40) CHARACTER SET utf8mb3 DEFAULT NULL,
|
||||||
|
`options` varchar(64000) DEFAULT NULL,
|
||||||
|
`catalog` varchar(255) DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `name_index` (`name`),
|
||||||
|
KEY `catalog_idx` (`catalog`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Dumping data for table `domains`
|
||||||
|
--
|
||||||
|
|
||||||
|
LOCK TABLES `domains` WRITE;
|
||||||
|
/*!40000 ALTER TABLE `domains` DISABLE KEYS */;
|
||||||
|
INSERT INTO `domains` VALUES
|
||||||
|
(1,'example.com','',NULL,'NATIVE',NULL,'',NULL,NULL);
|
||||||
|
/*!40000 ALTER TABLE `domains` ENABLE KEYS */;
|
||||||
|
UNLOCK TABLES;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `records`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `records`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `records` (
|
||||||
|
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
||||||
|
`domain_id` int(11) DEFAULT NULL,
|
||||||
|
`name` varchar(255) DEFAULT NULL,
|
||||||
|
`type` varchar(10) DEFAULT NULL,
|
||||||
|
`content` varchar(64000) DEFAULT NULL,
|
||||||
|
`ttl` int(11) DEFAULT NULL,
|
||||||
|
`prio` int(11) DEFAULT NULL,
|
||||||
|
`disabled` tinyint(1) DEFAULT 0,
|
||||||
|
`ordername` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin DEFAULT NULL,
|
||||||
|
`auth` tinyint(1) DEFAULT 1,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `nametype_index` (`name`,`type`),
|
||||||
|
KEY `domain_id` (`domain_id`),
|
||||||
|
KEY `ordername` (`ordername`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Dumping data for table `records`
|
||||||
|
--
|
||||||
|
|
||||||
|
LOCK TABLES `records` WRITE;
|
||||||
|
/*!40000 ALTER TABLE `records` DISABLE KEYS */;
|
||||||
|
INSERT INTO `records` VALUES
|
||||||
|
(1,1,'example.com','NS','ns1.pdns',1500,0,0,NULL,1),
|
||||||
|
(2,1,'example.com','NS','ns2.pdns',1500,0,0,NULL,1),
|
||||||
|
(3,1,'example.com','SOA','a.misconfigured.dns.server.invalid hostmaster.example.com 2023030501 10800 3600 604800 3600',1500,0,0,NULL,1);
|
||||||
|
/*!40000 ALTER TABLE `records` ENABLE KEYS */;
|
||||||
|
UNLOCK TABLES;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `supermasters`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `supermasters`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `supermasters` (
|
||||||
|
`ip` varchar(64) NOT NULL,
|
||||||
|
`nameserver` varchar(255) NOT NULL,
|
||||||
|
`account` varchar(40) CHARACTER SET utf8mb3 NOT NULL,
|
||||||
|
PRIMARY KEY (`ip`,`nameserver`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Dumping data for table `supermasters`
|
||||||
|
--
|
||||||
|
|
||||||
|
LOCK TABLES `supermasters` WRITE;
|
||||||
|
/*!40000 ALTER TABLE `supermasters` DISABLE KEYS */;
|
||||||
|
/*!40000 ALTER TABLE `supermasters` ENABLE KEYS */;
|
||||||
|
UNLOCK TABLES;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `tsigkeys`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `tsigkeys`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `tsigkeys` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`name` varchar(255) DEFAULT NULL,
|
||||||
|
`algorithm` varchar(50) DEFAULT NULL,
|
||||||
|
`secret` varchar(255) DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `namealgoindex` (`name`,`algorithm`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Dumping data for table `tsigkeys`
|
||||||
|
--
|
||||||
|
|
||||||
|
LOCK TABLES `tsigkeys` WRITE;
|
||||||
|
/*!40000 ALTER TABLE `tsigkeys` DISABLE KEYS */;
|
||||||
|
/*!40000 ALTER TABLE `tsigkeys` ENABLE KEYS */;
|
||||||
|
UNLOCK TABLES;
|
||||||
|
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
||||||
|
|
||||||
|
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
|
||||||
|
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
|
||||||
|
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
|
||||||
|
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||||
|
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
||||||
|
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
||||||
|
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
12
docker/dev/pebble-config.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"pebble": {
|
||||||
|
"listenAddress": "0.0.0.0:443",
|
||||||
|
"managementListenAddress": "0.0.0.0:15000",
|
||||||
|
"certificate": "test/certs/localhost/cert.pem",
|
||||||
|
"privateKey": "test/certs/localhost/key.pem",
|
||||||
|
"httpPort": 80,
|
||||||
|
"tlsPort": 443,
|
||||||
|
"ocspResponderURL": "",
|
||||||
|
"externalAccountBindingRequired": false
|
||||||
|
}
|
||||||
|
}
|
27
docker/docker-compose.ci.mysql.yml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# WARNING: This is a CI docker-compose file used for building and testing of the entire app, it should not be used for production.
|
||||||
|
services:
|
||||||
|
|
||||||
|
fullstack:
|
||||||
|
environment:
|
||||||
|
DB_MYSQL_HOST: 'db-mysql'
|
||||||
|
DB_MYSQL_PORT: '3306'
|
||||||
|
DB_MYSQL_USER: 'npm'
|
||||||
|
DB_MYSQL_PASSWORD: 'npmpass'
|
||||||
|
DB_MYSQL_NAME: 'npm'
|
||||||
|
depends_on:
|
||||||
|
- db-mysql
|
||||||
|
|
||||||
|
db-mysql:
|
||||||
|
image: jc21/mariadb-aria
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: 'npm'
|
||||||
|
MYSQL_DATABASE: 'npm'
|
||||||
|
MYSQL_USER: 'npm'
|
||||||
|
MYSQL_PASSWORD: 'npmpass'
|
||||||
|
volumes:
|
||||||
|
- mysql_vol:/var/lib/mysql
|
||||||
|
networks:
|
||||||
|
- fulltest
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mysql_vol:
|
9
docker/docker-compose.ci.sqlite.yml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# WARNING: This is a CI docker-compose file used for building and testing of the entire app, it should not be used for production.
|
||||||
|
services:
|
||||||
|
|
||||||
|
fullstack:
|
||||||
|
environment:
|
||||||
|
DB_SQLITE_FILE: '/data/mydb.sqlite'
|
||||||
|
PUID: 1000
|
||||||
|
PGID: 1000
|
||||||
|
DISABLE_IPV6: 'true'
|
@ -1,80 +1,110 @@
|
|||||||
# WARNING: This is a CI docker-compose file used for building and testing of the entire app, it should not be used for production.
|
# WARNING: This is a CI docker-compose file used for building
|
||||||
version: "3"
|
# and testing of the entire app, it should not be used for production.
|
||||||
|
# This is a base compose file, it should be extended with a
|
||||||
|
# docker-compose.ci.*.yml file
|
||||||
services:
|
services:
|
||||||
|
|
||||||
fullstack-mysql:
|
fullstack:
|
||||||
image: ${IMAGE}:ci-${BUILD_NUMBER}
|
image: "${IMAGE}:${BRANCH_LOWER}-ci-${BUILD_NUMBER}"
|
||||||
environment:
|
environment:
|
||||||
NODE_ENV: "development"
|
DEBUG: 'true'
|
||||||
FORCE_COLOR: 1
|
FORCE_COLOR: 1
|
||||||
DB_MYSQL_HOST: "db"
|
|
||||||
DB_MYSQL_PORT: 3306
|
|
||||||
DB_MYSQL_USER: "npm"
|
|
||||||
DB_MYSQL_PASSWORD: "npm"
|
|
||||||
DB_MYSQL_NAME: "npm"
|
|
||||||
volumes:
|
volumes:
|
||||||
- npm_data:/data
|
- 'npm_data_ci:/data'
|
||||||
expose:
|
- 'npm_le_ci:/etc/letsencrypt'
|
||||||
- 81
|
- './dev/letsencrypt.ini:/etc/letsencrypt.ini:ro'
|
||||||
- 80
|
- './dev/resolv.conf:/etc/resolv.conf:ro'
|
||||||
- 443
|
- '/etc/localtime:/etc/localtime:ro'
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "/usr/bin/check-health"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 3s
|
||||||
|
networks:
|
||||||
|
fulltest:
|
||||||
|
aliases:
|
||||||
|
- website1.example.com
|
||||||
|
- website2.example.com
|
||||||
|
- website3.example.com
|
||||||
|
|
||||||
|
stepca:
|
||||||
|
image: jc21/testca
|
||||||
|
volumes:
|
||||||
|
- './dev/resolv.conf:/etc/resolv.conf:ro'
|
||||||
|
- '/etc/localtime:/etc/localtime:ro'
|
||||||
|
networks:
|
||||||
|
fulltest:
|
||||||
|
aliases:
|
||||||
|
- ca.internal
|
||||||
|
|
||||||
|
pdns:
|
||||||
|
image: pschiffe/pdns-mysql
|
||||||
|
volumes:
|
||||||
|
- '/etc/localtime:/etc/localtime:ro'
|
||||||
|
environment:
|
||||||
|
PDNS_master: 'yes'
|
||||||
|
PDNS_api: 'yes'
|
||||||
|
PDNS_api_key: 'npm'
|
||||||
|
PDNS_webserver: 'yes'
|
||||||
|
PDNS_webserver_address: '0.0.0.0'
|
||||||
|
PDNS_webserver_password: 'npm'
|
||||||
|
PDNS_webserver-allow-from: '127.0.0.0/8,192.0.0.0/8,10.0.0.0/8,172.0.0.0/8'
|
||||||
|
PDNS_version_string: 'anonymous'
|
||||||
|
PDNS_default_ttl: 1500
|
||||||
|
PDNS_allow_axfr_ips: '127.0.0.0/8,192.0.0.0/8,10.0.0.0/8,172.0.0.0/8'
|
||||||
|
PDNS_gmysql_host: pdns-db
|
||||||
|
PDNS_gmysql_port: 3306
|
||||||
|
PDNS_gmysql_user: pdns
|
||||||
|
PDNS_gmysql_password: pdns
|
||||||
|
PDNS_gmysql_dbname: pdns
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- pdns-db
|
||||||
healthcheck:
|
networks:
|
||||||
test: ["CMD", "/bin/check-health"]
|
fulltest:
|
||||||
interval: 10s
|
aliases:
|
||||||
timeout: 3s
|
- ns1.pdns
|
||||||
|
- ns2.pdns
|
||||||
|
|
||||||
fullstack-sqlite:
|
pdns-db:
|
||||||
image: ${IMAGE}:ci-${BUILD_NUMBER}
|
image: mariadb
|
||||||
environment:
|
environment:
|
||||||
NODE_ENV: "development"
|
MYSQL_ROOT_PASSWORD: 'pdns'
|
||||||
FORCE_COLOR: 1
|
MYSQL_DATABASE: 'pdns'
|
||||||
DB_SQLITE_FILE: "/data/database.sqlite"
|
MYSQL_USER: 'pdns'
|
||||||
|
MYSQL_PASSWORD: 'pdns'
|
||||||
volumes:
|
volumes:
|
||||||
- npm_data:/data
|
- 'pdns_mysql_vol:/var/lib/mysql'
|
||||||
expose:
|
- '/etc/localtime:/etc/localtime:ro'
|
||||||
- 81
|
- './dev/pdns-db.sql:/docker-entrypoint-initdb.d/01_init.sql:ro'
|
||||||
- 80
|
networks:
|
||||||
- 443
|
- fulltest
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "/bin/check-health"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 3s
|
|
||||||
|
|
||||||
db:
|
dnsrouter:
|
||||||
image: jc21/mariadb-aria
|
image: jc21/dnsrouter
|
||||||
environment:
|
|
||||||
MYSQL_ROOT_PASSWORD: "npm"
|
|
||||||
MYSQL_DATABASE: "npm"
|
|
||||||
MYSQL_USER: "npm"
|
|
||||||
MYSQL_PASSWORD: "npm"
|
|
||||||
volumes:
|
volumes:
|
||||||
- db_data:/var/lib/mysql
|
- ./dev/dnsrouter-config.json.tmp:/dnsrouter-config.json:ro
|
||||||
|
networks:
|
||||||
|
- fulltest
|
||||||
|
|
||||||
cypress-mysql:
|
cypress:
|
||||||
image: ${IMAGE}-cypress:ci-${BUILD_NUMBER}
|
image: "${IMAGE}-cypress:ci-${BUILD_NUMBER}"
|
||||||
build:
|
build:
|
||||||
context: ../test/
|
context: ../
|
||||||
dockerfile: cypress/Dockerfile
|
dockerfile: test/cypress/Dockerfile
|
||||||
environment:
|
environment:
|
||||||
CYPRESS_baseUrl: "http://fullstack-mysql:81"
|
CYPRESS_baseUrl: 'http://fullstack:81'
|
||||||
volumes:
|
volumes:
|
||||||
- cypress-logs:/results
|
- 'cypress_logs:/results'
|
||||||
command: cypress run --browser chrome --config-file=${CYPRESS_CONFIG:-cypress/config/ci.json}
|
- './dev/resolv.conf:/etc/resolv.conf:ro'
|
||||||
|
command: cypress run --browser chrome --config-file=cypress/config/ci.js
|
||||||
cypress-sqlite:
|
networks:
|
||||||
image: ${IMAGE}-cypress:ci-${BUILD_NUMBER}
|
- fulltest
|
||||||
build:
|
|
||||||
context: ../test/
|
|
||||||
dockerfile: cypress/Dockerfile
|
|
||||||
environment:
|
|
||||||
CYPRESS_baseUrl: "http://fullstack-sqlite:81"
|
|
||||||
volumes:
|
|
||||||
- cypress-logs:/results
|
|
||||||
command: cypress run --browser chrome --config-file=${CYPRESS_CONFIG:-cypress/config/ci.json}
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
cypress-logs:
|
cypress_logs:
|
||||||
npm_data:
|
npm_data_ci:
|
||||||
db_data:
|
npm_le_ci:
|
||||||
|
pdns_mysql_vol:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
fulltest:
|
||||||
|
name: "npm-${BRANCH_LOWER}-ci-${BUILD_NUMBER}"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# WARNING: This is a DEVELOPMENT docker-compose file, it should not be used for production.
|
# WARNING: This is a DEVELOPMENT docker-compose file, it should not be used for production.
|
||||||
version: "3.5"
|
|
||||||
services:
|
services:
|
||||||
|
|
||||||
npm:
|
npm:
|
||||||
image: nginxproxymanager:dev
|
image: nginxproxymanager:dev
|
||||||
container_name: npm_core
|
container_name: npm_core
|
||||||
@ -14,14 +14,19 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- nginx_proxy_manager
|
- nginx_proxy_manager
|
||||||
environment:
|
environment:
|
||||||
NODE_ENV: "development"
|
PUID: 1000
|
||||||
|
PGID: 1000
|
||||||
FORCE_COLOR: 1
|
FORCE_COLOR: 1
|
||||||
DEVELOPMENT: "true"
|
# specifically for dev:
|
||||||
DB_MYSQL_HOST: "db"
|
DEBUG: 'true'
|
||||||
DB_MYSQL_PORT: 3306
|
DEVELOPMENT: 'true'
|
||||||
DB_MYSQL_USER: "npm"
|
LE_STAGING: 'true'
|
||||||
DB_MYSQL_PASSWORD: "npm"
|
# db:
|
||||||
DB_MYSQL_NAME: "npm"
|
DB_MYSQL_HOST: 'db'
|
||||||
|
DB_MYSQL_PORT: '3306'
|
||||||
|
DB_MYSQL_USER: 'npm'
|
||||||
|
DB_MYSQL_PASSWORD: 'npm'
|
||||||
|
DB_MYSQL_NAME: 'npm'
|
||||||
# DB_SQLITE_FILE: "/data/database.sqlite"
|
# DB_SQLITE_FILE: "/data/database.sqlite"
|
||||||
# DISABLE_IPV6: "true"
|
# DISABLE_IPV6: "true"
|
||||||
volumes:
|
volumes:
|
||||||
@ -42,10 +47,10 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- nginx_proxy_manager
|
- nginx_proxy_manager
|
||||||
environment:
|
environment:
|
||||||
MYSQL_ROOT_PASSWORD: "npm"
|
MYSQL_ROOT_PASSWORD: 'npm'
|
||||||
MYSQL_DATABASE: "npm"
|
MYSQL_DATABASE: 'npm'
|
||||||
MYSQL_USER: "npm"
|
MYSQL_USER: 'npm'
|
||||||
MYSQL_PASSWORD: "npm"
|
MYSQL_PASSWORD: 'npm'
|
||||||
volumes:
|
volumes:
|
||||||
- db_data:/var/lib/mysql
|
- db_data:/var/lib/mysql
|
||||||
|
|
||||||
|
@ -1,46 +0,0 @@
|
|||||||
#!/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.
|
|
||||||
|
|
||||||
# Lowercase
|
|
||||||
DISABLE_IPV6=$(echo "${DISABLE_IPV6:-}" | tr '[:upper:]' '[:lower:]')
|
|
||||||
|
|
||||||
CYAN='\E[1;36m'
|
|
||||||
BLUE='\E[1;34m'
|
|
||||||
YELLOW='\E[1;33m'
|
|
||||||
RED='\E[1;31m'
|
|
||||||
RESET='\E[0m'
|
|
||||||
|
|
||||||
FOLDER=$1
|
|
||||||
if [ "$FOLDER" == "" ]; then
|
|
||||||
echo -e "${RED}❯ $0 requires a absolute folder path as the first argument!${RESET}"
|
|
||||||
echo -e "${YELLOW} ie: $0 /data/nginx${RESET}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
FILES=$(find "$FOLDER" -type f -name "*.conf")
|
|
||||||
if [ "$DISABLE_IPV6" == "true" ] || [ "$DISABLE_IPV6" == "on" ] || [ "$DISABLE_IPV6" == "1" ] || [ "$DISABLE_IPV6" == "yes" ]; then
|
|
||||||
# IPV6 is disabled
|
|
||||||
echo "Disabling IPV6 in hosts"
|
|
||||||
echo -e "${BLUE}❯ ${CYAN}Disabling IPV6 in hosts: ${YELLOW}${FOLDER}${RESET}"
|
|
||||||
|
|
||||||
# Iterate over configs and run the regex
|
|
||||||
for FILE in $FILES
|
|
||||||
do
|
|
||||||
echo -e " ${BLUE}❯ ${YELLOW}${FILE}${RESET}"
|
|
||||||
sed -E -i 's/^([^#]*)listen \[::\]/\1#listen [::]/g' "$FILE"
|
|
||||||
done
|
|
||||||
|
|
||||||
else
|
|
||||||
# IPV6 is enabled
|
|
||||||
echo -e "${BLUE}❯ ${CYAN}Enabling IPV6 in hosts: ${YELLOW}${FOLDER}${RESET}"
|
|
||||||
|
|
||||||
# Iterate over configs and run the regex
|
|
||||||
for FILE in $FILES
|
|
||||||
do
|
|
||||||
echo -e " ${BLUE}❯ ${YELLOW}${FILE}${RESET}"
|
|
||||||
sed -E -i 's/^(\s*)#listen \[::\]/\1listen [::]/g' "$FILE"
|
|
||||||
done
|
|
||||||
|
|
||||||
fi
|
|
2
docker/rootfs/etc/cont-finish.d/.gitignore
vendored
@ -1,2 +0,0 @@
|
|||||||
*
|
|
||||||
!.gitignore
|
|
3
docker/rootfs/etc/cont-init.d/.gitignore
vendored
@ -1,3 +0,0 @@
|
|||||||
*
|
|
||||||
!.gitignore
|
|
||||||
!*.sh
|
|
@ -1,7 +0,0 @@
|
|||||||
#!/usr/bin/with-contenv bash
|
|
||||||
set -e
|
|
||||||
|
|
||||||
mkdir -p /data/logs
|
|
||||||
echo "Changing ownership of /data/logs to $(id -u):$(id -g)"
|
|
||||||
chown -R "$(id -u):$(id -g)" /data/logs
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
|||||||
#!/usr/bin/with-contenv bash
|
|
||||||
# ref: https://github.com/linuxserver/docker-baseimage-alpine/blob/master/root/etc/cont-init.d/01-envfile
|
|
||||||
|
|
||||||
# in s6, environmental variables are written as text files for s6 to monitor
|
|
||||||
# search through full-path filenames for files ending in "__FILE"
|
|
||||||
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})
|
|
||||||
# SECRETFILE=${FILENAME}
|
|
||||||
# echo "[secret-init] Set SECRETFILE to ${SECRETFILE}" # DEBUG - rm for prod!
|
|
||||||
|
|
||||||
# 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 "[secret-init] Success! ${STRIPFILE##*/} set from ${FILENAME##*/}"
|
|
||||||
|
|
||||||
else
|
|
||||||
echo "[secret-init] cannot find secret in ${FILENAME}"
|
|
||||||
fi
|
|
||||||
done
|
|
2
docker/rootfs/etc/fix-attrs.d/.gitignore
vendored
@ -1,2 +0,0 @@
|
|||||||
*
|
|
||||||
!.gitignore
|
|
@ -1,5 +1,6 @@
|
|||||||
/data/logs/*_access.log /data/logs/*/access.log {
|
/data/logs/*_access.log /data/logs/*/access.log {
|
||||||
create 0644 root root
|
su npm npm
|
||||||
|
create 0644
|
||||||
weekly
|
weekly
|
||||||
rotate 4
|
rotate 4
|
||||||
missingok
|
missingok
|
||||||
@ -12,7 +13,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/data/logs/*_error.log /data/logs/*/error.log {
|
/data/logs/*_error.log /data/logs/*/error.log {
|
||||||
create 0644 root root
|
su npm npm
|
||||||
|
create 0644
|
||||||
weekly
|
weekly
|
||||||
rotate 10
|
rotate 10
|
||||||
missingok
|
missingok
|
||||||
|
@ -32,6 +32,7 @@ server {
|
|||||||
server_name localhost;
|
server_name localhost;
|
||||||
access_log /data/logs/fallback_access.log standard;
|
access_log /data/logs/fallback_access.log standard;
|
||||||
error_log /dev/null crit;
|
error_log /dev/null crit;
|
||||||
|
include conf.d/include/ssl-ciphers.conf;
|
||||||
ssl_reject_handshake on;
|
ssl_reject_handshake on;
|
||||||
|
|
||||||
return 444;
|
return 444;
|
||||||
|
@ -1,3 +1,10 @@
|
|||||||
|
set $test "";
|
||||||
if ($scheme = "http") {
|
if ($scheme = "http") {
|
||||||
|
set $test "H";
|
||||||
|
}
|
||||||
|
if ($request_uri = /.well-known/acme-challenge/test-challenge) {
|
||||||
|
set $test "${test}T";
|
||||||
|
}
|
||||||
|
if ($test = H) {
|
||||||
return 301 https://$host$request_uri;
|
return 301 https://$host$request_uri;
|
||||||
}
|
}
|
||||||
|
4
docker/rootfs/etc/nginx/conf.d/include/log.conf
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
log_format proxy '[$time_local] $upstream_cache_status $upstream_status $status - $request_method $scheme $host "$request_uri" [Client $remote_addr] [Length $body_bytes_sent] [Gzip $gzip_ratio] [Sent-to $server] "$http_user_agent" "$http_referer"';
|
||||||
|
log_format standard '[$time_local] $status - $request_method $scheme $host "$request_uri" [Client $remote_addr] [Length $body_bytes_sent] [Gzip $gzip_ratio] "$http_user_agent" "$http_referer"';
|
||||||
|
|
||||||
|
access_log /data/logs/fallback_access.log proxy;
|
@ -1,7 +1,7 @@
|
|||||||
# run nginx in foreground
|
# run nginx in foreground
|
||||||
daemon off;
|
daemon off;
|
||||||
|
pid /run/nginx/nginx.pid;
|
||||||
user root;
|
user npm;
|
||||||
|
|
||||||
# Set number of worker processes automatically based on number of CPU cores.
|
# Set number of worker processes automatically based on number of CPU cores.
|
||||||
worker_processes auto;
|
worker_processes auto;
|
||||||
@ -14,6 +14,9 @@ error_log /data/logs/fallback_error.log warn;
|
|||||||
# Includes files with directives to load dynamic modules.
|
# Includes files with directives to load dynamic modules.
|
||||||
include /etc/nginx/modules/*.conf;
|
include /etc/nginx/modules/*.conf;
|
||||||
|
|
||||||
|
# Custom
|
||||||
|
include /data/nginx/custom/root_top[.]conf;
|
||||||
|
|
||||||
events {
|
events {
|
||||||
include /data/nginx/custom/events[.]conf;
|
include /data/nginx/custom/events[.]conf;
|
||||||
}
|
}
|
||||||
@ -43,10 +46,8 @@ http {
|
|||||||
proxy_cache_path /var/lib/nginx/cache/public levels=1:2 keys_zone=public-cache:30m max_size=192m;
|
proxy_cache_path /var/lib/nginx/cache/public levels=1:2 keys_zone=public-cache:30m max_size=192m;
|
||||||
proxy_cache_path /var/lib/nginx/cache/private levels=1:2 keys_zone=private-cache:5m max_size=1024m;
|
proxy_cache_path /var/lib/nginx/cache/private levels=1:2 keys_zone=private-cache:5m max_size=1024m;
|
||||||
|
|
||||||
log_format proxy '[$time_local] $upstream_cache_status $upstream_status $status - $request_method $scheme $host "$request_uri" [Client $remote_addr] [Length $body_bytes_sent] [Gzip $gzip_ratio] [Sent-to $server] "$http_user_agent" "$http_referer"';
|
# Log format and fallback log file
|
||||||
log_format standard '[$time_local] $status - $request_method $scheme $host "$request_uri" [Client $remote_addr] [Length $body_bytes_sent] [Gzip $gzip_ratio] "$http_user_agent" "$http_referer"';
|
include /etc/nginx/conf.d/include/log.conf;
|
||||||
|
|
||||||
access_log /data/logs/fallback_access.log proxy;
|
|
||||||
|
|
||||||
# Dynamically generated resolvers file
|
# Dynamically generated resolvers file
|
||||||
include /etc/nginx/conf.d/include/resolvers.conf;
|
include /etc/nginx/conf.d/include/resolvers.conf;
|
||||||
@ -57,7 +58,7 @@ http {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Real IP Determination
|
# Real IP Determination
|
||||||
|
|
||||||
# Local subnets:
|
# Local subnets:
|
||||||
set_real_ip_from 10.0.0.0/8;
|
set_real_ip_from 10.0.0.0/8;
|
||||||
set_real_ip_from 172.16.0.0/12; # Includes Docker subnet
|
set_real_ip_from 172.16.0.0/12; # Includes Docker subnet
|
||||||
|
@ -3,17 +3,19 @@
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
echo "❯ Starting backend ..."
|
. /usr/bin/common.sh
|
||||||
if [ "$DEVELOPMENT" == "true" ]; then
|
|
||||||
cd /app || exit 1
|
cd /app || exit 1
|
||||||
# If yarn install fails: add --verbose --network-concurrency 1
|
|
||||||
yarn install
|
log_info 'Starting backend ...'
|
||||||
node --max_old_space_size=250 --abort_on_uncaught_exception node_modules/nodemon/bin/nodemon.js
|
|
||||||
|
if [ "${DEVELOPMENT:-}" = 'true' ]; then
|
||||||
|
s6-setuidgid "$PUID:$PGID" yarn install
|
||||||
|
exec s6-setuidgid "$PUID:$PGID" bash -c "export HOME=$NPMHOME;node --max_old_space_size=250 --abort_on_uncaught_exception node_modules/nodemon/bin/nodemon.js"
|
||||||
else
|
else
|
||||||
cd /app || exit 1
|
|
||||||
while :
|
while :
|
||||||
do
|
do
|
||||||
node --abort_on_uncaught_exception --max_old_space_size=250 index.js
|
s6-setuidgid "$PUID:$PGID" bash -c "export HOME=$NPMHOME;node --abort_on_uncaught_exception --max_old_space_size=250 index.js"
|
||||||
sleep 1
|
sleep 1
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
@ -5,11 +5,17 @@ set -e
|
|||||||
|
|
||||||
# This service is DEVELOPMENT only.
|
# This service is DEVELOPMENT only.
|
||||||
|
|
||||||
if [ "$DEVELOPMENT" == "true" ]; then
|
if [ "$DEVELOPMENT" = 'true' ]; then
|
||||||
|
. /usr/bin/common.sh
|
||||||
cd /app/frontend || exit 1
|
cd /app/frontend || exit 1
|
||||||
# If yarn install fails: add --verbose --network-concurrency 1
|
HOME=$NPMHOME
|
||||||
yarn install
|
export HOME
|
||||||
yarn watch
|
mkdir -p /app/frontend/dist
|
||||||
|
chown -R "$PUID:$PGID" /app/frontend/dist
|
||||||
|
|
||||||
|
log_info 'Starting frontend ...'
|
||||||
|
s6-setuidgid "$PUID:$PGID" yarn install
|
||||||
|
exec s6-setuidgid "$PUID:$PGID" yarn watch
|
||||||
else
|
else
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
@ -3,5 +3,7 @@
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
echo "❯ Starting nginx ..."
|
. /usr/bin/common.sh
|
||||||
exec nginx
|
|
||||||
|
log_info 'Starting nginx ...'
|
||||||
|
exec s6-setuidgid "$PUID:$PGID" nginx
|
||||||
|
22
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/00-all.sh
Executable file
@ -0,0 +1,22 @@
|
|||||||
|
#!/command/with-contenv bash
|
||||||
|
# shellcheck shell=bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
. /usr/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
|
||||||
|
|
||||||
|
if [ "$DEBUG" = "true" ]; then
|
||||||
|
set -x
|
||||||
|
fi
|
||||||
|
|
||||||
|
. /etc/s6-overlay/s6-rc.d/prepare/10-usergroup.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
|
40
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/10-usergroup.sh
Executable file
@ -0,0 +1,40 @@
|
|||||||
|
#!/command/with-contenv bash
|
||||||
|
# shellcheck shell=bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
log_info "Configuring $NPMUSER user ..."
|
||||||
|
|
||||||
|
if id -u "$NPMUSER" 2>/dev/null; then
|
||||||
|
# user already exists
|
||||||
|
usermod -u "$PUID" "$NPMUSER"
|
||||||
|
else
|
||||||
|
# Add user
|
||||||
|
useradd -o -u "$PUID" -U -d "$NPMHOME" -s /bin/false "$NPMUSER"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Configuring $NPMGROUP group ..."
|
||||||
|
if [ "$(get_group_id "$NPMGROUP")" = '' ]; then
|
||||||
|
# Add group. This will not set the id properly if it's already taken
|
||||||
|
groupadd -f -g "$PGID" "$NPMGROUP"
|
||||||
|
else
|
||||||
|
groupmod -o -g "$PGID" "$NPMGROUP"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set the group ID and check it
|
||||||
|
groupmod -o -g "$PGID" "$NPMGROUP"
|
||||||
|
if [ "$(get_group_id "$NPMGROUP")" != "$PGID" ]; then
|
||||||
|
echo "ERROR: Unable to set group id properly"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set the group against the user and check it
|
||||||
|
usermod -G "$PGID" "$NPMGROUP"
|
||||||
|
if [ "$(id -g "$NPMUSER")" != "$PGID" ] ; then
|
||||||
|
echo "ERROR: Unable to set group against the user properly"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Home for user
|
||||||
|
mkdir -p "$NPMHOME"
|
||||||
|
chown -R "$PUID:$PGID" "$NPMHOME"
|
41
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/20-paths.sh
Executable 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
|
28
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/30-ownership.sh
Executable file
@ -0,0 +1,28 @@
|
|||||||
|
#!/command/with-contenv bash
|
||||||
|
# shellcheck shell=bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
log_info 'Setting ownership ...'
|
||||||
|
|
||||||
|
# root
|
||||||
|
chown root /tmp/nginx
|
||||||
|
|
||||||
|
# npm user and group
|
||||||
|
chown -R "$PUID:$PGID" /data
|
||||||
|
chown -R "$PUID:$PGID" /etc/letsencrypt
|
||||||
|
chown -R "$PUID:$PGID" /run/nginx
|
||||||
|
chown -R "$PUID:$PGID" /tmp/nginx
|
||||||
|
chown -R "$PUID:$PGID" /var/cache/nginx
|
||||||
|
chown -R "$PUID:$PGID" /var/lib/logrotate
|
||||||
|
chown -R "$PUID:$PGID" /var/lib/nginx
|
||||||
|
chown -R "$PUID:$PGID" /var/log/nginx
|
||||||
|
|
||||||
|
# Don't chown entire /etc/nginx folder as this causes crashes on some systems
|
||||||
|
chown -R "$PUID:$PGID" /etc/nginx/nginx
|
||||||
|
chown -R "$PUID:$PGID" /etc/nginx/nginx.conf
|
||||||
|
chown -R "$PUID:$PGID" /etc/nginx/conf.d
|
||||||
|
|
||||||
|
# Prevents errors when installing python certbot plugins when non-root
|
||||||
|
chown "$PUID:$PGID" /opt/certbot /opt/certbot/bin
|
||||||
|
find /opt/certbot/lib/python*/site-packages -not -user "$PUID" -execdir chown "$PUID:$PGID" {} \+
|
17
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/40-dynamic.sh
Executable 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
|
39
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/50-ipv6.sh
Executable file
@ -0,0 +1,39 @@
|
|||||||
|
#!/command/with-contenv bash
|
||||||
|
# shellcheck shell=bash
|
||||||
|
|
||||||
|
# This command reads the `DISABLE_IPV6` env var and will either enable
|
||||||
|
# or disable ipv6 in all nginx configs based on this setting.
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
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}"
|
||||||
|
echo "$(sed -E "$SED_REGEX" "$FILE")" > $FILE
|
||||||
|
done
|
||||||
|
|
||||||
|
# ensure the files are still owned by the npm user
|
||||||
|
chown -R "$PUID:$PGID" "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
process_folder /etc/nginx/conf.d
|
||||||
|
process_folder /data/nginx
|
30
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/60-secrets.sh
Executable 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
|
18
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/90-banner.sh
Executable file
@ -0,0 +1,18 @@
|
|||||||
|
#!/command/with-contenv bash
|
||||||
|
# shellcheck shell=bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
set +x
|
||||||
|
|
||||||
|
echo "
|
||||||
|
-------------------------------------
|
||||||
|
_ _ ____ __ __
|
||||||
|
| \ | | _ \| \/ |
|
||||||
|
| \| | |_) | |\/| |
|
||||||
|
| |\ | __/| | | |
|
||||||
|
|_| \_|_| |_| |_|
|
||||||
|
-------------------------------------
|
||||||
|
User: $NPMUSER PUID:$PUID ID:$(id -u "$NPMUSER") GROUP:$(id -g "$NPMUSER")
|
||||||
|
Group: $NPMGROUP PGID:$PGID ID:$(get_group_id "$NPMGROUP")
|
||||||
|
-------------------------------------
|
||||||
|
"
|
@ -1,63 +0,0 @@
|
|||||||
#!/command/with-contenv bash
|
|
||||||
# shellcheck shell=bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
DATA_PATH=/data
|
|
||||||
|
|
||||||
# Ensure /data is mounted
|
|
||||||
if [ ! -d "$DATA_PATH" ]; then
|
|
||||||
echo '--------------------------------------'
|
|
||||||
echo "ERROR: $DATA_PATH is not mounted! Check your docker configuration."
|
|
||||||
echo '--------------------------------------'
|
|
||||||
/run/s6/basedir/bin/halt
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "❯ Checking folder structure ..."
|
|
||||||
|
|
||||||
# Create required folders
|
|
||||||
mkdir -p /tmp/nginx/body \
|
|
||||||
/run/nginx \
|
|
||||||
/var/log/nginx \
|
|
||||||
/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 \
|
|
||||||
/var/lib/nginx/cache/public \
|
|
||||||
/var/lib/nginx/cache/private \
|
|
||||||
/var/cache/nginx/proxy_temp \
|
|
||||||
/data/letsencrypt-acme-challenge
|
|
||||||
|
|
||||||
touch /var/log/nginx/error.log && chmod 777 /var/log/nginx/error.log && chmod -R 777 /var/cache/nginx
|
|
||||||
chown root /tmp/nginx
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# Handle IPV6 settings
|
|
||||||
/bin/handle-ipv6-setting /etc/nginx/conf.d
|
|
||||||
/bin/handle-ipv6-setting /data/nginx
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "-------------------------------------
|
|
||||||
_ _ ____ __ __
|
|
||||||
| \ | | _ \| \/ |
|
|
||||||
| \| | |_) | |\/| |
|
|
||||||
| |\ | __/| | | |
|
|
||||||
|_| \_|_| |_| |_|
|
|
||||||
-------------------------------------
|
|
||||||
"
|
|
@ -1,2 +1,2 @@
|
|||||||
# shellcheck shell=bash
|
# shellcheck shell=bash
|
||||||
/etc/s6-overlay/s6-rc.d/prepare/script.sh
|
/etc/s6-overlay/s6-rc.d/prepare/00-all.sh
|
||||||
|
58
docker/rootfs/usr/bin/common.sh
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
#!/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 changing the username and group name below,
|
||||||
|
# ensure all references to this user is also changed.
|
||||||
|
# See docker/rootfs/etc/logrotate.d/nginx-proxy-manager
|
||||||
|
# and docker/rootfs/etc/nginx/nginx.conf
|
||||||
|
NPMUSER=npm
|
||||||
|
NPMGROUP=npm
|
||||||
|
NPMHOME=/tmp/npmuserhome
|
||||||
|
export NPMUSER NPMGROUP NPMHOME
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
# param $1: group_name
|
||||||
|
get_group_id () {
|
||||||
|
if [ "${1:-}" != '' ]; then
|
||||||
|
getent group "$1" | cut -d: -f3
|
||||||
|
fi
|
||||||
|
}
|
@ -8,8 +8,8 @@ BLUE='\E[1;34m'
|
|||||||
GREEN='\E[1;32m'
|
GREEN='\E[1;32m'
|
||||||
RESET='\E[0m'
|
RESET='\E[0m'
|
||||||
|
|
||||||
S6_OVERLAY_VERSION=3.1.4.1
|
S6_OVERLAY_VERSION=3.1.5.0
|
||||||
TARGETPLATFORM=${1:unspecified}
|
TARGETPLATFORM=${1:-linux/amd64}
|
||||||
|
|
||||||
# Determine the correct binary file for the architecture given
|
# Determine the correct binary file for the architecture given
|
||||||
case $TARGETPLATFORM in
|
case $TARGETPLATFORM in
|
||||||
|
13
docs/.gitignore
vendored
@ -1,3 +1,14 @@
|
|||||||
.vuepress/dist
|
dist
|
||||||
node_modules
|
node_modules
|
||||||
ts
|
ts
|
||||||
|
.temp
|
||||||
|
.cache
|
||||||
|
.vitepress/cache
|
||||||
|
|
||||||
|
.yarn/*
|
||||||
|
!.yarn/releases
|
||||||
|
!.yarn/plugins
|
||||||
|
!.yarn/sdks
|
||||||
|
!.yarn/versions
|
||||||
|
*.gz
|
||||||
|
*.tgz
|
||||||
|
61
docs/.vitepress/config.mts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { defineConfig, type DefaultTheme } from 'vitepress';
|
||||||
|
|
||||||
|
// https://vitepress.dev/reference/site-config
|
||||||
|
export default defineConfig({
|
||||||
|
title: "Nginx Proxy Manager",
|
||||||
|
description: "Expose your services easily and securely",
|
||||||
|
head: [
|
||||||
|
["link", { rel: "icon", href: "/icon.png" }],
|
||||||
|
["meta", { name: "description", content: "Docker container and built in Web Application for managing Nginx proxy hosts with a simple, powerful interface, providing free SSL support via Let's Encrypt" }],
|
||||||
|
["meta", { property: "og:title", content: "Nginx Proxy Manager" }],
|
||||||
|
["meta", { property: "og:description", content: "Docker container and built in Web Application for managing Nginx proxy hosts with a simple, powerful interface, providing free SSL support via Let's Encrypt"}],
|
||||||
|
["meta", { property: "og:type", content: "website" }],
|
||||||
|
["meta", { property: "og:url", content: "https://nginxproxymanager.com/" }],
|
||||||
|
["meta", { property: "og:image", content: "https://nginxproxymanager.com/icon.png" }],
|
||||||
|
["meta", { name: "twitter:card", content: "summary"}],
|
||||||
|
["meta", { name: "twitter:title", content: "Nginx Proxy Manager"}],
|
||||||
|
["meta", { name: "twitter:description", content: "Docker container and built in Web Application for managing Nginx proxy hosts with a simple, powerful interface, providing free SSL support via Let's Encrypt"}],
|
||||||
|
["meta", { name: "twitter:image", content: "https://nginxproxymanager.com/icon.png"}],
|
||||||
|
["meta", { name: "twitter:alt", content: "Nginx Proxy Manager"}],
|
||||||
|
// GA
|
||||||
|
['script', { async: 'true', src: 'https://www.googletagmanager.com/gtag/js?id=G-TXT8F5WY5B'}],
|
||||||
|
['script', {}, "window.dataLayer = window.dataLayer || [];\nfunction gtag(){dataLayer.push(arguments);}\ngtag('js', new Date());\ngtag('config', 'G-TXT8F5WY5B');"],
|
||||||
|
],
|
||||||
|
sitemap: {
|
||||||
|
hostname: 'https://nginxproxymanager.com'
|
||||||
|
},
|
||||||
|
metaChunk: true,
|
||||||
|
srcDir: './src',
|
||||||
|
outDir: './dist',
|
||||||
|
themeConfig: {
|
||||||
|
// https://vitepress.dev/reference/default-theme-config
|
||||||
|
logo: { src: '/logo.svg', width: 24, height: 24 },
|
||||||
|
nav: [
|
||||||
|
{ text: 'Setup', link: '/setup/' },
|
||||||
|
],
|
||||||
|
sidebar: [
|
||||||
|
{
|
||||||
|
items: [
|
||||||
|
// { text: 'Home', link: '/' },
|
||||||
|
{ text: 'Guide', link: '/guide/' },
|
||||||
|
{ text: 'Screenshots', link: '/screenshots/' },
|
||||||
|
{ text: 'Setup Instructions', link: '/setup/' },
|
||||||
|
{ text: 'Advanced Configuration', link: '/advanced-config/' },
|
||||||
|
{ text: 'Upgrading', link: '/upgrading/' },
|
||||||
|
{ text: 'Frequently Asked Questions', link: '/faq/' },
|
||||||
|
{ text: 'Third Party', link: '/third-party/' },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
socialLinks: [
|
||||||
|
{ icon: 'github', link: 'https://github.com/NginxProxyManager/nginx-proxy-manager' }
|
||||||
|
],
|
||||||
|
search: {
|
||||||
|
provider: 'local'
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
message: 'Released under the MIT License.',
|
||||||
|
copyright: 'Copyright © 2016-present jc21.com'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
27
docs/.vitepress/theme/custom.css
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
:root {
|
||||||
|
--vp-home-hero-name-color: transparent;
|
||||||
|
--vp-home-hero-name-background: -webkit-linear-gradient(120deg, #f15833 30%, #FAA42F);
|
||||||
|
|
||||||
|
--vp-home-hero-image-background-image: linear-gradient(-45deg, #aaaaaa 50%, #777777 50%);
|
||||||
|
--vp-home-hero-image-filter: blur(44px);
|
||||||
|
|
||||||
|
--vp-c-brand-1: #f15833;
|
||||||
|
--vp-c-brand-2: #FAA42F;
|
||||||
|
--vp-c-brand-3: #f15833;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
:root {
|
||||||
|
--vp-home-hero-image-filter: blur(56px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 960px) {
|
||||||
|
:root {
|
||||||
|
--vp-home-hero-image-filter: blur(68px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.inline-img img {
|
||||||
|
display: inline;
|
||||||
|
}
|
4
docs/.vitepress/theme/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import DefaultTheme from 'vitepress/theme'
|
||||||
|
import './custom.css'
|
||||||
|
|
||||||
|
export default DefaultTheme
|
@ -1,82 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
locales: {
|
|
||||||
"/": {
|
|
||||||
lang: "en-US",
|
|
||||||
title: "Nginx Proxy Manager",
|
|
||||||
description: "Expose your services easily and securely"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
head: [
|
|
||||||
["link", { rel: "icon", href: "/icon.png" }],
|
|
||||||
["meta", { name: "description", content: "Docker container and built in Web Application for managing Nginx proxy hosts with a simple, powerful interface, providing free SSL support via Let's Encrypt" }],
|
|
||||||
["meta", { property: "og:title", content: "Nginx Proxy Manager" }],
|
|
||||||
["meta", { property: "og:description", content: "Docker container and built in Web Application for managing Nginx proxy hosts with a simple, powerful interface, providing free SSL support via Let's Encrypt"}],
|
|
||||||
["meta", { property: "og:type", content: "website" }],
|
|
||||||
["meta", { property: "og:url", content: "https://nginxproxymanager.com/" }],
|
|
||||||
["meta", { property: "og:image", content: "https://nginxproxymanager.com/icon.png" }],
|
|
||||||
["meta", { name: "twitter:card", content: "summary"}],
|
|
||||||
["meta", { name: "twitter:title", content: "Nginx Proxy Manager"}],
|
|
||||||
["meta", { name: "twitter:description", content: "Docker container and built in Web Application for managing Nginx proxy hosts with a simple, powerful interface, providing free SSL support via Let's Encrypt"}],
|
|
||||||
["meta", { name: "twitter:image", content: "https://nginxproxymanager.com/icon.png"}],
|
|
||||||
["meta", { name: "twitter:alt", content: "Nginx Proxy Manager"}],
|
|
||||||
],
|
|
||||||
themeConfig: {
|
|
||||||
logo: "/icon.png",
|
|
||||||
// the GitHub repo path
|
|
||||||
repo: "jc21/nginx-proxy-manager",
|
|
||||||
// the label linking to the repo
|
|
||||||
repoLabel: "GitHub",
|
|
||||||
// if your docs are not at the root of the repo:
|
|
||||||
docsDir: "docs",
|
|
||||||
// defaults to false, set to true to enable
|
|
||||||
editLinks: true,
|
|
||||||
locales: {
|
|
||||||
"/": {
|
|
||||||
// text for the language dropdown
|
|
||||||
selectText: "Languages",
|
|
||||||
// label for this locale in the language dropdown
|
|
||||||
label: "English",
|
|
||||||
// Custom text for edit link. Defaults to "Edit this page"
|
|
||||||
editLinkText: "Edit this page on GitHub",
|
|
||||||
// Custom navbar values
|
|
||||||
nav: [{ text: "Setup", link: "/setup/" }],
|
|
||||||
// Custom sidebar values
|
|
||||||
sidebar: [
|
|
||||||
"/",
|
|
||||||
["/guide/", "Guide"],
|
|
||||||
["/screenshots/", "Screenshots"],
|
|
||||||
["/setup/", "Setup Instructions"],
|
|
||||||
["/advanced-config/", "Advanced Configuration"],
|
|
||||||
["/upgrading/", "Upgrading"],
|
|
||||||
["/faq/", "Frequently Asked Questions"],
|
|
||||||
["/third-party/", "Third Party"]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
[
|
|
||||||
"@vuepress/google-analytics",
|
|
||||||
{
|
|
||||||
ga: "UA-99675467-4"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"sitemap",
|
|
||||||
{
|
|
||||||
hostname: "https://nginxproxymanager.com"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'vuepress-plugin-zooming',
|
|
||||||
{
|
|
||||||
selector: '.zooming',
|
|
||||||
delay: 1000,
|
|
||||||
options: {
|
|
||||||
bgColor: 'black',
|
|
||||||
zIndex: 10000,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
]
|
|
||||||
};
|
|
Before Width: | Height: | Size: 57 KiB |
@ -1,23 +0,0 @@
|
|||||||
.home .hero img
|
|
||||||
max-width: 500px !important
|
|
||||||
min-width: 300px
|
|
||||||
width: 100%
|
|
||||||
|
|
||||||
.center
|
|
||||||
margin 0 auto;
|
|
||||||
width: 80%
|
|
||||||
|
|
||||||
#main-title
|
|
||||||
display: none
|
|
||||||
|
|
||||||
.hero
|
|
||||||
margin: 150px 25px 70px
|
|
||||||
|
|
||||||
@font-face
|
|
||||||
font-family: 'Nerd Font';
|
|
||||||
src: url("/nerd-font.woff2") format("woff2");
|
|
||||||
font-weight: 400;
|
|
||||||
font-style: normal
|
|
||||||
|
|
||||||
code
|
|
||||||
font-family: 'Nerd Font', source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
|
|
@ -1,4 +0,0 @@
|
|||||||
$accentColor = #f15833
|
|
||||||
$textColor = #663015
|
|
||||||
$borderColor = #eaecef
|
|
||||||
$codeBgColor = #282c34
|
|
@ -1,39 +0,0 @@
|
|||||||
---
|
|
||||||
home: true
|
|
||||||
heroImage: /logo.png
|
|
||||||
actionText: Get Started →
|
|
||||||
actionLink: /guide/
|
|
||||||
footer: MIT Licensed | Copyright © 2016-present jc21.com
|
|
||||||
---
|
|
||||||
|
|
||||||
<div class="features">
|
|
||||||
<div class="feature">
|
|
||||||
<h2>Get Connected</h2>
|
|
||||||
<p>
|
|
||||||
Expose web services on your network ·
|
|
||||||
Free SSL with Let's Encrypt ·
|
|
||||||
Designed with security in mind ·
|
|
||||||
Perfect for home networks
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="feature">
|
|
||||||
<h2>Proxy Hosts</h2>
|
|
||||||
<p>Expose your private network Web services and get connected anywhere.</p>
|
|
||||||
</div>
|
|
||||||
<div class="feature">
|
|
||||||
<h2>Beautiful UI</h2>
|
|
||||||
<p>Based on Tabler, the interface is a pleasure to use. Configuring a server has never been so fun.</p>
|
|
||||||
</div>
|
|
||||||
<div class="feature">
|
|
||||||
<h2>Free SSL</h2>
|
|
||||||
<p>Built in Let’s Encrypt support allows you to secure your Web services at no cost to you. The certificates even renew themselves!</p>
|
|
||||||
</div>
|
|
||||||
<div class="feature">
|
|
||||||
<h2>Docker FTW</h2>
|
|
||||||
<p>Built as a Docker Image, Nginx Proxy Manager only requires a database.</p>
|
|
||||||
</div>
|
|
||||||
<div class="feature">
|
|
||||||
<h2>Multiple Users</h2>
|
|
||||||
<p>Configure other users to either view or manage their own hosts. Full access permissions are available.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1 +0,0 @@
|
|||||||
../../README.md
|
|
@ -1,777 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "docs",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "",
|
|
||||||
"main": "index.js",
|
|
||||||
"dependencies": {
|
|
||||||
"@vuepress/plugin-google-analytics": "^1.5.3",
|
|
||||||
"abbrev": "^1.1.1",
|
|
||||||
"accepts": "^1.3.7",
|
|
||||||
"acorn": "^7.4.0",
|
|
||||||
"agentkeepalive": "^4.1.3",
|
|
||||||
"ajv": "^6.12.3",
|
|
||||||
"ajv-errors": "^1.0.1",
|
|
||||||
"ajv-keywords": "^3.5.2",
|
|
||||||
"algoliasearch": "^4.3.1",
|
|
||||||
"alphanum-sort": "^1.0.2",
|
|
||||||
"ansi-colors": "^4.1.1",
|
|
||||||
"ansi-escapes": "^4.3.1",
|
|
||||||
"ansi-html": "^0.0.8",
|
|
||||||
"ansi-regex": "^5.0.0",
|
|
||||||
"ansi-styles": "^4.2.1",
|
|
||||||
"anymatch": "^3.1.1",
|
|
||||||
"aproba": "^2.0.0",
|
|
||||||
"argparse": "^1.0.10",
|
|
||||||
"arr-diff": "^4.0.0",
|
|
||||||
"arr-flatten": "^1.1.0",
|
|
||||||
"arr-union": "^3.1.0",
|
|
||||||
"array-flatten": "^3.0.0",
|
|
||||||
"array-union": "^2.1.0",
|
|
||||||
"array-uniq": "^2.1.0",
|
|
||||||
"array-unique": "^0.3.2",
|
|
||||||
"asn1": "^0.2.4",
|
|
||||||
"asn1.js": "^5.4.1",
|
|
||||||
"assert": "^2.0.0",
|
|
||||||
"assert-plus": "^1.0.0",
|
|
||||||
"assign-symbols": "^2.0.2",
|
|
||||||
"async": "^3.2.0",
|
|
||||||
"async-each": "^1.0.3",
|
|
||||||
"async-limiter": "^2.0.0",
|
|
||||||
"asynckit": "^0.4.0",
|
|
||||||
"atob": "^2.1.2",
|
|
||||||
"autocomplete.js": "^0.37.1",
|
|
||||||
"autoprefixer": "^9.8.6",
|
|
||||||
"aws-sign2": "^0.7.0",
|
|
||||||
"aws4": "^1.10.0",
|
|
||||||
"babel-loader": "^8.1.0",
|
|
||||||
"babel-plugin-dynamic-import-node": "^2.3.3",
|
|
||||||
"babel-plugin-module-resolver": "^4.0.0",
|
|
||||||
"balanced-match": "^1.0.0",
|
|
||||||
"base": "^3.0.0",
|
|
||||||
"base64-js": "^1.3.1",
|
|
||||||
"batch": "^0.6.1",
|
|
||||||
"bcrypt-pbkdf": "^1.0.2",
|
|
||||||
"big.js": "^5.2.2",
|
|
||||||
"binary-extensions": "^2.1.0",
|
|
||||||
"bluebird": "^3.7.2",
|
|
||||||
"bn.js": "^5.1.2",
|
|
||||||
"body-parser": "^1.19.0",
|
|
||||||
"bonjour": "^3.5.0",
|
|
||||||
"boolbase": "^1.0.0",
|
|
||||||
"brace-expansion": "^1.1.11",
|
|
||||||
"braces": "^3.0.2",
|
|
||||||
"brorand": "^1.1.0",
|
|
||||||
"browserify-aes": "^1.2.0",
|
|
||||||
"browserify-cipher": "^1.0.1",
|
|
||||||
"browserify-des": "^1.0.2",
|
|
||||||
"browserify-rsa": "^4.0.1",
|
|
||||||
"browserify-sign": "^4.2.1",
|
|
||||||
"browserify-zlib": "^0.2.0",
|
|
||||||
"browserslist": "^4.13.0",
|
|
||||||
"buffer": "^5.6.0",
|
|
||||||
"buffer-from": "^1.1.1",
|
|
||||||
"buffer-indexof": "^1.1.1",
|
|
||||||
"buffer-json": "^2.0.0",
|
|
||||||
"buffer-xor": "^2.0.2",
|
|
||||||
"builtin-status-codes": "^3.0.0",
|
|
||||||
"bytes": "^3.1.0",
|
|
||||||
"cac": "^6.6.1",
|
|
||||||
"cacache": "^15.0.5",
|
|
||||||
"cache-base": "^4.0.0",
|
|
||||||
"cache-loader": "^4.1.0",
|
|
||||||
"call-me-maybe": "^1.0.1",
|
|
||||||
"caller-callsite": "^4.1.0",
|
|
||||||
"caller-path": "^3.0.0",
|
|
||||||
"callsites": "^3.1.0",
|
|
||||||
"camel-case": "^4.1.1",
|
|
||||||
"camelcase": "^6.0.0",
|
|
||||||
"caniuse-api": "^3.0.0",
|
|
||||||
"caniuse-lite": "^1.0.30001111",
|
|
||||||
"caseless": "^0.12.0",
|
|
||||||
"chalk": "^4.1.0",
|
|
||||||
"chokidar": "^3.4.1",
|
|
||||||
"chownr": "^2.0.0",
|
|
||||||
"chrome-trace-event": "^1.0.2",
|
|
||||||
"ci-info": "^2.0.0",
|
|
||||||
"cipher-base": "^1.0.4",
|
|
||||||
"class-utils": "^0.3.6",
|
|
||||||
"clean-css": "^4.2.3",
|
|
||||||
"clipboard": "^2.0.6",
|
|
||||||
"cliui": "^6.0.0",
|
|
||||||
"coa": "^2.0.2",
|
|
||||||
"code-point-at": "^1.1.0",
|
|
||||||
"collection-visit": "^1.0.0",
|
|
||||||
"color": "^3.1.2",
|
|
||||||
"color-convert": "^2.0.1",
|
|
||||||
"color-name": "^1.1.4",
|
|
||||||
"color-string": "^1.5.3",
|
|
||||||
"combined-stream": "^1.0.8",
|
|
||||||
"commander": "^6.0.0",
|
|
||||||
"commondir": "^1.0.1",
|
|
||||||
"component-emitter": "^1.3.0",
|
|
||||||
"compressible": "^2.0.18",
|
|
||||||
"compression": "^1.7.4",
|
|
||||||
"concat-map": "^0.0.1",
|
|
||||||
"concat-stream": "^2.0.0",
|
|
||||||
"connect-history-api-fallback": "^1.6.0",
|
|
||||||
"consola": "^2.15.0",
|
|
||||||
"console-browserify": "^1.2.0",
|
|
||||||
"consolidate": "^0.15.1",
|
|
||||||
"constants-browserify": "^1.0.0",
|
|
||||||
"content-disposition": "^0.5.3",
|
|
||||||
"content-type": "^1.0.4",
|
|
||||||
"convert-source-map": "^1.7.0",
|
|
||||||
"cookie": "^0.4.1",
|
|
||||||
"cookie-signature": "^1.1.0",
|
|
||||||
"copy-concurrently": "^1.0.5",
|
|
||||||
"copy-descriptor": "^0.1.1",
|
|
||||||
"copy-webpack-plugin": "^6.0.3",
|
|
||||||
"core-js": "^3.6.5",
|
|
||||||
"core-util-is": "^1.0.2",
|
|
||||||
"cosmiconfig": "^7.0.0",
|
|
||||||
"create-ecdh": "^4.0.4",
|
|
||||||
"create-hash": "^1.2.0",
|
|
||||||
"create-hmac": "^1.1.7",
|
|
||||||
"cross-spawn": "^7.0.3",
|
|
||||||
"crypto-browserify": "^3.12.0",
|
|
||||||
"css": "^3.0.0",
|
|
||||||
"css-color-names": "^1.0.1",
|
|
||||||
"css-declaration-sorter": "^5.1.2",
|
|
||||||
"css-loader": "^4.2.0",
|
|
||||||
"css-parse": "^2.0.0",
|
|
||||||
"css-select": "^2.1.0",
|
|
||||||
"css-select-base-adapter": "^0.1.1",
|
|
||||||
"css-tree": "^1.0.0-alpha.39",
|
|
||||||
"css-unit-converter": "^1.1.2",
|
|
||||||
"css-what": "^5.0.1",
|
|
||||||
"cssesc": "^3.0.0",
|
|
||||||
"cssnano": "^4.1.10",
|
|
||||||
"cssnano-preset-default": "^4.0.7",
|
|
||||||
"cssnano-util-get-arguments": "^4.0.0",
|
|
||||||
"cssnano-util-get-match": "^4.0.0",
|
|
||||||
"cssnano-util-raw-cache": "^4.0.1",
|
|
||||||
"cssnano-util-same-parent": "^4.0.1",
|
|
||||||
"csso": "^4.0.3",
|
|
||||||
"cyclist": "^1.0.1",
|
|
||||||
"dashdash": "^1.14.1",
|
|
||||||
"de-indent": "^1.0.2",
|
|
||||||
"debug": "^4.1.1",
|
|
||||||
"decamelize": "^4.0.0",
|
|
||||||
"decode-uri-component": "^0.2.0",
|
|
||||||
"deep-equal": "^2.0.3",
|
|
||||||
"deepmerge": "^4.2.2",
|
|
||||||
"default-gateway": "^6.0.1",
|
|
||||||
"define-properties": "^1.1.3",
|
|
||||||
"define-property": "^2.0.2",
|
|
||||||
"del": "^5.1.0",
|
|
||||||
"delayed-stream": "^1.0.0",
|
|
||||||
"delegate": "^3.2.0",
|
|
||||||
"depd": "^2.0.0",
|
|
||||||
"des.js": "^1.0.1",
|
|
||||||
"destroy": "^1.0.4",
|
|
||||||
"detect-node": "^2.0.4",
|
|
||||||
"diacritics": "^1.3.0",
|
|
||||||
"diffie-hellman": "^5.0.3",
|
|
||||||
"dir-glob": "^3.0.1",
|
|
||||||
"dns-equal": "^1.0.0",
|
|
||||||
"dns-packet": "^5.2.1",
|
|
||||||
"dns-txt": "^2.0.2",
|
|
||||||
"docsearch.js": "^2.6.3",
|
|
||||||
"dom-converter": "^0.2.0",
|
|
||||||
"dom-serializer": "^1.0.1",
|
|
||||||
"dom-walk": "^0.1.2",
|
|
||||||
"domain-browser": "^4.16.0",
|
|
||||||
"domelementtype": "^2.0.1",
|
|
||||||
"domhandler": "^3.0.0",
|
|
||||||
"domutils": "^2.1.0",
|
|
||||||
"dot-prop": "^5.2.0",
|
|
||||||
"duplexify": "^4.1.1",
|
|
||||||
"ecc-jsbn": "^0.2.0",
|
|
||||||
"ee-first": "^1.1.1",
|
|
||||||
"electron-to-chromium": "^1.3.522",
|
|
||||||
"elliptic": "^6.5.3",
|
|
||||||
"emoji-regex": "^9.0.0",
|
|
||||||
"emojis-list": "^3.0.0",
|
|
||||||
"encodeurl": "^1.0.2",
|
|
||||||
"end-of-stream": "^1.4.4",
|
|
||||||
"enhanced-resolve": "^4.3.0",
|
|
||||||
"entities": "^2.0.3",
|
|
||||||
"envify": "^4.1.0",
|
|
||||||
"envinfo": "^7.7.2",
|
|
||||||
"errno": "^0.1.7",
|
|
||||||
"error-ex": "^1.3.2",
|
|
||||||
"es-abstract": "^1.17.6",
|
|
||||||
"es-to-primitive": "^1.2.1",
|
|
||||||
"es6-promise": "^4.2.8",
|
|
||||||
"escape-html": "^1.0.3",
|
|
||||||
"escape-string-regexp": "^4.0.0",
|
|
||||||
"eslint-scope": "^5.1.0",
|
|
||||||
"esprima": "^4.0.1",
|
|
||||||
"esrecurse": "^4.2.1",
|
|
||||||
"estraverse": "^5.2.0",
|
|
||||||
"esutils": "^2.0.3",
|
|
||||||
"etag": "^1.8.1",
|
|
||||||
"eventemitter3": "^4.0.4",
|
|
||||||
"events": "^3.2.0",
|
|
||||||
"eventsource": "^2.0.2",
|
|
||||||
"evp_bytestokey": "^1.0.3",
|
|
||||||
"execa": "^4.0.3",
|
|
||||||
"expand-brackets": "^4.0.0",
|
|
||||||
"express": "^4.17.1",
|
|
||||||
"extend": "^3.0.2",
|
|
||||||
"extend-shallow": "^3.0.2",
|
|
||||||
"extglob": "^3.0.0",
|
|
||||||
"extsprintf": "^1.4.0",
|
|
||||||
"fast-deep-equal": "^3.1.3",
|
|
||||||
"fast-glob": "^3.2.4",
|
|
||||||
"fast-json-stable-stringify": "^2.1.0",
|
|
||||||
"faye-websocket": "^0.11.3",
|
|
||||||
"figgy-pudding": "^3.5.2",
|
|
||||||
"figures": "^3.2.0",
|
|
||||||
"file-loader": "^6.0.0",
|
|
||||||
"fill-range": "^7.0.1",
|
|
||||||
"finalhandler": "^1.1.2",
|
|
||||||
"find-babel-config": "^1.2.0",
|
|
||||||
"find-cache-dir": "^3.3.1",
|
|
||||||
"find-up": "^4.1.0",
|
|
||||||
"flush-write-stream": "^2.0.0",
|
|
||||||
"follow-redirects": "^1.12.1",
|
|
||||||
"for-in": "^1.0.2",
|
|
||||||
"foreach": "^2.0.5",
|
|
||||||
"forever-agent": "^0.6.1",
|
|
||||||
"form-data": "^3.0.0",
|
|
||||||
"forwarded": "^0.1.2",
|
|
||||||
"fragment-cache": "^0.2.1",
|
|
||||||
"fresh": "^0.5.2",
|
|
||||||
"from2": "^2.3.0",
|
|
||||||
"fs-extra": "^9.0.1",
|
|
||||||
"fs-write-stream-atomic": "^1.0.10",
|
|
||||||
"fs.realpath": "^1.0.0",
|
|
||||||
"function-bind": "^1.1.1",
|
|
||||||
"gensync": "^1.0.0-beta.1",
|
|
||||||
"get-caller-file": "^2.0.5",
|
|
||||||
"get-stream": "^5.1.0",
|
|
||||||
"get-value": "^3.0.1",
|
|
||||||
"getpass": "^0.1.7",
|
|
||||||
"glob": "^7.1.6",
|
|
||||||
"glob-parent": "^5.1.1",
|
|
||||||
"glob-to-regexp": "^0.4.1",
|
|
||||||
"global": "^4.4.0",
|
|
||||||
"globals": "^13.1.0",
|
|
||||||
"globby": "^11.0.1",
|
|
||||||
"good-listener": "^1.2.2",
|
|
||||||
"graceful-fs": "^4.2.4",
|
|
||||||
"gray-matter": "^4.0.2",
|
|
||||||
"handle-thing": "^2.0.1",
|
|
||||||
"har-schema": "^2.0.0",
|
|
||||||
"har-validator": "^5.1.5",
|
|
||||||
"has": "^1.0.3",
|
|
||||||
"has-ansi": "^4.0.0",
|
|
||||||
"has-flag": "^4.0.0",
|
|
||||||
"has-symbols": "^1.0.1",
|
|
||||||
"has-value": "^2.0.2",
|
|
||||||
"has-values": "^2.0.1",
|
|
||||||
"hash-base": "^3.1.0",
|
|
||||||
"hash-sum": "^2.0.0",
|
|
||||||
"hash.js": "^1.1.7",
|
|
||||||
"he": "^1.2.0",
|
|
||||||
"hex-color-regex": "^1.1.0",
|
|
||||||
"hmac-drbg": "^1.0.1",
|
|
||||||
"hogan.js": "^3.0.2",
|
|
||||||
"hpack.js": "^2.1.6",
|
|
||||||
"hsl-regex": "^1.0.0",
|
|
||||||
"hsla-regex": "^1.0.0",
|
|
||||||
"html-comment-regex": "^1.1.2",
|
|
||||||
"html-entities": "^1.3.1",
|
|
||||||
"html-minifier": "^4.0.0",
|
|
||||||
"html-tags": "^3.1.0",
|
|
||||||
"htmlparser2": "^4.1.0",
|
|
||||||
"http-deceiver": "^1.2.7",
|
|
||||||
"http-errors": "^1.8.0",
|
|
||||||
"http-parser-js": "^0.5.2",
|
|
||||||
"http-proxy": "^1.18.1",
|
|
||||||
"http-proxy-middleware": "^1.0.5",
|
|
||||||
"http-signature": "^1.3.4",
|
|
||||||
"https-browserify": "^1.0.0",
|
|
||||||
"iconv-lite": "^0.6.2",
|
|
||||||
"icss-replace-symbols": "^1.1.0",
|
|
||||||
"icss-utils": "^4.1.1",
|
|
||||||
"ieee754": "^1.1.13",
|
|
||||||
"iferr": "^1.0.2",
|
|
||||||
"ignore": "^5.1.8",
|
|
||||||
"immediate": "^3.3.0",
|
|
||||||
"import-cwd": "^3.0.0",
|
|
||||||
"import-fresh": "^3.2.1",
|
|
||||||
"import-from": "^3.0.0",
|
|
||||||
"import-local": "^3.0.2",
|
|
||||||
"imurmurhash": "^0.1.4",
|
|
||||||
"indexes-of": "^1.0.1",
|
|
||||||
"infer-owner": "^1.0.4",
|
|
||||||
"inflight": "^1.0.6",
|
|
||||||
"inherits": "^2.0.4",
|
|
||||||
"internal-ip": "^6.1.0",
|
|
||||||
"invariant": "^2.2.4",
|
|
||||||
"invert-kv": "^3.0.1",
|
|
||||||
"ip": "^1.1.5",
|
|
||||||
"ip-regex": "^4.1.0",
|
|
||||||
"ipaddr.js": "^1.9.1",
|
|
||||||
"is-absolute-url": "^3.0.3",
|
|
||||||
"is-accessor-descriptor": "^3.0.1",
|
|
||||||
"is-arguments": "^1.0.4",
|
|
||||||
"is-arrayish": "^0.3.2",
|
|
||||||
"is-binary-path": "^2.1.0",
|
|
||||||
"is-buffer": "^2.0.4",
|
|
||||||
"is-callable": "^1.2.0",
|
|
||||||
"is-color-stop": "^1.1.0",
|
|
||||||
"is-data-descriptor": "^2.0.0",
|
|
||||||
"is-date-object": "^1.0.2",
|
|
||||||
"is-descriptor": "^3.0.0",
|
|
||||||
"is-directory": "^0.3.1",
|
|
||||||
"is-extendable": "^1.0.1",
|
|
||||||
"is-extglob": "^2.1.1",
|
|
||||||
"is-fullwidth-code-point": "^3.0.0",
|
|
||||||
"is-glob": "^4.0.1",
|
|
||||||
"is-number": "^7.0.0",
|
|
||||||
"is-obj": "^2.0.0",
|
|
||||||
"is-path-cwd": "^2.2.0",
|
|
||||||
"is-path-in-cwd": "^3.0.0",
|
|
||||||
"is-path-inside": "^3.0.2",
|
|
||||||
"is-plain-obj": "^2.1.0",
|
|
||||||
"is-plain-object": "^4.1.1",
|
|
||||||
"is-regex": "^1.1.1",
|
|
||||||
"is-resolvable": "^1.1.0",
|
|
||||||
"is-stream": "^2.0.0",
|
|
||||||
"is-svg": "^4.2.1",
|
|
||||||
"is-symbol": "^1.0.3",
|
|
||||||
"is-typedarray": "^1.0.0",
|
|
||||||
"is-windows": "^1.0.2",
|
|
||||||
"is-wsl": "^2.2.0",
|
|
||||||
"isarray": "^2.0.5",
|
|
||||||
"isexe": "^2.0.0",
|
|
||||||
"isobject": "^4.0.0",
|
|
||||||
"isstream": "^0.1.2",
|
|
||||||
"javascript-stringify": "^2.0.1",
|
|
||||||
"js-levenshtein": "^1.1.6",
|
|
||||||
"js-tokens": "^6.0.0",
|
|
||||||
"js-yaml": "^3.14.0",
|
|
||||||
"jsbn": "^1.1.0",
|
|
||||||
"jsesc": "^3.0.1",
|
|
||||||
"json-parse-better-errors": "^1.0.2",
|
|
||||||
"json-schema": "^0.4.0",
|
|
||||||
"json-schema-traverse": "^0.4.1",
|
|
||||||
"json-stringify-safe": "^5.0.1",
|
|
||||||
"json3": "^3.3.3",
|
|
||||||
"json5": "^2.1.3",
|
|
||||||
"jsonfile": "^6.0.1",
|
|
||||||
"jsprim": "^2.0.0",
|
|
||||||
"killable": "^1.0.1",
|
|
||||||
"kind-of": "^6.0.3",
|
|
||||||
"last-call-webpack-plugin": "^3.0.0",
|
|
||||||
"lcid": "^3.1.1",
|
|
||||||
"linkify-it": "^3.0.2",
|
|
||||||
"load-script": "^1.0.0",
|
|
||||||
"loader-runner": "^4.0.0",
|
|
||||||
"loader-utils": "^2.0.0",
|
|
||||||
"locate-path": "^5.0.0",
|
|
||||||
"lodash": "^4.17.19",
|
|
||||||
"lodash._reinterpolate": "^3.0.0",
|
|
||||||
"lodash.chunk": "^4.2.0",
|
|
||||||
"lodash.clonedeep": "^4.5.0",
|
|
||||||
"lodash.debounce": "^4.0.8",
|
|
||||||
"lodash.kebabcase": "^4.1.1",
|
|
||||||
"lodash.memoize": "^4.1.2",
|
|
||||||
"lodash.padstart": "^4.6.1",
|
|
||||||
"lodash.sortby": "^4.7.0",
|
|
||||||
"lodash.template": "^4.5.0",
|
|
||||||
"lodash.templatesettings": "^4.2.0",
|
|
||||||
"lodash.uniq": "^4.5.0",
|
|
||||||
"loglevel": "^1.6.8",
|
|
||||||
"loose-envify": "^1.4.0",
|
|
||||||
"lower-case": "^2.0.1",
|
|
||||||
"lru-cache": "^6.0.0",
|
|
||||||
"make-dir": "^3.1.0",
|
|
||||||
"mamacro": "^0.0.7",
|
|
||||||
"map-age-cleaner": "^0.1.3",
|
|
||||||
"map-cache": "^0.2.2",
|
|
||||||
"map-visit": "^1.0.0",
|
|
||||||
"markdown-it": "^12.3.2",
|
|
||||||
"markdown-it-anchor": "^5.3.0",
|
|
||||||
"markdown-it-chain": "^1.3.0",
|
|
||||||
"markdown-it-container": "^3.0.0",
|
|
||||||
"markdown-it-emoji": "^1.4.0",
|
|
||||||
"markdown-it-table-of-contents": "^0.4.4",
|
|
||||||
"md5.js": "^1.3.5",
|
|
||||||
"mdn-data": "^2.0.11",
|
|
||||||
"mdurl": "^1.0.1",
|
|
||||||
"media-typer": "^1.1.0",
|
|
||||||
"mem": "^6.1.0",
|
|
||||||
"memory-fs": "^0.5.0",
|
|
||||||
"merge-descriptors": "^1.0.1",
|
|
||||||
"merge-source-map": "^1.1.0",
|
|
||||||
"merge2": "^1.4.1",
|
|
||||||
"methods": "^1.1.2",
|
|
||||||
"micromatch": "^4.0.2",
|
|
||||||
"miller-rabin": "^4.0.1",
|
|
||||||
"mime": "^2.4.6",
|
|
||||||
"mime-db": "^1.44.0",
|
|
||||||
"mime-types": "^2.1.27",
|
|
||||||
"mimic-fn": "^3.1.0",
|
|
||||||
"min-document": "^2.19.0",
|
|
||||||
"mini-css-extract-plugin": "^0.9.0",
|
|
||||||
"minimalistic-assert": "^1.0.1",
|
|
||||||
"minimalistic-crypto-utils": "^1.0.1",
|
|
||||||
"minimatch": "^3.0.4",
|
|
||||||
"minimist": "^1.2.5",
|
|
||||||
"mississippi": "^4.0.0",
|
|
||||||
"mixin-deep": "^2.0.1",
|
|
||||||
"mkdirp": "^1.0.4",
|
|
||||||
"move-concurrently": "^1.0.1",
|
|
||||||
"ms": "^2.1.2",
|
|
||||||
"multicast-dns": "^7.2.2",
|
|
||||||
"multicast-dns-service-types": "^1.1.0",
|
|
||||||
"nanomatch": "^1.2.13",
|
|
||||||
"negotiator": "^0.6.2",
|
|
||||||
"neo-async": "^2.6.2",
|
|
||||||
"nice-try": "^2.0.1",
|
|
||||||
"no-case": "^3.0.3",
|
|
||||||
"node-forge": "^1.0.0",
|
|
||||||
"node-libs-browser": "^2.2.1",
|
|
||||||
"node-releases": "^1.1.60",
|
|
||||||
"nopt": "^4.0.3",
|
|
||||||
"normalize-path": "^3.0.0",
|
|
||||||
"normalize-range": "^0.1.2",
|
|
||||||
"normalize-url": "^5.1.0",
|
|
||||||
"npm-run-path": "^4.0.1",
|
|
||||||
"nprogress": "^0.2.0",
|
|
||||||
"nth-check": "^2.0.1",
|
|
||||||
"num2fraction": "^1.2.2",
|
|
||||||
"number-is-nan": "^2.0.0",
|
|
||||||
"oauth-sign": "^0.9.0",
|
|
||||||
"object-assign": "^4.1.1",
|
|
||||||
"object-copy": "^1.0.0",
|
|
||||||
"object-inspect": "^1.8.0",
|
|
||||||
"object-is": "^1.1.2",
|
|
||||||
"object-keys": "^1.1.1",
|
|
||||||
"object-visit": "^1.0.1",
|
|
||||||
"object.assign": "^4.1.0",
|
|
||||||
"object.getownpropertydescriptors": "^2.1.0",
|
|
||||||
"object.pick": "^1.3.0",
|
|
||||||
"object.values": "^1.1.1",
|
|
||||||
"obuf": "^1.1.2",
|
|
||||||
"on-finished": "^2.3.0",
|
|
||||||
"on-headers": "^1.0.2",
|
|
||||||
"once": "^1.4.0",
|
|
||||||
"opencollective-postinstall": "^2.0.3",
|
|
||||||
"opn": "^6.0.0",
|
|
||||||
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
|
||||||
"original": "^1.0.2",
|
|
||||||
"os-browserify": "^0.3.0",
|
|
||||||
"os-locale": "^5.0.0",
|
|
||||||
"p-defer": "^3.0.0",
|
|
||||||
"p-finally": "^2.0.1",
|
|
||||||
"p-is-promise": "^3.0.0",
|
|
||||||
"p-limit": "^3.0.2",
|
|
||||||
"p-locate": "^4.1.0",
|
|
||||||
"p-map": "^4.0.0",
|
|
||||||
"p-retry": "^4.2.0",
|
|
||||||
"p-try": "^2.2.0",
|
|
||||||
"pako": "^1.0.11",
|
|
||||||
"parallel-transform": "^1.2.0",
|
|
||||||
"param-case": "^3.0.3",
|
|
||||||
"parse-asn1": "^5.1.5",
|
|
||||||
"parse-json": "^5.0.1",
|
|
||||||
"parseurl": "^1.3.3",
|
|
||||||
"pascalcase": "^1.0.0",
|
|
||||||
"path-browserify": "^1.0.1",
|
|
||||||
"path-dirname": "^1.0.2",
|
|
||||||
"path-exists": "^4.0.0",
|
|
||||||
"path-is-absolute": "^2.0.0",
|
|
||||||
"path-is-inside": "^1.0.2",
|
|
||||||
"path-key": "^3.1.1",
|
|
||||||
"path-parse": "^1.0.6",
|
|
||||||
"path-to-regexp": "^6.1.0",
|
|
||||||
"path-type": "^4.0.0",
|
|
||||||
"pbkdf2": "^3.1.1",
|
|
||||||
"performance-now": "^2.1.0",
|
|
||||||
"pify": "^5.0.0",
|
|
||||||
"pinkie": "^2.0.4",
|
|
||||||
"pinkie-promise": "^2.0.1",
|
|
||||||
"pkg-dir": "^4.2.0",
|
|
||||||
"pkg-up": "^3.1.0",
|
|
||||||
"portfinder": "^1.0.28",
|
|
||||||
"posix-character-classes": "^1.0.0",
|
|
||||||
"postcss": "^8.2.10",
|
|
||||||
"postcss-calc": "^7.0.2",
|
|
||||||
"postcss-colormin": "^4.0.3",
|
|
||||||
"postcss-convert-values": "^4.0.1",
|
|
||||||
"postcss-discard-comments": "^4.0.2",
|
|
||||||
"postcss-discard-duplicates": "^4.0.2",
|
|
||||||
"postcss-discard-empty": "^4.0.1",
|
|
||||||
"postcss-discard-overridden": "^4.0.1",
|
|
||||||
"postcss-load-config": "^2.1.0",
|
|
||||||
"postcss-loader": "^3.0.0",
|
|
||||||
"postcss-merge-longhand": "^4.0.11",
|
|
||||||
"postcss-merge-rules": "^4.0.3",
|
|
||||||
"postcss-minify-font-values": "^4.0.2",
|
|
||||||
"postcss-minify-gradients": "^4.0.2",
|
|
||||||
"postcss-minify-params": "^4.0.2",
|
|
||||||
"postcss-minify-selectors": "^4.0.2",
|
|
||||||
"postcss-modules-extract-imports": "^2.0.0",
|
|
||||||
"postcss-modules-local-by-default": "^3.0.3",
|
|
||||||
"postcss-modules-scope": "^2.2.0",
|
|
||||||
"postcss-modules-values": "^3.0.0",
|
|
||||||
"postcss-normalize-charset": "^4.0.1",
|
|
||||||
"postcss-normalize-display-values": "^4.0.2",
|
|
||||||
"postcss-normalize-positions": "^4.0.2",
|
|
||||||
"postcss-normalize-repeat-style": "^4.0.2",
|
|
||||||
"postcss-normalize-string": "^4.0.2",
|
|
||||||
"postcss-normalize-timing-functions": "^4.0.2",
|
|
||||||
"postcss-normalize-unicode": "^4.0.1",
|
|
||||||
"postcss-normalize-url": "^4.0.1",
|
|
||||||
"postcss-normalize-whitespace": "^4.0.2",
|
|
||||||
"postcss-ordered-values": "^4.1.2",
|
|
||||||
"postcss-reduce-initial": "^4.0.3",
|
|
||||||
"postcss-reduce-transforms": "^4.0.2",
|
|
||||||
"postcss-safe-parser": "^4.0.2",
|
|
||||||
"postcss-selector-parser": "^6.0.2",
|
|
||||||
"postcss-svgo": "^4.0.2",
|
|
||||||
"postcss-unique-selectors": "^4.0.1",
|
|
||||||
"postcss-value-parser": "^4.1.0",
|
|
||||||
"prepend-http": "^3.0.1",
|
|
||||||
"prettier": "^2.0.5",
|
|
||||||
"pretty-error": "^2.1.1",
|
|
||||||
"pretty-time": "^1.1.0",
|
|
||||||
"prismjs": "^1.20.0",
|
|
||||||
"private": "^0.1.8",
|
|
||||||
"process": "^0.11.10",
|
|
||||||
"process-nextick-args": "^2.0.1",
|
|
||||||
"promise-inflight": "^1.0.1",
|
|
||||||
"proxy-addr": "^2.0.6",
|
|
||||||
"prr": "^1.0.1",
|
|
||||||
"pseudomap": "^1.0.2",
|
|
||||||
"psl": "^1.8.0",
|
|
||||||
"public-encrypt": "^4.0.3",
|
|
||||||
"pump": "^3.0.0",
|
|
||||||
"pumpify": "^2.0.1",
|
|
||||||
"punycode": "^2.1.1",
|
|
||||||
"q": "^1.5.1",
|
|
||||||
"qs": "^6.9.4",
|
|
||||||
"query-string": "^6.13.1",
|
|
||||||
"querystring": "^0.2.0",
|
|
||||||
"querystring-es3": "^0.2.1",
|
|
||||||
"querystringify": "^2.1.1",
|
|
||||||
"randombytes": "^2.1.0",
|
|
||||||
"randomfill": "^1.0.4",
|
|
||||||
"range-parser": "^1.2.1",
|
|
||||||
"raw-body": "^2.4.1",
|
|
||||||
"readable-stream": "^3.6.0",
|
|
||||||
"readdirp": "^3.4.0",
|
|
||||||
"reduce": "^1.0.2",
|
|
||||||
"regenerate": "^1.4.1",
|
|
||||||
"regenerate-unicode-properties": "^8.2.0",
|
|
||||||
"regenerator-runtime": "^0.13.7",
|
|
||||||
"regenerator-transform": "^0.14.5",
|
|
||||||
"regex-not": "^1.0.2",
|
|
||||||
"regexp.prototype.flags": "^1.3.0",
|
|
||||||
"regexpu-core": "^4.7.0",
|
|
||||||
"regjsgen": "^0.5.2",
|
|
||||||
"regjsparser": "^0.6.4",
|
|
||||||
"relateurl": "^0.2.7",
|
|
||||||
"remove-trailing-separator": "^1.1.0",
|
|
||||||
"renderkid": "^2.0.3",
|
|
||||||
"repeat-element": "^1.1.3",
|
|
||||||
"repeat-string": "^1.6.1",
|
|
||||||
"request": "^2.88.2",
|
|
||||||
"require-directory": "^2.1.1",
|
|
||||||
"require-main-filename": "^2.0.0",
|
|
||||||
"requires-port": "^1.0.0",
|
|
||||||
"reselect": "^4.0.0",
|
|
||||||
"resolve": "^1.17.0",
|
|
||||||
"resolve-cwd": "^3.0.0",
|
|
||||||
"resolve-from": "^5.0.0",
|
|
||||||
"resolve-url": "^0.2.1",
|
|
||||||
"ret": "^0.3.1",
|
|
||||||
"retry": "^0.12.0",
|
|
||||||
"rgb-regex": "^1.0.1",
|
|
||||||
"rgba-regex": "^1.0.0",
|
|
||||||
"rimraf": "^3.0.2",
|
|
||||||
"ripemd160": "^2.0.2",
|
|
||||||
"run-queue": "^2.0.1",
|
|
||||||
"safe-buffer": "^5.2.1",
|
|
||||||
"safe-regex": "^2.1.1",
|
|
||||||
"safer-buffer": "^2.1.2",
|
|
||||||
"sax": "^1.2.4",
|
|
||||||
"schema-utils": "^2.7.0",
|
|
||||||
"section-matter": "^1.0.0",
|
|
||||||
"select": "^1.1.2",
|
|
||||||
"select-hose": "^2.0.0",
|
|
||||||
"selfsigned": "^1.10.7",
|
|
||||||
"semver": "^7.3.2",
|
|
||||||
"send": "^0.17.1",
|
|
||||||
"serialize-javascript": "^4.0.0",
|
|
||||||
"serve-index": "^1.9.1",
|
|
||||||
"serve-static": "^1.14.1",
|
|
||||||
"set-blocking": "^2.0.0",
|
|
||||||
"set-value": "^4.0.1",
|
|
||||||
"setimmediate": "^1.0.5",
|
|
||||||
"setprototypeof": "^1.2.0",
|
|
||||||
"sha.js": "^2.4.11",
|
|
||||||
"shebang-command": "^2.0.0",
|
|
||||||
"shebang-regex": "^3.0.0",
|
|
||||||
"signal-exit": "^3.0.3",
|
|
||||||
"simple-swizzle": "^0.2.2",
|
|
||||||
"sitemap": "^6.2.0",
|
|
||||||
"slash": "^3.0.0",
|
|
||||||
"smoothscroll-polyfill": "^0.4.4",
|
|
||||||
"snapdragon": "^0.12.0",
|
|
||||||
"snapdragon-node": "^3.0.0",
|
|
||||||
"snapdragon-util": "^5.0.1",
|
|
||||||
"sockjs": "^0.3.21",
|
|
||||||
"sockjs-client": "^1.5.0",
|
|
||||||
"sort-keys": "^4.0.0",
|
|
||||||
"source-list-map": "^2.0.1",
|
|
||||||
"source-map": "^0.7.3",
|
|
||||||
"source-map-resolve": "^0.6.0",
|
|
||||||
"source-map-support": "^0.5.19",
|
|
||||||
"source-map-url": "^0.4.0",
|
|
||||||
"spdy": "^4.0.2",
|
|
||||||
"spdy-transport": "^3.0.0",
|
|
||||||
"split-string": "^6.1.0",
|
|
||||||
"sprintf-js": "^1.1.2",
|
|
||||||
"sshpk": "^1.16.1",
|
|
||||||
"ssri": "^8.0.0",
|
|
||||||
"stable": "^0.1.8",
|
|
||||||
"stack-utils": "^2.0.2",
|
|
||||||
"static-extend": "^0.1.2",
|
|
||||||
"statuses": "^2.0.0",
|
|
||||||
"std-env": "^2.2.1",
|
|
||||||
"stream-browserify": "^3.0.0",
|
|
||||||
"stream-each": "^1.2.3",
|
|
||||||
"stream-http": "^3.1.1",
|
|
||||||
"stream-shift": "^1.0.1",
|
|
||||||
"strict-uri-encode": "^2.0.0",
|
|
||||||
"string-width": "^4.2.0",
|
|
||||||
"string.prototype.trimleft": "^2.1.2",
|
|
||||||
"string.prototype.trimright": "^2.1.2",
|
|
||||||
"string_decoder": "^1.3.0",
|
|
||||||
"strip-ansi": "^6.0.0",
|
|
||||||
"strip-bom-string": "^1.0.0",
|
|
||||||
"strip-eof": "^2.0.0",
|
|
||||||
"stylehacks": "^4.0.3",
|
|
||||||
"stylus": "^0.54.8",
|
|
||||||
"stylus-loader": "^3.0.2",
|
|
||||||
"supports-color": "^7.1.0",
|
|
||||||
"svg-tags": "^1.0.0",
|
|
||||||
"svgo": "^1.3.2",
|
|
||||||
"tapable": "^1.1.3",
|
|
||||||
"terser": "^5.0.0",
|
|
||||||
"terser-webpack-plugin": "^4.0.0",
|
|
||||||
"text-table": "^0.2.0",
|
|
||||||
"through": "^2.3.8",
|
|
||||||
"through2": "^4.0.2",
|
|
||||||
"thunky": "^1.1.0",
|
|
||||||
"timers-browserify": "^2.0.11",
|
|
||||||
"timsort": "^0.3.0",
|
|
||||||
"tiny-emitter": "^2.1.0",
|
|
||||||
"to-arraybuffer": "^1.0.1",
|
|
||||||
"to-factory": "^1.0.0",
|
|
||||||
"to-fast-properties": "^3.0.1",
|
|
||||||
"to-object-path": "^0.3.0",
|
|
||||||
"to-regex": "^3.0.2",
|
|
||||||
"to-regex-range": "^5.0.1",
|
|
||||||
"toidentifier": "^1.0.0",
|
|
||||||
"toml": "^3.0.0",
|
|
||||||
"toposort": "^2.0.2",
|
|
||||||
"tough-cookie": "^4.0.0",
|
|
||||||
"tr46": "^2.0.2",
|
|
||||||
"tslib": "^2.0.0",
|
|
||||||
"tty-browserify": "^0.0.1",
|
|
||||||
"tunnel-agent": "^0.6.0",
|
|
||||||
"tweetnacl": "^1.0.3",
|
|
||||||
"type-fest": "^0.16.0",
|
|
||||||
"type-is": "^1.6.18",
|
|
||||||
"typedarray": "^0.0.6",
|
|
||||||
"uc.micro": "^1.0.6",
|
|
||||||
"uglify-js": "^3.10.1",
|
|
||||||
"unicode-canonical-property-names-ecmascript": "^1.0.4",
|
|
||||||
"unicode-match-property-ecmascript": "^1.0.4",
|
|
||||||
"unicode-match-property-value-ecmascript": "^1.2.0",
|
|
||||||
"unicode-property-aliases-ecmascript": "^1.1.0",
|
|
||||||
"union-value": "^2.0.1",
|
|
||||||
"uniq": "^1.0.1",
|
|
||||||
"uniqs": "^2.0.0",
|
|
||||||
"unique-filename": "^1.1.1",
|
|
||||||
"unique-slug": "^2.0.2",
|
|
||||||
"universalify": "^2.0.0",
|
|
||||||
"unpipe": "^1.0.0",
|
|
||||||
"unquote": "^1.1.1",
|
|
||||||
"unset-value": "^1.0.0",
|
|
||||||
"upath": "^1.2.0",
|
|
||||||
"upper-case": "^2.0.1",
|
|
||||||
"uri-js": "^4.2.2",
|
|
||||||
"urix": "^0.1.0",
|
|
||||||
"url": "^0.11.0",
|
|
||||||
"url-loader": "^4.1.0",
|
|
||||||
"url-parse": "^1.4.7",
|
|
||||||
"use": "^3.1.1",
|
|
||||||
"util": "^0.12.3",
|
|
||||||
"util-deprecate": "^1.0.2",
|
|
||||||
"util.promisify": "^1.0.1",
|
|
||||||
"utila": "^0.4.0",
|
|
||||||
"utils-merge": "^1.0.1",
|
|
||||||
"uuid": "^8.3.0",
|
|
||||||
"vary": "^1.1.2",
|
|
||||||
"vendors": "^1.0.4",
|
|
||||||
"verror": "^1.10.0",
|
|
||||||
"vm-browserify": "^1.1.2",
|
|
||||||
"vue": "^2.6.11",
|
|
||||||
"vue-hot-reload-api": "^2.3.4",
|
|
||||||
"vue-loader": "^15.9.3",
|
|
||||||
"vue-router": "^3.4.0",
|
|
||||||
"vue-server-renderer": "^2.6.11",
|
|
||||||
"vue-style-loader": "^4.1.2",
|
|
||||||
"vue-template-compiler": "^2.6.11",
|
|
||||||
"vue-template-es2015-compiler": "^1.9.1",
|
|
||||||
"vuepress": "^1.5.3",
|
|
||||||
"vuepress-html-webpack-plugin": "^3.2.0",
|
|
||||||
"vuepress-plugin-container": "^2.1.4",
|
|
||||||
"vuepress-plugin-sitemap": "^2.3.1",
|
|
||||||
"vuepress-plugin-smooth-scroll": "^0.0.9",
|
|
||||||
"vuepress-plugin-zooming": "^1.1.7",
|
|
||||||
"watchpack": "^1.7.4",
|
|
||||||
"wbuf": "^1.7.3",
|
|
||||||
"webidl-conversions": "^6.1.0",
|
|
||||||
"webpack": "^4.44.1",
|
|
||||||
"webpack-chain": "^6.5.1",
|
|
||||||
"webpack-dev-middleware": "^3.7.2",
|
|
||||||
"webpack-dev-server": "^3.11.0",
|
|
||||||
"webpack-log": "^3.0.1",
|
|
||||||
"webpack-merge": "^5.1.1",
|
|
||||||
"webpack-sources": "^1.4.3",
|
|
||||||
"webpackbar": "^4.0.0",
|
|
||||||
"websocket-driver": "^0.7.4",
|
|
||||||
"websocket-extensions": "^0.1.4",
|
|
||||||
"whatwg-url": "^8.1.0",
|
|
||||||
"when": "^3.7.8",
|
|
||||||
"which": "^2.0.2",
|
|
||||||
"which-module": "^2.0.0",
|
|
||||||
"worker-farm": "^1.7.0",
|
|
||||||
"wrap-ansi": "^7.0.0",
|
|
||||||
"wrappy": "^1.0.2",
|
|
||||||
"ws": "^7.3.1",
|
|
||||||
"xmlbuilder": "^15.1.1",
|
|
||||||
"xtend": "^4.0.2",
|
|
||||||
"y18n": "^4.0.0",
|
|
||||||
"yallist": "^4.0.0",
|
|
||||||
"yargs": "^15.4.1",
|
|
||||||
"yargs-parser": "^18.1.3",
|
|
||||||
"zepto": "^1.2.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {},
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vuepress dev",
|
"dev": "vitepress dev --host",
|
||||||
"build": "vuepress build"
|
"build": "vitepress build",
|
||||||
|
"preview": "vitepress preview"
|
||||||
},
|
},
|
||||||
"author": "",
|
"devDependencies": {
|
||||||
"license": "ISC"
|
"vitepress": "^1.1.4"
|
||||||
|
},
|
||||||
|
"dependencies": {}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
# Screenshots
|
|
||||||
|
|
||||||
<img class="no-medium-zoom zooming" src="/screenshots/login.png" alt="Login" title="Login" width="200"/>
|
|
||||||
<img class="no-medium-zoom zooming" src="/screenshots/dashboard.png" alt="Dashboard" title="Dashboard" width="200"/>
|
|
||||||
<img class="no-medium-zoom zooming" src="/screenshots/proxy-hosts.png" alt="Proxy Hosts" title="Proxy Hosts" width="200"/>
|
|
||||||
<img class="no-medium-zoom zooming" src="/screenshots/proxy-hosts-add.png" alt="Add Proxy Host" title="Add Proxy Host" width="200"/>
|
|
||||||
<img class="no-medium-zoom zooming" src="/screenshots/redirection-hosts.png" alt="Redirection Hosts" title="Redirection Hosts" width="200"/>
|
|
||||||
<img class="no-medium-zoom zooming" src="/screenshots/dead-hosts.png" alt="404 Hosts" title="404 Hosts" width="200"/>
|
|
||||||
<img class="no-medium-zoom zooming" src="/screenshots/permissions.png" alt="User Permissions" title="User Permissions" width="200"/>
|
|
||||||
<img class="no-medium-zoom zooming" src="/screenshots/certificates.png" alt="Certificates" title="Certificates" width="200"/>
|
|
||||||
<img class="no-medium-zoom zooming" src="/screenshots/audit-log.png" alt="Audit Log" title="Audit Log" width="200"/>
|
|
||||||
<img class="no-medium-zoom zooming" src="/screenshots/custom-settings.png" alt="Custom Settings" title="Custom Settings" width="200"/>
|
|
@ -1,5 +1,30 @@
|
|||||||
|
---
|
||||||
|
outline: deep
|
||||||
|
---
|
||||||
|
|
||||||
# Advanced Configuration
|
# Advanced Configuration
|
||||||
|
|
||||||
|
## Running processes as a user/group
|
||||||
|
|
||||||
|
By default, the services (nginx etc) will run as `root` user inside the docker container.
|
||||||
|
You can change this behaviour by setting the following environment variables.
|
||||||
|
Not only will they run the services as this user/group, they will change the ownership
|
||||||
|
on the `data` and `letsencrypt` folders at startup.
|
||||||
|
|
||||||
|
```yml
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
image: 'jc21/nginx-proxy-manager:latest'
|
||||||
|
environment:
|
||||||
|
PUID: 1000
|
||||||
|
PGID: 1000
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
This may have the side effect of a failed container start due to permission denied trying
|
||||||
|
to open port 80 on some systems. The only course to fix that is to remove the variables
|
||||||
|
and run as the default root user.
|
||||||
|
|
||||||
## Best Practice: Use a Docker network
|
## Best Practice: Use a Docker network
|
||||||
|
|
||||||
For those who have a few of their upstream services running in Docker on the same Docker
|
For those who have a few of their upstream services running in Docker on the same Docker
|
||||||
@ -25,7 +50,7 @@ networks:
|
|||||||
Let's look at a Portainer example:
|
Let's look at a Portainer example:
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
version: '3'
|
version: '3.8'
|
||||||
services:
|
services:
|
||||||
|
|
||||||
portainer:
|
portainer:
|
||||||
@ -55,19 +80,19 @@ feature by adding the following to the service in your `docker-compose.yml` file
|
|||||||
|
|
||||||
```yml
|
```yml
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "/bin/check-health"]
|
test: ["CMD", "/usr/bin/check-health"]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 3s
|
timeout: 3s
|
||||||
```
|
```
|
||||||
|
|
||||||
## Docker Secrets
|
## Docker File Secrets
|
||||||
|
|
||||||
This image supports the use of Docker secrets to import from file and keep sensitive usernames or passwords from being passed or preserved in plaintext.
|
This image supports the use of Docker secrets to import from files and keep sensitive usernames or passwords from being passed or preserved in plaintext.
|
||||||
|
|
||||||
You can set any environment variable from a file by appending `__FILE` (double-underscore FILE) to the environmental variable name.
|
You can set any environment variable from a file by appending `__FILE` (double-underscore FILE) to the environmental variable name.
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
version: "3.7"
|
version: '3.8'
|
||||||
|
|
||||||
secrets:
|
secrets:
|
||||||
# Secrets are single-line text files where the sole content is the secret
|
# Secrets are single-line text files where the sole content is the secret
|
||||||
@ -96,9 +121,7 @@ services:
|
|||||||
# DB_MYSQL_PASSWORD: "npm" # use secret instead
|
# DB_MYSQL_PASSWORD: "npm" # use secret instead
|
||||||
DB_MYSQL_PASSWORD__FILE: /run/secrets/MYSQL_PWD
|
DB_MYSQL_PASSWORD__FILE: /run/secrets/MYSQL_PWD
|
||||||
DB_MYSQL_NAME: "npm"
|
DB_MYSQL_NAME: "npm"
|
||||||
# If you would rather use Sqlite uncomment this
|
# If you would rather use Sqlite, remove all DB_MYSQL_* lines above
|
||||||
# and remove all DB_MYSQL_* lines above
|
|
||||||
# DB_SQLITE_FILE: "/data/database.sqlite"
|
|
||||||
# Uncomment this if IPv6 is not enabled on your host
|
# Uncomment this if IPv6 is not enabled on your host
|
||||||
# DISABLE_IPV6: 'true'
|
# DISABLE_IPV6: 'true'
|
||||||
volumes:
|
volumes:
|
||||||
@ -108,6 +131,7 @@ services:
|
|||||||
- MYSQL_PWD
|
- MYSQL_PWD
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
|
|
||||||
db:
|
db:
|
||||||
image: jc21/mariadb-aria
|
image: jc21/mariadb-aria
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@ -118,8 +142,9 @@ services:
|
|||||||
MYSQL_USER: "npm"
|
MYSQL_USER: "npm"
|
||||||
# MYSQL_PASSWORD: "npm" # use secret instead
|
# MYSQL_PASSWORD: "npm" # use secret instead
|
||||||
MYSQL_PASSWORD__FILE: /run/secrets/MYSQL_PWD
|
MYSQL_PASSWORD__FILE: /run/secrets/MYSQL_PWD
|
||||||
|
MARIADB_AUTO_UPGRADE: '1'
|
||||||
volumes:
|
volumes:
|
||||||
- ./data/mysql:/var/lib/mysql
|
- ./mysql:/var/lib/mysql
|
||||||
secrets:
|
secrets:
|
||||||
- DB_ROOT_PWD
|
- DB_ROOT_PWD
|
||||||
- MYSQL_PWD
|
- MYSQL_PWD
|
||||||
@ -148,6 +173,7 @@ NPM has the ability to include different custom configuration snippets in differ
|
|||||||
|
|
||||||
You can add your custom configuration snippet files at `/data/nginx/custom` as follow:
|
You can add your custom configuration snippet files at `/data/nginx/custom` as follow:
|
||||||
|
|
||||||
|
- `/data/nginx/custom/root_top.conf`: Included at the top of nginx.conf
|
||||||
- `/data/nginx/custom/root.conf`: Included at the very end of nginx.conf
|
- `/data/nginx/custom/root.conf`: Included at the very end of nginx.conf
|
||||||
- `/data/nginx/custom/http_top.conf`: Included at the top of the main http block
|
- `/data/nginx/custom/http_top.conf`: Included at the top of the main http block
|
||||||
- `/data/nginx/custom/http.conf`: Included at the end of the main http block
|
- `/data/nginx/custom/http.conf`: Included at the end of the main http block
|
||||||
@ -173,3 +199,26 @@ value by specifying it as a Docker environment variable. The default if not spec
|
|||||||
X_FRAME_OPTIONS: "sameorigin"
|
X_FRAME_OPTIONS: "sameorigin"
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Customising logrotate settings
|
||||||
|
|
||||||
|
By default, NPM rotates the access- and error logs weekly and keeps 4 and 10 log files respectively.
|
||||||
|
Depending on the usage, this can lead to large log files, especially access logs.
|
||||||
|
You can customise the logrotate configuration through a mount (if your custom config is `logrotate.custom`):
|
||||||
|
|
||||||
|
```yml
|
||||||
|
volumes:
|
||||||
|
...
|
||||||
|
- ./logrotate.custom:/etc/logrotate.d/nginx-proxy-manager
|
||||||
|
```
|
||||||
|
|
||||||
|
For reference, the default configuration can be found [here](https://github.com/NginxProxyManager/nginx-proxy-manager/blob/develop/docker/rootfs/etc/logrotate.d/nginx-proxy-manager).
|
||||||
|
|
||||||
|
## Enabling the geoip2 module
|
||||||
|
|
||||||
|
To enable the geoip2 module, you can create the custom configuration file `/data/nginx/custom/root_top.conf` and include the following snippet:
|
||||||
|
|
||||||
|
```
|
||||||
|
load_module /usr/lib/nginx/modules/ngx_http_geoip2_module.so;
|
||||||
|
load_module /usr/lib/nginx/modules/ngx_stream_geoip2_module.so;
|
||||||
|
```
|
@ -1,26 +1,26 @@
|
|||||||
|
---
|
||||||
|
outline: deep
|
||||||
|
---
|
||||||
|
|
||||||
# FAQ
|
# FAQ
|
||||||
|
|
||||||
## Do I have to use Docker?
|
## Do I have to use Docker?
|
||||||
|
|
||||||
Yes, that's how this project is packaged.
|
Yes, that's how this project is packaged.
|
||||||
|
|
||||||
This makes it easier to support the project when I have control over the version of Nginx and NodeJS
|
This makes it easier to support the project when we have control over the version of Nginx other packages
|
||||||
being used. In future this could change if the backend was no longer using NodeJS and it's long list
|
use by the project.
|
||||||
of dependencies.
|
|
||||||
|
|
||||||
|
|
||||||
## Can I run it on a Raspberry Pi?
|
## Can I run it on a Raspberry Pi?
|
||||||
|
|
||||||
Yes! The docker image is multi-arch and is built for a variety of architectures. If yours is
|
Yes! The docker image is multi-arch and is built for a variety of architectures. If yours is
|
||||||
[not listed](https://hub.docker.com/r/jc21/nginx-proxy-manager/tags) please open a
|
[not listed](https://hub.docker.com/r/jc21/nginx-proxy-manager/tags) please open a
|
||||||
[GitHub issue](https://github.com/jc21/nginx-proxy-manager/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=).
|
[GitHub issue](https://github.com/NginxProxyManager/nginx-proxy-manager/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=).
|
||||||
|
|
||||||
## I can't get my service to proxy properly?
|
## I can't get my service to proxy properly?
|
||||||
|
|
||||||
Your best bet is to ask the [Reddit community for support](https://www.reddit.com/r/nginxproxymanager/). There's safety in numbers.
|
Your best bet is to ask the [Reddit community for support](https://www.reddit.com/r/nginxproxymanager/). There's safety in numbers.
|
||||||
|
|
||||||
Gitter is best left for anyone contributing to the project to ask for help about internals, code reviews etc.
|
|
||||||
|
|
||||||
## When adding username and password access control to a proxy host, I can no longer login into the app.
|
## When adding username and password access control to a proxy host, I can no longer login into the app.
|
||||||
|
|
||||||
Having an Access Control List (ACL) with username and password requires the browser to always send this username and password in the `Authorization` header on each request. If your proxied app also requires authentication (like Nginx Proxy Manager itself), most likely the app will also use the `Authorization` header to transmit this information, as this is the standardized header meant for this kind of information. However having multiples of the same headers is not allowed in the [internet standard](https://www.rfc-editor.org/rfc/rfc7230#section-3.2.2) and almost all apps do not support multiple values in the `Authorization` header. Hence one of the two logins will be broken. This can only be fixed by either removing one of the logins or by changing the app to use other non-standard headers for authorization.
|
Having an Access Control List (ACL) with username and password requires the browser to always send this username and password in the `Authorization` header on each request. If your proxied app also requires authentication (like Nginx Proxy Manager itself), most likely the app will also use the `Authorization` header to transmit this information, as this is the standardized header meant for this kind of information. However having multiples of the same headers is not allowed in the [internet standard](https://www.rfc-editor.org/rfc/rfc7230#section-3.2.2) and almost all apps do not support multiple values in the `Authorization` header. Hence one of the two logins will be broken. This can only be fixed by either removing one of the logins or by changing the app to use other non-standard headers for authorization.
|
126
docs/src/guide/index.md
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
---
|
||||||
|
outline: deep
|
||||||
|
---
|
||||||
|
|
||||||
|
# Guide
|
||||||
|
|
||||||
|
::: raw
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://hub.docker.com/repository/docker/jc21/nginx-proxy-manager" style="display:inline;margin-right:5px;">
|
||||||
|
<img src="https://img.shields.io/docker/stars/jc21/nginx-proxy-manager.svg?style=for-the-badge" style="display:inline;">
|
||||||
|
</a>
|
||||||
|
<a href="https://hub.docker.com/repository/docker/jc21/nginx-proxy-manager" style="display:inline;margin-right:5px;">
|
||||||
|
<img src="https://img.shields.io/docker/pulls/jc21/nginx-proxy-manager.svg?style=for-the-badge" style="display:inline;">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
:::
|
||||||
|
|
||||||
|
This project comes as a pre-built docker image that enables you to easily forward to your websites
|
||||||
|
running at home or otherwise, including free SSL, without having to know too much about Nginx or Letsencrypt.
|
||||||
|
|
||||||
|
- [Quick Setup](#quick-setup)
|
||||||
|
- [Full Setup](/setup/)
|
||||||
|
- [Screenshots](/screenshots/)
|
||||||
|
|
||||||
|
## Project Goal
|
||||||
|
|
||||||
|
I created this project to fill a personal need to provide users with an easy way to accomplish reverse
|
||||||
|
proxying hosts with SSL termination and it had to be so easy that a monkey could do it. This goal hasn't changed.
|
||||||
|
While there might be advanced options they are optional and the project should be as simple as possible
|
||||||
|
so that the barrier for entry here is low.
|
||||||
|
|
||||||
|
::: raw
|
||||||
|
<a href="https://www.buymeacoffee.com/jc21" target="_blank"><img src="http://public.jc21.com/github/by-me-a-coffee.png" alt="Buy Me A Coffee" style="height: 51px !important;width: 217px !important;" ></a>
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Beautiful and Secure Admin Interface based on [Tabler](https://tabler.github.io/)
|
||||||
|
- Easily create forwarding domains, redirections, streams and 404 hosts without knowing anything about Nginx
|
||||||
|
- Free SSL using Let's Encrypt or provide your own custom SSL certificates
|
||||||
|
- Access Lists and basic HTTP Authentication for your hosts
|
||||||
|
- Advanced Nginx configuration available for super users
|
||||||
|
- User management, permissions and audit log
|
||||||
|
|
||||||
|
|
||||||
|
## Hosting your home network
|
||||||
|
|
||||||
|
I won't go in to too much detail here but here are the basics for someone new to this self-hosted world.
|
||||||
|
|
||||||
|
1. Your home router will have a Port Forwarding section somewhere. Log in and find it
|
||||||
|
2. Add port forwarding for port 80 and 443 to the server hosting this project
|
||||||
|
3. Configure your domain name details to point to your home, either with a static ip or a service like DuckDNS or [Amazon Route53](https://github.com/jc21/route53-ddns)
|
||||||
|
4. Use the Nginx Proxy Manager as your gateway to forward to your other web based services
|
||||||
|
|
||||||
|
## Quick Setup
|
||||||
|
|
||||||
|
1. Install Docker and Docker-Compose
|
||||||
|
|
||||||
|
- [Docker Install documentation](https://docs.docker.com/get-docker/)
|
||||||
|
- [Docker-Compose Install documentation](https://docs.docker.com/compose/install/)
|
||||||
|
|
||||||
|
2. Create a docker-compose.yml file similar to this:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
version: '3.8'
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
image: 'jc21/nginx-proxy-manager:latest'
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- '80:80'
|
||||||
|
- '81:81'
|
||||||
|
- '443:443'
|
||||||
|
volumes:
|
||||||
|
- ./data:/data
|
||||||
|
- ./letsencrypt:/etc/letsencrypt
|
||||||
|
```
|
||||||
|
|
||||||
|
This is the bare minimum configuration required. See the [documentation](https://nginxproxymanager.com/setup/) for more.
|
||||||
|
|
||||||
|
3. Bring up your stack by running
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# If using docker-compose-plugin
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Log in to the Admin UI
|
||||||
|
|
||||||
|
When your docker container is running, connect to it on port `81` for the admin interface.
|
||||||
|
Sometimes this can take a little bit because of the entropy of keys.
|
||||||
|
|
||||||
|
[http://127.0.0.1:81](http://127.0.0.1:81)
|
||||||
|
|
||||||
|
Default Admin User:
|
||||||
|
```
|
||||||
|
Email: admin@example.com
|
||||||
|
Password: changeme
|
||||||
|
```
|
||||||
|
|
||||||
|
Immediately after logging in with this default user you will be asked to modify your details and change your password.
|
||||||
|
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
All are welcome to create pull requests for this project, against the `develop` branch. Official releases are created from the `master` branch.
|
||||||
|
|
||||||
|
CI is used in this project. All PR's must pass before being considered. After passing,
|
||||||
|
docker builds for PR's are available on dockerhub for manual verifications.
|
||||||
|
|
||||||
|
Documentation within the `develop` branch is available for preview at
|
||||||
|
[https://develop.nginxproxymanager.com](https://develop.nginxproxymanager.com)
|
||||||
|
|
||||||
|
|
||||||
|
### Contributors
|
||||||
|
|
||||||
|
Special thanks to [all of our contributors](https://github.com/NginxProxyManager/nginx-proxy-manager/graphs/contributors).
|
||||||
|
|
||||||
|
|
||||||
|
## Getting Support
|
||||||
|
|
||||||
|
1. [Found a bug?](https://github.com/NginxProxyManager/nginx-proxy-manager/issues)
|
||||||
|
2. [Discussions](https://github.com/NginxProxyManager/nginx-proxy-manager/discussions)
|
||||||
|
3. [Reddit](https://reddit.com/r/nginxproxymanager)
|
32
docs/src/index.md
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
# https://vitepress.dev/reference/default-theme-home-page
|
||||||
|
layout: home
|
||||||
|
|
||||||
|
hero:
|
||||||
|
name: "Nginx Proxy Manager"
|
||||||
|
tagline: Expose your services easily and securely
|
||||||
|
image:
|
||||||
|
src: /logo.svg
|
||||||
|
alt: NPM Logo
|
||||||
|
actions:
|
||||||
|
- theme: brand
|
||||||
|
text: Get Started
|
||||||
|
link: /guide/
|
||||||
|
- theme: alt
|
||||||
|
text: GitHub
|
||||||
|
link: https://github.com/NginxProxyManager/nginx-proxy-manager
|
||||||
|
|
||||||
|
features:
|
||||||
|
- title: Get Connected
|
||||||
|
details: Expose web services on your network · Free SSL with Let's Encrypt · Designed with security in mind · Perfect for home networks
|
||||||
|
- title: Proxy Hosts
|
||||||
|
details: Expose your private network Web services and get connected anywhere.
|
||||||
|
- title: Beautiful UI
|
||||||
|
details: Based on Tabler, the interface is a pleasure to use. Configuring a server has never been so fun.
|
||||||
|
- title: Free SSL
|
||||||
|
details: Built in Let’s Encrypt support allows you to secure your Web services at no cost to you. The certificates even renew themselves!
|
||||||
|
- title: Docker FTW
|
||||||
|
details: Built as a Docker Image, Nginx Proxy Manager only requires a database.
|
||||||
|
- title: Multiple Users
|
||||||
|
details: Configure other users to either view or manage their own hosts. Full access permissions are available.
|
||||||
|
---
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
Before Width: | Height: | Size: 178 KiB After Width: | Height: | Size: 178 KiB |
Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 173 KiB |
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 141 KiB |
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 102 KiB |
Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 150 KiB |
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 151 KiB |