mirror of
https://github.com/NginxProxyManager/nginx-proxy-manager.git
synced 2025-06-18 10:06:26 +00:00
Compare commits
11 Commits
v2.10.4
...
a91dcb144d
Author | SHA1 | Date | |
---|---|---|---|
a91dcb144d | |||
e7f7be2a2b | |||
076d89b5b5 | |||
8539930f89 | |||
87d9babbd3 | |||
9f2d3a1737 | |||
daf399163c | |||
cdf702e545 | |||
5811345050 | |||
53792a5cf7 | |||
8e10b7da37 |
111
Jenkinsfile
vendored
111
Jenkinsfile
vendored
@ -1,9 +1,3 @@
|
|||||||
import groovy.transform.Field
|
|
||||||
|
|
||||||
@Field
|
|
||||||
def shOutput = ""
|
|
||||||
def buildxPushTags = ""
|
|
||||||
|
|
||||||
pipeline {
|
pipeline {
|
||||||
agent {
|
agent {
|
||||||
label 'docker-multiarch'
|
label 'docker-multiarch'
|
||||||
@ -14,16 +8,14 @@ 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('/', '-')}"
|
||||||
COMPOSE_PROJECT_NAME = "npm_${BRANCH_LOWER}_${BUILD_NUMBER}"
|
COMPOSE_PROJECT_NAME = "npm_${BRANCH_LOWER}_${BUILD_NUMBER}"
|
||||||
COMPOSE_FILE = 'docker/docker-compose.ci.yml'
|
COMPOSE_FILE = 'docker/docker-compose.ci.yml'
|
||||||
COMPOSE_INTERACTIVE_NO_CLI = 1
|
COMPOSE_INTERACTIVE_NO_CLI = 1
|
||||||
BUILDX_NAME = "${COMPOSE_PROJECT_NAME}"
|
BUILDX_NAME = "${COMPOSE_PROJECT_NAME}"
|
||||||
DOCS_BUCKET = 'jc21-npm-site'
|
|
||||||
DOCS_CDN = 'EN1G6DEWZUTDT'
|
|
||||||
}
|
}
|
||||||
stages {
|
stages {
|
||||||
stage('Environment') {
|
stage('Environment') {
|
||||||
@ -34,7 +26,7 @@ pipeline {
|
|||||||
}
|
}
|
||||||
steps {
|
steps {
|
||||||
script {
|
script {
|
||||||
buildxPushTags = "-t docker.io/jc21/${IMAGE}:${BUILD_VERSION} -t docker.io/jc21/${IMAGE}:${MAJOR_VERSION} -t docker.io/jc21/${IMAGE}:latest"
|
env.BUILDX_PUSH_TAGS = "-t docker.io/jc21/${IMAGE}:${BUILD_VERSION} -t docker.io/jc21/${IMAGE}:${MAJOR_VERSION} -t docker.io/jc21/${IMAGE}:latest"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -47,7 +39,7 @@ pipeline {
|
|||||||
steps {
|
steps {
|
||||||
script {
|
script {
|
||||||
// Defaults to the Branch name, which is applies to all branches AND pr's
|
// Defaults to the Branch name, which is applies to all branches AND pr's
|
||||||
buildxPushTags = "-t docker.io/jc21/${IMAGE}:github-${BRANCH_LOWER}"
|
env.BUILDX_PUSH_TAGS = "-t docker.io/jc21/${IMAGE}:github-${BRANCH_LOWER}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -62,52 +54,54 @@ pipeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stage('Build and Test') {
|
stage('Frontend') {
|
||||||
steps {
|
steps {
|
||||||
script {
|
sh './scripts/frontend-build'
|
||||||
// Frontend and Backend
|
|
||||||
def shStatusCode = sh(label: 'Checking and Building', returnStatus: true, script: '''
|
|
||||||
set -e
|
|
||||||
./scripts/ci/frontend-build > ${WORKSPACE}/tmp-sh-build 2>&1
|
|
||||||
./scripts/ci/test-and-build > ${WORKSPACE}/tmp-sh-build 2>&1
|
|
||||||
''')
|
|
||||||
shOutput = readFile "${env.WORKSPACE}/tmp-sh-build"
|
|
||||||
if (shStatusCode != 0) {
|
|
||||||
error "${shOutput}"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
stage('Backend') {
|
||||||
post {
|
steps {
|
||||||
always {
|
echo 'Checking Syntax ...'
|
||||||
sh 'rm -f ${WORKSPACE}/tmp-sh-build'
|
// See: https://github.com/yarnpkg/yarn/issues/3254
|
||||||
}
|
sh '''docker run --rm \\
|
||||||
failure {
|
-v "$(pwd)/backend:/app" \\
|
||||||
npmGithubPrComment("CI Error:\n\n```\n${shOutput}\n```", true)
|
-v "$(pwd)/global:/app/global" \\
|
||||||
}
|
-w /app \\
|
||||||
|
node:latest \\
|
||||||
|
sh -c "yarn install && yarn eslint . && rm -rf node_modules"
|
||||||
|
'''
|
||||||
|
|
||||||
|
echo 'Docker Build ...'
|
||||||
|
sh '''docker build --pull --no-cache --squash --compress \\
|
||||||
|
-t "${IMAGE}:ci-${BUILD_NUMBER}" \\
|
||||||
|
-f docker/Dockerfile \\
|
||||||
|
--build-arg TARGETPLATFORM=linux/amd64 \\
|
||||||
|
--build-arg BUILDPLATFORM=linux/amd64 \\
|
||||||
|
--build-arg BUILD_VERSION="${BUILD_VERSION}" \\
|
||||||
|
--build-arg BUILD_COMMIT="${BUILD_COMMIT}" \\
|
||||||
|
--build-arg BUILD_DATE="$(date '+%Y-%m-%d %T %Z')" \\
|
||||||
|
.
|
||||||
|
'''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stage('Integration Tests Sqlite') {
|
stage('Integration Tests Sqlite') {
|
||||||
steps {
|
steps {
|
||||||
// Bring up a stack
|
// Bring up a stack
|
||||||
sh 'docker-compose up -d fullstack-sqlite'
|
sh 'docker-compose up -d fullstack-sqlite'
|
||||||
sh './scripts/wait-healthy $(docker-compose ps --all -q fullstack-sqlite) 120'
|
sh './scripts/wait-healthy $(docker-compose ps -q fullstack-sqlite) 120'
|
||||||
// Stop and Start it, as this will test it's ability to restart with existing data
|
|
||||||
sh 'docker-compose stop fullstack-sqlite'
|
|
||||||
sh 'docker-compose start fullstack-sqlite'
|
|
||||||
sh './scripts/wait-healthy $(docker-compose ps --all -q fullstack-sqlite) 120'
|
|
||||||
|
|
||||||
// Run tests
|
// Run tests
|
||||||
sh 'rm -rf test/results'
|
sh 'rm -rf test/results'
|
||||||
sh 'docker-compose up cypress-sqlite'
|
sh 'docker-compose up cypress-sqlite'
|
||||||
// Get results
|
// Get results
|
||||||
sh 'docker cp -L "$(docker-compose ps --all -q cypress-sqlite):/test/results" test/'
|
sh 'docker cp -L "$(docker-compose ps -q cypress-sqlite):/test/results" test/'
|
||||||
}
|
}
|
||||||
post {
|
post {
|
||||||
always {
|
always {
|
||||||
// Dumps to analyze later
|
// Dumps to analyze later
|
||||||
sh 'mkdir -p debug'
|
sh 'mkdir -p debug'
|
||||||
sh 'docker-compose logs fullstack-sqlite > debug/docker_fullstack_sqlite.log'
|
sh 'docker-compose logs fullstack-sqlite | gzip > debug/docker_fullstack_sqlite.log.gz'
|
||||||
sh 'docker-compose logs db > debug/docker_db.log'
|
sh 'docker-compose logs db | gzip > debug/docker_db.log.gz'
|
||||||
// Cypress videos and screenshot artifacts
|
// Cypress videos and screenshot artifacts
|
||||||
dir(path: 'test/results') {
|
dir(path: 'test/results') {
|
||||||
archiveArtifacts allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml'
|
archiveArtifacts allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml'
|
||||||
@ -120,20 +114,20 @@ pipeline {
|
|||||||
steps {
|
steps {
|
||||||
// Bring up a stack
|
// Bring up a stack
|
||||||
sh 'docker-compose up -d fullstack-mysql'
|
sh 'docker-compose up -d fullstack-mysql'
|
||||||
sh './scripts/wait-healthy $(docker-compose ps --all -q fullstack-mysql) 120'
|
sh './scripts/wait-healthy $(docker-compose ps -q fullstack-mysql) 120'
|
||||||
|
|
||||||
// Run tests
|
// Run tests
|
||||||
sh 'rm -rf test/results'
|
sh 'rm -rf test/results'
|
||||||
sh 'docker-compose up cypress-mysql'
|
sh 'docker-compose up cypress-mysql'
|
||||||
// Get results
|
// Get results
|
||||||
sh 'docker cp -L "$(docker-compose ps --all -q cypress-mysql):/test/results" test/'
|
sh 'docker cp -L "$(docker-compose ps -q cypress-mysql):/test/results" test/'
|
||||||
}
|
}
|
||||||
post {
|
post {
|
||||||
always {
|
always {
|
||||||
// Dumps to analyze later
|
// Dumps to analyze later
|
||||||
sh 'mkdir -p debug'
|
sh 'mkdir -p debug'
|
||||||
sh 'docker-compose logs fullstack-mysql > debug/docker_fullstack_mysql.log'
|
sh 'docker-compose logs fullstack-mysql | gzip > debug/docker_fullstack_mysql.log.gz'
|
||||||
sh 'docker-compose logs db > debug/docker_db.log'
|
sh 'docker-compose logs db | gzip > debug/docker_db.log.gz'
|
||||||
// Cypress videos and screenshot artifacts
|
// Cypress videos and screenshot artifacts
|
||||||
dir(path: 'test/results') {
|
dir(path: 'test/results') {
|
||||||
archiveArtifacts allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml'
|
archiveArtifacts allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml'
|
||||||
@ -169,8 +163,10 @@ pipeline {
|
|||||||
}
|
}
|
||||||
steps {
|
steps {
|
||||||
withCredentials([usernamePassword(credentialsId: 'jc21-dockerhub', passwordVariable: 'dpass', usernameVariable: 'duser')]) {
|
withCredentials([usernamePassword(credentialsId: 'jc21-dockerhub', passwordVariable: 'dpass', usernameVariable: 'duser')]) {
|
||||||
sh 'docker login -u "${duser}" -p "${dpass}"'
|
// Docker Login
|
||||||
sh "./scripts/buildx --push ${buildxPushTags}"
|
sh "docker login -u '${duser}' -p '${dpass}'"
|
||||||
|
// Buildx with push from cache
|
||||||
|
sh "./scripts/buildx --push ${BUILDX_PUSH_TAGS}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -184,7 +180,26 @@ pipeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
steps {
|
steps {
|
||||||
npmDocsRelease("$DOCS_BUCKET", "$DOCS_CDN")
|
withCredentials([[$class: 'AmazonWebServicesCredentialsBinding', accessKeyVariable: 'AWS_ACCESS_KEY_ID', credentialsId: 'npm-s3-docs', secretKeyVariable: 'AWS_SECRET_ACCESS_KEY']]) {
|
||||||
|
sh """docker run --rm \\
|
||||||
|
--name \${COMPOSE_PROJECT_NAME}-docs-upload \\
|
||||||
|
-e S3_BUCKET=jc21-npm-site \\
|
||||||
|
-e AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID \\
|
||||||
|
-e AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY \\
|
||||||
|
-v \$(pwd):/app \\
|
||||||
|
-w /app \\
|
||||||
|
jc21/ci-tools \\
|
||||||
|
scripts/docs-upload /app/docs/.vuepress/dist/
|
||||||
|
"""
|
||||||
|
|
||||||
|
sh """docker run --rm \\
|
||||||
|
--name \${COMPOSE_PROJECT_NAME}-docs-invalidate \\
|
||||||
|
-e AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID \\
|
||||||
|
-e AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY \\
|
||||||
|
jc21/ci-tools \\
|
||||||
|
aws cloudfront create-invalidation --distribution-id EN1G6DEWZUTDT --paths '/*'
|
||||||
|
"""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stage('PR Comment') {
|
stage('PR Comment') {
|
||||||
@ -198,14 +213,14 @@ pipeline {
|
|||||||
}
|
}
|
||||||
steps {
|
steps {
|
||||||
script {
|
script {
|
||||||
npmGithubPrComment("Docker Image for build ${BUILD_NUMBER} is available on [DockerHub](https://cloud.docker.com/repository/docker/jc21/${IMAGE}) as `jc21/${IMAGE}:github-${BRANCH_LOWER}`\n\n**Note:** ensure you backup your NPM instance before testing this PR image! Especially if this PR contains database changes.", true)
|
def comment = pullRequest.comment("This is an automated message from CI:\n\nDocker Image for build ${BUILD_NUMBER} is available on [DockerHub](https://cloud.docker.com/repository/docker/jc21/${IMAGE}) as `jc21/${IMAGE}:github-${BRANCH_LOWER}`\n\n**Note:** ensure you backup your NPM instance before testing this PR image! Especially if this PR contains database changes.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
post {
|
post {
|
||||||
always {
|
always {
|
||||||
sh 'docker-compose down --remove-orphans --volumes -t 30'
|
sh 'docker-compose down --rmi all --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'
|
||||||
}
|
}
|
||||||
|
412
README.md
412
README.md
@ -1,13 +1,22 @@
|
|||||||
<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.10.4-green.svg?style=for-the-badge">
|
<img src="https://img.shields.io/badge/version-2.9.8-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>
|
||||||
<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/pulls/jc21/nginx-proxy-manager.svg?style=for-the-badge">
|
<img src="https://img.shields.io/docker/pulls/jc21/nginx-proxy-manager.svg?style=for-the-badge">
|
||||||
</a>
|
</a>
|
||||||
|
<a href="https://ci.nginxproxymanager.com/blue/organizations/jenkins/nginx-proxy-manager/branches/">
|
||||||
|
<img src="https://img.shields.io/jenkins/build?jobUrl=https%3A%2F%2Fci.nginxproxymanager.com%2Fjob%2Fnginx-proxy-manager%2Fjob%2Fmaster&style=for-the-badge">
|
||||||
|
</a>
|
||||||
|
<a href="https://gitter.im/nginx-proxy-manager/community">
|
||||||
|
<img alt="Gitter" src="https://img.shields.io/gitter/room/nginx-proxy-manager/community?style=for-the-badge">
|
||||||
|
</a>
|
||||||
|
<a href="https://reddit.com/r/nginxproxymanager">
|
||||||
|
<img alt="Reddit" src="https://img.shields.io/reddit/subreddit-subscribers/nginxproxymanager?label=Reddit%20Community&style=for-the-badge">
|
||||||
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
This project comes as a pre-built docker image that enables you to easily forward to your websites
|
This project comes as a pre-built docker image that enables you to easily forward to your websites
|
||||||
@ -56,7 +65,7 @@ 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.8'
|
version: '3'
|
||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
image: 'jc21/nginx-proxy-manager:latest'
|
image: 'jc21/nginx-proxy-manager:latest'
|
||||||
@ -65,21 +74,31 @@ services:
|
|||||||
- '80:80'
|
- '80:80'
|
||||||
- '81:81'
|
- '81:81'
|
||||||
- '443:443'
|
- '443:443'
|
||||||
|
environment:
|
||||||
|
DB_MYSQL_HOST: "db"
|
||||||
|
DB_MYSQL_PORT: 3306
|
||||||
|
DB_MYSQL_USER: "npm"
|
||||||
|
DB_MYSQL_PASSWORD: "npm"
|
||||||
|
DB_MYSQL_NAME: "npm"
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/data
|
- ./data:/data
|
||||||
- ./letsencrypt:/etc/letsencrypt
|
- ./letsencrypt:/etc/letsencrypt
|
||||||
|
db:
|
||||||
|
image: 'jc21/mariadb-aria:latest'
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: 'npm'
|
||||||
|
MYSQL_DATABASE: 'npm'
|
||||||
|
MYSQL_USER: 'npm'
|
||||||
|
MYSQL_PASSWORD: 'npm'
|
||||||
|
volumes:
|
||||||
|
- ./data/mysql:/var/lib/mysql
|
||||||
```
|
```
|
||||||
|
|
||||||
This is the bare minimum configuration required. See the [documentation](https://nginxproxymanager.com/setup/) for more.
|
3. Bring up your stack
|
||||||
|
|
||||||
3. Bring up your stack by running
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
|
|
||||||
# If using docker-compose-plugin
|
|
||||||
docker compose up -d
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Log in to the Admin UI
|
4. Log in to the Admin UI
|
||||||
@ -100,12 +119,371 @@ Immediately after logging in with this default user you will be asked to modify
|
|||||||
|
|
||||||
## Contributors
|
## Contributors
|
||||||
|
|
||||||
Special thanks to [all of our contributors](https://github.com/NginxProxyManager/nginx-proxy-manager/graphs/contributors).
|
Special thanks to the following contributors:
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
## Getting Support
|
<!-- markdownlint-disable -->
|
||||||
|
<table>
|
||||||
1. [Found a bug?](https://github.com/NginxProxyManager/nginx-proxy-manager/issues)
|
<tr>
|
||||||
2. [Discussions](https://github.com/NginxProxyManager/nginx-proxy-manager/discussions)
|
<td align="center">
|
||||||
3. [Development Gitter](https://gitter.im/nginx-proxy-manager/community)
|
<a href="https://github.com/Subv">
|
||||||
4. [Reddit](https://reddit.com/r/nginxproxymanager)
|
<img src="https://avatars1.githubusercontent.com/u/357072?s=460&u=d8adcdc91d749ae53e177973ed9b6bb6c4c894a3&v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>Sebastian Valle</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/Indemnity83">
|
||||||
|
<img src="https://avatars3.githubusercontent.com/u/35218?s=460&u=7082004ff35138157c868d7d9c683ccebfce5968&v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>Kyle Klaus</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/theraw">
|
||||||
|
<img src="https://avatars1.githubusercontent.com/u/32969774?s=460&u=6b359971e15685fb0359e6a8c065a399b40dc228&v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>ƬHE ЯAW</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/spalger">
|
||||||
|
<img src="https://avatars2.githubusercontent.com/u/1329312?s=400&u=565223e38f1c052afb4c5dcca3fcf1c63ba17ae7&v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>Spencer</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/Xantios">
|
||||||
|
<img src="https://avatars3.githubusercontent.com/u/1507836?s=460&v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>Xantios Krugor</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/dpanesso">
|
||||||
|
<img src="https://avatars2.githubusercontent.com/u/2687121?s=460&v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>David Panesso</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/IronTooch">
|
||||||
|
<img src="https://avatars3.githubusercontent.com/u/27360514?s=460&u=69bf854a6647c55725f62ecb8d39249c6c0b2602&v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>IronTooch</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/damianog">
|
||||||
|
<img src="https://avatars1.githubusercontent.com/u/2786682?s=460&u=76c6136fae797abb76b951cd8a246dcaecaf21af&v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>Damiano</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/tfmm">
|
||||||
|
<img src="https://avatars3.githubusercontent.com/u/6880538?s=460&u=ce0160821cc4aa802df8395200f2d4956a5bc541&v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>Russ</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/margaale">
|
||||||
|
<img src="https://avatars3.githubusercontent.com/u/20794934?s=460&v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>Marcelo Castagna</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/Steven-Harris">
|
||||||
|
<img src="https://avatars2.githubusercontent.com/u/7720242?s=460&v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>Steven Harris</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/jlesage">
|
||||||
|
<img src="https://avatars0.githubusercontent.com/u/1791123?s=460&v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>Jocelyn Le Sage</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/cmer">
|
||||||
|
<img src="https://avatars0.githubusercontent.com/u/412?s=460&u=67dd8b2e3661bfd6f68ec1eaa5b9821bd8a321cd&v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>Carl Mercier</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/the1ts">
|
||||||
|
<img src="https://avatars1.githubusercontent.com/u/84956?s=460&v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>Paul Mansfield</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/OhHeyAlan">
|
||||||
|
<img src="https://avatars0.githubusercontent.com/u/11955126?s=460&u=fbaa5a1a4f73ef8960132c703349bfd037fe2630&v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>OhHeyAlan</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/dogmatic69">
|
||||||
|
<img src="https://avatars2.githubusercontent.com/u/94674?s=460&u=ca7647de53145c6283b6373ade5dc94ba99347db&v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>Carl Sutton</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/tg44">
|
||||||
|
<img src="https://avatars0.githubusercontent.com/u/31839?s=460&u=ad32f4cadfef5e5fb09cdfa4b7b7b36a99ba6811&v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>Gergő Törcsvári</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/vrenjith">
|
||||||
|
<img src="https://avatars3.githubusercontent.com/u/2093241?s=460&u=96ce93a9bebabdd0a60a2dc96cd093a41d5edaba&v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>vrenjith</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/duhruh">
|
||||||
|
<img src="https://avatars2.githubusercontent.com/u/1133969?s=460&u=c0691e6131ec6d516416c1c6fcedb5034f877bbe&v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>David Rivera</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/jipjan">
|
||||||
|
<img src="https://avatars2.githubusercontent.com/u/1384618?s=460&v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>Jaap-Jan de Wit</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/jmwebslave">
|
||||||
|
<img src="https://avatars2.githubusercontent.com/u/6118262?s=460&u=7db409c47135b1e141c366bbb03ed9fae6ac2638&v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>James Morgan</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/chaptergy">
|
||||||
|
<img src="https://avatars2.githubusercontent.com/u/26956711?s=460&u=7d9adebabb6b4e7af7cb05d98d751087a372304b&v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>chaptergy</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/Philip-Mooney">
|
||||||
|
<img src="https://avatars0.githubusercontent.com/u/48624631?s=460&v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>Philip Mooney</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/WaterCalm">
|
||||||
|
<img src="https://avatars1.githubusercontent.com/u/23502129?s=400&v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>WaterCalm</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/lebrou34">
|
||||||
|
<img src="https://avatars1.githubusercontent.com/u/16373103?s=460&v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>lebrou34</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/lightglitch">
|
||||||
|
<img src="https://avatars0.githubusercontent.com/u/196953?s=460&v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>Mário Franco</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/klutchell">
|
||||||
|
<img src="https://avatars3.githubusercontent.com/u/20458272?s=460&v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>Kyle Harding</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/ahgraber">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/24922003?s=460&u=8376c9f00af9b6057ba4d2fb03b4f1b20a75277f&v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>Alex Graber</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/MooBaloo">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/9493496?s=460&v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>MooBaloo</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/Shuro">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/944030?s=460&v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>Shuro</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/lorisbergeron">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/51918567?s=460&u=778e4ff284b7d7304450f98421c99f79298371fb&v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>Loris Bergeron</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/hepelayo">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/8243119?v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>hepelayo</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/jonasled">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/46790650?v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>Jonas Leder</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/stegmannb">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/12850482?v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>Bastian Stegmann</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/Stealthii">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/998920?v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>Stealthii</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/thegamingninja">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/8020534?v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>THEGamingninja</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/italobb">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/1801687?v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>Italo Borssatto</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/GurjinderSingh">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/3470709?v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>Gurjinder Singh</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/phantomski77">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/69464125?v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>David Dosoudil</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/ijaron">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/5156472?v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>ijaron</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/nielscil">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/9073152?v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>Niels Bouma</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/ogarai">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/2949572?v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>Orko Garai</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/baruffaldi">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/36949?v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>Filippo Baruffaldi</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/bikram990">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/6782131?v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>Bikramjeet Singh</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/razvanstoica89">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/28236583?v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>Razvan Stoica</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/psharma04">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/22587474?v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>RBXII3</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/demize">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/264914?v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>demize</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/PUP-Loki">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/75944209?v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>PUP-Loki</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/DSorlov">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/8133650?v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>Daniel Sörlöv</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/Theyooo">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/58510131?v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>Theyooo</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/mrdink">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/514751?v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>Justin Peacock</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/ChrisTracy">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/58871574?v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>Chris Tracy</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/Fuechslein">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/15112818?v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>Fuechslein</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/nightah">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/3339418?v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>Amir Zarrinkafsh</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/gabbe">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/156397?v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>gabbe</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/bmbvenom">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/20530371?v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>bmbvenom</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/FMeinicke">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/42121639?v=4" width="80" alt=""/>
|
||||||
|
<br /><sub><b>Florian Meinicke</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<!-- markdownlint-enable -->
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
@ -2,7 +2,6 @@ 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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -25,7 +24,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 (config.debug()) {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
app.set('json spaces', 2);
|
app.set('json spaces', 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,6 +40,7 @@ app.use(function (req, res, next) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
res.set({
|
res.set({
|
||||||
|
'Strict-Transport-Security': 'includeSubDomains; max-age=631138519; preload',
|
||||||
'X-XSS-Protection': '1; mode=block',
|
'X-XSS-Protection': '1; mode=block',
|
||||||
'X-Content-Type-Options': 'nosniff',
|
'X-Content-Type-Options': 'nosniff',
|
||||||
'X-Frame-Options': x_frame_options,
|
'X-Frame-Options': x_frame_options,
|
||||||
@ -66,7 +66,7 @@ app.use(function (err, req, res, next) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (config.debug() || (req.baseUrl + req.path).includes('nginx/certificates')) {
|
if (process.env.NODE_ENV === 'development' || (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
|
||||||
@ -75,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 (config.debug()) {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
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,22 +1,21 @@
|
|||||||
const config = require('./lib/config');
|
const config = require('config');
|
||||||
|
|
||||||
if (!config.has('database')) {
|
if (!config.has('database')) {
|
||||||
throw new Error('Database config does not exist! Please read the instructions: https://nginxproxymanager.com/setup/');
|
throw new Error('Database config does not exist! Please read the instructions: https://github.com/jc21/nginx-proxy-manager/blob/master/doc/INSTALL.md');
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateDbConfig() {
|
function generateDbConfig() {
|
||||||
const cfg = config.get('database');
|
if (config.database.engine === 'knex-native') {
|
||||||
if (cfg.engine === 'knex-native') {
|
return config.database.knex;
|
||||||
return cfg.knex;
|
} else
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
client: cfg.engine,
|
client: config.database.engine,
|
||||||
connection: {
|
connection: {
|
||||||
host: cfg.host,
|
host: config.database.host,
|
||||||
user: cfg.user,
|
user: config.database.user,
|
||||||
password: cfg.password,
|
password: config.database.password,
|
||||||
database: cfg.name,
|
database: config.database.name,
|
||||||
port: cfg.port
|
port: config.database.port
|
||||||
},
|
},
|
||||||
migrations: {
|
migrations: {
|
||||||
tableName: 'migrations'
|
tableName: 'migrations'
|
||||||
@ -24,4 +23,11 @@ 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,210 +40,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/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",
|
||||||
@ -259,10 +55,14 @@
|
|||||||
"get": {
|
"get": {
|
||||||
"operationId": "refreshToken",
|
"operationId": "refreshToken",
|
||||||
"summary": "Refresh your access token",
|
"summary": "Refresh your access token",
|
||||||
"tags": ["Tokens"],
|
"tags": [
|
||||||
|
"Tokens"
|
||||||
|
],
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": ["tokens"]
|
"BearerAuth": [
|
||||||
|
"tokens"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -304,14 +104,19 @@
|
|||||||
"scope": {
|
"scope": {
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": ["user"]
|
"enum": [
|
||||||
|
"user"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"secret": {
|
"secret": {
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["identity", "secret"],
|
"required": [
|
||||||
|
"identity",
|
||||||
|
"secret"
|
||||||
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -339,17 +144,23 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"summary": "Request a new access token from credentials",
|
"summary": "Request a new access token from credentials",
|
||||||
"tags": ["Tokens"]
|
"tags": [
|
||||||
|
"Tokens"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/settings": {
|
"/settings": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "getSettings",
|
"operationId": "getSettings",
|
||||||
"summary": "Get all settings",
|
"summary": "Get all settings",
|
||||||
"tags": ["Settings"],
|
"tags": [
|
||||||
|
"Settings"
|
||||||
|
],
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": ["settings"]
|
"BearerAuth": [
|
||||||
|
"settings"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -383,10 +194,14 @@
|
|||||||
"get": {
|
"get": {
|
||||||
"operationId": "getSetting",
|
"operationId": "getSetting",
|
||||||
"summary": "Get a setting",
|
"summary": "Get a setting",
|
||||||
"tags": ["Settings"],
|
"tags": [
|
||||||
|
"Settings"
|
||||||
|
],
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": ["settings"]
|
"BearerAuth": [
|
||||||
|
"settings"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@ -429,10 +244,14 @@
|
|||||||
"put": {
|
"put": {
|
||||||
"operationId": "updateSetting",
|
"operationId": "updateSetting",
|
||||||
"summary": "Update a setting",
|
"summary": "Update a setting",
|
||||||
"tags": ["Settings"],
|
"tags": [
|
||||||
|
"Settings"
|
||||||
|
],
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": ["settings"]
|
"BearerAuth": [
|
||||||
|
"settings"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@ -486,10 +305,14 @@
|
|||||||
"get": {
|
"get": {
|
||||||
"operationId": "getUsers",
|
"operationId": "getUsers",
|
||||||
"summary": "Get all users",
|
"summary": "Get all users",
|
||||||
"tags": ["Users"],
|
"tags": [
|
||||||
|
"Users"
|
||||||
|
],
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": ["users"]
|
"BearerAuth": [
|
||||||
|
"users"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@ -499,7 +322,9 @@
|
|||||||
"description": "Expansions",
|
"description": "Expansions",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": ["permissions"]
|
"enum": [
|
||||||
|
"permissions"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -520,7 +345,9 @@
|
|||||||
"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": ["admin"]
|
"roles": [
|
||||||
|
"admin"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -535,7 +362,9 @@
|
|||||||
"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": ["admin"],
|
"roles": [
|
||||||
|
"admin"
|
||||||
|
],
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"visibility": "all",
|
"visibility": "all",
|
||||||
"proxy_hosts": "manage",
|
"proxy_hosts": "manage",
|
||||||
@ -560,10 +389,14 @@
|
|||||||
"post": {
|
"post": {
|
||||||
"operationId": "createUser",
|
"operationId": "createUser",
|
||||||
"summary": "Create a User",
|
"summary": "Create a User",
|
||||||
"tags": ["Users"],
|
"tags": [
|
||||||
|
"Users"
|
||||||
|
],
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": ["users"]
|
"BearerAuth": [
|
||||||
|
"users"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@ -593,7 +426,9 @@
|
|||||||
"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": ["admin"],
|
"roles": [
|
||||||
|
"admin"
|
||||||
|
],
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"visibility": "all",
|
"visibility": "all",
|
||||||
"proxy_hosts": "manage",
|
"proxy_hosts": "manage",
|
||||||
@ -619,10 +454,14 @@
|
|||||||
"get": {
|
"get": {
|
||||||
"operationId": "getUser",
|
"operationId": "getUser",
|
||||||
"summary": "Get a user",
|
"summary": "Get a user",
|
||||||
"tags": ["Users"],
|
"tags": [
|
||||||
|
"Users"
|
||||||
|
],
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": ["users"]
|
"BearerAuth": [
|
||||||
|
"users"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@ -662,7 +501,9 @@
|
|||||||
"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": ["admin"]
|
"roles": [
|
||||||
|
"admin"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -677,10 +518,14 @@
|
|||||||
"put": {
|
"put": {
|
||||||
"operationId": "updateUser",
|
"operationId": "updateUser",
|
||||||
"summary": "Update a User",
|
"summary": "Update a User",
|
||||||
"tags": ["Users"],
|
"tags": [
|
||||||
|
"Users"
|
||||||
|
],
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": ["users"]
|
"BearerAuth": [
|
||||||
|
"users"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@ -729,7 +574,9 @@
|
|||||||
"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": ["admin"]
|
"roles": [
|
||||||
|
"admin"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -744,10 +591,14 @@
|
|||||||
"delete": {
|
"delete": {
|
||||||
"operationId": "deleteUser",
|
"operationId": "deleteUser",
|
||||||
"summary": "Delete a User",
|
"summary": "Delete a User",
|
||||||
"tags": ["Users"],
|
"tags": [
|
||||||
|
"Users"
|
||||||
|
],
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": ["users"]
|
"BearerAuth": [
|
||||||
|
"users"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@ -786,10 +637,14 @@
|
|||||||
"put": {
|
"put": {
|
||||||
"operationId": "updateUserAuth",
|
"operationId": "updateUserAuth",
|
||||||
"summary": "Update a User's Authentication",
|
"summary": "Update a User's Authentication",
|
||||||
"tags": ["Users"],
|
"tags": [
|
||||||
|
"Users"
|
||||||
|
],
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": ["users"]
|
"BearerAuth": [
|
||||||
|
"users"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@ -845,10 +700,14 @@
|
|||||||
"put": {
|
"put": {
|
||||||
"operationId": "updateUserPermissions",
|
"operationId": "updateUserPermissions",
|
||||||
"summary": "Update a User's Permissions",
|
"summary": "Update a User's Permissions",
|
||||||
"tags": ["Users"],
|
"tags": [
|
||||||
|
"Users"
|
||||||
|
],
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": ["users"]
|
"BearerAuth": [
|
||||||
|
"users"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@ -896,10 +755,14 @@
|
|||||||
"put": {
|
"put": {
|
||||||
"operationId": "loginAsUser",
|
"operationId": "loginAsUser",
|
||||||
"summary": "Login as this user",
|
"summary": "Login as this user",
|
||||||
"tags": ["Users"],
|
"tags": [
|
||||||
|
"Users"
|
||||||
|
],
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": ["users"]
|
"BearerAuth": [
|
||||||
|
"users"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@ -934,7 +797,9 @@
|
|||||||
"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": ["admin"]
|
"roles": [
|
||||||
|
"admin"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -942,7 +807,11 @@
|
|||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "Login object",
|
"description": "Login object",
|
||||||
"required": ["expires", "token", "user"],
|
"required": [
|
||||||
|
"expires",
|
||||||
|
"token",
|
||||||
|
"user"
|
||||||
|
],
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"expires": {
|
"expires": {
|
||||||
@ -971,10 +840,14 @@
|
|||||||
"get": {
|
"get": {
|
||||||
"operationId": "reportsHosts",
|
"operationId": "reportsHosts",
|
||||||
"summary": "Report on Host Statistics",
|
"summary": "Report on Host Statistics",
|
||||||
"tags": ["Reports"],
|
"tags": [
|
||||||
|
"Reports"
|
||||||
|
],
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": ["reports"]
|
"BearerAuth": [
|
||||||
|
"reports"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -1005,10 +878,14 @@
|
|||||||
"get": {
|
"get": {
|
||||||
"operationId": "getAuditLog",
|
"operationId": "getAuditLog",
|
||||||
"summary": "Get Audit Log",
|
"summary": "Get Audit Log",
|
||||||
"tags": ["Audit Log"],
|
"tags": [
|
||||||
|
"Audit Log"
|
||||||
|
],
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": ["audit-log"]
|
"BearerAuth": [
|
||||||
|
"audit-log"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -1048,7 +925,10 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "Health object",
|
"description": "Health object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"required": ["status", "version"],
|
"required": [
|
||||||
|
"status",
|
||||||
|
"version"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"status": {
|
"status": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@ -1064,7 +944,11 @@
|
|||||||
"revision": 0
|
"revision": 0
|
||||||
},
|
},
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"required": ["major", "minor", "revision"],
|
"required": [
|
||||||
|
"major",
|
||||||
|
"minor",
|
||||||
|
"revision"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"major": {
|
"major": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
@ -1085,7 +969,10 @@
|
|||||||
"TokenObject": {
|
"TokenObject": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "Token object",
|
"description": "Token object",
|
||||||
"required": ["expires", "token"],
|
"required": [
|
||||||
|
"expires",
|
||||||
|
"token"
|
||||||
|
],
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"expires": {
|
"expires": {
|
||||||
@ -1101,147 +988,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"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": ["id", "name", "description", "value", "meta"],
|
"required": [
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"description",
|
||||||
|
"value",
|
||||||
|
"meta"
|
||||||
|
],
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"id": {
|
||||||
@ -1301,7 +1057,17 @@
|
|||||||
"UserObject": {
|
"UserObject": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "User object",
|
"description": "User object",
|
||||||
"required": ["id", "created_on", "modified_on", "is_disabled", "email", "name", "nickname", "avatar", "roles"],
|
"required": [
|
||||||
|
"id",
|
||||||
|
"created_on",
|
||||||
|
"modified_on",
|
||||||
|
"is_disabled",
|
||||||
|
"email",
|
||||||
|
"name",
|
||||||
|
"nickname",
|
||||||
|
"avatar",
|
||||||
|
"roles"
|
||||||
|
],
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"id": {
|
||||||
@ -1351,7 +1117,9 @@
|
|||||||
},
|
},
|
||||||
"roles": {
|
"roles": {
|
||||||
"description": "Roles applied",
|
"description": "Roles applied",
|
||||||
"example": ["admin"],
|
"example": [
|
||||||
|
"admin"
|
||||||
|
],
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@ -1369,7 +1137,10 @@
|
|||||||
"AuthObject": {
|
"AuthObject": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "Authentication Object",
|
"description": "Authentication Object",
|
||||||
"required": ["type", "secret"],
|
"required": [
|
||||||
|
"type",
|
||||||
|
"secret"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@ -1396,37 +1167,64 @@
|
|||||||
"visibility": {
|
"visibility": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Visibility Type",
|
"description": "Visibility Type",
|
||||||
"enum": ["all", "user"]
|
"enum": [
|
||||||
|
"all",
|
||||||
|
"user"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"access_lists": {
|
"access_lists": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Access Lists Permissions",
|
"description": "Access Lists Permissions",
|
||||||
"enum": ["hidden", "view", "manage"]
|
"enum": [
|
||||||
|
"hidden",
|
||||||
|
"view",
|
||||||
|
"manage"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"dead_hosts": {
|
"dead_hosts": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "404 Hosts Permissions",
|
"description": "404 Hosts Permissions",
|
||||||
"enum": ["hidden", "view", "manage"]
|
"enum": [
|
||||||
|
"hidden",
|
||||||
|
"view",
|
||||||
|
"manage"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"proxy_hosts": {
|
"proxy_hosts": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Proxy Hosts Permissions",
|
"description": "Proxy Hosts Permissions",
|
||||||
"enum": ["hidden", "view", "manage"]
|
"enum": [
|
||||||
|
"hidden",
|
||||||
|
"view",
|
||||||
|
"manage"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"redirection_hosts": {
|
"redirection_hosts": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Redirection Permissions",
|
"description": "Redirection Permissions",
|
||||||
"enum": ["hidden", "view", "manage"]
|
"enum": [
|
||||||
|
"hidden",
|
||||||
|
"view",
|
||||||
|
"manage"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"streams": {
|
"streams": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Streams Permissions",
|
"description": "Streams Permissions",
|
||||||
"enum": ["hidden", "view", "manage"]
|
"enum": [
|
||||||
|
"hidden",
|
||||||
|
"view",
|
||||||
|
"manage"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"certificates": {
|
"certificates": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Certificates Permissions",
|
"description": "Certificates Permissions",
|
||||||
"enum": ["hidden", "view", "manage"]
|
"enum": [
|
||||||
|
"hidden",
|
||||||
|
"view",
|
||||||
|
"manage"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -3,6 +3,9 @@
|
|||||||
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');
|
||||||
@ -39,6 +42,89 @@ 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;
|
||||||
|
const envSqliteFile = process.env.DB_SQLITE_FILE || null;
|
||||||
|
|
||||||
|
if ((envMysqlHost && envMysqlPort && envMysqlUser && envMysqlName) || envSqliteFile) {
|
||||||
|
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) {
|
||||||
|
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 db 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 db configuration from environment variables');
|
||||||
|
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.info('Wrote db configuration to config file: ' + filename);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
appStart();
|
appStart();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -3,13 +3,13 @@ const fs = require('fs');
|
|||||||
const batchflow = require('batchflow');
|
const batchflow = require('batchflow');
|
||||||
const logger = require('../logger').access;
|
const logger = require('../logger').access;
|
||||||
const error = require('../lib/error');
|
const error = require('../lib/error');
|
||||||
const utils = require('../lib/utils');
|
|
||||||
const accessListModel = require('../models/access_list');
|
const accessListModel = require('../models/access_list');
|
||||||
const accessListAuthModel = require('../models/access_list_auth');
|
const accessListAuthModel = require('../models/access_list_auth');
|
||||||
const accessListClientModel = require('../models/access_list_client');
|
const accessListClientModel = require('../models/access_list_client');
|
||||||
const proxyHostModel = require('../models/proxy_host');
|
const proxyHostModel = require('../models/proxy_host');
|
||||||
const internalAuditLog = require('./audit-log');
|
const internalAuditLog = require('./audit-log');
|
||||||
const internalNginx = require('./nginx');
|
const internalNginx = require('./nginx');
|
||||||
|
const utils = require('../lib/utils');
|
||||||
|
|
||||||
function omissions () {
|
function omissions () {
|
||||||
return ['is_deleted'];
|
return ['is_deleted'];
|
||||||
@ -27,13 +27,13 @@ const internalAccessList = {
|
|||||||
.then((/*access_data*/) => {
|
.then((/*access_data*/) => {
|
||||||
return accessListModel
|
return accessListModel
|
||||||
.query()
|
.query()
|
||||||
|
.omit(omissions())
|
||||||
.insertAndFetch({
|
.insertAndFetch({
|
||||||
name: data.name,
|
name: data.name,
|
||||||
satisfy_any: data.satisfy_any,
|
satisfy_any: data.satisfy_any,
|
||||||
pass_auth: data.pass_auth,
|
pass_auth: data.pass_auth,
|
||||||
owner_user_id: access.token.getUserId(1)
|
owner_user_id: access.token.getUserId(1)
|
||||||
})
|
});
|
||||||
.then(utils.omitRow(omissions()));
|
|
||||||
})
|
})
|
||||||
.then((row) => {
|
.then((row) => {
|
||||||
data.id = row.id;
|
data.id = row.id;
|
||||||
@ -218,7 +218,7 @@ const internalAccessList = {
|
|||||||
// re-fetch with expansions
|
// re-fetch with expansions
|
||||||
return internalAccessList.get(access, {
|
return internalAccessList.get(access, {
|
||||||
id: data.id,
|
id: data.id,
|
||||||
expand: ['owner', 'items', 'clients', 'proxy_hosts.[certificate,access_list.[clients,items]]']
|
expand: ['owner', 'items', 'clients', 'proxy_hosts.access_list.[clients,items]']
|
||||||
}, true /* <- skip masking */);
|
}, true /* <- skip masking */);
|
||||||
})
|
})
|
||||||
.then((row) => {
|
.then((row) => {
|
||||||
@ -256,31 +256,35 @@ const internalAccessList = {
|
|||||||
.joinRaw('LEFT JOIN `proxy_host` ON `proxy_host`.`access_list_id` = `access_list`.`id` AND `proxy_host`.`is_deleted` = 0')
|
.joinRaw('LEFT JOIN `proxy_host` ON `proxy_host`.`access_list_id` = `access_list`.`id` AND `proxy_host`.`is_deleted` = 0')
|
||||||
.where('access_list.is_deleted', 0)
|
.where('access_list.is_deleted', 0)
|
||||||
.andWhere('access_list.id', data.id)
|
.andWhere('access_list.id', data.id)
|
||||||
.allowGraph('[owner,items,clients,proxy_hosts.[certificate,access_list.[clients,items]]]')
|
.allowEager('[owner,items,clients,proxy_hosts.[*, access_list.[clients,items]]]')
|
||||||
|
.omit(['access_list.is_deleted'])
|
||||||
.first();
|
.first();
|
||||||
|
|
||||||
if (access_data.permission_visibility !== 'all') {
|
if (access_data.permission_visibility !== 'all') {
|
||||||
query.andWhere('access_list.owner_user_id', access.token.getUserId(1));
|
query.andWhere('access_list.owner_user_id', access.token.getUserId(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof data.expand !== 'undefined' && data.expand !== null) {
|
// Custom omissions
|
||||||
query.withGraphFetched('[' + data.expand.join(', ') + ']');
|
if (typeof data.omit !== 'undefined' && data.omit !== null) {
|
||||||
|
query.omit(data.omit);
|
||||||
}
|
}
|
||||||
|
|
||||||
return query.then(utils.omitRow(omissions()));
|
if (typeof data.expand !== 'undefined' && data.expand !== null) {
|
||||||
|
query.eager('[' + data.expand.join(', ') + ']');
|
||||||
|
}
|
||||||
|
|
||||||
|
return query;
|
||||||
})
|
})
|
||||||
.then((row) => {
|
.then((row) => {
|
||||||
if (!row) {
|
if (row) {
|
||||||
throw new error.ItemNotFoundError(data.id);
|
|
||||||
}
|
|
||||||
if (!skip_masking && typeof row.items !== 'undefined' && row.items) {
|
if (!skip_masking && typeof row.items !== 'undefined' && row.items) {
|
||||||
row = internalAccessList.maskItems(row);
|
row = internalAccessList.maskItems(row);
|
||||||
}
|
}
|
||||||
// Custom omissions
|
|
||||||
if (typeof data.omit !== 'undefined' && data.omit !== null) {
|
return _.omit(row, omissions());
|
||||||
row = _.omit(row, data.omit);
|
} else {
|
||||||
|
throw new error.ItemNotFoundError(data.id);
|
||||||
}
|
}
|
||||||
return row;
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -377,7 +381,8 @@ const internalAccessList = {
|
|||||||
.joinRaw('LEFT JOIN `proxy_host` ON `proxy_host`.`access_list_id` = `access_list`.`id` AND `proxy_host`.`is_deleted` = 0')
|
.joinRaw('LEFT JOIN `proxy_host` ON `proxy_host`.`access_list_id` = `access_list`.`id` AND `proxy_host`.`is_deleted` = 0')
|
||||||
.where('access_list.is_deleted', 0)
|
.where('access_list.is_deleted', 0)
|
||||||
.groupBy('access_list.id')
|
.groupBy('access_list.id')
|
||||||
.allowGraph('[owner,items,clients]')
|
.omit(['access_list.is_deleted'])
|
||||||
|
.allowEager('[owner,items,clients]')
|
||||||
.orderBy('access_list.name', 'ASC');
|
.orderBy('access_list.name', 'ASC');
|
||||||
|
|
||||||
if (access_data.permission_visibility !== 'all') {
|
if (access_data.permission_visibility !== 'all') {
|
||||||
@ -392,10 +397,10 @@ const internalAccessList = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (typeof expand !== 'undefined' && expand !== null) {
|
if (typeof expand !== 'undefined' && expand !== null) {
|
||||||
query.withGraphFetched('[' + expand.join(', ') + ']');
|
query.eager('[' + expand.join(', ') + ']');
|
||||||
}
|
}
|
||||||
|
|
||||||
return query.then(utils.omitRows(omissions()));
|
return query;
|
||||||
})
|
})
|
||||||
.then((rows) => {
|
.then((rows) => {
|
||||||
if (rows) {
|
if (rows) {
|
||||||
@ -502,7 +507,7 @@ const internalAccessList = {
|
|||||||
if (typeof item.password !== 'undefined' && item.password.length) {
|
if (typeof item.password !== 'undefined' && item.password.length) {
|
||||||
logger.info('Adding: ' + item.username);
|
logger.info('Adding: ' + item.username);
|
||||||
|
|
||||||
utils.execFile('/usr/bin/htpasswd', ['-b', htpasswd_file, item.username, item.password])
|
utils.exec('/usr/bin/htpasswd -b "' + htpasswd_file + '" "' + item.username + '" "' + item.password + '"')
|
||||||
.then((/*result*/) => {
|
.then((/*result*/) => {
|
||||||
next();
|
next();
|
||||||
})
|
})
|
||||||
|
@ -19,7 +19,7 @@ const internalAuditLog = {
|
|||||||
.orderBy('created_on', 'DESC')
|
.orderBy('created_on', 'DESC')
|
||||||
.orderBy('id', 'DESC')
|
.orderBy('id', 'DESC')
|
||||||
.limit(100)
|
.limit(100)
|
||||||
.allowGraph('[user]');
|
.allowEager('[user]');
|
||||||
|
|
||||||
// Query is used for searching
|
// Query is used for searching
|
||||||
if (typeof search_query === 'string') {
|
if (typeof search_query === 'string') {
|
||||||
@ -29,7 +29,7 @@ const internalAuditLog = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (typeof expand !== 'undefined' && expand !== null) {
|
if (typeof expand !== 'undefined' && expand !== null) {
|
||||||
query.withGraphFetched('[' + expand.join(', ') + ']');
|
query.eager('[' + expand.join(', ') + ']');
|
||||||
}
|
}
|
||||||
|
|
||||||
return query;
|
return query;
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
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');
|
||||||
@ -12,13 +10,11 @@ const dnsPlugins = require('../global/certbot-dns-plugins');
|
|||||||
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 archiver = require('archiver');
|
const letsencryptStaging = process.env.NODE_ENV !== 'production';
|
||||||
const path = require('path');
|
|
||||||
const { isArray } = require('lodash');
|
|
||||||
|
|
||||||
const letsencryptStaging = config.useLetsencryptStaging();
|
|
||||||
const letsencryptConfig = '/etc/letsencrypt.ini';
|
const letsencryptConfig = '/etc/letsencrypt.ini';
|
||||||
const certbotCommand = 'certbot';
|
const certbotCommand = 'certbot';
|
||||||
|
const archiver = require('archiver');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
function omissions() {
|
function omissions() {
|
||||||
return ['is_deleted'];
|
return ['is_deleted'];
|
||||||
@ -48,8 +44,6 @@ const internalCertificate = {
|
|||||||
|
|
||||||
const cmd = certbotCommand + ' renew --non-interactive --quiet ' +
|
const cmd = certbotCommand + ' renew --non-interactive --quiet ' +
|
||||||
'--config "' + letsencryptConfig + '" ' +
|
'--config "' + letsencryptConfig + '" ' +
|
||||||
'--work-dir "/tmp/letsencrypt-lib" ' +
|
|
||||||
'--logs-dir "/tmp/letsencrypt-log" ' +
|
|
||||||
'--preferred-challenges "dns,http" ' +
|
'--preferred-challenges "dns,http" ' +
|
||||||
'--disable-hook-validation ' +
|
'--disable-hook-validation ' +
|
||||||
(letsencryptStaging ? '--staging' : '');
|
(letsencryptStaging ? '--staging' : '');
|
||||||
@ -120,13 +114,13 @@ const internalCertificate = {
|
|||||||
data.owner_user_id = access.token.getUserId(1);
|
data.owner_user_id = access.token.getUserId(1);
|
||||||
|
|
||||||
if (data.provider === 'letsencrypt') {
|
if (data.provider === 'letsencrypt') {
|
||||||
data.nice_name = data.domain_names.join(', ');
|
data.nice_name = data.domain_names.sort().join(', ');
|
||||||
}
|
}
|
||||||
|
|
||||||
return certificateModel
|
return certificateModel
|
||||||
.query()
|
.query()
|
||||||
.insertAndFetch(data)
|
.omit(omissions())
|
||||||
.then(utils.omitRow(omissions()));
|
.insertAndFetch(data);
|
||||||
})
|
})
|
||||||
.then((certificate) => {
|
.then((certificate) => {
|
||||||
if (certificate.provider === 'letsencrypt') {
|
if (certificate.provider === 'letsencrypt') {
|
||||||
@ -175,7 +169,6 @@ const internalCertificate = {
|
|||||||
// 3. Generate the LE config
|
// 3. Generate the LE config
|
||||||
return internalNginx.generateLetsEncryptRequestConfig(certificate)
|
return internalNginx.generateLetsEncryptRequestConfig(certificate)
|
||||||
.then(internalNginx.reload)
|
.then(internalNginx.reload)
|
||||||
.then(async() => await new Promise((r) => setTimeout(r, 5000)))
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// 4. Request cert
|
// 4. Request cert
|
||||||
return internalCertificate.requestLetsEncryptSsl(certificate);
|
return internalCertificate.requestLetsEncryptSsl(certificate);
|
||||||
@ -273,8 +266,8 @@ const internalCertificate = {
|
|||||||
|
|
||||||
return certificateModel
|
return certificateModel
|
||||||
.query()
|
.query()
|
||||||
|
.omit(omissions())
|
||||||
.patchAndFetchById(row.id, data)
|
.patchAndFetchById(row.id, data)
|
||||||
.then(utils.omitRow(omissions()))
|
|
||||||
.then((saved_row) => {
|
.then((saved_row) => {
|
||||||
saved_row.meta = internalCertificate.cleanMeta(saved_row.meta);
|
saved_row.meta = internalCertificate.cleanMeta(saved_row.meta);
|
||||||
data.meta = internalCertificate.cleanMeta(data.meta);
|
data.meta = internalCertificate.cleanMeta(data.meta);
|
||||||
@ -292,7 +285,7 @@ const internalCertificate = {
|
|||||||
meta: _.omit(data, ['expires_on']) // this prevents json circular reference because expires_on might be raw
|
meta: _.omit(data, ['expires_on']) // this prevents json circular reference because expires_on might be raw
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return saved_row;
|
return _.omit(saved_row, omissions());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -317,28 +310,30 @@ const internalCertificate = {
|
|||||||
.query()
|
.query()
|
||||||
.where('is_deleted', 0)
|
.where('is_deleted', 0)
|
||||||
.andWhere('id', data.id)
|
.andWhere('id', data.id)
|
||||||
.allowGraph('[owner]')
|
.allowEager('[owner]')
|
||||||
.first();
|
.first();
|
||||||
|
|
||||||
if (access_data.permission_visibility !== 'all') {
|
if (access_data.permission_visibility !== 'all') {
|
||||||
query.andWhere('owner_user_id', access.token.getUserId(1));
|
query.andWhere('owner_user_id', access.token.getUserId(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof data.expand !== 'undefined' && data.expand !== null) {
|
|
||||||
query.withGraphFetched('[' + data.expand.join(', ') + ']');
|
|
||||||
}
|
|
||||||
|
|
||||||
return query.then(utils.omitRow(omissions()));
|
|
||||||
})
|
|
||||||
.then((row) => {
|
|
||||||
if (!row) {
|
|
||||||
throw new error.ItemNotFoundError(data.id);
|
|
||||||
}
|
|
||||||
// Custom omissions
|
// Custom omissions
|
||||||
if (typeof data.omit !== 'undefined' && data.omit !== null) {
|
if (typeof data.omit !== 'undefined' && data.omit !== null) {
|
||||||
row = _.omit(row, data.omit);
|
query.omit(data.omit);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof data.expand !== 'undefined' && data.expand !== null) {
|
||||||
|
query.eager('[' + data.expand.join(', ') + ']');
|
||||||
|
}
|
||||||
|
|
||||||
|
return query;
|
||||||
|
})
|
||||||
|
.then((row) => {
|
||||||
|
if (row) {
|
||||||
|
return _.omit(row, omissions());
|
||||||
|
} else {
|
||||||
|
throw new error.ItemNotFoundError(data.id);
|
||||||
}
|
}
|
||||||
return row;
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -468,7 +463,8 @@ const internalCertificate = {
|
|||||||
.query()
|
.query()
|
||||||
.where('is_deleted', 0)
|
.where('is_deleted', 0)
|
||||||
.groupBy('id')
|
.groupBy('id')
|
||||||
.allowGraph('[owner]')
|
.omit(['is_deleted'])
|
||||||
|
.allowEager('[owner]')
|
||||||
.orderBy('nice_name', 'ASC');
|
.orderBy('nice_name', 'ASC');
|
||||||
|
|
||||||
if (access_data.permission_visibility !== 'all') {
|
if (access_data.permission_visibility !== 'all') {
|
||||||
@ -478,15 +474,15 @@ const internalCertificate = {
|
|||||||
// Query is used for searching
|
// Query is used for searching
|
||||||
if (typeof search_query === 'string') {
|
if (typeof search_query === 'string') {
|
||||||
query.where(function () {
|
query.where(function () {
|
||||||
this.where('nice_name', 'like', '%' + search_query + '%');
|
this.where('name', 'like', '%' + search_query + '%');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof expand !== 'undefined' && expand !== null) {
|
if (typeof expand !== 'undefined' && expand !== null) {
|
||||||
query.withGraphFetched('[' + expand.join(', ') + ']');
|
query.eager('[' + expand.join(', ') + ']');
|
||||||
}
|
}
|
||||||
|
|
||||||
return query.then(utils.omitRows(omissions()));
|
return query;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -663,6 +659,7 @@ const internalCertificate = {
|
|||||||
meta: _.clone(row.meta) // Prevent the update method from changing this value that we'll use later
|
meta: _.clone(row.meta) // Prevent the update method from changing this value that we'll use later
|
||||||
})
|
})
|
||||||
.then((certificate) => {
|
.then((certificate) => {
|
||||||
|
console.log('ROWMETA:', row.meta);
|
||||||
certificate.meta = row.meta;
|
certificate.meta = row.meta;
|
||||||
return internalCertificate.writeCustomCert(certificate);
|
return internalCertificate.writeCustomCert(certificate);
|
||||||
});
|
});
|
||||||
@ -835,10 +832,8 @@ const internalCertificate = {
|
|||||||
requestLetsEncryptSsl: (certificate) => {
|
requestLetsEncryptSsl: (certificate) => {
|
||||||
logger.info('Requesting Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
|
logger.info('Requesting Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
|
||||||
|
|
||||||
const cmd = certbotCommand + ' certonly ' +
|
const cmd = certbotCommand + ' certonly --non-interactive ' +
|
||||||
'--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 ' +
|
||||||
@ -873,19 +868,13 @@ const internalCertificate = {
|
|||||||
logger.info(`Requesting Let'sEncrypt certificates via ${dns_plugin.display_name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`);
|
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
|
const credentialsCmd = 'mkdir -p /etc/letsencrypt/credentials 2> /dev/null; echo \'' + certificate.meta.dns_provider_credentials.replace('\'', '\\\'') + '\' > \'' + credentialsLocation + '\' && chmod 600 \'' + credentialsLocation + '\'';
|
||||||
const escapedCredentials = certificate.meta.dns_provider_credentials.replaceAll('\'', '\\\'').replaceAll('\\', '\\\\');
|
const prepareCmd = 'pip install ' + dns_plugin.package_name + '==' + dns_plugin.package_version + ' ' + dns_plugin.dependencies;
|
||||||
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
|
|
||||||
const prepareCmd = '. /opt/certbot/bin/activate && pip install --no-cache-dir ' + 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 --non-interactive ' +
|
||||||
'--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 + '" ' +
|
||||||
@ -980,13 +969,10 @@ const internalCertificate = {
|
|||||||
renewLetsEncryptSsl: (certificate) => {
|
renewLetsEncryptSsl: (certificate) => {
|
||||||
logger.info('Renewing Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
|
logger.info('Renewing Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
|
||||||
|
|
||||||
const cmd = certbotCommand + ' renew --force-renewal ' +
|
const cmd = certbotCommand + ' renew --force-renewal --non-interactive ' +
|
||||||
'--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 ' +
|
|
||||||
'--disable-hook-validation ' +
|
'--disable-hook-validation ' +
|
||||||
(letsencryptStaging ? '--staging' : '');
|
(letsencryptStaging ? '--staging' : '');
|
||||||
|
|
||||||
@ -1012,13 +998,9 @@ const internalCertificate = {
|
|||||||
|
|
||||||
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 ${dns_plugin.display_name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`);
|
||||||
|
|
||||||
let mainCmd = certbotCommand + ' renew ' +
|
let mainCmd = certbotCommand + ' renew --non-interactive ' +
|
||||||
'--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 ' +
|
|
||||||
(letsencryptStaging ? ' --staging' : '');
|
(letsencryptStaging ? ' --staging' : '');
|
||||||
|
|
||||||
// Prepend the path to the credentials file as an environment variable
|
// Prepend the path to the credentials file as an environment variable
|
||||||
@ -1044,8 +1026,7 @@ const internalCertificate = {
|
|||||||
revokeLetsEncryptSsl: (certificate, throw_errors) => {
|
revokeLetsEncryptSsl: (certificate, throw_errors) => {
|
||||||
logger.info('Revoking Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
|
logger.info('Revoking Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
|
||||||
|
|
||||||
const mainCmd = certbotCommand + ' revoke ' +
|
const mainCmd = certbotCommand + ' revoke --non-interactive ' +
|
||||||
'--config "' + letsencryptConfig + '" ' +
|
|
||||||
'--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' : '');
|
||||||
@ -1138,94 +1119,6 @@ const internalCertificate = {
|
|||||||
} else {
|
} else {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
testHttpsChallenge: async (access, domains) => {
|
|
||||||
await access.can('certificates:list');
|
|
||||||
|
|
||||||
if (!isArray(domains)) {
|
|
||||||
throw new error.InternalValidationError('Domains must be an array of strings');
|
|
||||||
}
|
|
||||||
if (domains.length === 0) {
|
|
||||||
throw new error.InternalValidationError('No domains provided');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a test challenge file
|
|
||||||
const testChallengeDir = '/data/letsencrypt-acme-challenge/.well-known/acme-challenge';
|
|
||||||
const testChallengeFile = testChallengeDir + '/test-challenge';
|
|
||||||
fs.mkdirSync(testChallengeDir, {recursive: true});
|
|
||||||
fs.writeFileSync(testChallengeFile, 'Success', {encoding: 'utf8'});
|
|
||||||
|
|
||||||
async function performTestForDomain (domain) {
|
|
||||||
logger.info('Testing http challenge for ' + domain);
|
|
||||||
const url = `http://${domain}/.well-known/acme-challenge/test-challenge`;
|
|
||||||
const formBody = `method=G&url=${encodeURI(url)}&bodytype=T&requestbody=&headername=User-Agent&headervalue=None&locationid=1&ch=false&cc=false`;
|
|
||||||
const options = {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
'Content-Length': Buffer.byteLength(formBody)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = await new Promise((resolve) => {
|
|
||||||
|
|
||||||
const req = https.request('https://www.site24x7.com/tools/restapi-tester', options, function (res) {
|
|
||||||
let responseBody = '';
|
|
||||||
|
|
||||||
res.on('data', (chunk) => responseBody = responseBody + chunk);
|
|
||||||
res.on('end', function () {
|
|
||||||
const parsedBody = JSON.parse(responseBody + '');
|
|
||||||
if (res.statusCode !== 200) {
|
|
||||||
logger.warn(`Failed to test HTTP challenge for domain ${domain}`, res);
|
|
||||||
resolve(undefined);
|
|
||||||
}
|
|
||||||
resolve(parsedBody);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Make sure to write the request body.
|
|
||||||
req.write(formBody);
|
|
||||||
req.end();
|
|
||||||
req.on('error', function (e) { logger.warn(`Failed to test HTTP challenge for domain ${domain}`, e);
|
|
||||||
resolve(undefined); });
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
// Some error occurred while trying to get the data
|
|
||||||
return 'failed';
|
|
||||||
} else if (`${result.responsecode}` === '200' && result.htmlresponse === 'Success') {
|
|
||||||
// Server exists and has responded with the correct data
|
|
||||||
return 'ok';
|
|
||||||
} else if (`${result.responsecode}` === '200') {
|
|
||||||
// Server exists but has responded with wrong data
|
|
||||||
logger.info(`HTTP challenge test failed for domain ${domain} because of invalid returned data:`, result.htmlresponse);
|
|
||||||
return 'wrong-data';
|
|
||||||
} else if (`${result.responsecode}` === '404') {
|
|
||||||
// Server exists but responded with a 404
|
|
||||||
logger.info(`HTTP challenge test failed for domain ${domain} because code 404 was returned`);
|
|
||||||
return '404';
|
|
||||||
} else if (`${result.responsecode}` === '0' || (typeof result.reason === 'string' && result.reason.toLowerCase() === 'host unavailable')) {
|
|
||||||
// Server does not exist at domain
|
|
||||||
logger.info(`HTTP challenge test failed for domain ${domain} the host was not found`);
|
|
||||||
return 'no-host';
|
|
||||||
} else {
|
|
||||||
// Other errors
|
|
||||||
logger.info(`HTTP challenge test failed for domain ${domain} because code ${result.responsecode} was returned`);
|
|
||||||
return `other:${result.responsecode}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const results = {};
|
|
||||||
|
|
||||||
for (const domain of domains){
|
|
||||||
results[domain] = await performTestForDomain(domain);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the test challenge file
|
|
||||||
fs.unlinkSync(testChallengeFile);
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const error = require('../lib/error');
|
const error = require('../lib/error');
|
||||||
const utils = require('../lib/utils');
|
|
||||||
const deadHostModel = require('../models/dead_host');
|
const deadHostModel = require('../models/dead_host');
|
||||||
const internalHost = require('./host');
|
const internalHost = require('./host');
|
||||||
const internalNginx = require('./nginx');
|
const internalNginx = require('./nginx');
|
||||||
@ -50,8 +49,8 @@ const internalDeadHost = {
|
|||||||
|
|
||||||
return deadHostModel
|
return deadHostModel
|
||||||
.query()
|
.query()
|
||||||
.insertAndFetch(data)
|
.omit(omissions())
|
||||||
.then(utils.omitRow(omissions()));
|
.insertAndFetch(data);
|
||||||
})
|
})
|
||||||
.then((row) => {
|
.then((row) => {
|
||||||
if (create_certificate) {
|
if (create_certificate) {
|
||||||
@ -219,28 +218,31 @@ const internalDeadHost = {
|
|||||||
.query()
|
.query()
|
||||||
.where('is_deleted', 0)
|
.where('is_deleted', 0)
|
||||||
.andWhere('id', data.id)
|
.andWhere('id', data.id)
|
||||||
.allowGraph('[owner,certificate]')
|
.allowEager('[owner,certificate]')
|
||||||
.first();
|
.first();
|
||||||
|
|
||||||
if (access_data.permission_visibility !== 'all') {
|
if (access_data.permission_visibility !== 'all') {
|
||||||
query.andWhere('owner_user_id', access.token.getUserId(1));
|
query.andWhere('owner_user_id', access.token.getUserId(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof data.expand !== 'undefined' && data.expand !== null) {
|
|
||||||
query.withGraphFetched('[' + data.expand.join(', ') + ']');
|
|
||||||
}
|
|
||||||
|
|
||||||
return query.then(utils.omitRow(omissions()));
|
|
||||||
})
|
|
||||||
.then((row) => {
|
|
||||||
if (!row) {
|
|
||||||
throw new error.ItemNotFoundError(data.id);
|
|
||||||
}
|
|
||||||
// Custom omissions
|
// Custom omissions
|
||||||
if (typeof data.omit !== 'undefined' && data.omit !== null) {
|
if (typeof data.omit !== 'undefined' && data.omit !== null) {
|
||||||
row = _.omit(row, data.omit);
|
query.omit(data.omit);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof data.expand !== 'undefined' && data.expand !== null) {
|
||||||
|
query.eager('[' + data.expand.join(', ') + ']');
|
||||||
|
}
|
||||||
|
|
||||||
|
return query;
|
||||||
|
})
|
||||||
|
.then((row) => {
|
||||||
|
if (row) {
|
||||||
|
row = internalHost.cleanRowCertificateMeta(row);
|
||||||
|
return _.omit(row, omissions());
|
||||||
|
} else {
|
||||||
|
throw new error.ItemNotFoundError(data.id);
|
||||||
}
|
}
|
||||||
return row;
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -402,7 +404,8 @@ const internalDeadHost = {
|
|||||||
.query()
|
.query()
|
||||||
.where('is_deleted', 0)
|
.where('is_deleted', 0)
|
||||||
.groupBy('id')
|
.groupBy('id')
|
||||||
.allowGraph('[owner,certificate]')
|
.omit(['is_deleted'])
|
||||||
|
.allowEager('[owner,certificate]')
|
||||||
.orderBy('domain_names', 'ASC');
|
.orderBy('domain_names', 'ASC');
|
||||||
|
|
||||||
if (access_data.permission_visibility !== 'all') {
|
if (access_data.permission_visibility !== 'all') {
|
||||||
@ -417,10 +420,10 @@ const internalDeadHost = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (typeof expand !== 'undefined' && expand !== null) {
|
if (typeof expand !== 'undefined' && expand !== null) {
|
||||||
query.withGraphFetched('[' + expand.join(', ') + ']');
|
query.eager('[' + expand.join(', ') + ']');
|
||||||
}
|
}
|
||||||
|
|
||||||
return query.then(utils.omitRows(omissions()));
|
return query;
|
||||||
})
|
})
|
||||||
.then((rows) => {
|
.then((rows) => {
|
||||||
if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) {
|
if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) {
|
||||||
|
@ -2,16 +2,13 @@ const https = require('https');
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const logger = require('../logger').ip_ranges;
|
const logger = require('../logger').ip_ranges;
|
||||||
const error = require('../lib/error');
|
const error = require('../lib/error');
|
||||||
const utils = require('../lib/utils');
|
|
||||||
const internalNginx = require('./nginx');
|
const internalNginx = require('./nginx');
|
||||||
|
const { Liquid } = require('liquidjs');
|
||||||
|
|
||||||
const CLOUDFRONT_URL = 'https://ip-ranges.amazonaws.com/ip-ranges.json';
|
const CLOUDFRONT_URL = 'https://ip-ranges.amazonaws.com/ip-ranges.json';
|
||||||
const CLOUDFARE_V4_URL = 'https://www.cloudflare.com/ips-v4';
|
const CLOUDFARE_V4_URL = 'https://www.cloudflare.com/ips-v4';
|
||||||
const CLOUDFARE_V6_URL = 'https://www.cloudflare.com/ips-v6';
|
const CLOUDFARE_V6_URL = 'https://www.cloudflare.com/ips-v6';
|
||||||
|
|
||||||
const regIpV4 = /^(\d+\.?){4}\/\d+/;
|
|
||||||
const regIpV6 = /^(([\da-fA-F]+)?:)+\/\d+/;
|
|
||||||
|
|
||||||
const internalIpRanges = {
|
const internalIpRanges = {
|
||||||
|
|
||||||
interval_timeout: 1000 * 60 * 60 * 6, // 6 hours
|
interval_timeout: 1000 * 60 * 60 * 6, // 6 hours
|
||||||
@ -77,14 +74,14 @@ const internalIpRanges = {
|
|||||||
return internalIpRanges.fetchUrl(CLOUDFARE_V4_URL);
|
return internalIpRanges.fetchUrl(CLOUDFARE_V4_URL);
|
||||||
})
|
})
|
||||||
.then((cloudfare_data) => {
|
.then((cloudfare_data) => {
|
||||||
let items = cloudfare_data.split('\n').filter((line) => regIpV4.test(line));
|
let items = cloudfare_data.split('\n');
|
||||||
ip_ranges = [... ip_ranges, ... items];
|
ip_ranges = [... ip_ranges, ... items];
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return internalIpRanges.fetchUrl(CLOUDFARE_V6_URL);
|
return internalIpRanges.fetchUrl(CLOUDFARE_V6_URL);
|
||||||
})
|
})
|
||||||
.then((cloudfare_data) => {
|
.then((cloudfare_data) => {
|
||||||
let items = cloudfare_data.split('\n').filter((line) => regIpV6.test(line));
|
let items = cloudfare_data.split('\n');
|
||||||
ip_ranges = [... ip_ranges, ... items];
|
ip_ranges = [... ip_ranges, ... items];
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@ -119,7 +116,10 @@ const internalIpRanges = {
|
|||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
generateConfig: (ip_ranges) => {
|
generateConfig: (ip_ranges) => {
|
||||||
const renderEngine = utils.getRenderEngine();
|
let renderEngine = new Liquid({
|
||||||
|
root: __dirname + '/../templates/'
|
||||||
|
});
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let template = null;
|
let template = null;
|
||||||
let filename = '/etc/nginx/conf.d/include/ip_ranges.conf';
|
let filename = '/etc/nginx/conf.d/include/ip_ranges.conf';
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
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 { Liquid } = require('liquidjs');
|
||||||
|
const debug_mode = process.env.NODE_ENV !== 'production' || !!process.env.DEBUG;
|
||||||
|
|
||||||
const internalNginx = {
|
const internalNginx = {
|
||||||
|
|
||||||
@ -28,9 +29,7 @@ const internalNginx = {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
// Nginx is OK
|
// Nginx is OK
|
||||||
// We're deleting this config regardless.
|
// We're deleting this config regardless.
|
||||||
// Don't throw errors, as the file may not exist at all
|
return internalNginx.deleteConfig(host_type, host); // Don't throw errors, as the file may not exist at all
|
||||||
// Delete the .err file too
|
|
||||||
return internalNginx.deleteConfig(host_type, host, false, true);
|
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return internalNginx.generateConfig(host_type, host);
|
return internalNginx.generateConfig(host_type, host);
|
||||||
@ -65,7 +64,7 @@ const internalNginx = {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (config.debug()) {
|
if (debug_mode) {
|
||||||
logger.error('Nginx test failed:', valid_lines.join('\n'));
|
logger.error('Nginx test failed:', valid_lines.join('\n'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,9 +80,6 @@ const internalNginx = {
|
|||||||
.patch({
|
.patch({
|
||||||
meta: combined_meta
|
meta: combined_meta
|
||||||
})
|
})
|
||||||
.then(() => {
|
|
||||||
internalNginx.renameConfigAsError(host_type, host);
|
|
||||||
})
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return internalNginx.deleteConfig(host_type, host, true);
|
return internalNginx.deleteConfig(host_type, host, true);
|
||||||
});
|
});
|
||||||
@ -101,7 +97,7 @@ const internalNginx = {
|
|||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
test: () => {
|
test: () => {
|
||||||
if (config.debug()) {
|
if (debug_mode) {
|
||||||
logger.info('Testing Nginx configuration');
|
logger.info('Testing Nginx configuration');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,10 +121,13 @@ const internalNginx = {
|
|||||||
* @returns {String}
|
* @returns {String}
|
||||||
*/
|
*/
|
||||||
getConfigName: (host_type, host_id) => {
|
getConfigName: (host_type, host_id) => {
|
||||||
|
host_type = host_type.replace(new RegExp('-', 'g'), '_');
|
||||||
|
|
||||||
if (host_type === 'default') {
|
if (host_type === 'default') {
|
||||||
return '/data/nginx/default_host/site.conf';
|
return '/data/nginx/default_host/site.conf';
|
||||||
}
|
}
|
||||||
return '/data/nginx/' + internalNginx.getFileFriendlyHostType(host_type) + '/' + host_id + '.conf';
|
|
||||||
|
return '/data/nginx/' + host_type + '/' + host_id + '.conf';
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -137,6 +136,8 @@ const internalNginx = {
|
|||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
renderLocations: (host) => {
|
renderLocations: (host) => {
|
||||||
|
|
||||||
|
//logger.info('host = ' + JSON.stringify(host, null, 2));
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let template;
|
let template;
|
||||||
|
|
||||||
@ -147,7 +148,9 @@ const internalNginx = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderEngine = utils.getRenderEngine();
|
let renderer = new Liquid({
|
||||||
|
root: __dirname + '/../templates/'
|
||||||
|
});
|
||||||
let renderedLocations = '';
|
let renderedLocations = '';
|
||||||
|
|
||||||
const locationRendering = async () => {
|
const locationRendering = async () => {
|
||||||
@ -165,8 +168,10 @@ const internalNginx = {
|
|||||||
locationCopy.forward_path = `/${splitted.join('/')}`;
|
locationCopy.forward_path = `/${splitted.join('/')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//logger.info('locationCopy = ' + JSON.stringify(locationCopy, null, 2));
|
||||||
|
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
renderedLocations += await renderEngine.parseAndRender(template, locationCopy);
|
renderedLocations += await renderer.parseAndRender(template, locationCopy);
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
@ -182,20 +187,24 @@ const internalNginx = {
|
|||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
generateConfig: (host_type, host) => {
|
generateConfig: (host_type, host) => {
|
||||||
const nice_host_type = internalNginx.getFileFriendlyHostType(host_type);
|
host_type = host_type.replace(new RegExp('-', 'g'), '_');
|
||||||
|
|
||||||
if (config.debug()) {
|
if (debug_mode) {
|
||||||
logger.info('Generating ' + nice_host_type + ' Config:', JSON.stringify(host, null, 2));
|
logger.info('Generating ' + host_type + ' Config:', host);
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderEngine = utils.getRenderEngine();
|
// logger.info('host = ' + JSON.stringify(host, null, 2));
|
||||||
|
|
||||||
|
let renderEngine = new Liquid({
|
||||||
|
root: __dirname + '/../templates/'
|
||||||
|
});
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let template = null;
|
let template = null;
|
||||||
let filename = internalNginx.getConfigName(nice_host_type, host.id);
|
let filename = internalNginx.getConfigName(host_type, host.id);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
template = fs.readFileSync(__dirname + '/../templates/' + nice_host_type + '.conf', {encoding: 'utf8'});
|
template = fs.readFileSync(__dirname + '/../templates/' + host_type + '.conf', {encoding: 'utf8'});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
reject(new error.ConfigurationError(err.message));
|
reject(new error.ConfigurationError(err.message));
|
||||||
return;
|
return;
|
||||||
@ -205,7 +214,7 @@ const internalNginx = {
|
|||||||
let origLocations;
|
let origLocations;
|
||||||
|
|
||||||
// Manipulate the data a bit before sending it to the template
|
// Manipulate the data a bit before sending it to the template
|
||||||
if (nice_host_type !== 'default') {
|
if (host_type !== 'default') {
|
||||||
host.use_default_location = true;
|
host.use_default_location = true;
|
||||||
if (typeof host.advanced_config !== 'undefined' && host.advanced_config) {
|
if (typeof host.advanced_config !== 'undefined' && host.advanced_config) {
|
||||||
host.use_default_location = !internalNginx.advancedConfigHasDefaultLocation(host.advanced_config);
|
host.use_default_location = !internalNginx.advancedConfigHasDefaultLocation(host.advanced_config);
|
||||||
@ -239,7 +248,7 @@ const internalNginx = {
|
|||||||
.then((config_text) => {
|
.then((config_text) => {
|
||||||
fs.writeFileSync(filename, config_text, {encoding: 'utf8'});
|
fs.writeFileSync(filename, config_text, {encoding: 'utf8'});
|
||||||
|
|
||||||
if (config.debug()) {
|
if (debug_mode) {
|
||||||
logger.success('Wrote config:', filename, config_text);
|
logger.success('Wrote config:', filename, config_text);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,7 +258,7 @@ const internalNginx = {
|
|||||||
resolve(true);
|
resolve(true);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if (config.debug()) {
|
if (debug_mode) {
|
||||||
logger.warn('Could not write ' + filename + ':', err.message);
|
logger.warn('Could not write ' + filename + ':', err.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,11 +277,13 @@ const internalNginx = {
|
|||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
generateLetsEncryptRequestConfig: (certificate) => {
|
generateLetsEncryptRequestConfig: (certificate) => {
|
||||||
if (config.debug()) {
|
if (debug_mode) {
|
||||||
logger.info('Generating LetsEncrypt Request Config:', certificate);
|
logger.info('Generating LetsEncrypt Request Config:', certificate);
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderEngine = utils.getRenderEngine();
|
let renderEngine = new Liquid({
|
||||||
|
root: __dirname + '/../templates/'
|
||||||
|
});
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let template = null;
|
let template = null;
|
||||||
@ -292,14 +303,14 @@ const internalNginx = {
|
|||||||
.then((config_text) => {
|
.then((config_text) => {
|
||||||
fs.writeFileSync(filename, config_text, {encoding: 'utf8'});
|
fs.writeFileSync(filename, config_text, {encoding: 'utf8'});
|
||||||
|
|
||||||
if (config.debug()) {
|
if (debug_mode) {
|
||||||
logger.success('Wrote config:', filename, config_text);
|
logger.success('Wrote config:', filename, config_text);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(true);
|
resolve(true);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if (config.debug()) {
|
if (debug_mode) {
|
||||||
logger.warn('Could not write ' + filename + ':', err.message);
|
logger.warn('Could not write ' + filename + ':', err.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -308,58 +319,33 @@ const internalNginx = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* A simple wrapper around unlinkSync that writes to the logger
|
|
||||||
*
|
|
||||||
* @param {String} filename
|
|
||||||
*/
|
|
||||||
deleteFile: (filename) => {
|
|
||||||
logger.debug('Deleting file: ' + filename);
|
|
||||||
try {
|
|
||||||
fs.unlinkSync(filename);
|
|
||||||
} catch (err) {
|
|
||||||
logger.debug('Could not delete file:', JSON.stringify(err, null, 2));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {String} host_type
|
|
||||||
* @returns String
|
|
||||||
*/
|
|
||||||
getFileFriendlyHostType: (host_type) => {
|
|
||||||
return host_type.replace(new RegExp('-', 'g'), '_');
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This removes the temporary nginx config file generated by `generateLetsEncryptRequestConfig`
|
* This removes the temporary nginx config file generated by `generateLetsEncryptRequestConfig`
|
||||||
*
|
*
|
||||||
* @param {Object} certificate
|
* @param {Object} certificate
|
||||||
|
* @param {Boolean} [throw_errors]
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
deleteLetsEncryptRequestConfig: (certificate) => {
|
deleteLetsEncryptRequestConfig: (certificate, throw_errors) => {
|
||||||
const config_file = '/data/nginx/temp/letsencrypt_' + certificate.id + '.conf';
|
return new Promise((resolve, reject) => {
|
||||||
return new Promise((resolve/*, reject*/) => {
|
try {
|
||||||
internalNginx.deleteFile(config_file);
|
let config_file = '/data/nginx/temp/letsencrypt_' + certificate.id + '.conf';
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
if (debug_mode) {
|
||||||
* @param {String} host_type
|
logger.warn('Deleting nginx config: ' + config_file);
|
||||||
* @param {Object} [host]
|
|
||||||
* @param {Boolean} [delete_err_file]
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
deleteConfig: (host_type, host, delete_err_file) => {
|
|
||||||
const config_file = internalNginx.getConfigName(internalNginx.getFileFriendlyHostType(host_type), typeof host === 'undefined' ? 0 : host.id);
|
|
||||||
const config_file_err = config_file + '.err';
|
|
||||||
|
|
||||||
return new Promise((resolve/*, reject*/) => {
|
|
||||||
internalNginx.deleteFile(config_file);
|
|
||||||
if (delete_err_file) {
|
|
||||||
internalNginx.deleteFile(config_file_err);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fs.unlinkSync(config_file);
|
||||||
|
} catch (err) {
|
||||||
|
if (debug_mode) {
|
||||||
|
logger.warn('Could not delete config:', err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (throw_errors) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -367,21 +353,33 @@ const internalNginx = {
|
|||||||
/**
|
/**
|
||||||
* @param {String} host_type
|
* @param {String} host_type
|
||||||
* @param {Object} [host]
|
* @param {Object} [host]
|
||||||
|
* @param {Boolean} [throw_errors]
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
renameConfigAsError: (host_type, host) => {
|
deleteConfig: (host_type, host, throw_errors) => {
|
||||||
const config_file = internalNginx.getConfigName(internalNginx.getFileFriendlyHostType(host_type), typeof host === 'undefined' ? 0 : host.id);
|
host_type = host_type.replace(new RegExp('-', 'g'), '_');
|
||||||
const config_file_err = config_file + '.err';
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
let config_file = internalNginx.getConfigName(host_type, typeof host === 'undefined' ? 0 : host.id);
|
||||||
|
|
||||||
|
if (debug_mode) {
|
||||||
|
logger.warn('Deleting nginx config: ' + config_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.unlinkSync(config_file);
|
||||||
|
} catch (err) {
|
||||||
|
if (debug_mode) {
|
||||||
|
logger.warn('Could not delete config:', err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (throw_errors) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return new Promise((resolve/*, reject*/) => {
|
|
||||||
fs.unlink(config_file, () => {
|
|
||||||
// ignore result, continue
|
|
||||||
fs.rename(config_file, config_file_err, () => {
|
|
||||||
// also ignore result, as this is a debugging informative file anyway
|
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -401,12 +399,13 @@ const internalNginx = {
|
|||||||
/**
|
/**
|
||||||
* @param {String} host_type
|
* @param {String} host_type
|
||||||
* @param {Array} hosts
|
* @param {Array} hosts
|
||||||
|
* @param {Boolean} [throw_errors]
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
bulkDeleteConfigs: (host_type, hosts) => {
|
bulkDeleteConfigs: (host_type, hosts, throw_errors) => {
|
||||||
let promises = [];
|
let promises = [];
|
||||||
hosts.map(function (host) {
|
hosts.map(function (host) {
|
||||||
promises.push(internalNginx.deleteConfig(host_type, host, true));
|
promises.push(internalNginx.deleteConfig(host_type, host, throw_errors));
|
||||||
});
|
});
|
||||||
|
|
||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
@ -416,8 +415,8 @@ const internalNginx = {
|
|||||||
* @param {string} config
|
* @param {string} config
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
advancedConfigHasDefaultLocation: function (cfg) {
|
advancedConfigHasDefaultLocation: function (config) {
|
||||||
return !!cfg.match(/^(?:.*;)?\s*?location\s*?\/\s*?{/im);
|
return !!config.match(/^(?:.*;)?\s*?location\s*?\/\s*?{/im);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const error = require('../lib/error');
|
const error = require('../lib/error');
|
||||||
const utils = require('../lib/utils');
|
|
||||||
const proxyHostModel = require('../models/proxy_host');
|
const proxyHostModel = require('../models/proxy_host');
|
||||||
const internalHost = require('./host');
|
const internalHost = require('./host');
|
||||||
const internalNginx = require('./nginx');
|
const internalNginx = require('./nginx');
|
||||||
@ -50,8 +49,8 @@ const internalProxyHost = {
|
|||||||
|
|
||||||
return proxyHostModel
|
return proxyHostModel
|
||||||
.query()
|
.query()
|
||||||
.insertAndFetch(data)
|
.omit(omissions())
|
||||||
.then(utils.omitRow(omissions()));
|
.insertAndFetch(data);
|
||||||
})
|
})
|
||||||
.then((row) => {
|
.then((row) => {
|
||||||
if (create_certificate) {
|
if (create_certificate) {
|
||||||
@ -171,7 +170,6 @@ const internalProxyHost = {
|
|||||||
.query()
|
.query()
|
||||||
.where({id: data.id})
|
.where({id: data.id})
|
||||||
.patch(data)
|
.patch(data)
|
||||||
.then(utils.omitRow(omissions()))
|
|
||||||
.then((saved_row) => {
|
.then((saved_row) => {
|
||||||
// Add to audit log
|
// Add to audit log
|
||||||
return internalAuditLog.add(access, {
|
return internalAuditLog.add(access, {
|
||||||
@ -181,7 +179,7 @@ const internalProxyHost = {
|
|||||||
meta: data
|
meta: data
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return saved_row;
|
return _.omit(saved_row, omissions());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@ -225,29 +223,31 @@ 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]')
|
.allowEager('[owner,access_list,access_list.[clients,items],certificate]')
|
||||||
.first();
|
.first();
|
||||||
|
|
||||||
if (access_data.permission_visibility !== 'all') {
|
if (access_data.permission_visibility !== 'all') {
|
||||||
query.andWhere('owner_user_id', access.token.getUserId(1));
|
query.andWhere('owner_user_id', access.token.getUserId(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof data.expand !== 'undefined' && data.expand !== null) {
|
|
||||||
query.withGraphFetched('[' + data.expand.join(', ') + ']');
|
|
||||||
}
|
|
||||||
|
|
||||||
return query.then(utils.omitRow(omissions()));
|
|
||||||
})
|
|
||||||
.then((row) => {
|
|
||||||
if (!row) {
|
|
||||||
throw new error.ItemNotFoundError(data.id);
|
|
||||||
}
|
|
||||||
row = internalHost.cleanRowCertificateMeta(row);
|
|
||||||
// Custom omissions
|
// Custom omissions
|
||||||
if (typeof data.omit !== 'undefined' && data.omit !== null) {
|
if (typeof data.omit !== 'undefined' && data.omit !== null) {
|
||||||
row = _.omit(row, data.omit);
|
query.omit(data.omit);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof data.expand !== 'undefined' && data.expand !== null) {
|
||||||
|
query.eager('[' + data.expand.join(', ') + ']');
|
||||||
|
}
|
||||||
|
|
||||||
|
return query;
|
||||||
|
})
|
||||||
|
.then((row) => {
|
||||||
|
if (row) {
|
||||||
|
row = internalHost.cleanRowCertificateMeta(row);
|
||||||
|
return _.omit(row, omissions());
|
||||||
|
} else {
|
||||||
|
throw new error.ItemNotFoundError(data.id);
|
||||||
}
|
}
|
||||||
return row;
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -409,7 +409,8 @@ const internalProxyHost = {
|
|||||||
.query()
|
.query()
|
||||||
.where('is_deleted', 0)
|
.where('is_deleted', 0)
|
||||||
.groupBy('id')
|
.groupBy('id')
|
||||||
.allowGraph('[owner,access_list,certificate]')
|
.omit(['is_deleted'])
|
||||||
|
.allowEager('[owner,access_list,certificate]')
|
||||||
.orderBy('domain_names', 'ASC');
|
.orderBy('domain_names', 'ASC');
|
||||||
|
|
||||||
if (access_data.permission_visibility !== 'all') {
|
if (access_data.permission_visibility !== 'all') {
|
||||||
@ -424,10 +425,10 @@ const internalProxyHost = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (typeof expand !== 'undefined' && expand !== null) {
|
if (typeof expand !== 'undefined' && expand !== null) {
|
||||||
query.withGraphFetched('[' + expand.join(', ') + ']');
|
query.eager('[' + expand.join(', ') + ']');
|
||||||
}
|
}
|
||||||
|
|
||||||
return query.then(utils.omitRows(omissions()));
|
return query;
|
||||||
})
|
})
|
||||||
.then((rows) => {
|
.then((rows) => {
|
||||||
if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) {
|
if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const error = require('../lib/error');
|
const error = require('../lib/error');
|
||||||
const utils = require('../lib/utils');
|
|
||||||
const redirectionHostModel = require('../models/redirection_host');
|
const redirectionHostModel = require('../models/redirection_host');
|
||||||
const internalHost = require('./host');
|
const internalHost = require('./host');
|
||||||
const internalNginx = require('./nginx');
|
const internalNginx = require('./nginx');
|
||||||
@ -50,8 +49,8 @@ const internalRedirectionHost = {
|
|||||||
|
|
||||||
return redirectionHostModel
|
return redirectionHostModel
|
||||||
.query()
|
.query()
|
||||||
.insertAndFetch(data)
|
.omit(omissions())
|
||||||
.then(utils.omitRow(omissions()));
|
.insertAndFetch(data);
|
||||||
})
|
})
|
||||||
.then((row) => {
|
.then((row) => {
|
||||||
if (create_certificate) {
|
if (create_certificate) {
|
||||||
@ -66,8 +65,9 @@ const internalRedirectionHost = {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
return row;
|
return row;
|
||||||
});
|
});
|
||||||
}
|
} else {
|
||||||
return row;
|
return row;
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.then((row) => {
|
.then((row) => {
|
||||||
// re-fetch with cert
|
// re-fetch with cert
|
||||||
@ -218,29 +218,31 @@ const internalRedirectionHost = {
|
|||||||
.query()
|
.query()
|
||||||
.where('is_deleted', 0)
|
.where('is_deleted', 0)
|
||||||
.andWhere('id', data.id)
|
.andWhere('id', data.id)
|
||||||
.allowGraph('[owner,certificate]')
|
.allowEager('[owner,certificate]')
|
||||||
.first();
|
.first();
|
||||||
|
|
||||||
if (access_data.permission_visibility !== 'all') {
|
if (access_data.permission_visibility !== 'all') {
|
||||||
query.andWhere('owner_user_id', access.token.getUserId(1));
|
query.andWhere('owner_user_id', access.token.getUserId(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof data.expand !== 'undefined' && data.expand !== null) {
|
|
||||||
query.withGraphFetched('[' + data.expand.join(', ') + ']');
|
|
||||||
}
|
|
||||||
|
|
||||||
return query.then(utils.omitRow(omissions()));
|
|
||||||
})
|
|
||||||
.then((row) => {
|
|
||||||
if (!row) {
|
|
||||||
throw new error.ItemNotFoundError(data.id);
|
|
||||||
}
|
|
||||||
row = internalHost.cleanRowCertificateMeta(row);
|
|
||||||
// Custom omissions
|
// Custom omissions
|
||||||
if (typeof data.omit !== 'undefined' && data.omit !== null) {
|
if (typeof data.omit !== 'undefined' && data.omit !== null) {
|
||||||
row = _.omit(row, data.omit);
|
query.omit(data.omit);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof data.expand !== 'undefined' && data.expand !== null) {
|
||||||
|
query.eager('[' + data.expand.join(', ') + ']');
|
||||||
|
}
|
||||||
|
|
||||||
|
return query;
|
||||||
|
})
|
||||||
|
.then((row) => {
|
||||||
|
if (row) {
|
||||||
|
row = internalHost.cleanRowCertificateMeta(row);
|
||||||
|
return _.omit(row, omissions());
|
||||||
|
} else {
|
||||||
|
throw new error.ItemNotFoundError(data.id);
|
||||||
}
|
}
|
||||||
return row;
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -402,7 +404,8 @@ const internalRedirectionHost = {
|
|||||||
.query()
|
.query()
|
||||||
.where('is_deleted', 0)
|
.where('is_deleted', 0)
|
||||||
.groupBy('id')
|
.groupBy('id')
|
||||||
.allowGraph('[owner,certificate]')
|
.omit(['is_deleted'])
|
||||||
|
.allowEager('[owner,certificate]')
|
||||||
.orderBy('domain_names', 'ASC');
|
.orderBy('domain_names', 'ASC');
|
||||||
|
|
||||||
if (access_data.permission_visibility !== 'all') {
|
if (access_data.permission_visibility !== 'all') {
|
||||||
@ -417,10 +420,10 @@ const internalRedirectionHost = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (typeof expand !== 'undefined' && expand !== null) {
|
if (typeof expand !== 'undefined' && expand !== null) {
|
||||||
query.withGraphFetched('[' + expand.join(', ') + ']');
|
query.eager('[' + expand.join(', ') + ']');
|
||||||
}
|
}
|
||||||
|
|
||||||
return query.then(utils.omitRows(omissions()));
|
return query;
|
||||||
})
|
})
|
||||||
.then((rows) => {
|
.then((rows) => {
|
||||||
if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) {
|
if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const error = require('../lib/error');
|
const error = require('../lib/error');
|
||||||
const utils = require('../lib/utils');
|
|
||||||
const streamModel = require('../models/stream');
|
const streamModel = require('../models/stream');
|
||||||
const internalNginx = require('./nginx');
|
const internalNginx = require('./nginx');
|
||||||
const internalAuditLog = require('./audit-log');
|
const internalAuditLog = require('./audit-log');
|
||||||
@ -28,8 +27,8 @@ const internalStream = {
|
|||||||
|
|
||||||
return streamModel
|
return streamModel
|
||||||
.query()
|
.query()
|
||||||
.insertAndFetch(data)
|
.omit(omissions())
|
||||||
.then(utils.omitRow(omissions()));
|
.insertAndFetch(data);
|
||||||
})
|
})
|
||||||
.then((row) => {
|
.then((row) => {
|
||||||
// Configure nginx
|
// Configure nginx
|
||||||
@ -72,8 +71,8 @@ const internalStream = {
|
|||||||
|
|
||||||
return streamModel
|
return streamModel
|
||||||
.query()
|
.query()
|
||||||
|
.omit(omissions())
|
||||||
.patchAndFetchById(row.id, data)
|
.patchAndFetchById(row.id, data)
|
||||||
.then(utils.omitRow(omissions()))
|
|
||||||
.then((saved_row) => {
|
.then((saved_row) => {
|
||||||
return internalNginx.configure(streamModel, 'stream', saved_row)
|
return internalNginx.configure(streamModel, 'stream', saved_row)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@ -89,7 +88,7 @@ const internalStream = {
|
|||||||
meta: data
|
meta: data
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return saved_row;
|
return _.omit(saved_row, omissions());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -114,28 +113,30 @@ const internalStream = {
|
|||||||
.query()
|
.query()
|
||||||
.where('is_deleted', 0)
|
.where('is_deleted', 0)
|
||||||
.andWhere('id', data.id)
|
.andWhere('id', data.id)
|
||||||
.allowGraph('[owner]')
|
.allowEager('[owner]')
|
||||||
.first();
|
.first();
|
||||||
|
|
||||||
if (access_data.permission_visibility !== 'all') {
|
if (access_data.permission_visibility !== 'all') {
|
||||||
query.andWhere('owner_user_id', access.token.getUserId(1));
|
query.andWhere('owner_user_id', access.token.getUserId(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof data.expand !== 'undefined' && data.expand !== null) {
|
|
||||||
query.withGraphFetched('[' + data.expand.join(', ') + ']');
|
|
||||||
}
|
|
||||||
|
|
||||||
return query.then(utils.omitRow(omissions()));
|
|
||||||
})
|
|
||||||
.then((row) => {
|
|
||||||
if (!row) {
|
|
||||||
throw new error.ItemNotFoundError(data.id);
|
|
||||||
}
|
|
||||||
// Custom omissions
|
// Custom omissions
|
||||||
if (typeof data.omit !== 'undefined' && data.omit !== null) {
|
if (typeof data.omit !== 'undefined' && data.omit !== null) {
|
||||||
row = _.omit(row, data.omit);
|
query.omit(data.omit);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof data.expand !== 'undefined' && data.expand !== null) {
|
||||||
|
query.eager('[' + data.expand.join(', ') + ']');
|
||||||
|
}
|
||||||
|
|
||||||
|
return query;
|
||||||
|
})
|
||||||
|
.then((row) => {
|
||||||
|
if (row) {
|
||||||
|
return _.omit(row, omissions());
|
||||||
|
} else {
|
||||||
|
throw new error.ItemNotFoundError(data.id);
|
||||||
}
|
}
|
||||||
return row;
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -297,7 +298,8 @@ const internalStream = {
|
|||||||
.query()
|
.query()
|
||||||
.where('is_deleted', 0)
|
.where('is_deleted', 0)
|
||||||
.groupBy('id')
|
.groupBy('id')
|
||||||
.allowGraph('[owner]')
|
.omit(['is_deleted'])
|
||||||
|
.allowEager('[owner]')
|
||||||
.orderBy('incoming_port', 'ASC');
|
.orderBy('incoming_port', 'ASC');
|
||||||
|
|
||||||
if (access_data.permission_visibility !== 'all') {
|
if (access_data.permission_visibility !== 'all') {
|
||||||
@ -312,10 +314,10 @@ const internalStream = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (typeof expand !== 'undefined' && expand !== null) {
|
if (typeof expand !== 'undefined' && expand !== null) {
|
||||||
query.withGraphFetched('[' + expand.join(', ') + ']');
|
query.eager('[' + expand.join(', ') + ']');
|
||||||
}
|
}
|
||||||
|
|
||||||
return query.then(utils.omitRows(omissions()));
|
return query;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ module.exports = {
|
|||||||
|
|
||||||
return userModel
|
return userModel
|
||||||
.query()
|
.query()
|
||||||
.where('email', data.identity.toLowerCase().trim())
|
.where('email', data.identity)
|
||||||
.andWhere('is_deleted', 0)
|
.andWhere('is_deleted', 0)
|
||||||
.andWhere('is_disabled', 0)
|
.andWhere('is_disabled', 0)
|
||||||
.first()
|
.first()
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const error = require('../lib/error');
|
const error = require('../lib/error');
|
||||||
const utils = require('../lib/utils');
|
|
||||||
const userModel = require('../models/user');
|
const userModel = require('../models/user');
|
||||||
const userPermissionModel = require('../models/user_permission');
|
const userPermissionModel = require('../models/user_permission');
|
||||||
const authModel = require('../models/auth');
|
const authModel = require('../models/auth');
|
||||||
@ -36,8 +35,8 @@ const internalUser = {
|
|||||||
|
|
||||||
return userModel
|
return userModel
|
||||||
.query()
|
.query()
|
||||||
.insertAndFetch(data)
|
.omit(omissions())
|
||||||
.then(utils.omitRow(omissions()));
|
.insertAndFetch(data);
|
||||||
})
|
})
|
||||||
.then((user) => {
|
.then((user) => {
|
||||||
if (auth) {
|
if (auth) {
|
||||||
@ -141,8 +140,11 @@ const internalUser = {
|
|||||||
|
|
||||||
return userModel
|
return userModel
|
||||||
.query()
|
.query()
|
||||||
|
.omit(omissions())
|
||||||
.patchAndFetchById(user.id, data)
|
.patchAndFetchById(user.id, data)
|
||||||
.then(utils.omitRow(omissions()));
|
.then((saved_user) => {
|
||||||
|
return _.omit(saved_user, omissions());
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return internalUser.get(access, {id: data.id});
|
return internalUser.get(access, {id: data.id});
|
||||||
@ -184,24 +186,26 @@ const internalUser = {
|
|||||||
.query()
|
.query()
|
||||||
.where('is_deleted', 0)
|
.where('is_deleted', 0)
|
||||||
.andWhere('id', data.id)
|
.andWhere('id', data.id)
|
||||||
.allowGraph('[permissions]')
|
.allowEager('[permissions]')
|
||||||
.first();
|
.first();
|
||||||
|
|
||||||
if (typeof data.expand !== 'undefined' && data.expand !== null) {
|
|
||||||
query.withGraphFetched('[' + data.expand.join(', ') + ']');
|
|
||||||
}
|
|
||||||
|
|
||||||
return query.then(utils.omitRow(omissions()));
|
|
||||||
})
|
|
||||||
.then((row) => {
|
|
||||||
if (!row) {
|
|
||||||
throw new error.ItemNotFoundError(data.id);
|
|
||||||
}
|
|
||||||
// Custom omissions
|
// Custom omissions
|
||||||
if (typeof data.omit !== 'undefined' && data.omit !== null) {
|
if (typeof data.omit !== 'undefined' && data.omit !== null) {
|
||||||
row = _.omit(row, data.omit);
|
query.omit(data.omit);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof data.expand !== 'undefined' && data.expand !== null) {
|
||||||
|
query.eager('[' + data.expand.join(', ') + ']');
|
||||||
|
}
|
||||||
|
|
||||||
|
return query;
|
||||||
|
})
|
||||||
|
.then((row) => {
|
||||||
|
if (row) {
|
||||||
|
return _.omit(row, omissions());
|
||||||
|
} else {
|
||||||
|
throw new error.ItemNotFoundError(data.id);
|
||||||
}
|
}
|
||||||
return row;
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -318,7 +322,8 @@ const internalUser = {
|
|||||||
.query()
|
.query()
|
||||||
.where('is_deleted', 0)
|
.where('is_deleted', 0)
|
||||||
.groupBy('id')
|
.groupBy('id')
|
||||||
.allowGraph('[permissions]')
|
.omit(['is_deleted'])
|
||||||
|
.allowEager('[permissions]')
|
||||||
.orderBy('name', 'ASC');
|
.orderBy('name', 'ASC');
|
||||||
|
|
||||||
// Query is used for searching
|
// Query is used for searching
|
||||||
@ -330,10 +335,10 @@ const internalUser = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (typeof expand !== 'undefined' && expand !== null) {
|
if (typeof expand !== 'undefined' && expand !== null) {
|
||||||
query.withGraphFetched('[' + expand.join(', ') + ']');
|
query.eager('[' + expand.join(', ') + ']');
|
||||||
}
|
}
|
||||||
|
|
||||||
return query.then(utils.omitRows(omissions()));
|
return query;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -55,8 +55,8 @@ module.exports = function (token_string) {
|
|||||||
.where('id', token_data.attrs.id)
|
.where('id', token_data.attrs.id)
|
||||||
.andWhere('is_deleted', 0)
|
.andWhere('is_deleted', 0)
|
||||||
.andWhere('is_disabled', 0)
|
.andWhere('is_disabled', 0)
|
||||||
.allowGraph('[permissions]')
|
.allowEager('[permissions]')
|
||||||
.withGraphFetched('[permissions]')
|
.eager('[permissions]')
|
||||||
.first()
|
.first()
|
||||||
.then((user) => {
|
.then((user) => {
|
||||||
if (user) {
|
if (user) {
|
||||||
|
@ -1,184 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,8 +1,4 @@
|
|||||||
const _ = require('lodash');
|
|
||||||
const exec = require('child_process').exec;
|
const exec = require('child_process').exec;
|
||||||
const execFile = require('child_process').execFile;
|
|
||||||
const { Liquid } = require('liquidjs');
|
|
||||||
const logger = require('../logger').global;
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
||||||
@ -20,82 +16,5 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {String} cmd
|
|
||||||
* @param {Array} args
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
execFile: function (cmd, args) {
|
|
||||||
logger.debug('CMD: ' + cmd + ' ' + (args ? args.join(' ') : ''));
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
execFile(cmd, args, function (err, stdout, /*stderr*/) {
|
|
||||||
if (err && typeof err === 'object') {
|
|
||||||
reject(err);
|
|
||||||
} else {
|
|
||||||
resolve(stdout.trim());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used in objection query builder
|
|
||||||
*
|
|
||||||
* @param {Array} omissions
|
|
||||||
* @returns {Function}
|
|
||||||
*/
|
|
||||||
omitRow: function (omissions) {
|
|
||||||
/**
|
|
||||||
* @param {Object} row
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
return (row) => {
|
|
||||||
return _.omit(row, omissions);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used in objection query builder
|
|
||||||
*
|
|
||||||
* @param {Array} omissions
|
|
||||||
* @returns {Function}
|
|
||||||
*/
|
|
||||||
omitRows: function (omissions) {
|
|
||||||
/**
|
|
||||||
* @param {Array} rows
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
return (rows) => {
|
|
||||||
rows.forEach((row, idx) => {
|
|
||||||
rows[idx] = _.omit(row, omissions);
|
|
||||||
});
|
|
||||||
return rows;
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {Object} Liquid render engine
|
|
||||||
*/
|
|
||||||
getRenderEngine: function () {
|
|
||||||
const renderEngine = new Liquid({
|
|
||||||
root: __dirname + '/../templates/'
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* nginxAccessRule expects the object given to have 2 properties:
|
|
||||||
*
|
|
||||||
* directive string
|
|
||||||
* address string
|
|
||||||
*/
|
|
||||||
renderEngine.registerFilter('nginxAccessRule', (v) => {
|
|
||||||
if (typeof v.directive !== 'undefined' && typeof v.address !== 'undefined' && v.directive && v.address) {
|
|
||||||
return `${v.directive} ${v.address};`;
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
});
|
|
||||||
|
|
||||||
return renderEngine;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -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,
|
verbose: true, //process.env.NODE_ENV === 'development',
|
||||||
allErrors: true,
|
allErrors: true,
|
||||||
format: 'full', // strict regexes for format checks
|
format: 'full', // strict regexes for format checks
|
||||||
coerceTypes: true,
|
coerceTypes: true,
|
||||||
|
48
backend/migrations/20200522113248_openid_connect.js
Normal file
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
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,50 +0,0 @@
|
|||||||
const migrate_name = 'stream_domain';
|
|
||||||
const logger = require('../logger').migrate;
|
|
||||||
const internalNginx = require('../internal/nginx');
|
|
||||||
|
|
||||||
async function regenerateDefaultHost(knex) {
|
|
||||||
const row = await knex('setting').select('*').where('id', 'default-site').first();
|
|
||||||
|
|
||||||
if (!row) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
return internalNginx.deleteConfig('default')
|
|
||||||
.then(() => {
|
|
||||||
return internalNginx.generateConfig('default', row);
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
return internalNginx.test();
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
return internalNginx.reload();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Migrate
|
|
||||||
*
|
|
||||||
* @see http://knexjs.org/#Schema
|
|
||||||
*
|
|
||||||
* @param {Object} knex
|
|
||||||
* @param {Promise} Promise
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
exports.up = function (knex) {
|
|
||||||
logger.info('[' + migrate_name + '] Migrating Up...');
|
|
||||||
|
|
||||||
return regenerateDefaultHost(knex);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Undo Migrate
|
|
||||||
*
|
|
||||||
* @param {Object} knex
|
|
||||||
* @param {Promise} Promise
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
exports.down = function (knex) {
|
|
||||||
logger.info('[' + migrate_name + '] Migrating Down...');
|
|
||||||
|
|
||||||
return regenerateDefaultHost(knex);
|
|
||||||
};
|
|
@ -50,6 +50,7 @@ class AccessList extends Model {
|
|||||||
},
|
},
|
||||||
modify: function (qb) {
|
modify: function (qb) {
|
||||||
qb.where('user.is_deleted', 0);
|
qb.where('user.is_deleted', 0);
|
||||||
|
qb.omit(['id', 'created_on', 'modified_on', 'is_deleted', 'email', 'roles']);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
items: {
|
items: {
|
||||||
@ -58,6 +59,9 @@ class AccessList extends Model {
|
|||||||
join: {
|
join: {
|
||||||
from: 'access_list.id',
|
from: 'access_list.id',
|
||||||
to: 'access_list_auth.access_list_id'
|
to: 'access_list_auth.access_list_id'
|
||||||
|
},
|
||||||
|
modify: function (qb) {
|
||||||
|
qb.omit(['id', 'created_on', 'modified_on', 'access_list_id', 'meta']);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
clients: {
|
clients: {
|
||||||
@ -66,6 +70,9 @@ class AccessList extends Model {
|
|||||||
join: {
|
join: {
|
||||||
from: 'access_list.id',
|
from: 'access_list.id',
|
||||||
to: 'access_list_client.access_list_id'
|
to: 'access_list_client.access_list_id'
|
||||||
|
},
|
||||||
|
modify: function (qb) {
|
||||||
|
qb.omit(['id', 'created_on', 'modified_on', 'access_list_id', 'meta']);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
proxy_hosts: {
|
proxy_hosts: {
|
||||||
@ -77,10 +84,19 @@ class AccessList extends Model {
|
|||||||
},
|
},
|
||||||
modify: function (qb) {
|
modify: function (qb) {
|
||||||
qb.where('proxy_host.is_deleted', 0);
|
qb.where('proxy_host.is_deleted', 0);
|
||||||
|
qb.omit(['is_deleted', 'meta']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get satisfy() {
|
||||||
|
return this.satisfy_any ? 'satisfy any' : 'satisfy all';
|
||||||
|
}
|
||||||
|
|
||||||
|
get passauth() {
|
||||||
|
return this.pass_auth ? '' : 'proxy_set_header Authorization "";';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = AccessList;
|
module.exports = AccessList;
|
||||||
|
@ -45,6 +45,7 @@ class AccessListAuth extends Model {
|
|||||||
},
|
},
|
||||||
modify: function (qb) {
|
modify: function (qb) {
|
||||||
qb.where('access_list.is_deleted', 0);
|
qb.where('access_list.is_deleted', 0);
|
||||||
|
qb.omit(['created_on', 'modified_on', 'is_deleted', 'access_list_id']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -45,10 +45,15 @@ class AccessListClient extends Model {
|
|||||||
},
|
},
|
||||||
modify: function (qb) {
|
modify: function (qb) {
|
||||||
qb.where('access_list.is_deleted', 0);
|
qb.where('access_list.is_deleted', 0);
|
||||||
|
qb.omit(['created_on', 'modified_on', 'is_deleted', 'access_list_id']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get rule() {
|
||||||
|
return `${this.directive} ${this.address}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = AccessListClient;
|
module.exports = AccessListClient;
|
||||||
|
@ -43,6 +43,9 @@ class AuditLog extends Model {
|
|||||||
join: {
|
join: {
|
||||||
from: 'audit_log.user_id',
|
from: 'audit_log.user_id',
|
||||||
to: 'user.id'
|
to: 'user.id'
|
||||||
|
},
|
||||||
|
modify: function (qb) {
|
||||||
|
qb.omit(['id', 'created_on', 'modified_on', 'roles']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -74,6 +74,9 @@ class Auth extends Model {
|
|||||||
},
|
},
|
||||||
filter: {
|
filter: {
|
||||||
is_deleted: 0
|
is_deleted: 0
|
||||||
|
},
|
||||||
|
modify: function (qb) {
|
||||||
|
qb.omit(['is_deleted']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -63,6 +63,7 @@ class Certificate extends Model {
|
|||||||
},
|
},
|
||||||
modify: function (qb) {
|
modify: function (qb) {
|
||||||
qb.where('user.is_deleted', 0);
|
qb.where('user.is_deleted', 0);
|
||||||
|
qb.omit(['id', 'created_on', 'modified_on', 'is_deleted', 'email', 'roles']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -59,6 +59,7 @@ class DeadHost extends Model {
|
|||||||
},
|
},
|
||||||
modify: function (qb) {
|
modify: function (qb) {
|
||||||
qb.where('user.is_deleted', 0);
|
qb.where('user.is_deleted', 0);
|
||||||
|
qb.omit(['id', 'created_on', 'modified_on', 'is_deleted', 'email', 'roles']);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
certificate: {
|
certificate: {
|
||||||
@ -70,6 +71,7 @@ class DeadHost extends Model {
|
|||||||
},
|
},
|
||||||
modify: function (qb) {
|
modify: function (qb) {
|
||||||
qb.where('certificate.is_deleted', 0);
|
qb.where('certificate.is_deleted', 0);
|
||||||
|
qb.omit(['id', 'created_on', 'modified_on', 'is_deleted']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
const db = require('../db');
|
const db = require('../db');
|
||||||
const config = require('../lib/config');
|
const config = require('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.isSqlite()) {
|
if (config.database.knex && config.database.knex.client === 'sqlite3') {
|
||||||
// eslint-disable-next-line
|
return Model.raw('datetime(\'now\',\'localtime\')');
|
||||||
return Model.raw("datetime('now','localtime')");
|
} else {
|
||||||
}
|
|
||||||
return Model.raw('NOW()');
|
return Model.raw('NOW()');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
@ -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 () {
|
||||||
@ -60,6 +76,7 @@ class ProxyHost extends Model {
|
|||||||
},
|
},
|
||||||
modify: function (qb) {
|
modify: function (qb) {
|
||||||
qb.where('user.is_deleted', 0);
|
qb.where('user.is_deleted', 0);
|
||||||
|
qb.omit(['id', 'created_on', 'modified_on', 'is_deleted', 'email', 'roles']);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
access_list: {
|
access_list: {
|
||||||
@ -71,6 +88,7 @@ class ProxyHost extends Model {
|
|||||||
},
|
},
|
||||||
modify: function (qb) {
|
modify: function (qb) {
|
||||||
qb.where('access_list.is_deleted', 0);
|
qb.where('access_list.is_deleted', 0);
|
||||||
|
qb.omit(['id', 'created_on', 'modified_on', 'is_deleted']);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
certificate: {
|
certificate: {
|
||||||
@ -82,6 +100,7 @@ class ProxyHost extends Model {
|
|||||||
},
|
},
|
||||||
modify: function (qb) {
|
modify: function (qb) {
|
||||||
qb.where('certificate.is_deleted', 0);
|
qb.where('certificate.is_deleted', 0);
|
||||||
|
qb.omit(['id', 'created_on', 'modified_on', 'is_deleted']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
// Objection Docs:
|
// Objection Docs:
|
||||||
// http://vincit.github.io/objection.js/
|
// http://vincit.github.io/objection.js/
|
||||||
|
|
||||||
@ -60,6 +59,7 @@ class RedirectionHost extends Model {
|
|||||||
},
|
},
|
||||||
modify: function (qb) {
|
modify: function (qb) {
|
||||||
qb.where('user.is_deleted', 0);
|
qb.where('user.is_deleted', 0);
|
||||||
|
qb.omit(['id', 'created_on', 'modified_on', 'is_deleted', 'email', 'roles']);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
certificate: {
|
certificate: {
|
||||||
@ -71,6 +71,7 @@ class RedirectionHost extends Model {
|
|||||||
},
|
},
|
||||||
modify: function (qb) {
|
modify: function (qb) {
|
||||||
qb.where('certificate.is_deleted', 0);
|
qb.where('certificate.is_deleted', 0);
|
||||||
|
qb.omit(['id', 'created_on', 'modified_on', 'is_deleted']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -46,6 +46,7 @@ class Stream extends Model {
|
|||||||
},
|
},
|
||||||
modify: function (qb) {
|
modify: function (qb) {
|
||||||
qb.where('user.is_deleted', 0);
|
qb.where('user.is_deleted', 0);
|
||||||
|
qb.omit(['id', 'created_on', 'modified_on', 'is_deleted', 'email', 'roles']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -6,36 +6,44 @@
|
|||||||
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 = {};
|
||||||
|
|
||||||
const self = {
|
let 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
|
||||||
const options = {
|
let 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')
|
||||||
.substring(-8);
|
.substr(-8);
|
||||||
|
|
||||||
|
checkJWTKeyPair();
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
jwt.sign(payload, config.getPrivateKey(), options, (err, token) => {
|
jwt.sign(payload, private_key, options, (err, token) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
} else {
|
} else {
|
||||||
@ -54,15 +62,13 @@ 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, config.getPublicKey(), {ignoreExpiration: false, algorithms: [ALGO]}, (err, result) => {
|
jwt.verify(token, public_key, {ignoreExpiration: false, algorithms: [ALGO]}, (err, result) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
|
||||||
if (err.name === 'TokenExpiredError') {
|
if (err.name === 'TokenExpiredError') {
|
||||||
@ -77,6 +83,8 @@ module.exports = function () {
|
|||||||
// Hack: some tokens out in the wild have a scope of 'all' instead of 'user'.
|
// Hack: some tokens out in the wild have a scope of 'all' instead of 'user'.
|
||||||
// For 30 days at least, we need to replace 'all' with user.
|
// For 30 days at least, we need to replace 'all' with user.
|
||||||
if ((typeof token_data.scope !== 'undefined' && _.indexOf(token_data.scope, 'all') !== -1)) {
|
if ((typeof token_data.scope !== 'undefined' && _.indexOf(token_data.scope, 'all') !== -1)) {
|
||||||
|
//console.log('Warning! Replacing "all" scope with "user"');
|
||||||
|
|
||||||
token_data.scope = ['user'];
|
token_data.scope = ['user'];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,7 +134,7 @@ module.exports = function () {
|
|||||||
* @returns {Integer}
|
* @returns {Integer}
|
||||||
*/
|
*/
|
||||||
getUserId: (default_value) => {
|
getUserId: (default_value) => {
|
||||||
const attrs = self.get('attrs');
|
let 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;
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,9 @@ class User extends Model {
|
|||||||
join: {
|
join: {
|
||||||
from: 'user.id',
|
from: 'user.id',
|
||||||
to: 'user_permission.user_id'
|
to: 'user_permission.user_id'
|
||||||
|
},
|
||||||
|
modify: function (qb) {
|
||||||
|
qb.omit(['id', 'created_on', 'modified_on', 'user_id']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -10,22 +10,29 @@
|
|||||||
"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",
|
||||||
"express": "^4.17.3",
|
"config": "^3.3.1",
|
||||||
|
"diskdb": "^0.1.17",
|
||||||
|
"express": "^4.17.1",
|
||||||
"express-fileupload": "^1.1.9",
|
"express-fileupload": "^1.1.9",
|
||||||
"gravatar": "^1.8.0",
|
"gravatar": "^1.8.0",
|
||||||
|
"html-entities": "^1.2.1",
|
||||||
"json-schema-ref-parser": "^8.0.0",
|
"json-schema-ref-parser": "^8.0.0",
|
||||||
"jsonwebtoken": "^9.0.0",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"knex": "2.4.2",
|
"knex": "^0.20.13",
|
||||||
"liquidjs": "10.6.1",
|
"liquidjs": "^9.11.10",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.24.0",
|
||||||
"mysql": "^2.18.1",
|
"mysql": "^2.18.1",
|
||||||
"node-rsa": "^1.0.8",
|
"node-rsa": "^1.0.8",
|
||||||
"objection": "3.0.1",
|
"nodemon": "^2.0.2",
|
||||||
|
"objection": "^2.1.3",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"signale": "1.4.0",
|
"pg": "^7.12.1",
|
||||||
"sqlite3": "5.1.6",
|
"restler": "^3.4.0",
|
||||||
"temp-write": "^4.0.0"
|
"signale": "^1.4.0",
|
||||||
|
"sqlite3": "^4.1.1",
|
||||||
|
"temp-write": "^4.0.0",
|
||||||
|
"unix-timestamp": "^0.2.0"
|
||||||
},
|
},
|
||||||
"signale": {
|
"signale": {
|
||||||
"displayDate": true,
|
"displayDate": true,
|
||||||
@ -34,9 +41,8 @@
|
|||||||
"author": "Jamie Curnow <jc@jc21.com>",
|
"author": "Jamie Curnow <jc@jc21.com>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "^8.36.0",
|
"eslint": "^6.8.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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,32 +68,6 @@ router
|
|||||||
.catch(next);
|
.catch(next);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Test HTTP challenge for domains
|
|
||||||
*
|
|
||||||
* /api/nginx/certificates/test-http
|
|
||||||
*/
|
|
||||||
router
|
|
||||||
.route('/test-http')
|
|
||||||
.options((req, res) => {
|
|
||||||
res.sendStatus(204);
|
|
||||||
})
|
|
||||||
.all(jwtdecode())
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GET /api/nginx/certificates/test-http
|
|
||||||
*
|
|
||||||
* Test HTTP challenge for domains
|
|
||||||
*/
|
|
||||||
.get((req, res, next) => {
|
|
||||||
internalCertificate.testHttpsChallenge(res.locals.access, JSON.parse(req.query.domains))
|
|
||||||
.then((result) => {
|
|
||||||
res.status(200)
|
|
||||||
.send(result);
|
|
||||||
})
|
|
||||||
.catch(next);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specific certificate
|
* Specific certificate
|
||||||
*
|
*
|
||||||
@ -235,6 +209,7 @@ router
|
|||||||
.catch(next);
|
.catch(next);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download LE Certs
|
* Download LE Certs
|
||||||
*
|
*
|
||||||
|
@ -153,7 +153,7 @@
|
|||||||
"example": "john@example.com",
|
"example": "john@example.com",
|
||||||
"format": "email",
|
"format": "email",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"minLength": 6,
|
"minLength": 8,
|
||||||
"maxLength": 100
|
"maxLength": 100
|
||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -157,17 +157,6 @@
|
|||||||
"targetSchema": {
|
"targetSchema": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Test HTTP Challenge",
|
|
||||||
"description": "Tests whether the HTTP challenge should work",
|
|
||||||
"href": "/nginx/certificates/{definitions.identity.example}/test-http",
|
|
||||||
"access": "private",
|
|
||||||
"method": "GET",
|
|
||||||
"rel": "info",
|
|
||||||
"http_header": {
|
|
||||||
"$ref": "../examples.json#/definitions/auth_header"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
},
|
},
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
const config = require('./lib/config');
|
const fs = require('fs');
|
||||||
|
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');
|
||||||
@ -7,6 +9,62 @@ 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 dns_plugins = require('./global/certbot-dns-plugins');
|
||||||
|
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
|
||||||
@ -61,8 +119,8 @@ const setupDefaultUser = () => {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
logger.info('Initial admin setup completed');
|
logger.info('Initial admin setup completed');
|
||||||
});
|
});
|
||||||
} else if (config.debug()) {
|
} else if (debug_mode) {
|
||||||
logger.info('Admin user setup not required');
|
logger.debug('Admin user setup not required');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -93,8 +151,8 @@ const setupDefaultSettings = () => {
|
|||||||
logger.info('Default settings added');
|
logger.info('Default settings added');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (config.debug()) {
|
if (debug_mode) {
|
||||||
logger.info('Default setting setup not required');
|
logger.debug('Default setting setup not required');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -117,21 +175,19 @@ 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];
|
const dns_plugin = dns_plugins[certificate.meta.dns_provider];
|
||||||
|
const packages_to_install = `${dns_plugin.package_name}==${dns_plugin.package_version} ${dns_plugin.dependencies}`;
|
||||||
|
|
||||||
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);
|
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;
|
||||||
// Escape single quotes and backslashes
|
const credentials_cmd = '[ -f \'' + credentials_loc + '\' ] || { mkdir -p /etc/letsencrypt/credentials 2> /dev/null; echo \'' + certificate.meta.dns_provider_credentials.replace('\'', '\\\'') + '\' > \'' + credentials_loc + '\' && chmod 600 \'' + credentials_loc + '\'; }';
|
||||||
const escapedCredentials = certificate.meta.dns_provider_credentials.replaceAll('\'', '\\\'').replaceAll('\\', '\\\\');
|
|
||||||
const credentials_cmd = '[ -f \'' + credentials_loc + '\' ] || { mkdir -p /etc/letsencrypt/credentials 2> /dev/null; echo \'' + escapedCredentials + '\' > \'' + credentials_loc + '\' && chmod 600 \'' + credentials_loc + '\'; }';
|
|
||||||
promises.push(utils.exec(credentials_cmd));
|
promises.push(utils.exec(credentials_cmd));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (plugins.length) {
|
if (plugins.length) {
|
||||||
const install_cmd = '. /opt/certbot/bin/activate && pip install --no-cache-dir ' + plugins.join(' ') + ' && deactivate';
|
const install_cmd = 'pip install ' + plugins.join(' ');
|
||||||
promises.push(utils.exec(install_cmd));
|
promises.push(utils.exec(install_cmd));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,7 +223,8 @@ const setupLogrotation = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
module.exports = function () {
|
module.exports = function () {
|
||||||
return setupDefaultUser()
|
return setupJwt()
|
||||||
|
.then(setupDefaultUser)
|
||||||
.then(setupDefaultSettings)
|
.then(setupDefaultSettings)
|
||||||
.then(setupCertbotPlugins)
|
.then(setupCertbotPlugins)
|
||||||
.then(setupLogrotation);
|
.then(setupLogrotation);
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
{% if access_list_id > 0 %}
|
|
||||||
{% if access_list.items.length > 0 %}
|
|
||||||
# Authorization
|
|
||||||
auth_basic "Authorization required";
|
|
||||||
auth_basic_user_file /data/access/{{ access_list_id }};
|
|
||||||
|
|
||||||
{% if access_list.pass_auth == 0 %}
|
|
||||||
proxy_set_header Authorization "";
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
# Access Rules: {{ access_list.clients | size }} total
|
|
||||||
{% for client in access_list.clients %}
|
|
||||||
{{client | nginxAccessRule}}
|
|
||||||
{% endfor %}
|
|
||||||
deny all;
|
|
||||||
|
|
||||||
# Access checks must...
|
|
||||||
{% if access_list.satisfy_any == 1 %}
|
|
||||||
satisfy any;
|
|
||||||
{% else %}
|
|
||||||
satisfy all;
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
@ -1,14 +1,36 @@
|
|||||||
location {{ path }} {
|
location {{ path }} {
|
||||||
|
set $upstream {{ forward_scheme }}://{{ forward_host }}:{{ forward_port }}{{ forward_path }};
|
||||||
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;
|
||||||
proxy_set_header X-Forwarded-For $remote_addr;
|
proxy_set_header X-Forwarded-For $remote_addr;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_pass {{ forward_scheme }}://{{ forward_host }}:{{ forward_port }}{{ forward_path }};
|
proxy_pass $upstream;
|
||||||
|
|
||||||
|
{% if access_list_id > 0 %}
|
||||||
|
{% if access_list.items.length > 0 %}
|
||||||
|
# Authorization
|
||||||
|
auth_basic "Authorization required";
|
||||||
|
auth_basic_user_file /data/access/{{ access_list_id }};
|
||||||
|
|
||||||
|
{{ 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 "_access.conf" %}
|
|
||||||
{% include "_assets.conf" %}
|
{% include "_assets.conf" %}
|
||||||
{% include "_exploits.conf" %}
|
{% include "_exploits.conf" %}
|
||||||
|
|
||||||
{% include "_forced_ssl.conf" %}
|
{% include "_forced_ssl.conf" %}
|
||||||
{% include "_hsts.conf" %}
|
{% include "_hsts.conf" %}
|
||||||
|
|
||||||
|
47
backend/templates/_openid_connect.conf
Normal file
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 %}
|
@ -7,9 +7,9 @@
|
|||||||
server {
|
server {
|
||||||
listen 80 default;
|
listen 80 default;
|
||||||
{% if ipv6 -%}
|
{% if ipv6 -%}
|
||||||
listen [::]:80 default;
|
listen [::]:80;
|
||||||
{% else -%}
|
{% else -%}
|
||||||
#listen [::]:80 default;
|
#listen [::]:80;
|
||||||
{% endif %}
|
{% endif %}
|
||||||
server_name default-host.localhost;
|
server_name default-host.localhost;
|
||||||
access_log /data/logs/default-host_access.log combined;
|
access_log /data/logs/default-host_access.log combined;
|
||||||
@ -24,12 +24,6 @@ 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 }};
|
||||||
|
@ -30,8 +30,29 @@ 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 "_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;
|
||||||
|
2785
backend/yarn.lock
2785
backend/yarn.lock
File diff suppressed because it is too large
Load Diff
@ -3,20 +3,16 @@
|
|||||||
|
|
||||||
# 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: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_BEHAVIOUR_IF_STAGE2_FAILS=1 \
|
|
||||||
S6_CMD_WAIT_FOR_SERVICES_MAXTIME=0 \
|
|
||||||
S6_FIX_ATTRS_HIDDEN=1 \
|
S6_FIX_ATTRS_HIDDEN=1 \
|
||||||
S6_KILL_FINISH_MAXTIME=10000 \
|
S6_BEHAVIOUR_IF_STAGE2_FAILS=1 \
|
||||||
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}" \
|
||||||
@ -29,7 +25,7 @@ RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \
|
|||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# s6 overlay
|
# s6 overlay
|
||||||
COPY docker/scripts/install-s6 /tmp/install-s6
|
COPY scripts/install-s6 /tmp/install-s6
|
||||||
RUN /tmp/install-s6 "${TARGETPLATFORM}" && rm -f /tmp/install-s6
|
RUN /tmp/install-s6 "${TARGETPLATFORM}" && rm -f /tmp/install-s6
|
||||||
|
|
||||||
EXPOSE 80 81 443
|
EXPOSE 80 81 443
|
||||||
@ -39,17 +35,16 @@ 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/s6-overlay/s6-rc.d/user/contents.d/frontend /etc/nginx/conf.d/dev.conf \
|
RUN rm -rf /etc/services.d/frontend /etc/nginx/conf.d/dev.conf
|
||||||
&& chmod 644 /etc/logrotate.d/nginx-proxy-manager \
|
|
||||||
&& pip uninstall --yes setuptools \
|
# Change permission of logrotate config file
|
||||||
&& pip install --no-cache-dir "setuptools==58.0.0"
|
RUN chmod 644 /etc/logrotate.d/nginx-proxy-manager
|
||||||
|
|
||||||
VOLUME [ "/data", "/etc/letsencrypt" ]
|
VOLUME [ "/data", "/etc/letsencrypt" ]
|
||||||
ENTRYPOINT [ "/init" ]
|
ENTRYPOINT [ "/init" ]
|
||||||
|
@ -1,17 +1,13 @@
|
|||||||
FROM jc21/nginx-full:certbot-node
|
FROM nginxproxymanager/nginx-full:node
|
||||||
LABEL maintainer="Jamie Curnow <jc@jc21.com>"
|
LABEL maintainer="Jamie Curnow <jc@jc21.com>"
|
||||||
|
|
||||||
# See: https://github.com/just-containers/s6-overlay/blob/master/README.md
|
ENV S6_LOGGING=0 \
|
||||||
ENV SUPPRESS_NO_CONFIG_WARNING=1 \
|
SUPPRESS_NO_CONFIG_WARNING=1 \
|
||||||
S6_BEHAVIOUR_IF_STAGE2_FAILS=1 \
|
S6_FIX_ATTRS_HIDDEN=1
|
||||||
S6_CMD_WAIT_FOR_SERVICES_MAXTIME=0 \
|
|
||||||
S6_FIX_ATTRS_HIDDEN=1 \
|
|
||||||
S6_KILL_FINISH_MAXTIME=10000 \
|
|
||||||
S6_VERBOSITY=2
|
|
||||||
|
|
||||||
RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \
|
RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \
|
||||||
&& apt-get update \
|
&& apt-get update \
|
||||||
&& apt-get install -y jq python3-pip logrotate \
|
&& apt-get install -y certbot jq python3-pip logrotate \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
@ -25,8 +21,9 @@ RUN rm -f /etc/nginx/conf.d/production.conf
|
|||||||
RUN chmod 644 /etc/logrotate.d/nginx-proxy-manager
|
RUN chmod 644 /etc/logrotate.d/nginx-proxy-manager
|
||||||
|
|
||||||
# s6 overlay
|
# s6 overlay
|
||||||
COPY scripts/install-s6 /tmp/install-s6
|
RUN curl -L -o /tmp/s6-overlay-amd64.tar.gz "https://github.com/just-containers/s6-overlay/releases/download/v1.22.1.0/s6-overlay-amd64.tar.gz" \
|
||||||
RUN /tmp/install-s6 "${TARGETPLATFORM}" && rm -f /tmp/install-s6
|
&& tar -xzf /tmp/s6-overlay-amd64.tar.gz -C /
|
||||||
|
|
||||||
EXPOSE 80 81 443
|
EXPOSE 80 81 443
|
||||||
ENTRYPOINT [ "/init" ]
|
ENTRYPOINT [ "/init" ]
|
||||||
|
|
||||||
|
@ -1,18 +1,17 @@
|
|||||||
# 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 and testing of the entire app, it should not be used for production.
|
||||||
version: '3.8'
|
version: "3"
|
||||||
services:
|
services:
|
||||||
|
|
||||||
fullstack-mysql:
|
fullstack-mysql:
|
||||||
image: "${IMAGE}:ci-${BUILD_NUMBER}"
|
image: ${IMAGE}:ci-${BUILD_NUMBER}
|
||||||
environment:
|
environment:
|
||||||
DEBUG: 'true'
|
NODE_ENV: "development"
|
||||||
LE_STAGING: 'true'
|
|
||||||
FORCE_COLOR: 1
|
FORCE_COLOR: 1
|
||||||
DB_MYSQL_HOST: 'db'
|
DB_MYSQL_HOST: "db"
|
||||||
DB_MYSQL_PORT: '3306'
|
DB_MYSQL_PORT: 3306
|
||||||
DB_MYSQL_USER: 'npm'
|
DB_MYSQL_USER: "npm"
|
||||||
DB_MYSQL_PASSWORD: 'npm'
|
DB_MYSQL_PASSWORD: "npm"
|
||||||
DB_MYSQL_NAME: 'npm'
|
DB_MYSQL_NAME: "npm"
|
||||||
volumes:
|
volumes:
|
||||||
- npm_data:/data
|
- npm_data:/data
|
||||||
expose:
|
expose:
|
||||||
@ -27,15 +26,11 @@ services:
|
|||||||
timeout: 3s
|
timeout: 3s
|
||||||
|
|
||||||
fullstack-sqlite:
|
fullstack-sqlite:
|
||||||
image: "${IMAGE}:ci-${BUILD_NUMBER}"
|
image: ${IMAGE}:ci-${BUILD_NUMBER}
|
||||||
environment:
|
environment:
|
||||||
DEBUG: 'true'
|
NODE_ENV: "development"
|
||||||
LE_STAGING: 'true'
|
|
||||||
FORCE_COLOR: 1
|
FORCE_COLOR: 1
|
||||||
DB_SQLITE_FILE: '/data/mydb.sqlite'
|
DB_SQLITE_FILE: "/data/database.sqlite"
|
||||||
PUID: 1000
|
|
||||||
PGID: 1000
|
|
||||||
DISABLE_IPV6: 'true'
|
|
||||||
volumes:
|
volumes:
|
||||||
- npm_data:/data
|
- npm_data:/data
|
||||||
expose:
|
expose:
|
||||||
@ -50,26 +45,26 @@ services:
|
|||||||
db:
|
db:
|
||||||
image: jc21/mariadb-aria
|
image: jc21/mariadb-aria
|
||||||
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
|
||||||
|
|
||||||
cypress-mysql:
|
cypress-mysql:
|
||||||
image: "${IMAGE}-cypress:ci-${BUILD_NUMBER}"
|
image: ${IMAGE}-cypress:ci-${BUILD_NUMBER}
|
||||||
build:
|
build:
|
||||||
context: ../test/
|
context: ../test/
|
||||||
dockerfile: cypress/Dockerfile
|
dockerfile: cypress/Dockerfile
|
||||||
environment:
|
environment:
|
||||||
CYPRESS_baseUrl: 'http://fullstack-mysql:81'
|
CYPRESS_baseUrl: "http://fullstack-mysql:81"
|
||||||
volumes:
|
volumes:
|
||||||
- cypress-logs:/results
|
- cypress-logs:/results
|
||||||
command: cypress run --browser chrome --config-file=${CYPRESS_CONFIG:-cypress/config/ci.json}
|
command: cypress run --browser chrome --config-file=${CYPRESS_CONFIG:-cypress/config/ci.json}
|
||||||
|
|
||||||
cypress-sqlite:
|
cypress-sqlite:
|
||||||
image: "${IMAGE}-cypress:ci-${BUILD_NUMBER}"
|
image: ${IMAGE}-cypress:ci-${BUILD_NUMBER}
|
||||||
build:
|
build:
|
||||||
context: ../test/
|
context: ../test/
|
||||||
dockerfile: cypress/Dockerfile
|
dockerfile: cypress/Dockerfile
|
||||||
|
@ -1,7 +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.8'
|
version: "3.5"
|
||||||
services:
|
services:
|
||||||
|
|
||||||
npm:
|
npm:
|
||||||
image: nginxproxymanager:dev
|
image: nginxproxymanager:dev
|
||||||
container_name: npm_core
|
container_name: npm_core
|
||||||
@ -15,19 +14,14 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- nginx_proxy_manager
|
- nginx_proxy_manager
|
||||||
environment:
|
environment:
|
||||||
PUID: 1000
|
NODE_ENV: "development"
|
||||||
PGID: 1000
|
|
||||||
FORCE_COLOR: 1
|
FORCE_COLOR: 1
|
||||||
# specifically for dev:
|
DEVELOPMENT: "true"
|
||||||
DEBUG: 'true'
|
DB_MYSQL_HOST: "db"
|
||||||
DEVELOPMENT: 'true'
|
DB_MYSQL_PORT: 3306
|
||||||
LE_STAGING: 'true'
|
DB_MYSQL_USER: "npm"
|
||||||
# db:
|
DB_MYSQL_PASSWORD: "npm"
|
||||||
DB_MYSQL_HOST: 'db'
|
DB_MYSQL_NAME: "npm"
|
||||||
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:
|
||||||
@ -43,18 +37,29 @@ services:
|
|||||||
db:
|
db:
|
||||||
image: jc21/mariadb-aria
|
image: jc21/mariadb-aria
|
||||||
container_name: npm_db
|
container_name: npm_db
|
||||||
ports:
|
|
||||||
- 33306:3306
|
|
||||||
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
|
||||||
|
|
||||||
|
swagger:
|
||||||
|
image: "swaggerapi/swagger-ui:latest"
|
||||||
|
container_name: npm_swagger
|
||||||
|
ports:
|
||||||
|
- 3001:80
|
||||||
|
networks:
|
||||||
|
- nginx_proxy_manager
|
||||||
|
environment:
|
||||||
|
URL: "http://127.0.0.1:3081/api/schema"
|
||||||
|
PORT: "80"
|
||||||
|
depends_on:
|
||||||
|
- npm
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
npm_data:
|
npm_data:
|
||||||
name: npm_core_data
|
name: npm_core_data
|
||||||
|
@ -1,54 +0,0 @@
|
|||||||
#!/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}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
46
docker/rootfs/bin/handle-ipv6-setting
Executable file
46
docker/rootfs/bin/handle-ipv6-setting
Executable file
@ -0,0 +1,46 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# This command reads the `DISABLE_IPV6` env var and will either enable
|
||||||
|
# or disable ipv6 in all nginx configs based on this setting.
|
||||||
|
|
||||||
|
# Lowercase
|
||||||
|
DISABLE_IPV6=$(echo "${DISABLE_IPV6:-}" | tr '[:upper:]' '[:lower:]')
|
||||||
|
|
||||||
|
CYAN='\E[1;36m'
|
||||||
|
BLUE='\E[1;34m'
|
||||||
|
YELLOW='\E[1;33m'
|
||||||
|
RED='\E[1;31m'
|
||||||
|
RESET='\E[0m'
|
||||||
|
|
||||||
|
FOLDER=$1
|
||||||
|
if [ "$FOLDER" == "" ]; then
|
||||||
|
echo -e "${RED}❯ $0 requires a absolute folder path as the first argument!${RESET}"
|
||||||
|
echo -e "${YELLOW} ie: $0 /data/nginx${RESET}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
FILES=$(find "$FOLDER" -type f -name "*.conf")
|
||||||
|
if [ "$DISABLE_IPV6" == "true" ] || [ "$DISABLE_IPV6" == "on" ] || [ "$DISABLE_IPV6" == "1" ] || [ "$DISABLE_IPV6" == "yes" ]; then
|
||||||
|
# IPV6 is disabled
|
||||||
|
echo "Disabling IPV6 in hosts"
|
||||||
|
echo -e "${BLUE}❯ ${CYAN}Disabling IPV6 in hosts: ${YELLOW}${FOLDER}${RESET}"
|
||||||
|
|
||||||
|
# Iterate over configs and run the regex
|
||||||
|
for FILE in $FILES
|
||||||
|
do
|
||||||
|
echo -e " ${BLUE}❯ ${YELLOW}${FILE}${RESET}"
|
||||||
|
sed -E -i 's/^([^#]*)listen \[::\]/\1#listen [::]/g' "$FILE"
|
||||||
|
done
|
||||||
|
|
||||||
|
else
|
||||||
|
# IPV6 is enabled
|
||||||
|
echo -e "${BLUE}❯ ${CYAN}Enabling IPV6 in hosts: ${YELLOW}${FOLDER}${RESET}"
|
||||||
|
|
||||||
|
# Iterate over configs and run the regex
|
||||||
|
for FILE in $FILES
|
||||||
|
do
|
||||||
|
echo -e " ${BLUE}❯ ${YELLOW}${FILE}${RESET}"
|
||||||
|
sed -E -i 's/^(\s*)#listen \[::\]/\1listen [::]/g' "$FILE"
|
||||||
|
done
|
||||||
|
|
||||||
|
fi
|
2
docker/rootfs/etc/cont-finish.d/.gitignore
vendored
Normal file
2
docker/rootfs/etc/cont-finish.d/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
3
docker/rootfs/etc/cont-init.d/.gitignore
vendored
Normal file
3
docker/rootfs/etc/cont-init.d/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
||||||
|
!*.sh
|
7
docker/rootfs/etc/cont-init.d/01_perms.sh
Executable file
7
docker/rootfs/etc/cont-init.d/01_perms.sh
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/with-contenv bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
mkdir -p /data/logs
|
||||||
|
echo "Changing ownership of /data/logs to $(id -u):$(id -g)"
|
||||||
|
chown -R "$(id -u):$(id -g)" /data/logs
|
||||||
|
|
29
docker/rootfs/etc/cont-init.d/01_s6-secret-init.sh
Normal file
29
docker/rootfs/etc/cont-init.d/01_s6-secret-init.sh
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#!/usr/bin/with-contenv bash
|
||||||
|
# ref: https://github.com/linuxserver/docker-baseimage-alpine/blob/master/root/etc/cont-init.d/01-envfile
|
||||||
|
|
||||||
|
# in s6, environmental variables are written as text files for s6 to monitor
|
||||||
|
# seach through full-path filenames for files ending in "__FILE"
|
||||||
|
for FILENAME in $(find /var/run/s6/container_environment/ | grep "__FILE$"); do
|
||||||
|
echo "[secret-init] Evaluating ${FILENAME##*/} ..."
|
||||||
|
|
||||||
|
# set SECRETFILE to the contents of the full-path textfile
|
||||||
|
SECRETFILE=$(cat ${FILENAME})
|
||||||
|
# SECRETFILE=${FILENAME}
|
||||||
|
# echo "[secret-init] Set SECRETFILE to ${SECRETFILE}" # DEBUG - rm for prod!
|
||||||
|
|
||||||
|
# if SECRETFILE exists / is not null
|
||||||
|
if [[ -f ${SECRETFILE} ]]; then
|
||||||
|
# strip the appended "__FILE" from environmental variable name ...
|
||||||
|
STRIPFILE=$(echo ${FILENAME} | sed "s/__FILE//g")
|
||||||
|
# echo "[secret-init] Set STRIPFILE to ${STRIPFILE}" # DEBUG - rm for prod!
|
||||||
|
|
||||||
|
# ... and set value to contents of secretfile
|
||||||
|
# since s6 uses text files, this is effectively "export ..."
|
||||||
|
printf $(cat ${SECRETFILE}) > ${STRIPFILE}
|
||||||
|
# echo "[secret-init] Set ${STRIPFILE##*/} to $(cat ${STRIPFILE})" # DEBUG - rm for prod!"
|
||||||
|
echo "[secret-init] Success! ${STRIPFILE##*/} set from ${FILENAME##*/}"
|
||||||
|
|
||||||
|
else
|
||||||
|
echo "[secret-init] cannot find secret in ${FILENAME}"
|
||||||
|
fi
|
||||||
|
done
|
2
docker/rootfs/etc/fix-attrs.d/.gitignore
vendored
Normal file
2
docker/rootfs/etc/fix-attrs.d/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
@ -3,4 +3,3 @@ non-interactive = True
|
|||||||
webroot-path = /data/letsencrypt-acme-challenge
|
webroot-path = /data/letsencrypt-acme-challenge
|
||||||
key-type = ecdsa
|
key-type = ecdsa
|
||||||
elliptic-curve = secp384r1
|
elliptic-curve = secp384r1
|
||||||
preferred-chain = ISRG Root X1
|
|
||||||
|
@ -30,10 +30,11 @@ server {
|
|||||||
set $port "443";
|
set $port "443";
|
||||||
|
|
||||||
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;
|
||||||
|
ssl_certificate /data/nginx/dummycert.pem;
|
||||||
|
ssl_certificate_key /data/nginx/dummykey.pem;
|
||||||
include conf.d/include/ssl-ciphers.conf;
|
include conf.d/include/ssl-ciphers.conf;
|
||||||
ssl_reject_handshake on;
|
|
||||||
|
|
||||||
return 444;
|
return 444;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
location ~* ^.*\.(css|js|jpe?g|gif|png|webp|woff|eot|ttf|svg|ico|css\.map|js\.map)$ {
|
location ~* ^.*\.(css|js|jpe?g|gif|png|woff|eot|ttf|svg|ico|css\.map|js\.map)$ {
|
||||||
if_modified_since off;
|
if_modified_since off;
|
||||||
|
|
||||||
# use the public cache
|
# use the public cache
|
||||||
|
@ -2,7 +2,7 @@ add_header X-Served-By $host;
|
|||||||
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;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $remote_addr;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_pass $forward_scheme://$server:$port$request_uri;
|
proxy_pass $forward_scheme://$server:$port;
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# run nginx in foreground
|
# run nginx in foreground
|
||||||
daemon off;
|
daemon off;
|
||||||
pid /run/nginx/nginx.pid;
|
|
||||||
user npm;
|
user root;
|
||||||
|
|
||||||
# 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;
|
||||||
@ -15,7 +15,7 @@ error_log /data/logs/fallback_error.log warn;
|
|||||||
include /etc/nginx/modules/*.conf;
|
include /etc/nginx/modules/*.conf;
|
||||||
|
|
||||||
events {
|
events {
|
||||||
include /data/nginx/custom/events[.]conf;
|
worker_connections 1024;
|
||||||
}
|
}
|
||||||
|
|
||||||
http {
|
http {
|
||||||
@ -43,6 +43,16 @@ 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"';
|
||||||
|
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
#!/command/with-contenv bash
|
|
||||||
# shellcheck shell=bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
. /bin/common.sh
|
|
||||||
|
|
||||||
cd /app || exit 1
|
|
||||||
|
|
||||||
log_info 'Starting backend ...'
|
|
||||||
|
|
||||||
if [ "${DEVELOPMENT:-}" = 'true' ]; then
|
|
||||||
s6-setuidgid "$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
|
|
||||||
while :
|
|
||||||
do
|
|
||||||
s6-setuidgid "$PUID:$PGID" bash -c "export HOME=$NPMHOME;node --abort_on_uncaught_exception --max_old_space_size=250 index.js"
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
fi
|
|
@ -1 +0,0 @@
|
|||||||
longrun
|
|
@ -1,21 +0,0 @@
|
|||||||
#!/command/with-contenv bash
|
|
||||||
# shellcheck shell=bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# This service is DEVELOPMENT only.
|
|
||||||
|
|
||||||
if [ "$DEVELOPMENT" = 'true' ]; then
|
|
||||||
. /bin/common.sh
|
|
||||||
cd /app/frontend || exit 1
|
|
||||||
HOME=$NPMHOME
|
|
||||||
export HOME
|
|
||||||
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
|
|
||||||
exit 0
|
|
||||||
fi
|
|
@ -1 +0,0 @@
|
|||||||
longrun
|
|
@ -1,9 +0,0 @@
|
|||||||
#!/command/with-contenv bash
|
|
||||||
# shellcheck shell=bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
. /bin/common.sh
|
|
||||||
|
|
||||||
log_info 'Starting nginx ...'
|
|
||||||
exec s6-setuidgid "$PUID:$PGID" nginx
|
|
@ -1 +0,0 @@
|
|||||||
longrun
|
|
@ -1,22 +0,0 @@
|
|||||||
#!/command/with-contenv bash
|
|
||||||
# shellcheck shell=bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
. /bin/common.sh
|
|
||||||
|
|
||||||
if [ "$(id -u)" != "0" ]; then
|
|
||||||
log_fatal "This docker container must be run as root, do not specify a user.\nYou can specify PUID and PGID env vars to run processes as that user and group after initialization."
|
|
||||||
fi
|
|
||||||
|
|
||||||
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
|
|
@ -1,40 +0,0 @@
|
|||||||
#!/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"
|
|
@ -1,41 +0,0 @@
|
|||||||
#!/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
|
|
@ -1,27 +0,0 @@
|
|||||||
#!/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 -R "$PUID:$PGID" /opt/certbot
|
|
@ -1,17 +0,0 @@
|
|||||||
#!/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
|
|
@ -1,39 +0,0 @@
|
|||||||
#!/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
|
|
@ -1,30 +0,0 @@
|
|||||||
#!/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
|
|
@ -1,18 +0,0 @@
|
|||||||
#!/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 +0,0 @@
|
|||||||
oneshot
|
|
@ -1,2 +0,0 @@
|
|||||||
# shellcheck shell=bash
|
|
||||||
/etc/s6-overlay/s6-rc.d/prepare/00-all.sh
|
|
6
docker/rootfs/etc/services.d/frontend/finish
Executable file
6
docker/rootfs/etc/services.d/frontend/finish
Executable file
@ -0,0 +1,6 @@
|
|||||||
|
#!/usr/bin/execlineb -S1
|
||||||
|
if { s6-test ${1} -ne 0 }
|
||||||
|
if { s6-test ${1} -ne 256 }
|
||||||
|
|
||||||
|
s6-svscanctl -t /var/run/s6/services
|
||||||
|
|
12
docker/rootfs/etc/services.d/frontend/run
Executable file
12
docker/rootfs/etc/services.d/frontend/run
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
#!/usr/bin/with-contenv bash
|
||||||
|
|
||||||
|
# This service is DEVELOPMENT only.
|
||||||
|
|
||||||
|
if [ "$DEVELOPMENT" == "true" ]; then
|
||||||
|
cd /app/frontend || exit 1
|
||||||
|
# If yarn install fails: add --verbose --network-concurrency 1
|
||||||
|
yarn install
|
||||||
|
yarn watch
|
||||||
|
else
|
||||||
|
exit 0
|
||||||
|
fi
|
3
docker/rootfs/etc/services.d/manager/finish
Executable file
3
docker/rootfs/etc/services.d/manager/finish
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/with-contenv bash
|
||||||
|
|
||||||
|
s6-svscanctl -t /var/run/s6/services
|
19
docker/rootfs/etc/services.d/manager/run
Executable file
19
docker/rootfs/etc/services.d/manager/run
Executable file
@ -0,0 +1,19 @@
|
|||||||
|
#!/usr/bin/with-contenv bash
|
||||||
|
|
||||||
|
mkdir -p /data/letsencrypt-acme-challenge
|
||||||
|
|
||||||
|
cd /app || echo
|
||||||
|
|
||||||
|
if [ "$DEVELOPMENT" == "true" ]; then
|
||||||
|
cd /app || exit 1
|
||||||
|
# If yarn install fails: add --verbose --network-concurrency 1
|
||||||
|
yarn install
|
||||||
|
node --max_old_space_size=250 --abort_on_uncaught_exception node_modules/nodemon/bin/nodemon.js
|
||||||
|
else
|
||||||
|
cd /app || exit 1
|
||||||
|
while :
|
||||||
|
do
|
||||||
|
node --abort_on_uncaught_exception --max_old_space_size=250 index.js
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
fi
|
1
docker/rootfs/etc/services.d/nginx/finish
Symbolic link
1
docker/rootfs/etc/services.d/nginx/finish
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
/bin/true
|
49
docker/rootfs/etc/services.d/nginx/run
Executable file
49
docker/rootfs/etc/services.d/nginx/run
Executable file
@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/with-contenv bash
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
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
|
||||||
|
echo resolver "$(awk 'BEGIN{ORS=" "} $1=="nameserver" {print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf);" > /etc/nginx/conf.d/include/resolvers.conf
|
||||||
|
|
||||||
|
# Generate dummy self-signed certificate.
|
||||||
|
if [ ! -f /data/nginx/dummycert.pem ] || [ ! -f /data/nginx/dummykey.pem ]
|
||||||
|
then
|
||||||
|
echo "Generating dummy SSL certificate..."
|
||||||
|
openssl req \
|
||||||
|
-new \
|
||||||
|
-newkey rsa:2048 \
|
||||||
|
-days 3650 \
|
||||||
|
-nodes \
|
||||||
|
-x509 \
|
||||||
|
-subj '/O=localhost/OU=localhost/CN=localhost' \
|
||||||
|
-keyout /data/nginx/dummykey.pem \
|
||||||
|
-out /data/nginx/dummycert.pem
|
||||||
|
echo "Complete"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Handle IPV6 settings
|
||||||
|
/bin/handle-ipv6-setting /etc/nginx/conf.d
|
||||||
|
/bin/handle-ipv6-setting /data/nginx
|
||||||
|
|
||||||
|
exec nginx
|
@ -1,26 +1,5 @@
|
|||||||
# 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
|
||||||
@ -39,14 +18,14 @@ services running on this Docker host:
|
|||||||
```yml
|
```yml
|
||||||
networks:
|
networks:
|
||||||
default:
|
default:
|
||||||
external: true
|
external:
|
||||||
name: scoobydoo
|
name: scoobydoo
|
||||||
```
|
```
|
||||||
|
|
||||||
Let's look at a Portainer example:
|
Let's look at a Portainer example:
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
version: '3.8'
|
version: '3'
|
||||||
services:
|
services:
|
||||||
|
|
||||||
portainer:
|
portainer:
|
||||||
@ -59,7 +38,7 @@ services:
|
|||||||
|
|
||||||
networks:
|
networks:
|
||||||
default:
|
default:
|
||||||
external: true
|
external:
|
||||||
name: scoobydoo
|
name: scoobydoo
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -81,14 +60,14 @@ healthcheck:
|
|||||||
timeout: 3s
|
timeout: 3s
|
||||||
```
|
```
|
||||||
|
|
||||||
## Docker File Secrets
|
## Docker Secrets
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
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.8'
|
version: "3.7"
|
||||||
|
|
||||||
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
|
||||||
@ -117,7 +96,9 @@ 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, remove all DB_MYSQL_* lines above
|
# If you would rather use Sqlite uncomment this
|
||||||
|
# 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:
|
||||||
@ -127,7 +108,6 @@ 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
|
||||||
@ -139,7 +119,7 @@ services:
|
|||||||
# 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
|
||||||
volumes:
|
volumes:
|
||||||
- ./mysql:/var/lib/mysql
|
- ./data/mysql:/var/lib/mysql
|
||||||
secrets:
|
secrets:
|
||||||
- DB_ROOT_PWD
|
- DB_ROOT_PWD
|
||||||
- MYSQL_PWD
|
- MYSQL_PWD
|
||||||
@ -171,7 +151,6 @@ You can add your custom configuration snippet files at `/data/nginx/custom` as f
|
|||||||
- `/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
|
||||||
- `/data/nginx/custom/events.conf`: Included at the end of the events block
|
|
||||||
- `/data/nginx/custom/stream.conf`: Included at the end of the main stream block
|
- `/data/nginx/custom/stream.conf`: Included at the end of the main stream block
|
||||||
- `/data/nginx/custom/server_proxy.conf`: Included at the end of every proxy server block
|
- `/data/nginx/custom/server_proxy.conf`: Included at the end of every proxy server block
|
||||||
- `/data/nginx/custom/server_redirect.conf`: Included at the end of every redirection server block
|
- `/data/nginx/custom/server_redirect.conf`: Included at the end of every redirection server block
|
||||||
@ -193,3 +172,26 @@ 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.
|
||||||
|
|
||||||
|
@ -21,6 +21,3 @@ Your best bet is to ask the [Reddit community for support](https://www.reddit.co
|
|||||||
|
|
||||||
Gitter is best left for anyone contributing to the project to ask for help about internals, code reviews etc.
|
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.
|
|
||||||
|
|
||||||
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.
|
|
@ -16,7 +16,7 @@
|
|||||||
"alphanum-sort": "^1.0.2",
|
"alphanum-sort": "^1.0.2",
|
||||||
"ansi-colors": "^4.1.1",
|
"ansi-colors": "^4.1.1",
|
||||||
"ansi-escapes": "^4.3.1",
|
"ansi-escapes": "^4.3.1",
|
||||||
"ansi-html": "^0.0.8",
|
"ansi-html": "^0.0.7",
|
||||||
"ansi-regex": "^5.0.0",
|
"ansi-regex": "^5.0.0",
|
||||||
"ansi-styles": "^4.2.1",
|
"ansi-styles": "^4.2.1",
|
||||||
"anymatch": "^3.1.1",
|
"anymatch": "^3.1.1",
|
||||||
@ -213,7 +213,7 @@
|
|||||||
"etag": "^1.8.1",
|
"etag": "^1.8.1",
|
||||||
"eventemitter3": "^4.0.4",
|
"eventemitter3": "^4.0.4",
|
||||||
"events": "^3.2.0",
|
"events": "^3.2.0",
|
||||||
"eventsource": "^2.0.2",
|
"eventsource": "^1.0.7",
|
||||||
"evp_bytestokey": "^1.0.3",
|
"evp_bytestokey": "^1.0.3",
|
||||||
"execa": "^4.0.3",
|
"execa": "^4.0.3",
|
||||||
"expand-brackets": "^4.0.0",
|
"expand-brackets": "^4.0.0",
|
||||||
@ -357,7 +357,7 @@
|
|||||||
"jsbn": "^1.1.0",
|
"jsbn": "^1.1.0",
|
||||||
"jsesc": "^3.0.1",
|
"jsesc": "^3.0.1",
|
||||||
"json-parse-better-errors": "^1.0.2",
|
"json-parse-better-errors": "^1.0.2",
|
||||||
"json-schema": "^0.4.0",
|
"json-schema": "^0.2.5",
|
||||||
"json-schema-traverse": "^0.4.1",
|
"json-schema-traverse": "^0.4.1",
|
||||||
"json-stringify-safe": "^5.0.1",
|
"json-stringify-safe": "^5.0.1",
|
||||||
"json3": "^3.3.3",
|
"json3": "^3.3.3",
|
||||||
@ -394,7 +394,7 @@
|
|||||||
"map-age-cleaner": "^0.1.3",
|
"map-age-cleaner": "^0.1.3",
|
||||||
"map-cache": "^0.2.2",
|
"map-cache": "^0.2.2",
|
||||||
"map-visit": "^1.0.0",
|
"map-visit": "^1.0.0",
|
||||||
"markdown-it": "^12.3.2",
|
"markdown-it": "^11.0.0",
|
||||||
"markdown-it-anchor": "^5.3.0",
|
"markdown-it-anchor": "^5.3.0",
|
||||||
"markdown-it-chain": "^1.3.0",
|
"markdown-it-chain": "^1.3.0",
|
||||||
"markdown-it-container": "^3.0.0",
|
"markdown-it-container": "^3.0.0",
|
||||||
@ -434,7 +434,7 @@
|
|||||||
"neo-async": "^2.6.2",
|
"neo-async": "^2.6.2",
|
||||||
"nice-try": "^2.0.1",
|
"nice-try": "^2.0.1",
|
||||||
"no-case": "^3.0.3",
|
"no-case": "^3.0.3",
|
||||||
"node-forge": "^1.0.0",
|
"node-forge": "^0.10.0",
|
||||||
"node-libs-browser": "^2.2.1",
|
"node-libs-browser": "^2.2.1",
|
||||||
"node-releases": "^1.1.60",
|
"node-releases": "^1.1.60",
|
||||||
"nopt": "^4.0.3",
|
"nopt": "^4.0.3",
|
||||||
@ -443,7 +443,7 @@
|
|||||||
"normalize-url": "^5.1.0",
|
"normalize-url": "^5.1.0",
|
||||||
"npm-run-path": "^4.0.1",
|
"npm-run-path": "^4.0.1",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"nth-check": "^2.0.1",
|
"nth-check": "^1.0.2",
|
||||||
"num2fraction": "^1.2.2",
|
"num2fraction": "^1.2.2",
|
||||||
"number-is-nan": "^2.0.0",
|
"number-is-nan": "^2.0.0",
|
||||||
"oauth-sign": "^0.9.0",
|
"oauth-sign": "^0.9.0",
|
||||||
@ -612,7 +612,7 @@
|
|||||||
"serve-index": "^1.9.1",
|
"serve-index": "^1.9.1",
|
||||||
"serve-static": "^1.14.1",
|
"serve-static": "^1.14.1",
|
||||||
"set-blocking": "^2.0.0",
|
"set-blocking": "^2.0.0",
|
||||||
"set-value": "^4.0.1",
|
"set-value": "^3.0.2",
|
||||||
"setimmediate": "^1.0.5",
|
"setimmediate": "^1.0.5",
|
||||||
"setprototypeof": "^1.2.0",
|
"setprototypeof": "^1.2.0",
|
||||||
"sha.js": "^2.4.11",
|
"sha.js": "^2.4.11",
|
||||||
|
@ -1,44 +1,6 @@
|
|||||||
# Full Setup Instructions
|
# Full Setup Instructions
|
||||||
|
|
||||||
## Running the App
|
## MySQL Database
|
||||||
|
|
||||||
Create a `docker-compose.yml` file:
|
|
||||||
|
|
||||||
```yml
|
|
||||||
version: '3.8'
|
|
||||||
services:
|
|
||||||
app:
|
|
||||||
image: 'jc21/nginx-proxy-manager:latest'
|
|
||||||
restart: unless-stopped
|
|
||||||
ports:
|
|
||||||
# These ports are in format <host-port>:<container-port>
|
|
||||||
- '80:80' # Public HTTP Port
|
|
||||||
- '443:443' # Public HTTPS Port
|
|
||||||
- '81:81' # Admin Web Port
|
|
||||||
# Add any other Stream port you want to expose
|
|
||||||
# - '21:21' # FTP
|
|
||||||
|
|
||||||
# Uncomment the next line if you uncomment anything in the section
|
|
||||||
# environment:
|
|
||||||
# Uncomment this if you want to change the location of
|
|
||||||
# the SQLite DB file within the container
|
|
||||||
# DB_SQLITE_FILE: "/data/database.sqlite"
|
|
||||||
|
|
||||||
# Uncomment this if IPv6 is not enabled on your host
|
|
||||||
# DISABLE_IPV6: 'true'
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
- ./data:/data
|
|
||||||
- ./letsencrypt:/etc/letsencrypt
|
|
||||||
```
|
|
||||||
|
|
||||||
Then:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker-compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
## Using MySQL / MariaDB Database
|
|
||||||
|
|
||||||
If you opt for the MySQL configuration you will have to provide the database server yourself. You can also use MariaDB. Here are the minimum supported versions:
|
If you opt for the MySQL configuration you will have to provide the database server yourself. You can also use MariaDB. Here are the minimum supported versions:
|
||||||
|
|
||||||
@ -48,28 +10,41 @@ If you opt for the MySQL configuration you will have to provide the database ser
|
|||||||
It's easy to use another docker container for your database also and link it as part of the docker stack, so that's what the following examples
|
It's easy to use another docker container for your database also and link it as part of the docker stack, so that's what the following examples
|
||||||
are going to use.
|
are going to use.
|
||||||
|
|
||||||
Here is an example of what your `docker-compose.yml` will look like when using a MariaDB container:
|
::: warning
|
||||||
|
|
||||||
|
When using a `mariadb` database, the NPM configuration file should still use the `mysql` engine!
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Running the App
|
||||||
|
|
||||||
|
Via `docker-compose`:
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
version: '3.8'
|
version: "3"
|
||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
image: 'jc21/nginx-proxy-manager:latest'
|
image: 'jc21/nginx-proxy-manager:latest'
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
# These ports are in format <host-port>:<container-port>
|
# Public HTTP Port:
|
||||||
- '80:80' # Public HTTP Port
|
- '80:80'
|
||||||
- '443:443' # Public HTTPS Port
|
# Public HTTPS Port:
|
||||||
- '81:81' # Admin Web Port
|
- '443:443'
|
||||||
|
# Admin Web Port:
|
||||||
|
- '81:81'
|
||||||
# Add any other Stream port you want to expose
|
# Add any other Stream port you want to expose
|
||||||
# - '21:21' # FTP
|
# - '21:21' # FTP
|
||||||
environment:
|
environment:
|
||||||
# Mysql/Maria connection parameters:
|
# These are the settings to access your db
|
||||||
DB_MYSQL_HOST: "db"
|
DB_MYSQL_HOST: "db"
|
||||||
DB_MYSQL_PORT: 3306
|
DB_MYSQL_PORT: 3306
|
||||||
DB_MYSQL_USER: "npm"
|
DB_MYSQL_USER: "npm"
|
||||||
DB_MYSQL_PASSWORD: "npm"
|
DB_MYSQL_PASSWORD: "npm"
|
||||||
DB_MYSQL_NAME: "npm"
|
DB_MYSQL_NAME: "npm"
|
||||||
|
# If you would rather use Sqlite uncomment this
|
||||||
|
# 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:
|
||||||
@ -77,7 +52,6 @@ services:
|
|||||||
- ./letsencrypt:/etc/letsencrypt
|
- ./letsencrypt:/etc/letsencrypt
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
|
|
||||||
db:
|
db:
|
||||||
image: 'jc21/mariadb-aria:latest'
|
image: 'jc21/mariadb-aria:latest'
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@ -87,14 +61,16 @@ services:
|
|||||||
MYSQL_USER: 'npm'
|
MYSQL_USER: 'npm'
|
||||||
MYSQL_PASSWORD: 'npm'
|
MYSQL_PASSWORD: 'npm'
|
||||||
volumes:
|
volumes:
|
||||||
- ./mysql:/var/lib/mysql
|
- ./data/mysql:/var/lib/mysql
|
||||||
```
|
```
|
||||||
|
|
||||||
::: warning
|
_Please note, that `DB_MYSQL_*` environment variables will take precedent over `DB_SQLITE_*` variables. So if you keep the MySQL variables, you will not be able to use Sqlite._
|
||||||
|
|
||||||
Please note, that `DB_MYSQL_*` environment variables will take precedent over `DB_SQLITE_*` variables. So if you keep the MySQL variables, you will not be able to use SQLite.
|
Then:
|
||||||
|
|
||||||
:::
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
## Running on Raspberry PI / ARM devices
|
## Running on Raspberry PI / ARM devices
|
||||||
|
|
||||||
@ -108,23 +84,74 @@ you don't have to worry about doing anything special and you can follow the comm
|
|||||||
|
|
||||||
Check out the [dockerhub tags](https://hub.docker.com/r/jc21/nginx-proxy-manager/tags)
|
Check out the [dockerhub tags](https://hub.docker.com/r/jc21/nginx-proxy-manager/tags)
|
||||||
for a list of supported architectures and if you want one that doesn't exist,
|
for a list of supported architectures and if you want one that doesn't exist,
|
||||||
[create a feature request](https://github.com/NginxProxyManager/nginx-proxy-manager/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=).
|
[create a feature request](https://github.com/jc21/nginx-proxy-manager/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=).
|
||||||
|
|
||||||
Also, if you don't know how to already, follow [this guide to install docker and docker-compose](https://manre-universe.net/how-to-run-docker-and-docker-compose-on-raspbian/)
|
Also, if you don't know how to already, follow [this guide to install docker and docker-compose](https://manre-universe.net/how-to-run-docker-and-docker-compose-on-raspbian/)
|
||||||
on Raspbian.
|
on Raspbian.
|
||||||
|
|
||||||
Please note that the `jc21/mariadb-aria:latest` image might have some problems on some ARM devices, if you want a separate database container, use the `yobasystems/alpine-mariadb:latest` image.
|
Via `docker-compose`:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
image: 'jc21/nginx-proxy-manager:latest'
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
# Public HTTP Port:
|
||||||
|
- '80:80'
|
||||||
|
# Public HTTPS Port:
|
||||||
|
- '443:443'
|
||||||
|
# Admin Web Port:
|
||||||
|
- '81:81'
|
||||||
|
environment:
|
||||||
|
# These are the settings to access your db
|
||||||
|
DB_MYSQL_HOST: "db"
|
||||||
|
DB_MYSQL_PORT: 3306
|
||||||
|
DB_MYSQL_USER: "changeuser"
|
||||||
|
DB_MYSQL_PASSWORD: "changepass"
|
||||||
|
DB_MYSQL_NAME: "npm"
|
||||||
|
# If you would rather use Sqlite uncomment this
|
||||||
|
# and remove all DB_MYSQL_* lines above
|
||||||
|
# DB_SQLITE_FILE: "/data/database.sqlite"
|
||||||
|
# Uncomment this if IPv6 is not enabled on your host
|
||||||
|
# DISABLE_IPV6: 'true'
|
||||||
|
volumes:
|
||||||
|
- ./data/nginx-proxy-manager:/data
|
||||||
|
- ./letsencrypt:/etc/letsencrypt
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
db:
|
||||||
|
image: yobasystems/alpine-mariadb:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: "changeme"
|
||||||
|
MYSQL_DATABASE: "npm"
|
||||||
|
MYSQL_USER: "changeuser"
|
||||||
|
MYSQL_PASSWORD: "changepass"
|
||||||
|
volumes:
|
||||||
|
- ./data/mariadb:/var/lib/mysql
|
||||||
|
```
|
||||||
|
|
||||||
|
_Please note, that `DB_MYSQL_*` environment variables will take precedent over `DB_SQLITE_*` var>
|
||||||
|
|
||||||
|
Then:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
## Initial Run
|
## Initial Run
|
||||||
|
|
||||||
After the app is running for the first time, the following will happen:
|
After the app is running for the first time, the following will happen:
|
||||||
|
|
||||||
1. GPG keys will be generated and saved in the data folder
|
1. The database will initialize with table structures
|
||||||
2. The database will initialize with table structures
|
2. GPG keys will be generated and saved in the configuration file
|
||||||
3. A default admin user will be created
|
3. A default admin user will be created
|
||||||
|
|
||||||
This process can take a couple of minutes depending on your machine.
|
This process can take a couple of minutes depending on your machine.
|
||||||
|
|
||||||
|
|
||||||
## Default Administrator User
|
## Default Administrator User
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -134,3 +161,49 @@ 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.
|
||||||
|
|
||||||
|
## Configuration File
|
||||||
|
|
||||||
|
::: warning
|
||||||
|
|
||||||
|
This section is meant for advanced users
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
If you would like more control over the database settings you can define a custom config JSON file.
|
||||||
|
|
||||||
|
|
||||||
|
Here's an example for `sqlite` configuration as it is generated from the environment variables:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"database": {
|
||||||
|
"engine": "knex-native",
|
||||||
|
"knex": {
|
||||||
|
"client": "sqlite3",
|
||||||
|
"connection": {
|
||||||
|
"filename": "/data/database.sqlite"
|
||||||
|
},
|
||||||
|
"useNullAsDefault": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can modify the `knex` object with your custom configuration, but note that not all knex clients might be installed in the image.
|
||||||
|
|
||||||
|
Once you've created your configuration file you can mount it to `/app/config/production.json` inside you container using:
|
||||||
|
|
||||||
|
```
|
||||||
|
[...]
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
image: 'jc21/nginx-proxy-manager:latest'
|
||||||
|
[...]
|
||||||
|
volumes:
|
||||||
|
- ./config.json:/app/config/production.json
|
||||||
|
[...]
|
||||||
|
[...]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** After the first run of the application, the config file will be altered to include generated encryption keys unique to your installation.
|
||||||
|
These keys affect the login and session management of the application. If these keys change for any reason, all users will be logged out.
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user