Compare commits
309 Commits
Author | SHA1 | Date | |
---|---|---|---|
fdb22e467b | |||
694d8a0f21 | |||
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 | |||
c40e48e678 | |||
124cb18e17 | |||
5ac9dc0758 | |||
9a799d51ce | |||
77eb618758 | |||
79fedfcea4 | |||
8fdb8ac853 | |||
4fdc80be01 | |||
f8e6c8d018 | |||
c3469de61b | |||
ea61b15a40 | |||
60175e6d8c | |||
2a07445005 | |||
dad3e1da7c | |||
a91dcb144d | |||
e7f7be2a2b | |||
076d89b5b5 | |||
8539930f89 | |||
87d9babbd3 | |||
9f2d3a1737 | |||
daf399163c | |||
cdf702e545 | |||
5811345050 | |||
53792a5cf7 | |||
8e10b7da37 |
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
|
||||||
|
148
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,7 +58,9 @@ pipeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stage('Build and Test') {
|
stage('Builds') {
|
||||||
|
parallel {
|
||||||
|
stage('Project') {
|
||||||
steps {
|
steps {
|
||||||
script {
|
script {
|
||||||
// Frontend and Backend
|
// Frontend and Backend
|
||||||
@ -86,75 +84,70 @@ pipeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stage('Integration Tests Sqlite') {
|
|
||||||
steps {
|
|
||||||
// Bring up a stack
|
|
||||||
sh 'docker-compose up -d fullstack-sqlite'
|
|
||||||
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') {
|
stage('Docs') {
|
||||||
|
steps {
|
||||||
|
dir(path: 'docs') {
|
||||||
|
sh 'yarn install'
|
||||||
|
sh 'yarn build'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Test Sqlite') {
|
||||||
|
environment {
|
||||||
|
COMPOSE_PROJECT_NAME = "npm_${BRANCH_LOWER}_${BUILD_NUMBER}_sqlite"
|
||||||
|
COMPOSE_FILE = 'docker/docker-compose.ci.yml:docker/docker-compose.ci.sqlite.yml'
|
||||||
|
}
|
||||||
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 {
|
||||||
dir(path: 'docs/.vuepress/dist') {
|
always {
|
||||||
sh 'tar -czf ../../docs.tgz *'
|
// 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'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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,17 +163,19 @@ pipeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stage('Docs Deploy') {
|
stage('Docs / Comment') {
|
||||||
|
parallel {
|
||||||
|
stage('Docs Job') {
|
||||||
when {
|
when {
|
||||||
allOf {
|
allOf {
|
||||||
branch 'master'
|
branch pattern: "^(develop|master)\$", comparator: "REGEXP"
|
||||||
not {
|
not {
|
||||||
equals expected: 'UNSTABLE', actual: currentBuild.result
|
equals expected: 'UNSTABLE', actual: currentBuild.result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
steps {
|
steps {
|
||||||
npmDocsRelease("$DOCS_BUCKET", "$DOCS_CDN")
|
build wait: false, job: 'nginx-proxy-manager-docs', parameters: [string(name: 'docs_branch', value: "$BRANCH_NAME")]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stage('PR Comment') {
|
stage('PR Comment') {
|
||||||
@ -199,23 +194,24 @@ pipeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
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.22-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,21 +1,22 @@
|
|||||||
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 {
|
return {
|
||||||
client: config.database.engine,
|
client: cfg.engine,
|
||||||
connection: {
|
connection: {
|
||||||
host: config.database.host,
|
host: cfg.host,
|
||||||
user: config.database.user,
|
user: cfg.user,
|
||||||
password: config.database.password,
|
password: cfg.password,
|
||||||
database: config.database.name,
|
database: cfg.name,
|
||||||
port: config.database.port
|
port: cfg.port
|
||||||
},
|
},
|
||||||
migrations: {
|
migrations: {
|
||||||
tableName: 'migrations'
|
tableName: 'migrations'
|
||||||
@ -23,11 +24,4 @@ function generateDbConfig() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
|
@ -4,20 +4,24 @@ 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 config = require('../lib/config');
|
||||||
const error = require('../lib/error');
|
const error = require('../lib/error');
|
||||||
const utils = require('../lib/utils');
|
const utils = require('../lib/utils');
|
||||||
const certificateModel = require('../models/certificate');
|
const certificateModel = require('../models/certificate');
|
||||||
const dnsPlugins = require('../global/certbot-dns-plugins');
|
const tokenModel = require('../models/token');
|
||||||
|
const dnsPlugins = require('../global/certbot-dns-plugins.json');
|
||||||
const internalAuditLog = require('./audit-log');
|
const internalAuditLog = require('./audit-log');
|
||||||
const internalNginx = require('./nginx');
|
const internalNginx = require('./nginx');
|
||||||
const internalHost = require('./host');
|
const internalHost = require('./host');
|
||||||
const letsencryptStaging = process.env.NODE_ENV !== 'production';
|
const certbot = require('../lib/certbot');
|
||||||
const letsencryptConfig = '/etc/letsencrypt.ini';
|
|
||||||
const certbotCommand = 'certbot';
|
|
||||||
const archiver = require('archiver');
|
const archiver = require('archiver');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { isArray } = require('lodash');
|
const { isArray } = require('lodash');
|
||||||
|
|
||||||
|
const letsencryptStaging = config.useLetsencryptStaging();
|
||||||
|
const letsencryptConfig = '/etc/letsencrypt.ini';
|
||||||
|
const certbotCommand = 'certbot';
|
||||||
|
|
||||||
function omissions() {
|
function omissions() {
|
||||||
return ['is_deleted'];
|
return ['is_deleted'];
|
||||||
}
|
}
|
||||||
@ -28,6 +32,7 @@ const internalCertificate = {
|
|||||||
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) {
|
|
||||||
logger.info('Renew Result: ' + result);
|
|
||||||
}
|
|
||||||
|
|
||||||
return internalNginx.reload()
|
|
||||||
.then(() => {
|
|
||||||
logger.info('Renew Complete');
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
// Now go and fetch all the letsencrypt certs from the db and query the files and update expiry times
|
|
||||||
return certificateModel
|
|
||||||
.query()
|
.query()
|
||||||
.where('is_deleted', 0)
|
.where('is_deleted', 0)
|
||||||
.andWhere('provider', 'letsencrypt')
|
.andWhere('provider', 'letsencrypt')
|
||||||
|
.andWhere('expires_on', '<', expirationThreshold)
|
||||||
.then((certificates) => {
|
.then((certificates) => {
|
||||||
if (certificates && certificates.length) {
|
if (!certificates || !certificates.length) {
|
||||||
let promises = [];
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
certificates.map(function (certificate) {
|
/**
|
||||||
promises.push(
|
* Renews must be run sequentially or we'll get an error 'Another
|
||||||
internalCertificate.getCertificateInfoFromFile('/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem')
|
* instance of Certbot is already running.'
|
||||||
.then((cert_info) => {
|
*/
|
||||||
return certificateModel
|
let sequence = Promise.resolve();
|
||||||
.query()
|
|
||||||
.where('id', certificate.id)
|
certificates.forEach(function (certificate) {
|
||||||
.andWhere('provider', 'letsencrypt')
|
sequence = sequence.then(() =>
|
||||||
.patch({
|
internalCertificate
|
||||||
expires_on: moment(cert_info.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss')
|
.renew(
|
||||||
});
|
{
|
||||||
})
|
can: () =>
|
||||||
|
Promise.resolve({
|
||||||
|
permission_visibility: 'all',
|
||||||
|
}),
|
||||||
|
token: new tokenModel(),
|
||||||
|
},
|
||||||
|
{ id: certificate.id },
|
||||||
|
)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
// Don't want to stop the train here, just log the error
|
// Don't want to stop the train here, just log the error
|
||||||
logger.error(err.message);
|
logger.error(err.message);
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return Promise.all(promises);
|
return sequence;
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
.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)
|
|
||||||
.then(async (result) => {
|
|
||||||
logger.info(result);
|
logger.info(result);
|
||||||
return result;
|
return result;
|
||||||
});
|
} catch (err) {
|
||||||
});
|
// Don't fail if file does not exist, so no need for action in the callback
|
||||||
}).catch(async (err) => {
|
fs.unlink(credentialsLocation, () => {});
|
||||||
// Don't fail if file does not exist
|
|
||||||
const delete_credentialsCmd = `rm -f '${credentialsLocation}' || true`;
|
|
||||||
await utils.exec(delete_credentialsCmd);
|
|
||||||
throw err;
|
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 () {
|
||||||
|
try {
|
||||||
const parsedBody = JSON.parse(responseBody + '');
|
const parsedBody = JSON.parse(responseBody + '');
|
||||||
if (res.statusCode !== 200) {
|
if (res.statusCode !== 200) {
|
||||||
logger.warn(`Failed to test HTTP challenge for domain ${domain}`, res);
|
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 config = require('../lib/config');
|
||||||
const utils = require('../lib/utils');
|
const utils = require('../lib/utils');
|
||||||
const error = require('../lib/error');
|
const error = require('../lib/error');
|
||||||
const debug_mode = process.env.NODE_ENV !== 'production' || !!process.env.DEBUG;
|
|
||||||
|
|
||||||
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'})
|
||||||
|
48
backend/migrations/20200522113248_openid_connect.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
const migrate_name = 'openid_connect';
|
||||||
|
const logger = require('../logger').migrate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrate
|
||||||
|
*
|
||||||
|
* @see http://knexjs.org/#Schema
|
||||||
|
*
|
||||||
|
* @param {Object} knex
|
||||||
|
* @param {Promise} Promise
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
exports.up = function (knex/*, Promise*/) {
|
||||||
|
logger.info('[' + migrate_name + '] Migrating Up...');
|
||||||
|
|
||||||
|
return knex.schema.table('proxy_host', function (proxy_host) {
|
||||||
|
proxy_host.integer('openidc_enabled').notNull().unsigned().defaultTo(0);
|
||||||
|
proxy_host.text('openidc_redirect_uri').notNull().defaultTo('');
|
||||||
|
proxy_host.text('openidc_discovery').notNull().defaultTo('');
|
||||||
|
proxy_host.text('openidc_auth_method').notNull().defaultTo('');
|
||||||
|
proxy_host.text('openidc_client_id').notNull().defaultTo('');
|
||||||
|
proxy_host.text('openidc_client_secret').notNull().defaultTo('');
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
logger.info('[' + migrate_name + '] proxy_host Table altered');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Undo Migrate
|
||||||
|
*
|
||||||
|
* @param {Object} knex
|
||||||
|
* @param {Promise} Promise
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
exports.down = function (knex/*, Promise*/) {
|
||||||
|
return knex.schema.table('proxy_host', function (proxy_host) {
|
||||||
|
proxy_host.dropColumn('openidc_enabled');
|
||||||
|
proxy_host.dropColumn('openidc_redirect_uri');
|
||||||
|
proxy_host.dropColumn('openidc_discovery');
|
||||||
|
proxy_host.dropColumn('openidc_auth_method');
|
||||||
|
proxy_host.dropColumn('openidc_client_id');
|
||||||
|
proxy_host.dropColumn('openidc_client_secret');
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
logger.info('[' + migrate_name + '] proxy_host Table altered');
|
||||||
|
});
|
||||||
|
};
|
40
backend/migrations/20200522144240_openid_allowed_users.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
const migrate_name = 'openid_allowed_users';
|
||||||
|
const logger = require('../logger').migrate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrate
|
||||||
|
*
|
||||||
|
* @see http://knexjs.org/#Schema
|
||||||
|
*
|
||||||
|
* @param {Object} knex
|
||||||
|
* @param {Promise} Promise
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
exports.up = function (knex/*, Promise*/) {
|
||||||
|
logger.info('[' + migrate_name + '] Migrating Up...');
|
||||||
|
|
||||||
|
return knex.schema.table('proxy_host', function (proxy_host) {
|
||||||
|
proxy_host.integer('openidc_restrict_users_enabled').notNull().unsigned().defaultTo(0);
|
||||||
|
proxy_host.json('openidc_allowed_users').notNull().defaultTo([]);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
logger.info('[' + migrate_name + '] proxy_host Table altered');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Undo Migrate
|
||||||
|
*
|
||||||
|
* @param {Object} knex
|
||||||
|
* @param {Promise} Promise
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
exports.down = function (knex/*, Promise*/) {
|
||||||
|
return knex.schema.table('proxy_host', function (proxy_host) {
|
||||||
|
proxy_host.dropColumn('openidc_restrict_users_enabled');
|
||||||
|
proxy_host.dropColumn('openidc_allowed_users');
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
logger.info('[' + migrate_name + '] proxy_host Table altered');
|
||||||
|
});
|
||||||
|
};
|
@ -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')");
|
||||||
}
|
}
|
||||||
|
@ -20,12 +20,23 @@ class ProxyHost extends Model {
|
|||||||
this.domain_names = [];
|
this.domain_names = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default for openidc_allowed_users
|
||||||
|
if (typeof this.openidc_allowed_users === 'undefined') {
|
||||||
|
this.openidc_allowed_users = [];
|
||||||
|
}
|
||||||
|
|
||||||
// Default for meta
|
// Default for meta
|
||||||
if (typeof this.meta === 'undefined') {
|
if (typeof this.meta === 'undefined') {
|
||||||
this.meta = {};
|
this.meta = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Openidc defaults
|
||||||
|
if (typeof this.openidc_auth_method === 'undefined') {
|
||||||
|
this.openidc_auth_method = 'client_secret_post';
|
||||||
|
}
|
||||||
|
|
||||||
this.domain_names.sort();
|
this.domain_names.sort();
|
||||||
|
this.openidc_allowed_users.sort();
|
||||||
}
|
}
|
||||||
|
|
||||||
$beforeUpdate () {
|
$beforeUpdate () {
|
||||||
@ -35,6 +46,11 @@ class ProxyHost extends Model {
|
|||||||
if (typeof this.domain_names !== 'undefined') {
|
if (typeof this.domain_names !== 'undefined') {
|
||||||
this.domain_names.sort();
|
this.domain_names.sort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sort openidc_allowed_users
|
||||||
|
if (typeof this.openidc_allowed_users !== 'undefined') {
|
||||||
|
this.openidc_allowed_users.sort();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static get name () {
|
static get name () {
|
||||||
@ -46,7 +62,7 @@ class ProxyHost extends Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static get jsonAttributes () {
|
static get jsonAttributes () {
|
||||||
return ['domain_names', 'meta', 'locations'];
|
return ['domain_names', 'meta', 'locations', 'openidc_allowed_users'];
|
||||||
}
|
}
|
||||||
|
|
||||||
static get relationMappings () {
|
static get relationMappings () {
|
||||||
|
@ -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",
|
||||||
@ -235,6 +235,43 @@
|
|||||||
"description": "Should we cache assets",
|
"description": "Should we cache assets",
|
||||||
"example": true,
|
"example": true,
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"openidc_enabled": {
|
||||||
|
"description": "Is OpenID Connect authentication enabled",
|
||||||
|
"example": true,
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"openidc_redirect_uri": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"openidc_discovery": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"openidc_auth_method": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^(client_secret_basic|client_secret_post)$"
|
||||||
|
},
|
||||||
|
"openidc_client_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"openidc_client_secret": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"openidc_restrict_users_enabled": {
|
||||||
|
"description": "Only allow a specific set of OpenID Connect emails to access the resource",
|
||||||
|
"example": true,
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"openidc_allowed_users": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 0,
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Email Address",
|
||||||
|
"example": "john@example.com",
|
||||||
|
"format": "email",
|
||||||
|
"minLength": 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,30 @@
|
|||||||
"advanced_config": {
|
"advanced_config": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"openidc_enabled": {
|
||||||
|
"$ref": "../definitions.json#/definitions/openidc_enabled"
|
||||||
|
},
|
||||||
|
"openidc_redirect_uri": {
|
||||||
|
"$ref": "../definitions.json#/definitions/openidc_redirect_uri"
|
||||||
|
},
|
||||||
|
"openidc_discovery": {
|
||||||
|
"$ref": "../definitions.json#/definitions/openidc_discovery"
|
||||||
|
},
|
||||||
|
"openidc_auth_method": {
|
||||||
|
"$ref": "../definitions.json#/definitions/openidc_auth_method"
|
||||||
|
},
|
||||||
|
"openidc_client_id": {
|
||||||
|
"$ref": "../definitions.json#/definitions/openidc_client_id"
|
||||||
|
},
|
||||||
|
"openidc_client_secret": {
|
||||||
|
"$ref": "../definitions.json#/definitions/openidc_client_secret"
|
||||||
|
},
|
||||||
|
"openidc_restrict_users_enabled": {
|
||||||
|
"$ref": "../definitions.json#/definitions/openidc_restrict_users_enabled"
|
||||||
|
},
|
||||||
|
"openidc_allowed_users": {
|
||||||
|
"$ref": "../definitions.json#/definitions/openidc_allowed_users"
|
||||||
|
},
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"$ref": "../definitions.json#/definitions/enabled"
|
"$ref": "../definitions.json#/definitions/enabled"
|
||||||
},
|
},
|
||||||
@ -161,6 +185,30 @@
|
|||||||
"advanced_config": {
|
"advanced_config": {
|
||||||
"$ref": "#/definitions/advanced_config"
|
"$ref": "#/definitions/advanced_config"
|
||||||
},
|
},
|
||||||
|
"openidc_enabled": {
|
||||||
|
"$ref": "#/definitions/openidc_enabled"
|
||||||
|
},
|
||||||
|
"openidc_redirect_uri": {
|
||||||
|
"$ref": "#/definitions/openidc_redirect_uri"
|
||||||
|
},
|
||||||
|
"openidc_discovery": {
|
||||||
|
"$ref": "#/definitions/openidc_discovery"
|
||||||
|
},
|
||||||
|
"openidc_auth_method": {
|
||||||
|
"$ref": "#/definitions/openidc_auth_method"
|
||||||
|
},
|
||||||
|
"openidc_client_id": {
|
||||||
|
"$ref": "#/definitions/openidc_client_id"
|
||||||
|
},
|
||||||
|
"openidc_client_secret": {
|
||||||
|
"$ref": "#/definitions/openidc_client_secret"
|
||||||
|
},
|
||||||
|
"openidc_restrict_users_enabled": {
|
||||||
|
"$ref": "#/definitions/openidc_restrict_users_enabled"
|
||||||
|
},
|
||||||
|
"openidc_allowed_users": {
|
||||||
|
"$ref": "#/definitions/openidc_allowed_users"
|
||||||
|
},
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"$ref": "#/definitions/enabled"
|
"$ref": "#/definitions/enabled"
|
||||||
},
|
},
|
||||||
@ -251,6 +299,30 @@
|
|||||||
"advanced_config": {
|
"advanced_config": {
|
||||||
"$ref": "#/definitions/advanced_config"
|
"$ref": "#/definitions/advanced_config"
|
||||||
},
|
},
|
||||||
|
"openidc_enabled": {
|
||||||
|
"$ref": "#/definitions/openidc_enabled"
|
||||||
|
},
|
||||||
|
"openidc_redirect_uri": {
|
||||||
|
"$ref": "#/definitions/openidc_redirect_uri"
|
||||||
|
},
|
||||||
|
"openidc_discovery": {
|
||||||
|
"$ref": "#/definitions/openidc_discovery"
|
||||||
|
},
|
||||||
|
"openidc_auth_method": {
|
||||||
|
"$ref": "#/definitions/openidc_auth_method"
|
||||||
|
},
|
||||||
|
"openidc_client_id": {
|
||||||
|
"$ref": "#/definitions/openidc_client_id"
|
||||||
|
},
|
||||||
|
"openidc_client_secret": {
|
||||||
|
"$ref": "#/definitions/openidc_client_secret"
|
||||||
|
},
|
||||||
|
"openidc_restrict_users_enabled": {
|
||||||
|
"$ref": "#/definitions/openidc_restrict_users_enabled"
|
||||||
|
},
|
||||||
|
"openidc_allowed_users": {
|
||||||
|
"$ref": "#/definitions/openidc_allowed_users"
|
||||||
|
},
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"$ref": "#/definitions/enabled"
|
"$ref": "#/definitions/enabled"
|
||||||
},
|
},
|
||||||
@ -324,6 +396,30 @@
|
|||||||
"advanced_config": {
|
"advanced_config": {
|
||||||
"$ref": "#/definitions/advanced_config"
|
"$ref": "#/definitions/advanced_config"
|
||||||
},
|
},
|
||||||
|
"openidc_enabled": {
|
||||||
|
"$ref": "#/definitions/openidc_enabled"
|
||||||
|
},
|
||||||
|
"openidc_redirect_uri": {
|
||||||
|
"$ref": "#/definitions/openidc_redirect_uri"
|
||||||
|
},
|
||||||
|
"openidc_discovery": {
|
||||||
|
"$ref": "#/definitions/openidc_discovery"
|
||||||
|
},
|
||||||
|
"openidc_auth_method": {
|
||||||
|
"$ref": "#/definitions/openidc_auth_method"
|
||||||
|
},
|
||||||
|
"openidc_client_id": {
|
||||||
|
"$ref": "#/definitions/openidc_client_id"
|
||||||
|
},
|
||||||
|
"openidc_client_secret": {
|
||||||
|
"$ref": "#/definitions/openidc_client_secret"
|
||||||
|
},
|
||||||
|
"openidc_restrict_users_enabled": {
|
||||||
|
"$ref": "#/definitions/openidc_restrict_users_enabled"
|
||||||
|
},
|
||||||
|
"openidc_allowed_users": {
|
||||||
|
"$ref": "#/definitions/openidc_allowed_users"
|
||||||
|
},
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"$ref": "#/definitions/enabled"
|
"$ref": "#/definitions/enabled"
|
||||||
},
|
},
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
});
|
@ -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) {
|
if (promises.length) {
|
||||||
return Promise.all(promises)
|
return Promise.all(promises)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
logger.info('Added Certbot plugins ' + plugins.join(', '));
|
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 }}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
47
backend/templates/_openid_connect.conf
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
{% if openidc_enabled == 1 or openidc_enabled == true -%}
|
||||||
|
access_by_lua_block {
|
||||||
|
local openidc = require("resty.openidc")
|
||||||
|
local opts = {
|
||||||
|
redirect_uri = "{{- openidc_redirect_uri -}}",
|
||||||
|
discovery = "{{- openidc_discovery -}}",
|
||||||
|
token_endpoint_auth_method = "{{- openidc_auth_method -}}",
|
||||||
|
client_id = "{{- openidc_client_id -}}",
|
||||||
|
client_secret = "{{- openidc_client_secret -}}",
|
||||||
|
scope = "openid email profile"
|
||||||
|
}
|
||||||
|
|
||||||
|
local res, err = openidc.authenticate(opts)
|
||||||
|
|
||||||
|
if err then
|
||||||
|
ngx.status = 500
|
||||||
|
ngx.say(err)
|
||||||
|
ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
|
||||||
|
end
|
||||||
|
|
||||||
|
{% if openidc_restrict_users_enabled == 1 or openidc_restrict_users_enabled == true -%}
|
||||||
|
local function contains(table, val)
|
||||||
|
for i=1,#table do
|
||||||
|
if table[i] == val then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local allowed_users = {
|
||||||
|
{% for user in openidc_allowed_users %}
|
||||||
|
"{{ user }}",
|
||||||
|
{% endfor %}
|
||||||
|
}
|
||||||
|
|
||||||
|
if not contains(allowed_users, res.id_token.email) then
|
||||||
|
ngx.exit(ngx.HTTP_FORBIDDEN)
|
||||||
|
end
|
||||||
|
{% endif -%}
|
||||||
|
|
||||||
|
|
||||||
|
ngx.req.set_header("X-OIDC-SUB", res.id_token.sub)
|
||||||
|
ngx.req.set_header("X-OIDC-EMAIL", res.id_token.email)
|
||||||
|
ngx.req.set_header("X-OIDC-NAME", res.id_token.name)
|
||||||
|
}
|
||||||
|
{% endif %}
|
@ -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 }}";
|
||||||
@ -30,8 +33,30 @@ proxy_http_version 1.1;
|
|||||||
|
|
||||||
location / {
|
location / {
|
||||||
|
|
||||||
{% include "_access.conf" %}
|
{% if access_list_id > 0 %}
|
||||||
{% include "_hsts.conf" %}
|
{% if access_list.items.length > 0 %}
|
||||||
|
# Authorization
|
||||||
|
auth_basic "Authorization required";
|
||||||
|
auth_basic_user_file /data/access/{{ access_list_id }};
|
||||||
|
|
||||||
|
{{ access_list.passauth }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
# Access Rules
|
||||||
|
{% for client in access_list.clients %}
|
||||||
|
{{- client.rule -}};
|
||||||
|
{% endfor %}deny all;
|
||||||
|
|
||||||
|
# Access checks must...
|
||||||
|
{% if access_list.satisfy %}
|
||||||
|
{{ access_list.satisfy }};
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% include "_openid_connect.conf" %}
|
||||||
|
{% include "_access.conf" %}
|
||||||
|
{% include "_hsts.conf" %}
|
||||||
|
|
||||||
{% if allow_websocket_upgrade == 1 or allow_websocket_upgrade == true %}
|
{% if allow_websocket_upgrade == 1 or allow_websocket_upgrade == true %}
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
@ -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
|
|
@ -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,11 +46,23 @@ 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;
|
||||||
|
|
||||||
|
lua_package_path '~/lua/?.lua;;';
|
||||||
|
|
||||||
|
lua_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
|
||||||
|
lua_ssl_verify_depth 5;
|
||||||
|
|
||||||
|
# cache for discovery metadata documents
|
||||||
|
lua_shared_dict discovery 1m;
|
||||||
|
# cache for JWKs
|
||||||
|
lua_shared_dict jwks 1m;
|
||||||
|
|
||||||
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 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"';
|
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;
|
access_log /data/logs/fallback_access.log proxy;
|
||||||
|
|
||||||
|
include /etc/nginx/conf.d/include/log.conf;
|
||||||
|
|
||||||
# Dynamically generated resolvers file
|
# Dynamically generated resolvers file
|
||||||
include /etc/nginx/conf.d/include/resolvers.conf;
|
include /etc/nginx/conf.d/include/resolvers.conf;
|
||||||
|
|
||||||
|
@ -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,93 +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
|
|
||||||
|
|
||||||
echo "Changing ownership of /data/logs to $(id -u):$(id -g)"
|
|
||||||
chown -R "$(id -u):$(id -g)" /data/logs
|
|
||||||
|
|
||||||
# Handle IPV6 settings
|
|
||||||
/bin/handle-ipv6-setting /etc/nginx/conf.d
|
|
||||||
/bin/handle-ipv6-setting /data/nginx
|
|
||||||
|
|
||||||
# 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"
|
|
||||||
echo "❯ Secrets-init ..."
|
|
||||||
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 "[secret-init] Success! ${STRIPFILE##*/} set from ${FILENAME##*/}"
|
|
||||||
|
|
||||||
else
|
|
||||||
echo "[secret-init] cannot find secret in ${FILENAME}"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
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,48 @@ value by specifying it as a Docker environment variable. The default if not spec
|
|||||||
X_FRAME_OPTIONS: "sameorigin"
|
X_FRAME_OPTIONS: "sameorigin"
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## OpenID Connect SSO
|
||||||
|
|
||||||
|
You can secure any of your proxy hosts with OpenID Connect authentication, providing SSO support from an identity provider like Azure AD or KeyCloak. OpenID Connect support is provided through the [`lua-resty-openidc`](https://github.com/zmartzone/lua-resty-openidc) library of [`OpenResty`](https://github.com/openresty/openresty).
|
||||||
|
|
||||||
|
You will need a few things to get started with OpenID Connect:
|
||||||
|
|
||||||
|
- A registered application with your identity provider, they will provide you with a `Client ID` and a `Client Secret`. Public OpenID Connect applications (without a client secret) are not yet supported.
|
||||||
|
|
||||||
|
- A redirect URL to send the users to after they login with the identity provider, this can be any unused URL under the proxy host, like `https://<proxy host url>/private/callback`, the server will take care of capturing that URL and redirecting you to the proxy host root. You will need to add this URL to the list of allowed redirect URLs for the application you registered with your identity provider.
|
||||||
|
|
||||||
|
- The well-known discovery endpoint of the identity provider you want to use, this is an URL usually with the form `https://<provider URL>/.well-known/openid-configuration`.
|
||||||
|
|
||||||
|
After you have all this you can proceed to configure the proxy host with OpenID Connect authentication.
|
||||||
|
|
||||||
|
You can also add some rudimentary access control through a list of allowed emails in case your identity provider doesn't let you do that, if this option is enabled, any email not on that list will be denied access to the proxied host.
|
||||||
|
|
||||||
|
The proxy adds some headers based on the authentication result from the identity provider:
|
||||||
|
|
||||||
|
- `X-OIDC-SUB`: The subject identifier, according to the OpenID Coonect spec: `A locally unique and never reassigned identifier within the Issuer for the End-User`.
|
||||||
|
- `X-OIDC-EMAIL`: The email of the user that logged in, as specified in the `id_token` returned from the identity provider. The same value that will be checked for the email whitelist.
|
||||||
|
- `X-OIDC-NAME`: The user's name claim from the `id_token`, please note that not all id tokens necessarily contain this claim.
|
||||||
|
|
||||||
|
## 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 |