mirror of
https://github.com/NginxProxyManager/nginx-proxy-manager.git
synced 2025-06-18 02:06:25 +00:00
Compare commits
4 Commits
v2.9.21
...
static-con
Author | SHA1 | Date | |
---|---|---|---|
066a765a4d | |||
5a3d32db7b | |||
8de118d875 | |||
f61ab55b52 |
30
.github/ISSUE_TEMPLATE/bug_report.md
vendored
30
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -6,30 +6,20 @@ labels: bug
|
|||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
<!--
|
|
||||||
|
|
||||||
Are you in the right place?
|
**Are you in the right place?**
|
||||||
- If you are looking for support on how to get your upstream server forwarding, please consider asking the community on Reddit.
|
- If you are looking for support on how to get your upstream server forwarding, please consider asking the community on Reddit.
|
||||||
- If you are writing code changes to contribute and need to ask about the internals of the software, Gitter is the best place to ask.
|
- If you are writing code changes to contribute and need to ask about the internals of the software, Gitter is the best place to ask.
|
||||||
- If you think you found a bug with NPM (not Nginx, or your upstream server or MySql) then you are in the *right place.*
|
- If you think you found a bug with NPM (not Nginx, or your upstream server or MySql) then you are in the *right place.*
|
||||||
|
|
||||||
-->
|
|
||||||
|
|
||||||
**Checklist**
|
**Checklist**
|
||||||
- Have you pulled and found the error with `jc21/nginx-proxy-manager:latest` docker image?
|
- Have you pulled and found the error with `jc21/nginx-proxy-manager:latest` docker image?
|
||||||
- Yes / No
|
|
||||||
- Are you sure you're not using someone else's docker image?
|
- Are you sure you're not using someone else's docker image?
|
||||||
- Yes / No
|
- If having problems with Lets Encrypt, have you made absolutely sure your site is accessible from outside of your network?
|
||||||
- Have you searched for similar issues (both open and closed)?
|
|
||||||
- Yes / No
|
|
||||||
|
|
||||||
**Describe the bug**
|
**Describe the bug**
|
||||||
<!-- A clear and concise description of what the bug is. -->
|
- A clear and concise description of what the bug is.
|
||||||
|
- What version of Nginx Proxy Manager is reported on the login page?
|
||||||
|
|
||||||
**Nginx Proxy Manager Version**
|
|
||||||
<!-- What version of Nginx Proxy Manager is reported on the login page? -->
|
|
||||||
|
|
||||||
|
|
||||||
**To Reproduce**
|
**To Reproduce**
|
||||||
Steps to reproduce the behavior:
|
Steps to reproduce the behavior:
|
||||||
@ -38,18 +28,14 @@ Steps to reproduce the behavior:
|
|||||||
3. Scroll down to '....'
|
3. Scroll down to '....'
|
||||||
4. See error
|
4. See error
|
||||||
|
|
||||||
|
|
||||||
**Expected behavior**
|
**Expected behavior**
|
||||||
<!-- A clear and concise description of what you expected to happen. -->
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
|
||||||
**Screenshots**
|
**Screenshots**
|
||||||
<!-- If applicable, add screenshots to help explain your problem. -->
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
|
||||||
**Operating System**
|
**Operating System**
|
||||||
<!-- Please specify if using a Rpi, Mac, orchestration tool or any other setups that might affect the reproduction of this error. -->
|
- Please specify if using a Rpi, Mac, orchestration tool or any other setups that might affect the reproduction of this error.
|
||||||
|
|
||||||
|
|
||||||
**Additional context**
|
**Additional context**
|
||||||
<!-- Add any other context about the problem here, docker version, browser version, logs if applicable to the problem. Too much info is better than too little. -->
|
Add any other context about the problem here, docker version, browser version if applicable to the problem. Too much info is better than too little.
|
||||||
|
18
.github/ISSUE_TEMPLATE/dns_challenge_request.md
vendored
18
.github/ISSUE_TEMPLATE/dns_challenge_request.md
vendored
@ -1,18 +0,0 @@
|
|||||||
---
|
|
||||||
name: DNS challenge provider request
|
|
||||||
about: Suggest a new provider to be available for a certificate DNS challenge
|
|
||||||
title: ''
|
|
||||||
labels: dns provider request
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**What provider would you like to see added to NPM?**
|
|
||||||
<!-- What is this provider called? -->
|
|
||||||
|
|
||||||
|
|
||||||
**Have you checked if a certbot plugin exists?**
|
|
||||||
<!--
|
|
||||||
Currently NPM only supports DNS challenge providers for which a certbot plugin exists.
|
|
||||||
You can visit pypi.org, and search for a package with the name `certbot-dns-<privider>`.
|
|
||||||
-->
|
|
19
.github/ISSUE_TEMPLATE/feature_request.md
vendored
19
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -7,26 +7,19 @@ assignees: ''
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<!--
|
**Are you in the right place?**
|
||||||
|
|
||||||
Are you in the right place?
|
|
||||||
- If you are looking for support on how to get your upstream server forwarding, please consider asking the community on Reddit.
|
- If you are looking for support on how to get your upstream server forwarding, please consider asking the community on Reddit.
|
||||||
- If you are writing code changes to contribute and need to ask about the internals of the software, Gitter is the best place to ask.
|
- If you are writing code changes to contribute and need to ask about the internals of the software, Gitter is the best place to ask.
|
||||||
- If you think you found a bug with NPM (not Nginx, or your upstream server or MySql) then you are in the *right place.*
|
- If you have a feature request for NPM then you are in the *right place.*
|
||||||
|
|
||||||
-->
|
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
**Is your feature request related to a problem? Please describe.**
|
||||||
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
**Describe the solution you'd like**
|
||||||
<!-- A clear and concise description of what you want to happen. -->
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
|
||||||
**Describe alternatives you've considered**
|
**Describe alternatives you've considered**
|
||||||
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
|
||||||
**Additional context**
|
**Additional context**
|
||||||
<!-- Add any other context or screenshots about the feature request here. -->
|
Add any other context or screenshots about the feature request here.
|
||||||
|
89
Jenkinsfile
vendored
89
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'
|
||||||
@ -22,8 +16,6 @@ pipeline {
|
|||||||
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,28 +54,34 @@ 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}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
post {
|
}
|
||||||
always {
|
stage('Backend') {
|
||||||
sh 'rm -f ${WORKSPACE}/tmp-sh-build'
|
steps {
|
||||||
}
|
echo 'Checking Syntax ...'
|
||||||
failure {
|
// See: https://github.com/yarnpkg/yarn/issues/3254
|
||||||
npmGithubPrComment("CI Error:\n\n```\n${shOutput}\n```", true)
|
sh '''docker run --rm \\
|
||||||
}
|
-v "$(pwd)/backend:/app" \\
|
||||||
|
-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') {
|
||||||
@ -165,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}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -180,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') {
|
||||||
@ -194,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("Docker Image for build ${BUILD_NUMBER} is available on [DockerHub](https://cloud.docker.com/repository/docker/jc21/${IMAGE}) as `jc21/${IMAGE}:github-${BRANCH_LOWER}`")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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'
|
||||||
}
|
}
|
||||||
|
270
README.md
270
README.md
@ -1,19 +1,25 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://nginxproxymanager.com/github.png">
|
<img src="https://nginxproxymanager.com/github.png">
|
||||||
<br><br>
|
<br><br>
|
||||||
<img src="https://img.shields.io/badge/version-2.9.19-green.svg?style=for-the-badge">
|
<img src="https://img.shields.io/badge/version-2.8.1-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>
|
||||||
</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
|
||||||
running at home or otherwise, including free SSL, without having to know too much about Nginx or Letsencrypt.
|
running at home or otherwise, including free SSL, without having to know too much about Nginx or Letsencrypt.
|
||||||
|
|
||||||
- [Quick Setup](#quick-setup)
|
- [Quick Setup](https://nginxproxymanager.com#quick-setup)
|
||||||
- [Full Setup](https://nginxproxymanager.com/setup/)
|
- [Full Setup](https://nginxproxymanager.com/setup/)
|
||||||
- [Screenshots](https://nginxproxymanager.com/screenshots/)
|
- [Screenshots](https://nginxproxymanager.com/screenshots/)
|
||||||
|
|
||||||
@ -46,64 +52,210 @@ I won't go in to too much detail here but here are the basics for someone new to
|
|||||||
3. Configure your domain name details to point to your home, either with a static ip or a service like DuckDNS or [Amazon Route53](https://github.com/jc21/route53-ddns)
|
3. Configure your domain name details to point to your home, either with a static ip or a service like DuckDNS or [Amazon Route53](https://github.com/jc21/route53-ddns)
|
||||||
4. Use the Nginx Proxy Manager as your gateway to forward to your other web based services
|
4. Use the Nginx Proxy Manager as your gateway to forward to your other web based services
|
||||||
|
|
||||||
## Quick Setup
|
|
||||||
|
|
||||||
1. Install Docker and Docker-Compose
|
|
||||||
|
|
||||||
- [Docker Install documentation](https://docs.docker.com/install/)
|
|
||||||
- [Docker-Compose Install documentation](https://docs.docker.com/compose/install/)
|
|
||||||
|
|
||||||
2. Create a docker-compose.yml file similar to this:
|
|
||||||
|
|
||||||
```yml
|
|
||||||
version: '3'
|
|
||||||
services:
|
|
||||||
app:
|
|
||||||
image: 'jc21/nginx-proxy-manager:latest'
|
|
||||||
restart: unless-stopped
|
|
||||||
ports:
|
|
||||||
- '80:80'
|
|
||||||
- '81:81'
|
|
||||||
- '443:443'
|
|
||||||
volumes:
|
|
||||||
- ./data:/data
|
|
||||||
- ./letsencrypt:/etc/letsencrypt
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Bring up your stack by running
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker-compose up -d
|
|
||||||
|
|
||||||
# If using docker-compose-plugin
|
|
||||||
docker compose up -d
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Log in to the Admin UI
|
|
||||||
|
|
||||||
When your docker container is running, connect to it on port `81` for the admin interface.
|
|
||||||
Sometimes this can take a little bit because of the entropy of keys.
|
|
||||||
|
|
||||||
[http://127.0.0.1:81](http://127.0.0.1:81)
|
|
||||||
|
|
||||||
Default Admin User:
|
|
||||||
```
|
|
||||||
Email: admin@example.com
|
|
||||||
Password: changeme
|
|
||||||
```
|
|
||||||
|
|
||||||
Immediately after logging in with this default user you will be asked to modify your details and change your password.
|
|
||||||
|
|
||||||
|
|
||||||
## 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="80px;" 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="80px;" 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="80px;" 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="80px;" 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="80px;" 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="80px;" 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="80px;" 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="80px;" 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="80px;" 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="80px;" 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="80px;" 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="80px;" 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="80px;" 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="80px;" 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="80px;" 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="80px;" 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="80px;" 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="80px;" 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="80px;" 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="80px;" 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="80px;" 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="80px;" 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="80px;" 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="80px;" 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="80px;" 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="80px;" 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="80px;" 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="80px;" 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="80px;" 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="80px;" 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="80px;" alt=""/>
|
||||||
|
<br /><sub><b>Loris Bergeron</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<!-- markdownlint-enable -->
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
@ -40,12 +40,13 @@ app.use(function (req, res, next) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
res.set({
|
res.set({
|
||||||
'X-XSS-Protection': '1; mode=block',
|
'Strict-Transport-Security': 'includeSubDomains; max-age=631138519; preload',
|
||||||
'X-Content-Type-Options': 'nosniff',
|
'X-XSS-Protection': '1; mode=block',
|
||||||
'X-Frame-Options': x_frame_options,
|
'X-Content-Type-Options': 'nosniff',
|
||||||
'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate',
|
'X-Frame-Options': x_frame_options,
|
||||||
Pragma: 'no-cache',
|
'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate',
|
||||||
Expires: 0
|
Pragma: 'no-cache',
|
||||||
|
Expires: 0
|
||||||
});
|
});
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
@ -74,7 +75,7 @@ app.use(function (err, req, res, next) {
|
|||||||
|
|
||||||
// Not every error is worth logging - but this is good for now until it gets annoying.
|
// Not every error is worth logging - but this is good for now until it gets annoying.
|
||||||
if (typeof err.stack !== 'undefined' && err.stack) {
|
if (typeof err.stack !== 'undefined' && err.stack) {
|
||||||
if (process.env.NODE_ENV === 'development' || process.env.DEBUG) {
|
if (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);
|
||||||
|
130
backend/index.js
130
backend/index.js
@ -44,85 +44,83 @@ async function appStart () {
|
|||||||
|
|
||||||
async function createDbConfigFromEnvironment() {
|
async function createDbConfigFromEnvironment() {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const envMysqlHost = process.env.DB_MYSQL_HOST || null;
|
const envMysqlHost = process.env.DB_MYSQL_HOST || null;
|
||||||
const envMysqlPort = process.env.DB_MYSQL_PORT || null;
|
const envMysqlPort = process.env.DB_MYSQL_PORT || null;
|
||||||
const envMysqlUser = process.env.DB_MYSQL_USER || null;
|
const envMysqlUser = process.env.DB_MYSQL_USER || null;
|
||||||
const envMysqlName = process.env.DB_MYSQL_NAME || null;
|
const envMysqlName = process.env.DB_MYSQL_NAME || null;
|
||||||
let envSqliteFile = process.env.DB_SQLITE_FILE || null;
|
const envSqliteFile = process.env.DB_SQLITE_FILE || null;
|
||||||
|
|
||||||
const fs = require('fs');
|
if ((envMysqlHost && envMysqlPort && envMysqlUser && envMysqlName) || envSqliteFile) {
|
||||||
const filename = (process.env.NODE_CONFIG_DIR || './config') + '/' + (process.env.NODE_ENV || 'default') + '.json';
|
const fs = require('fs');
|
||||||
let configData = {};
|
const filename = (process.env.NODE_CONFIG_DIR || './config') + '/' + (process.env.NODE_ENV || 'default') + '.json';
|
||||||
|
let configData = {};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
configData = require(filename);
|
configData = require(filename);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
if (configData.database && configData.database.engine && !configData.database.fromEnv) {
|
if (configData.database && configData.database.engine && !configData.database.fromEnv) {
|
||||||
logger.info('Manual db configuration already exists, skipping config creation from environment variables');
|
logger.info('Manual db configuration already exists, skipping config creation from environment variables');
|
||||||
resolve();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((!envMysqlHost || !envMysqlPort || !envMysqlUser || !envMysqlName) && !envSqliteFile){
|
|
||||||
envSqliteFile = '/data/database.sqlite';
|
|
||||||
logger.info(`No valid environment variables for database provided, using default SQLite file '${envSqliteFile}'`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (envMysqlHost && envMysqlPort && envMysqlUser && envMysqlName) {
|
|
||||||
const newConfig = {
|
|
||||||
fromEnv: true,
|
|
||||||
engine: 'mysql',
|
|
||||||
host: envMysqlHost,
|
|
||||||
port: envMysqlPort,
|
|
||||||
user: envMysqlUser,
|
|
||||||
password: process.env.DB_MYSQL_PASSWORD,
|
|
||||||
name: envMysqlName,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (JSON.stringify(configData.database) === JSON.stringify(newConfig)) {
|
|
||||||
// Config is unchanged, skip overwrite
|
|
||||||
resolve();
|
resolve();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info('Generating MySQL knex configuration from environment variables');
|
if (envMysqlHost && envMysqlPort && envMysqlUser && envMysqlName) {
|
||||||
configData.database = newConfig;
|
const newConfig = {
|
||||||
|
fromEnv: true,
|
||||||
|
engine: 'mysql',
|
||||||
|
host: envMysqlHost,
|
||||||
|
port: envMysqlPort,
|
||||||
|
user: envMysqlUser,
|
||||||
|
password: process.env.DB_MYSQL_PASSWORD,
|
||||||
|
name: envMysqlName,
|
||||||
|
};
|
||||||
|
|
||||||
} else {
|
if (JSON.stringify(configData.database) === JSON.stringify(newConfig)) {
|
||||||
const newConfig = {
|
// Config is unchanged, skip overwrite
|
||||||
fromEnv: true,
|
resolve();
|
||||||
engine: 'knex-native',
|
return;
|
||||||
knex: {
|
|
||||||
client: 'sqlite3',
|
|
||||||
connection: {
|
|
||||||
filename: envSqliteFile
|
|
||||||
},
|
|
||||||
useNullAsDefault: true
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
if (JSON.stringify(configData.database) === JSON.stringify(newConfig)) {
|
|
||||||
// Config is unchanged, skip overwrite
|
|
||||||
resolve();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info('Generating SQLite knex configuration');
|
logger.info('Generating MySQL db configuration from environment variables');
|
||||||
configData.database = newConfig;
|
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 {
|
} else {
|
||||||
logger.debug('Wrote db configuration to config file: ' + filename);
|
const newConfig = {
|
||||||
resolve();
|
fromEnv: true,
|
||||||
|
engine: 'knex-native',
|
||||||
|
knex: {
|
||||||
|
client: 'sqlite3',
|
||||||
|
connection: {
|
||||||
|
filename: envSqliteFile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
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();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
@ -118,6 +118,7 @@ const internalAccessList = {
|
|||||||
// Sanity check that something crazy hasn't happened
|
// Sanity check that something crazy hasn't happened
|
||||||
throw new error.InternalValidationError('Access List could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id);
|
throw new error.InternalValidationError('Access List could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// patch name if specified
|
// patch name if specified
|
||||||
@ -204,7 +205,6 @@ const internalAccessList = {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(internalNginx.reload)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// Add to audit log
|
// Add to audit log
|
||||||
return internalAuditLog.add(access, {
|
return internalAuditLog.add(access, {
|
||||||
@ -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) {
|
|
||||||
query.withGraphFetched('[' + data.expand.join(', ') + ']');
|
|
||||||
}
|
|
||||||
|
|
||||||
return query.then(utils.omitRow(omissions()));
|
|
||||||
})
|
|
||||||
.then((row) => {
|
|
||||||
if (!row) {
|
|
||||||
throw new error.ItemNotFoundError(data.id);
|
|
||||||
}
|
|
||||||
if (!skip_masking && typeof row.items !== 'undefined' && row.items) {
|
|
||||||
row = internalAccessList.maskItems(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) {
|
||||||
|
if (!skip_masking && typeof row.items !== 'undefined' && row.items) {
|
||||||
|
row = internalAccessList.maskItems(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _.omit(row, omissions());
|
||||||
|
} 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,22 +1,19 @@
|
|||||||
const _ = require('lodash');
|
const fs = require('fs');
|
||||||
const fs = require('fs');
|
const _ = require('lodash');
|
||||||
const https = require('https');
|
const logger = require('../logger').ssl;
|
||||||
const tempWrite = require('temp-write');
|
const error = require('../lib/error');
|
||||||
const moment = require('moment');
|
const certificateModel = require('../models/certificate');
|
||||||
const logger = require('../logger').ssl;
|
const internalAuditLog = require('./audit-log');
|
||||||
const error = require('../lib/error');
|
const tempWrite = require('temp-write');
|
||||||
const utils = require('../lib/utils');
|
const utils = require('../lib/utils');
|
||||||
const certificateModel = require('../models/certificate');
|
const moment = require('moment');
|
||||||
const dnsPlugins = require('../global/certbot-dns-plugins');
|
const debug_mode = process.env.NODE_ENV !== 'production' || !!process.env.DEBUG;
|
||||||
const internalAuditLog = require('./audit-log');
|
const le_staging = process.env.NODE_ENV !== 'production';
|
||||||
const internalNginx = require('./nginx');
|
const internalNginx = require('./nginx');
|
||||||
const internalHost = require('./host');
|
const internalHost = require('./host');
|
||||||
const letsencryptStaging = process.env.NODE_ENV !== 'production';
|
const certbot_command = '/usr/bin/certbot';
|
||||||
const letsencryptConfig = '/etc/letsencrypt.ini';
|
const le_config = '/etc/letsencrypt.ini';
|
||||||
const certbotCommand = 'certbot';
|
const dns_plugins = require('../global/certbot-dns-plugins');
|
||||||
const archiver = require('archiver');
|
|
||||||
const path = require('path');
|
|
||||||
const { isArray } = require('lodash');
|
|
||||||
|
|
||||||
function omissions() {
|
function omissions() {
|
||||||
return ['is_deleted'];
|
return ['is_deleted'];
|
||||||
@ -24,14 +21,14 @@ function omissions() {
|
|||||||
|
|
||||||
const internalCertificate = {
|
const internalCertificate = {
|
||||||
|
|
||||||
allowedSslFiles: ['certificate', 'certificate_key', 'intermediate_certificate'],
|
allowed_ssl_files: ['certificate', 'certificate_key', 'intermediate_certificate'],
|
||||||
intervalTimeout: 1000 * 60 * 60, // 1 hour
|
interval_timeout: 1000 * 60 * 60, // 1 hour
|
||||||
interval: null,
|
interval: null,
|
||||||
intervalProcessing: false,
|
interval_processing: false,
|
||||||
|
|
||||||
initTimer: () => {
|
initTimer: () => {
|
||||||
logger.info('Let\'s Encrypt Renewal Timer initialized');
|
logger.info('Let\'s Encrypt Renewal Timer initialized');
|
||||||
internalCertificate.interval = setInterval(internalCertificate.processExpiringHosts, internalCertificate.intervalTimeout);
|
internalCertificate.interval = setInterval(internalCertificate.processExpiringHosts, internalCertificate.interval_timeout);
|
||||||
// And do this now as well
|
// And do this now as well
|
||||||
internalCertificate.processExpiringHosts();
|
internalCertificate.processExpiringHosts();
|
||||||
},
|
},
|
||||||
@ -40,15 +37,15 @@ const internalCertificate = {
|
|||||||
* Triggered by a timer, this will check for expiring hosts and renew their ssl certs if required
|
* Triggered by a timer, this will check for expiring hosts and renew their ssl certs if required
|
||||||
*/
|
*/
|
||||||
processExpiringHosts: () => {
|
processExpiringHosts: () => {
|
||||||
if (!internalCertificate.intervalProcessing) {
|
if (!internalCertificate.interval_processing) {
|
||||||
internalCertificate.intervalProcessing = true;
|
internalCertificate.interval_processing = true;
|
||||||
logger.info('Renewing SSL certs close to expiry...');
|
logger.info('Renewing SSL certs close to expiry...');
|
||||||
|
|
||||||
const cmd = certbotCommand + ' renew --non-interactive --quiet ' +
|
let cmd = certbot_command + ' renew --non-interactive --quiet ' +
|
||||||
'--config "' + letsencryptConfig + '" ' +
|
'--config "' + le_config + '" ' +
|
||||||
'--preferred-challenges "dns,http" ' +
|
'--preferred-challenges "dns,http" ' +
|
||||||
'--disable-hook-validation ' +
|
'--disable-hook-validation ' +
|
||||||
(letsencryptStaging ? '--staging' : '');
|
(le_staging ? '--staging' : '');
|
||||||
|
|
||||||
return utils.exec(cmd)
|
return utils.exec(cmd)
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
@ -96,11 +93,11 @@ const internalCertificate = {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
internalCertificate.intervalProcessing = false;
|
internalCertificate.interval_processing = false;
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
internalCertificate.intervalProcessing = false;
|
internalCertificate.interval_processing = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -116,13 +113,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') {
|
||||||
@ -171,7 +168,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);
|
||||||
@ -269,8 +265,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);
|
||||||
@ -288,7 +284,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());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -313,96 +309,33 @@ 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;
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Access} access
|
|
||||||
* @param {Object} data
|
|
||||||
* @param {Number} data.id
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
download: (access, data) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
access.can('certificates:get', data)
|
|
||||||
.then(() => {
|
|
||||||
return internalCertificate.get(access, data);
|
|
||||||
})
|
|
||||||
.then((certificate) => {
|
|
||||||
if (certificate.provider === 'letsencrypt') {
|
|
||||||
const zipDirectory = '/etc/letsencrypt/live/npm-' + data.id;
|
|
||||||
|
|
||||||
if (!fs.existsSync(zipDirectory)) {
|
|
||||||
throw new error.ItemNotFoundError('Certificate ' + certificate.nice_name + ' does not exists');
|
|
||||||
}
|
|
||||||
|
|
||||||
let certFiles = fs.readdirSync(zipDirectory)
|
|
||||||
.filter((fn) => fn.endsWith('.pem'))
|
|
||||||
.map((fn) => fs.realpathSync(path.join(zipDirectory, fn)));
|
|
||||||
const downloadName = 'npm-' + data.id + '-' + `${Date.now()}.zip`;
|
|
||||||
const opName = '/tmp/' + downloadName;
|
|
||||||
internalCertificate.zipFiles(certFiles, opName)
|
|
||||||
.then(() => {
|
|
||||||
logger.debug('zip completed : ', opName);
|
|
||||||
const resp = {
|
|
||||||
fileName: opName
|
|
||||||
};
|
|
||||||
resolve(resp);
|
|
||||||
}).catch((err) => reject(err));
|
|
||||||
} else {
|
|
||||||
throw new error.ValidationError('Only Let\'sEncrypt certificates can be downloaded');
|
|
||||||
}
|
|
||||||
}).catch((err) => reject(err));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {String} source
|
|
||||||
* @param {String} out
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
zipFiles(source, out) {
|
|
||||||
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
||||||
const stream = fs.createWriteStream(out);
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
source
|
|
||||||
.map((fl) => {
|
|
||||||
let fileName = path.basename(fl);
|
|
||||||
logger.debug(fl, 'added to certificate zip');
|
|
||||||
archive.file(fl, { name: fileName });
|
|
||||||
});
|
|
||||||
archive
|
|
||||||
.on('error', (err) => reject(err))
|
|
||||||
.pipe(stream);
|
|
||||||
|
|
||||||
stream.on('close', () => resolve());
|
|
||||||
archive.finalize();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Access} access
|
* @param {Access} access
|
||||||
* @param {Object} data
|
* @param {Object} data
|
||||||
@ -464,7 +397,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') {
|
||||||
@ -474,15 +408,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;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -514,9 +448,11 @@ const internalCertificate = {
|
|||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
writeCustomCert: (certificate) => {
|
writeCustomCert: (certificate) => {
|
||||||
logger.info('Writing Custom Certificate:', certificate);
|
if (debug_mode) {
|
||||||
|
logger.info('Writing Custom Certificate:', certificate);
|
||||||
|
}
|
||||||
|
|
||||||
const dir = '/data/custom_ssl/npm-' + certificate.id;
|
let dir = '/data/custom_ssl/npm-' + certificate.id;
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (certificate.provider === 'letsencrypt') {
|
if (certificate.provider === 'letsencrypt') {
|
||||||
@ -524,9 +460,9 @@ const internalCertificate = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let certData = certificate.meta.certificate;
|
let cert_data = certificate.meta.certificate;
|
||||||
if (typeof certificate.meta.intermediate_certificate !== 'undefined') {
|
if (typeof certificate.meta.intermediate_certificate !== 'undefined') {
|
||||||
certData = certData + '\n' + certificate.meta.intermediate_certificate;
|
cert_data = cert_data + '\n' + certificate.meta.intermediate_certificate;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -538,7 +474,7 @@ const internalCertificate = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.writeFile(dir + '/fullchain.pem', certData, function (err) {
|
fs.writeFile(dir + '/fullchain.pem', cert_data, function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
} else {
|
} else {
|
||||||
@ -588,7 +524,7 @@ const internalCertificate = {
|
|||||||
// Put file contents into an object
|
// Put file contents into an object
|
||||||
let files = {};
|
let files = {};
|
||||||
_.map(data.files, (file, name) => {
|
_.map(data.files, (file, name) => {
|
||||||
if (internalCertificate.allowedSslFiles.indexOf(name) !== -1) {
|
if (internalCertificate.allowed_ssl_files.indexOf(name) !== -1) {
|
||||||
files[name] = file.data.toString();
|
files[name] = file.data.toString();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -646,7 +582,7 @@ const internalCertificate = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_.map(data.files, (file, name) => {
|
_.map(data.files, (file, name) => {
|
||||||
if (internalCertificate.allowedSslFiles.indexOf(name) !== -1) {
|
if (internalCertificate.allowed_ssl_files.indexOf(name) !== -1) {
|
||||||
row.meta[name] = file.data.toString();
|
row.meta[name] = file.data.toString();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -659,12 +595,13 @@ 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);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return _.pick(row.meta, internalCertificate.allowedSslFiles);
|
return _.pick(row.meta, internalCertificate.allowed_ssl_files);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -712,9 +649,9 @@ const internalCertificate = {
|
|||||||
return tempWrite(certificate, '/tmp')
|
return tempWrite(certificate, '/tmp')
|
||||||
.then((filepath) => {
|
.then((filepath) => {
|
||||||
return internalCertificate.getCertificateInfoFromFile(filepath, throw_expired)
|
return internalCertificate.getCertificateInfoFromFile(filepath, throw_expired)
|
||||||
.then((certData) => {
|
.then((cert_data) => {
|
||||||
fs.unlinkSync(filepath);
|
fs.unlinkSync(filepath);
|
||||||
return certData;
|
return cert_data;
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
fs.unlinkSync(filepath);
|
fs.unlinkSync(filepath);
|
||||||
throw err;
|
throw err;
|
||||||
@ -730,33 +667,33 @@ const internalCertificate = {
|
|||||||
* @param {Boolean} [throw_expired] Throw when the certificate is out of date
|
* @param {Boolean} [throw_expired] Throw when the certificate is out of date
|
||||||
*/
|
*/
|
||||||
getCertificateInfoFromFile: (certificate_file, throw_expired) => {
|
getCertificateInfoFromFile: (certificate_file, throw_expired) => {
|
||||||
let certData = {};
|
let cert_data = {};
|
||||||
|
|
||||||
return utils.exec('openssl x509 -in ' + certificate_file + ' -subject -noout')
|
return utils.exec('openssl x509 -in ' + certificate_file + ' -subject -noout')
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
// subject=CN = something.example.com
|
// subject=CN = something.example.com
|
||||||
const regex = /(?:subject=)?[^=]+=\s+(\S+)/gim;
|
let regex = /(?:subject=)?[^=]+=\s+(\S+)/gim;
|
||||||
const match = regex.exec(result);
|
let match = regex.exec(result);
|
||||||
|
|
||||||
if (typeof match[1] === 'undefined') {
|
if (typeof match[1] === 'undefined') {
|
||||||
throw new error.ValidationError('Could not determine subject from certificate: ' + result);
|
throw new error.ValidationError('Could not determine subject from certificate: ' + result);
|
||||||
}
|
}
|
||||||
|
|
||||||
certData['cn'] = match[1];
|
cert_data['cn'] = match[1];
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return utils.exec('openssl x509 -in ' + certificate_file + ' -issuer -noout');
|
return utils.exec('openssl x509 -in ' + certificate_file + ' -issuer -noout');
|
||||||
})
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
// issuer=C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
|
// issuer=C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
|
||||||
const regex = /^(?:issuer=)?(.*)$/gim;
|
let regex = /^(?:issuer=)?(.*)$/gim;
|
||||||
const match = regex.exec(result);
|
let match = regex.exec(result);
|
||||||
|
|
||||||
if (typeof match[1] === 'undefined') {
|
if (typeof match[1] === 'undefined') {
|
||||||
throw new error.ValidationError('Could not determine issuer from certificate: ' + result);
|
throw new error.ValidationError('Could not determine issuer from certificate: ' + result);
|
||||||
}
|
}
|
||||||
|
|
||||||
certData['issuer'] = match[1];
|
cert_data['issuer'] = match[1];
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return utils.exec('openssl x509 -in ' + certificate_file + ' -dates -noout');
|
return utils.exec('openssl x509 -in ' + certificate_file + ' -dates -noout');
|
||||||
@ -764,39 +701,39 @@ const internalCertificate = {
|
|||||||
.then((result) => {
|
.then((result) => {
|
||||||
// notBefore=Jul 14 04:04:29 2018 GMT
|
// notBefore=Jul 14 04:04:29 2018 GMT
|
||||||
// notAfter=Oct 12 04:04:29 2018 GMT
|
// notAfter=Oct 12 04:04:29 2018 GMT
|
||||||
let validFrom = null;
|
let valid_from = null;
|
||||||
let validTo = null;
|
let valid_to = null;
|
||||||
|
|
||||||
const lines = result.split('\n');
|
let lines = result.split('\n');
|
||||||
lines.map(function (str) {
|
lines.map(function (str) {
|
||||||
const regex = /^(\S+)=(.*)$/gim;
|
let regex = /^(\S+)=(.*)$/gim;
|
||||||
const match = regex.exec(str.trim());
|
let match = regex.exec(str.trim());
|
||||||
|
|
||||||
if (match && typeof match[2] !== 'undefined') {
|
if (match && typeof match[2] !== 'undefined') {
|
||||||
const date = parseInt(moment(match[2], 'MMM DD HH:mm:ss YYYY z').format('X'), 10);
|
let date = parseInt(moment(match[2], 'MMM DD HH:mm:ss YYYY z').format('X'), 10);
|
||||||
|
|
||||||
if (match[1].toLowerCase() === 'notbefore') {
|
if (match[1].toLowerCase() === 'notbefore') {
|
||||||
validFrom = date;
|
valid_from = date;
|
||||||
} else if (match[1].toLowerCase() === 'notafter') {
|
} else if (match[1].toLowerCase() === 'notafter') {
|
||||||
validTo = date;
|
valid_to = date;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!validFrom || !validTo) {
|
if (!valid_from || !valid_to) {
|
||||||
throw new error.ValidationError('Could not determine dates from certificate: ' + result);
|
throw new error.ValidationError('Could not determine dates from certificate: ' + result);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (throw_expired && validTo < parseInt(moment().format('X'), 10)) {
|
if (throw_expired && valid_to < parseInt(moment().format('X'), 10)) {
|
||||||
throw new error.ValidationError('Certificate has expired');
|
throw new error.ValidationError('Certificate has expired');
|
||||||
}
|
}
|
||||||
|
|
||||||
certData['dates'] = {
|
cert_data['dates'] = {
|
||||||
from: validFrom,
|
from: valid_from,
|
||||||
to: validTo
|
to: valid_to
|
||||||
};
|
};
|
||||||
|
|
||||||
return certData;
|
return cert_data;
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
throw new error.ValidationError('Certificate is not valid (' + err.message + ')', err);
|
throw new error.ValidationError('Certificate is not valid (' + err.message + ')', err);
|
||||||
});
|
});
|
||||||
@ -810,7 +747,7 @@ const internalCertificate = {
|
|||||||
* @returns {Object}
|
* @returns {Object}
|
||||||
*/
|
*/
|
||||||
cleanMeta: function (meta, remove) {
|
cleanMeta: function (meta, remove) {
|
||||||
internalCertificate.allowedSslFiles.map((key) => {
|
internalCertificate.allowed_ssl_files.map((key) => {
|
||||||
if (typeof meta[key] !== 'undefined' && meta[key]) {
|
if (typeof meta[key] !== 'undefined' && meta[key]) {
|
||||||
if (remove) {
|
if (remove) {
|
||||||
delete meta[key];
|
delete meta[key];
|
||||||
@ -824,24 +761,24 @@ const internalCertificate = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request a certificate using the http challenge
|
|
||||||
* @param {Object} certificate the certificate row
|
* @param {Object} certificate the certificate row
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
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 ' +
|
let cmd = certbot_command + ' certonly --non-interactive ' +
|
||||||
'--config "' + letsencryptConfig + '" ' +
|
'--config "' + le_config + '" ' +
|
||||||
'--cert-name "npm-' + certificate.id + '" ' +
|
'--cert-name "npm-' + certificate.id + '" ' +
|
||||||
'--agree-tos ' +
|
'--agree-tos ' +
|
||||||
'--authenticator webroot ' +
|
|
||||||
'--email "' + certificate.meta.letsencrypt_email + '" ' +
|
'--email "' + certificate.meta.letsencrypt_email + '" ' +
|
||||||
'--preferred-challenges "dns,http" ' +
|
'--preferred-challenges "dns,http" ' +
|
||||||
'--domains "' + certificate.domain_names.join(',') + '" ' +
|
'--domains "' + certificate.domain_names.join(',') + '" ' +
|
||||||
(letsencryptStaging ? '--staging' : '');
|
(le_staging ? '--staging' : '');
|
||||||
|
|
||||||
logger.info('Command:', cmd);
|
if (debug_mode) {
|
||||||
|
logger.info('Command:', cmd);
|
||||||
|
}
|
||||||
|
|
||||||
return utils.exec(cmd)
|
return utils.exec(cmd)
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
@ -851,14 +788,14 @@ const internalCertificate = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Object} certificate the certificate row
|
* @param {Object} certificate the certificate row
|
||||||
* @param {String} dns_provider the dns provider name (key used in `certbot-dns-plugins.js`)
|
* @param {String} dns_provider the dns provider name (key used in `certbot-dns-plugins.js`)
|
||||||
* @param {String | null} credentials the content of this providers credentials file
|
* @param {String | null} credentials the content of this providers credentials file
|
||||||
* @param {String} propagation_seconds the cloudflare api token
|
* @param {String} propagation_seconds the cloudflare api token
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
requestLetsEncryptSslWithDnsChallenge: (certificate) => {
|
requestLetsEncryptSslWithDnsChallenge: (certificate) => {
|
||||||
const dns_plugin = dnsPlugins[certificate.meta.dns_provider];
|
const dns_plugin = dns_plugins[certificate.meta.dns_provider];
|
||||||
|
|
||||||
if (!dns_plugin) {
|
if (!dns_plugin) {
|
||||||
throw Error(`Unknown DNS provider '${certificate.meta.dns_provider}'`);
|
throw Error(`Unknown DNS provider '${certificate.meta.dns_provider}'`);
|
||||||
@ -866,26 +803,23 @@ 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 credentials_loc = '/etc/letsencrypt/credentials/credentials-' + certificate.id;
|
||||||
// Escape single quotes and backslashes
|
const credentials_cmd = '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 prepare_cmd = 'pip3 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
|
|
||||||
let prepareCmd = '. /opt/certbot/bin/activate && pip install ' + dns_plugin.package_name + (dns_plugin.version_requirement || '') + ' ' + dns_plugin.dependencies + ' && deactivate';
|
|
||||||
|
|
||||||
// Whether the plugin has a --<name>-credentials argument
|
// Whether the plugin has a --<name>-credentials argument
|
||||||
const hasConfigArg = certificate.meta.dns_provider !== 'route53';
|
const has_config_arg = certificate.meta.dns_provider !== 'route53';
|
||||||
|
|
||||||
let mainCmd = certbotCommand + ' certonly ' +
|
let main_cmd =
|
||||||
'--config "' + letsencryptConfig + '" ' +
|
certbot_command + ' certonly --non-interactive ' +
|
||||||
'--cert-name "npm-' + certificate.id + '" ' +
|
'--cert-name "npm-' + certificate.id + '" ' +
|
||||||
'--agree-tos ' +
|
'--agree-tos ' +
|
||||||
'--email "' + certificate.meta.letsencrypt_email + '" ' +
|
'--email "' + certificate.meta.letsencrypt_email + '" ' +
|
||||||
'--domains "' + certificate.domain_names.join(',') + '" ' +
|
'--domains "' + certificate.domain_names.join(',') + '" ' +
|
||||||
'--authenticator ' + dns_plugin.full_plugin_name + ' ' +
|
'--authenticator ' + dns_plugin.full_plugin_name + ' ' +
|
||||||
(
|
(
|
||||||
hasConfigArg
|
has_config_arg
|
||||||
? '--' + dns_plugin.full_plugin_name + '-credentials "' + credentialsLocation + '"'
|
? '--' + dns_plugin.full_plugin_name + '-credentials "' + credentials_loc + '"'
|
||||||
: ''
|
: ''
|
||||||
) +
|
) +
|
||||||
(
|
(
|
||||||
@ -893,20 +827,22 @@ const internalCertificate = {
|
|||||||
? ' --' + dns_plugin.full_plugin_name + '-propagation-seconds ' + certificate.meta.propagation_seconds
|
? ' --' + dns_plugin.full_plugin_name + '-propagation-seconds ' + certificate.meta.propagation_seconds
|
||||||
: ''
|
: ''
|
||||||
) +
|
) +
|
||||||
(letsencryptStaging ? ' --staging' : '');
|
(le_staging ? ' --staging' : '');
|
||||||
|
|
||||||
// Prepend the path to the credentials file as an environment variable
|
// Prepend the path to the credentials file as an environment variable
|
||||||
if (certificate.meta.dns_provider === 'route53') {
|
if (certificate.meta.dns_provider === 'route53') {
|
||||||
mainCmd = 'AWS_CONFIG_FILE=\'' + credentialsLocation + '\' ' + mainCmd;
|
main_cmd = 'AWS_CONFIG_FILE=\'' + credentials_loc + '\' ' + main_cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info('Command:', `${credentialsCmd} && ${prepareCmd} && ${mainCmd}`);
|
if (debug_mode) {
|
||||||
|
logger.info('Command:', `${credentials_cmd} && ${prepare_cmd} && ${main_cmd}`);
|
||||||
|
}
|
||||||
|
|
||||||
return utils.exec(credentialsCmd)
|
return utils.exec(credentials_cmd)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return utils.exec(prepareCmd)
|
return utils.exec(prepare_cmd)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return utils.exec(mainCmd)
|
return utils.exec(main_cmd)
|
||||||
.then(async (result) => {
|
.then(async (result) => {
|
||||||
logger.info(result);
|
logger.info(result);
|
||||||
return result;
|
return result;
|
||||||
@ -914,8 +850,8 @@ const internalCertificate = {
|
|||||||
});
|
});
|
||||||
}).catch(async (err) => {
|
}).catch(async (err) => {
|
||||||
// Don't fail if file does not exist
|
// Don't fail if file does not exist
|
||||||
const delete_credentialsCmd = `rm -f '${credentialsLocation}' || true`;
|
const delete_credentials_cmd = `rm -f '${credentials_loc}' || true`;
|
||||||
await utils.exec(delete_credentialsCmd);
|
await utils.exec(delete_credentials_cmd);
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -934,7 +870,7 @@ const internalCertificate = {
|
|||||||
})
|
})
|
||||||
.then((certificate) => {
|
.then((certificate) => {
|
||||||
if (certificate.provider === 'letsencrypt') {
|
if (certificate.provider === 'letsencrypt') {
|
||||||
const renewMethod = certificate.meta.dns_challenge ? internalCertificate.renewLetsEncryptSslWithDnsChallenge : internalCertificate.renewLetsEncryptSsl;
|
let renewMethod = certificate.meta.dns_challenge ? internalCertificate.renewLetsEncryptSslWithDnsChallenge : internalCertificate.renewLetsEncryptSsl;
|
||||||
|
|
||||||
return renewMethod(certificate)
|
return renewMethod(certificate)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@ -972,15 +908,16 @@ 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 ' +
|
let cmd = certbot_command + ' renew --non-interactive ' +
|
||||||
'--config "' + letsencryptConfig + '" ' +
|
'--config "' + le_config + '" ' +
|
||||||
'--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' : '');
|
(le_staging ? '--staging' : '');
|
||||||
|
|
||||||
logger.info('Command:', cmd);
|
if (debug_mode) {
|
||||||
|
logger.info('Command:', cmd);
|
||||||
|
}
|
||||||
|
|
||||||
return utils.exec(cmd)
|
return utils.exec(cmd)
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
@ -994,7 +931,7 @@ const internalCertificate = {
|
|||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
renewLetsEncryptSslWithDnsChallenge: (certificate) => {
|
renewLetsEncryptSslWithDnsChallenge: (certificate) => {
|
||||||
const dns_plugin = dnsPlugins[certificate.meta.dns_provider];
|
const dns_plugin = dns_plugins[certificate.meta.dns_provider];
|
||||||
|
|
||||||
if (!dns_plugin) {
|
if (!dns_plugin) {
|
||||||
throw Error(`Unknown DNS provider '${certificate.meta.dns_provider}'`);
|
throw Error(`Unknown DNS provider '${certificate.meta.dns_provider}'`);
|
||||||
@ -1002,22 +939,23 @@ 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 main_cmd =
|
||||||
'--config "' + letsencryptConfig + '" ' +
|
certbot_command + ' renew --non-interactive ' +
|
||||||
'--cert-name "npm-' + certificate.id + '" ' +
|
'--cert-name "npm-' + certificate.id + '" ' +
|
||||||
'--disable-hook-validation ' +
|
'--disable-hook-validation' +
|
||||||
'--no-random-sleep-on-renew ' +
|
(le_staging ? ' --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
|
||||||
if (certificate.meta.dns_provider === 'route53') {
|
if (certificate.meta.dns_provider === 'route53') {
|
||||||
const credentialsLocation = '/etc/letsencrypt/credentials/credentials-' + certificate.id;
|
const credentials_loc = '/etc/letsencrypt/credentials/credentials-' + certificate.id;
|
||||||
mainCmd = 'AWS_CONFIG_FILE=\'' + credentialsLocation + '\' ' + mainCmd;
|
main_cmd = 'AWS_CONFIG_FILE=\'' + credentials_loc + '\' ' + main_cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info('Command:', mainCmd);
|
if (debug_mode) {
|
||||||
|
logger.info('Command:', main_cmd);
|
||||||
|
}
|
||||||
|
|
||||||
return utils.exec(mainCmd)
|
return utils.exec(main_cmd)
|
||||||
.then(async (result) => {
|
.then(async (result) => {
|
||||||
logger.info(result);
|
logger.info(result);
|
||||||
return result;
|
return result;
|
||||||
@ -1032,25 +970,28 @@ 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 main_cmd = certbot_command + ' 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' : '');
|
(le_staging ? '--staging' : '');
|
||||||
|
|
||||||
// Don't fail command if file does not exist
|
// Don't fail command if file does not exist
|
||||||
const delete_credentialsCmd = `rm -f '/etc/letsencrypt/credentials/credentials-${certificate.id}' || true`;
|
const delete_credentials_cmd = `rm -f '/etc/letsencrypt/credentials/credentials-${certificate.id}' || true`;
|
||||||
|
|
||||||
logger.info('Command:', mainCmd + '; ' + delete_credentialsCmd);
|
if (debug_mode) {
|
||||||
|
logger.info('Command:', main_cmd + '; ' + delete_credentials_cmd);
|
||||||
|
}
|
||||||
|
|
||||||
return utils.exec(mainCmd)
|
return utils.exec(main_cmd)
|
||||||
.then(async (result) => {
|
.then(async (result) => {
|
||||||
await utils.exec(delete_credentialsCmd);
|
await utils.exec(delete_credentials_cmd);
|
||||||
logger.info(result);
|
logger.info(result);
|
||||||
return result;
|
return result;
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
logger.error(err.message);
|
if (debug_mode) {
|
||||||
|
logger.error(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
if (throw_errors) {
|
if (throw_errors) {
|
||||||
throw err;
|
throw err;
|
||||||
@ -1063,9 +1004,9 @@ const internalCertificate = {
|
|||||||
* @returns {Boolean}
|
* @returns {Boolean}
|
||||||
*/
|
*/
|
||||||
hasLetsEncryptSslCerts: (certificate) => {
|
hasLetsEncryptSslCerts: (certificate) => {
|
||||||
const letsencryptPath = '/etc/letsencrypt/live/npm-' + certificate.id;
|
let le_path = '/etc/letsencrypt/live/npm-' + certificate.id;
|
||||||
|
|
||||||
return fs.existsSync(letsencryptPath + '/fullchain.pem') && fs.existsSync(letsencryptPath + '/privkey.pem');
|
return fs.existsSync(le_path + '/fullchain.pem') && fs.existsSync(le_path + '/privkey.pem');
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1126,94 +1067,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';
|
||||||
|
@ -3,6 +3,7 @@ const fs = require('fs');
|
|||||||
const logger = require('../logger').nginx;
|
const logger = require('../logger').nginx;
|
||||||
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 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);
|
||||||
@ -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);
|
||||||
});
|
});
|
||||||
@ -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';
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -147,16 +146,12 @@ const internalNginx = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderEngine = utils.getRenderEngine();
|
let renderer = new Liquid();
|
||||||
let renderedLocations = '';
|
let renderedLocations = '';
|
||||||
|
|
||||||
const locationRendering = async () => {
|
const locationRendering = async () => {
|
||||||
for (let i = 0; i < host.locations.length; i++) {
|
for (let i = 0; i < host.locations.length; i++) {
|
||||||
let locationCopy = Object.assign({}, {access_list_id: host.access_list_id}, {certificate_id: host.certificate_id},
|
let locationCopy = Object.assign({}, host.locations[i]);
|
||||||
{ssl_forced: host.ssl_forced}, {caching_enabled: host.caching_enabled}, {block_exploits: host.block_exploits},
|
|
||||||
{allow_websocket_upgrade: host.allow_websocket_upgrade}, {http2_support: host.http2_support},
|
|
||||||
{hsts_enabled: host.hsts_enabled}, {hsts_subdomains: host.hsts_subdomains}, {access_list: host.access_list},
|
|
||||||
{certificate: host.certificate}, host.locations[i]);
|
|
||||||
|
|
||||||
if (locationCopy.forward_host.indexOf('/') > -1) {
|
if (locationCopy.forward_host.indexOf('/') > -1) {
|
||||||
const splitted = locationCopy.forward_host.split('/');
|
const splitted = locationCopy.forward_host.split('/');
|
||||||
@ -166,13 +161,11 @@ const internalNginx = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
renderedLocations += await renderEngine.parseAndRender(template, locationCopy);
|
renderedLocations += await renderer.parseAndRender(template, locationCopy);
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
locationRendering().then(() => resolve(renderedLocations));
|
locationRendering().then(() => resolve(renderedLocations));
|
||||||
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -182,20 +175,22 @@ 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 (debug_mode) {
|
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();
|
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 +200,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);
|
||||||
@ -213,7 +208,6 @@ const internalNginx = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (host.locations) {
|
if (host.locations) {
|
||||||
//logger.info ('host.locations = ' + JSON.stringify(host.locations, null, 2));
|
|
||||||
origLocations = [].concat(host.locations);
|
origLocations = [].concat(host.locations);
|
||||||
locationsPromise = internalNginx.renderLocations(host).then((renderedLocations) => {
|
locationsPromise = internalNginx.renderLocations(host).then((renderedLocations) => {
|
||||||
host.locations = renderedLocations;
|
host.locations = renderedLocations;
|
||||||
@ -272,7 +266,9 @@ const internalNginx = {
|
|||||||
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;
|
||||||
@ -308,58 +304,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*/) => {
|
fs.unlinkSync(config_file);
|
||||||
internalNginx.deleteFile(config_file);
|
} catch (err) {
|
||||||
if (delete_err_file) {
|
if (debug_mode) {
|
||||||
internalNginx.deleteFile(config_file_err);
|
logger.warn('Could not delete config:', err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (throw_errors) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -367,20 +338,32 @@ 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*/) => {
|
return new Promise((resolve, reject) => {
|
||||||
fs.unlink(config_file, () => {
|
try {
|
||||||
// ignore result, continue
|
let config_file = internalNginx.getConfigName(host_type, typeof host === 'undefined' ? 0 : host.id);
|
||||||
fs.rename(config_file, config_file_err, () => {
|
|
||||||
// also ignore result, as this is a debugging informative file anyway
|
if (debug_mode) {
|
||||||
resolve();
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -401,12 +384,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);
|
||||||
|
@ -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,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;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -69,6 +69,9 @@ exports.up = function (knex/*, Promise*/) {
|
|||||||
table.json('domain_names').notNull();
|
table.json('domain_names').notNull();
|
||||||
table.string('forward_ip').notNull();
|
table.string('forward_ip').notNull();
|
||||||
table.integer('forward_port').notNull().unsigned();
|
table.integer('forward_port').notNull().unsigned();
|
||||||
|
table.string('root_dir').notNull();
|
||||||
|
table.string('index_file').notNull();
|
||||||
|
table.integer('static').notNull().unsigned().defaultTo(0);
|
||||||
table.integer('access_list_id').notNull().unsigned().defaultTo(0);
|
table.integer('access_list_id').notNull().unsigned().defaultTo(0);
|
||||||
table.integer('certificate_id').notNull().unsigned().defaultTo(0);
|
table.integer('certificate_id').notNull().unsigned().defaultTo(0);
|
||||||
table.integer('ssl_forced').notNull().unsigned().defaultTo(0);
|
table.integer('ssl_forced').notNull().unsigned().defaultTo(0);
|
||||||
|
@ -1,40 +0,0 @@
|
|||||||
const migrate_name = 'stream_domain';
|
|
||||||
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('stream', (table) => {
|
|
||||||
table.renameColumn('forward_ip', 'forwarding_host');
|
|
||||||
})
|
|
||||||
.then(function () {
|
|
||||||
logger.info('[' + migrate_name + '] stream Table altered');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Undo Migrate
|
|
||||||
*
|
|
||||||
* @param {Object} knex
|
|
||||||
* @param {Promise} Promise
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
exports.down = function (knex/*, Promise*/) {
|
|
||||||
logger.info('[' + migrate_name + '] Migrating Down...');
|
|
||||||
|
|
||||||
return knex.schema.table('stream', (table) => {
|
|
||||||
table.renameColumn('forwarding_host', 'forward_ip');
|
|
||||||
})
|
|
||||||
.then(function () {
|
|
||||||
logger.info('[' + migrate_name + '] stream 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']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -6,8 +6,8 @@ Model.knex(db);
|
|||||||
|
|
||||||
module.exports = function () {
|
module.exports = function () {
|
||||||
if (config.database.knex && config.database.knex.client === 'sqlite3') {
|
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()');
|
|
||||||
};
|
};
|
||||||
|
@ -60,6 +60,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 +72,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 +84,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']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -83,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'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -5,29 +5,33 @@
|
|||||||
"main": "js/index.js",
|
"main": "js/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ajv": "^6.12.0",
|
"ajv": "^6.12.0",
|
||||||
"archiver": "^5.3.0",
|
|
||||||
"batchflow": "^0.4.0",
|
"batchflow": "^0.4.0",
|
||||||
"bcrypt": "^5.0.0",
|
"bcrypt": "^5.0.0",
|
||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.0",
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"config": "^3.3.1",
|
"config": "^3.3.1",
|
||||||
"express": "^4.17.3",
|
"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.19",
|
||||||
"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",
|
||||||
"nodemon": "^2.0.2",
|
"nodemon": "^2.0.2",
|
||||||
"objection": "3.0.1",
|
"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,
|
||||||
|
@ -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,34 +209,6 @@ router
|
|||||||
.catch(next);
|
.catch(next);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Download LE Certs
|
|
||||||
*
|
|
||||||
* /api/nginx/certificates/123/download
|
|
||||||
*/
|
|
||||||
router
|
|
||||||
.route('/:certificate_id/download')
|
|
||||||
.options((req, res) => {
|
|
||||||
res.sendStatus(204);
|
|
||||||
})
|
|
||||||
.all(jwtdecode())
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GET /api/nginx/certificates/123/download
|
|
||||||
*
|
|
||||||
* Renew certificate
|
|
||||||
*/
|
|
||||||
.get((req, res, next) => {
|
|
||||||
internalCertificate.download(res.locals.access, {
|
|
||||||
id: parseInt(req.params.certificate_id, 10)
|
|
||||||
})
|
|
||||||
.then((result) => {
|
|
||||||
res.status(200)
|
|
||||||
.download(result.fileName);
|
|
||||||
})
|
|
||||||
.catch(next);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate Certs before saving
|
* Validate Certs before saving
|
||||||
*
|
*
|
||||||
|
@ -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,11 @@
|
|||||||
"description": "Should we cache assets",
|
"description": "Should we cache assets",
|
||||||
"example": true,
|
"example": true,
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"static": {
|
||||||
|
"description": "Should the proxy point to static files",
|
||||||
|
"example": true,
|
||||||
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -24,14 +24,22 @@
|
|||||||
},
|
},
|
||||||
"forward_host": {
|
"forward_host": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"minLength": 1,
|
"minLength": 0,
|
||||||
"maxLength": 255
|
"maxLength": 255
|
||||||
},
|
},
|
||||||
"forward_port": {
|
"forward_port": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"minimum": 1,
|
"minimum": 0,
|
||||||
"maximum": 65535
|
"maximum": 65535
|
||||||
},
|
},
|
||||||
|
"root_dir": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 0,
|
||||||
|
},
|
||||||
|
"index_file": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 0,
|
||||||
|
},
|
||||||
"certificate_id": {
|
"certificate_id": {
|
||||||
"$ref": "../definitions.json#/definitions/certificate_id"
|
"$ref": "../definitions.json#/definitions/certificate_id"
|
||||||
},
|
},
|
||||||
@ -53,6 +61,9 @@
|
|||||||
"caching_enabled": {
|
"caching_enabled": {
|
||||||
"$ref": "../definitions.json#/definitions/caching_enabled"
|
"$ref": "../definitions.json#/definitions/caching_enabled"
|
||||||
},
|
},
|
||||||
|
"static": {
|
||||||
|
"$ref": "../definitions.json#/definitions/static"
|
||||||
|
},
|
||||||
"allow_websocket_upgrade": {
|
"allow_websocket_upgrade": {
|
||||||
"description": "Allow Websocket Upgrade for all paths",
|
"description": "Allow Websocket Upgrade for all paths",
|
||||||
"example": true,
|
"example": true,
|
||||||
@ -76,10 +87,7 @@
|
|||||||
"items": {
|
"items": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"forward_scheme",
|
"forward_scheme"
|
||||||
"forward_host",
|
|
||||||
"forward_port",
|
|
||||||
"path"
|
|
||||||
],
|
],
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -99,6 +107,15 @@
|
|||||||
"forward_port": {
|
"forward_port": {
|
||||||
"$ref": "#/definitions/forward_port"
|
"$ref": "#/definitions/forward_port"
|
||||||
},
|
},
|
||||||
|
"root_dir": {
|
||||||
|
"$ref": "#/definitions/root_dir"
|
||||||
|
},
|
||||||
|
"index_file": {
|
||||||
|
"$ref": "#/definitions/index_file"
|
||||||
|
},
|
||||||
|
"static": {
|
||||||
|
"$ref": "#/definitions/static"
|
||||||
|
},
|
||||||
"forward_path": {
|
"forward_path": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@ -131,6 +148,12 @@
|
|||||||
"forward_port": {
|
"forward_port": {
|
||||||
"$ref": "#/definitions/forward_port"
|
"$ref": "#/definitions/forward_port"
|
||||||
},
|
},
|
||||||
|
"root_dir": {
|
||||||
|
"$ref": "#/definitions/root_dir"
|
||||||
|
},
|
||||||
|
"index_file": {
|
||||||
|
"$ref": "#/definitions/index_file"
|
||||||
|
},
|
||||||
"certificate_id": {
|
"certificate_id": {
|
||||||
"$ref": "#/definitions/certificate_id"
|
"$ref": "#/definitions/certificate_id"
|
||||||
},
|
},
|
||||||
@ -152,6 +175,9 @@
|
|||||||
"caching_enabled": {
|
"caching_enabled": {
|
||||||
"$ref": "#/definitions/caching_enabled"
|
"$ref": "#/definitions/caching_enabled"
|
||||||
},
|
},
|
||||||
|
"static": {
|
||||||
|
"$ref": "#/definitions/static"
|
||||||
|
},
|
||||||
"allow_websocket_upgrade": {
|
"allow_websocket_upgrade": {
|
||||||
"$ref": "#/definitions/allow_websocket_upgrade"
|
"$ref": "#/definitions/allow_websocket_upgrade"
|
||||||
},
|
},
|
||||||
@ -204,9 +230,7 @@
|
|||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"required": [
|
"required": [
|
||||||
"domain_names",
|
"domain_names",
|
||||||
"forward_scheme",
|
"forward_scheme"
|
||||||
"forward_host",
|
|
||||||
"forward_port"
|
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"domain_names": {
|
"domain_names": {
|
||||||
@ -221,6 +245,12 @@
|
|||||||
"forward_port": {
|
"forward_port": {
|
||||||
"$ref": "#/definitions/forward_port"
|
"$ref": "#/definitions/forward_port"
|
||||||
},
|
},
|
||||||
|
"root_dir": {
|
||||||
|
"$ref": "#/definitions/root_dir"
|
||||||
|
},
|
||||||
|
"index_file": {
|
||||||
|
"$ref": "#/definitions/index_file"
|
||||||
|
},
|
||||||
"certificate_id": {
|
"certificate_id": {
|
||||||
"$ref": "#/definitions/certificate_id"
|
"$ref": "#/definitions/certificate_id"
|
||||||
},
|
},
|
||||||
@ -242,6 +272,9 @@
|
|||||||
"caching_enabled": {
|
"caching_enabled": {
|
||||||
"$ref": "#/definitions/caching_enabled"
|
"$ref": "#/definitions/caching_enabled"
|
||||||
},
|
},
|
||||||
|
"static": {
|
||||||
|
"$ref": "#/definitions/static"
|
||||||
|
},
|
||||||
"allow_websocket_upgrade": {
|
"allow_websocket_upgrade": {
|
||||||
"$ref": "#/definitions/allow_websocket_upgrade"
|
"$ref": "#/definitions/allow_websocket_upgrade"
|
||||||
},
|
},
|
||||||
@ -294,6 +327,12 @@
|
|||||||
"forward_port": {
|
"forward_port": {
|
||||||
"$ref": "#/definitions/forward_port"
|
"$ref": "#/definitions/forward_port"
|
||||||
},
|
},
|
||||||
|
"root_dir": {
|
||||||
|
"$ref": "#/definitions/root_dir"
|
||||||
|
},
|
||||||
|
"index_file": {
|
||||||
|
"$ref": "#/definitions/index_file"
|
||||||
|
},
|
||||||
"certificate_id": {
|
"certificate_id": {
|
||||||
"$ref": "#/definitions/certificate_id"
|
"$ref": "#/definitions/certificate_id"
|
||||||
},
|
},
|
||||||
@ -315,6 +354,9 @@
|
|||||||
"caching_enabled": {
|
"caching_enabled": {
|
||||||
"$ref": "#/definitions/caching_enabled"
|
"$ref": "#/definitions/caching_enabled"
|
||||||
},
|
},
|
||||||
|
"static": {
|
||||||
|
"$ref": "#/definitions/static"
|
||||||
|
},
|
||||||
"allow_websocket_upgrade": {
|
"allow_websocket_upgrade": {
|
||||||
"$ref": "#/definitions/allow_websocket_upgrade"
|
"$ref": "#/definitions/allow_websocket_upgrade"
|
||||||
},
|
},
|
||||||
|
@ -20,20 +20,9 @@
|
|||||||
"minimum": 1,
|
"minimum": 1,
|
||||||
"maximum": 65535
|
"maximum": 65535
|
||||||
},
|
},
|
||||||
"forwarding_host": {
|
"forward_ip": {
|
||||||
"anyOf": [
|
"type": "string",
|
||||||
{
|
"format": "ipv4"
|
||||||
"$ref": "../definitions.json#/definitions/domain_name"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"format": "ipv4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"format": "ipv6"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"forwarding_port": {
|
"forwarding_port": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
@ -66,8 +55,8 @@
|
|||||||
"incoming_port": {
|
"incoming_port": {
|
||||||
"$ref": "#/definitions/incoming_port"
|
"$ref": "#/definitions/incoming_port"
|
||||||
},
|
},
|
||||||
"forwarding_host": {
|
"forward_ip": {
|
||||||
"$ref": "#/definitions/forwarding_host"
|
"$ref": "#/definitions/forward_ip"
|
||||||
},
|
},
|
||||||
"forwarding_port": {
|
"forwarding_port": {
|
||||||
"$ref": "#/definitions/forwarding_port"
|
"$ref": "#/definitions/forwarding_port"
|
||||||
@ -118,15 +107,15 @@
|
|||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"required": [
|
"required": [
|
||||||
"incoming_port",
|
"incoming_port",
|
||||||
"forwarding_host",
|
"forward_ip",
|
||||||
"forwarding_port"
|
"forwarding_port"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"incoming_port": {
|
"incoming_port": {
|
||||||
"$ref": "#/definitions/incoming_port"
|
"$ref": "#/definitions/incoming_port"
|
||||||
},
|
},
|
||||||
"forwarding_host": {
|
"forward_ip": {
|
||||||
"$ref": "#/definitions/forwarding_host"
|
"$ref": "#/definitions/forward_ip"
|
||||||
},
|
},
|
||||||
"forwarding_port": {
|
"forwarding_port": {
|
||||||
"$ref": "#/definitions/forwarding_port"
|
"$ref": "#/definitions/forwarding_port"
|
||||||
@ -165,8 +154,8 @@
|
|||||||
"incoming_port": {
|
"incoming_port": {
|
||||||
"$ref": "#/definitions/incoming_port"
|
"$ref": "#/definitions/incoming_port"
|
||||||
},
|
},
|
||||||
"forwarding_host": {
|
"forward_ip": {
|
||||||
"$ref": "#/definitions/forwarding_host"
|
"$ref": "#/definitions/forward_ip"
|
||||||
},
|
},
|
||||||
"forwarding_port": {
|
"forwarding_port": {
|
||||||
"$ref": "#/definitions/forwarding_port"
|
"$ref": "#/definitions/forwarding_port"
|
||||||
|
@ -174,22 +174,20 @@ 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 ' + plugins.join(' ') + ' && deactivate';
|
const install_cmd = 'pip3 install ' + plugins.join(' ');
|
||||||
promises.push(utils.exec(install_cmd));
|
promises.push(utils.exec(install_cmd));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,31 +201,9 @@ const setupCertbotPlugins = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts a timer to call run the logrotation binary every two days
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
const setupLogrotation = () => {
|
|
||||||
const intervalTimeout = 1000 * 60 * 60 * 24 * 2; // 2 days
|
|
||||||
|
|
||||||
const runLogrotate = async () => {
|
|
||||||
try {
|
|
||||||
await utils.exec('logrotate /etc/logrotate.d/nginx-proxy-manager');
|
|
||||||
logger.info('Logrotate completed.');
|
|
||||||
} catch (e) { logger.warn(e); }
|
|
||||||
};
|
|
||||||
|
|
||||||
logger.info('Logrotate Timer initialized');
|
|
||||||
setInterval(runLogrotate, intervalTimeout);
|
|
||||||
// And do this now as well
|
|
||||||
return runLogrotate();
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = function () {
|
module.exports = function () {
|
||||||
return setupJwt()
|
return setupJwt()
|
||||||
.then(setupDefaultUser)
|
.then(setupDefaultUser)
|
||||||
.then(setupDefaultSettings)
|
.then(setupDefaultSettings)
|
||||||
.then(setupCertbotPlugins)
|
.then(setupCertbotPlugins);
|
||||||
.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 %}
|
|
@ -7,9 +7,14 @@
|
|||||||
{% if certificate -%}
|
{% if certificate -%}
|
||||||
listen 443 ssl{% if http2_support %} http2{% endif %};
|
listen 443 ssl{% if http2_support %} http2{% endif %};
|
||||||
{% if ipv6 -%}
|
{% if ipv6 -%}
|
||||||
listen [::]:443 ssl{% if http2_support %} http2{% endif %};
|
listen [::]:443;
|
||||||
{% else -%}
|
{% else -%}
|
||||||
#listen [::]:443;
|
#listen [::]:443;
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
server_name {{ domain_names | join: " " }};
|
server_name {{ domain_names | join: " " }};
|
||||||
|
{% if static == 1 or static == true %}
|
||||||
|
root {{ root_dir }};
|
||||||
|
index {{ index_file }};
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
@ -1,23 +1,15 @@
|
|||||||
location {{ path }} {
|
location {{ path }} {
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Forwarded-Scheme $scheme;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header X-Forwarded-For $remote_addr;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_pass {{ forward_scheme }}://{{ forward_host }}:{{ forward_port }}{{ forward_path }};
|
|
||||||
|
|
||||||
{% include "_access.conf" %}
|
|
||||||
{% include "_assets.conf" %}
|
|
||||||
{% include "_exploits.conf" %}
|
|
||||||
{% include "_forced_ssl.conf" %}
|
|
||||||
{% include "_hsts.conf" %}
|
|
||||||
|
|
||||||
{% if allow_websocket_upgrade == 1 or allow_websocket_upgrade == true %}
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection $http_connection;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
|
{% if static == 0 or static == false %}
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-Scheme $scheme;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-For $remote_addr;
|
||||||
|
proxy_pass {{ forward_scheme }}://{{ forward_host }}:{{ forward_port }}{{ forward_path }};
|
||||||
|
{% else %}
|
||||||
|
alias {{ root_dir }}/$1;
|
||||||
|
try_files $uri /{{ index_file }} =200;
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{{ advanced_config }}
|
{{ advanced_config }}
|
||||||
}
|
}
|
||||||
|
@ -5,15 +5,14 @@ server {
|
|||||||
{% include "_listen.conf" %}
|
{% include "_listen.conf" %}
|
||||||
{% include "_certificates.conf" %}
|
{% include "_certificates.conf" %}
|
||||||
{% include "_hsts.conf" %}
|
{% include "_hsts.conf" %}
|
||||||
{% include "_forced_ssl.conf" %}
|
|
||||||
|
|
||||||
access_log /data/logs/dead-host-{{ id }}_access.log standard;
|
access_log /data/logs/dead_host-{{ id }}.log standard;
|
||||||
error_log /data/logs/dead-host-{{ id }}_error.log warn;
|
|
||||||
|
|
||||||
{{ advanced_config }}
|
{{ advanced_config }}
|
||||||
|
|
||||||
{% if use_default_location %}
|
{% if use_default_location %}
|
||||||
location / {
|
location / {
|
||||||
|
{% include "_forced_ssl.conf" %}
|
||||||
{% include "_hsts.conf" %}
|
{% include "_hsts.conf" %}
|
||||||
return 404;
|
return 404;
|
||||||
}
|
}
|
||||||
|
@ -7,17 +7,14 @@
|
|||||||
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.log combined;
|
||||||
error_log /data/logs/default-host_error.log warn;
|
|
||||||
{% include "_exploits.conf" %}
|
{% include "_exploits.conf" %}
|
||||||
|
|
||||||
include conf.d/include/letsencrypt-acme-challenge.conf;
|
|
||||||
|
|
||||||
{%- if value == "404" %}
|
{%- if value == "404" %}
|
||||||
location / {
|
location / {
|
||||||
return 404;
|
return 404;
|
||||||
@ -32,6 +29,7 @@ server {
|
|||||||
|
|
||||||
{%- if value == "html" %}
|
{%- if value == "html" %}
|
||||||
root /data/nginx/default_www;
|
root /data/nginx/default_www;
|
||||||
|
# root /var/www/test2;
|
||||||
location / {
|
location / {
|
||||||
try_files $uri /index.html;
|
try_files $uri /index.html;
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,7 @@ server {
|
|||||||
|
|
||||||
server_name {{ domain_names | join: " " }};
|
server_name {{ domain_names | join: " " }};
|
||||||
|
|
||||||
access_log /data/logs/letsencrypt-requests_access.log standard;
|
access_log /data/logs/letsencrypt-requests.log standard;
|
||||||
error_log /data/logs/letsencrypt-requests_error.log warn;
|
|
||||||
|
|
||||||
include conf.d/include/letsencrypt-acme-challenge.conf;
|
include conf.d/include/letsencrypt-acme-challenge.conf;
|
||||||
|
|
||||||
|
@ -11,16 +11,8 @@ server {
|
|||||||
{% include "_assets.conf" %}
|
{% include "_assets.conf" %}
|
||||||
{% include "_exploits.conf" %}
|
{% include "_exploits.conf" %}
|
||||||
{% include "_hsts.conf" %}
|
{% include "_hsts.conf" %}
|
||||||
{% include "_forced_ssl.conf" %}
|
|
||||||
|
|
||||||
{% if allow_websocket_upgrade == 1 or allow_websocket_upgrade == true %}
|
access_log /data/logs/proxy_host-{{ id }}.log proxy;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection $http_connection;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
access_log /data/logs/proxy-host-{{ id }}_access.log proxy;
|
|
||||||
error_log /data/logs/proxy-host-{{ id }}_error.log warn;
|
|
||||||
|
|
||||||
{{ advanced_config }}
|
{{ advanced_config }}
|
||||||
|
|
||||||
@ -30,7 +22,28 @@ proxy_http_version 1.1;
|
|||||||
|
|
||||||
location / {
|
location / {
|
||||||
|
|
||||||
{% include "_access.conf" %}
|
{% 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 "_forced_ssl.conf" %}
|
||||||
{% include "_hsts.conf" %}
|
{% include "_hsts.conf" %}
|
||||||
|
|
||||||
{% if allow_websocket_upgrade == 1 or allow_websocket_upgrade == true %}
|
{% if allow_websocket_upgrade == 1 or allow_websocket_upgrade == true %}
|
||||||
@ -39,8 +52,14 @@ proxy_http_version 1.1;
|
|||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
# Proxy!
|
{% if static == 1 or static == true %}
|
||||||
include conf.d/include/proxy.conf;
|
alias {{ root_dir }}/$1;
|
||||||
|
try_files $uri /{{index_file}} =200;
|
||||||
|
{% else %}
|
||||||
|
# Proxy!
|
||||||
|
include conf.d/include/proxy.conf;
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
}
|
}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
@ -7,15 +7,14 @@ server {
|
|||||||
{% include "_assets.conf" %}
|
{% include "_assets.conf" %}
|
||||||
{% include "_exploits.conf" %}
|
{% include "_exploits.conf" %}
|
||||||
{% include "_hsts.conf" %}
|
{% include "_hsts.conf" %}
|
||||||
{% include "_forced_ssl.conf" %}
|
|
||||||
|
|
||||||
access_log /data/logs/redirection-host-{{ id }}_access.log standard;
|
access_log /data/logs/redirection_host-{{ id }}.log standard;
|
||||||
error_log /data/logs/redirection-host-{{ id }}_error.log warn;
|
|
||||||
|
|
||||||
{{ advanced_config }}
|
{{ advanced_config }}
|
||||||
|
|
||||||
{% if use_default_location %}
|
{% if use_default_location %}
|
||||||
location / {
|
location / {
|
||||||
|
{% include "_forced_ssl.conf" %}
|
||||||
{% include "_hsts.conf" %}
|
{% include "_hsts.conf" %}
|
||||||
|
|
||||||
{% if preserve_path == 1 or preserve_path == true %}
|
{% if preserve_path == 1 or preserve_path == true %}
|
||||||
|
@ -12,7 +12,7 @@ server {
|
|||||||
#listen [::]:{{ incoming_port }};
|
#listen [::]:{{ incoming_port }};
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
proxy_pass {{ forwarding_host }}:{{ forwarding_port }};
|
proxy_pass {{ forward_ip }}:{{ forwarding_port }};
|
||||||
|
|
||||||
# Custom
|
# Custom
|
||||||
include /data/nginx/custom/server_stream[.]conf;
|
include /data/nginx/custom/server_stream[.]conf;
|
||||||
@ -27,7 +27,7 @@ server {
|
|||||||
{% else -%}
|
{% else -%}
|
||||||
#listen [::]:{{ incoming_port }} udp;
|
#listen [::]:{{ incoming_port }} udp;
|
||||||
{% endif %}
|
{% endif %}
|
||||||
proxy_pass {{ forwarding_host }}:{{ forwarding_port }};
|
proxy_pass {{ forward_ip }}:{{ forwarding_port }};
|
||||||
|
|
||||||
# Custom
|
# Custom
|
||||||
include /data/nginx/custom/server_stream[.]conf;
|
include /data/nginx/custom/server_stream[.]conf;
|
||||||
|
2297
backend/yarn.lock
2297
backend/yarn.lock
File diff suppressed because it is too large
Load Diff
@ -1,14 +0,0 @@
|
|||||||
rules:
|
|
||||||
# If the efficiency is measured below X%, mark as failed.
|
|
||||||
# Expressed as a ratio between 0-1.
|
|
||||||
lowestEfficiency: 0.99
|
|
||||||
|
|
||||||
# If the amount of wasted space is at least X or larger than X, mark as failed.
|
|
||||||
# Expressed in B, KB, MB, and GB.
|
|
||||||
highestWastedBytes: 15MB
|
|
||||||
|
|
||||||
# If the amount of wasted space makes up for X% or more of the image, mark as failed.
|
|
||||||
# Note: the base image layer is NOT included in the total image size.
|
|
||||||
# Expressed as a ratio between 0-1; fails if the threshold is met or crossed.
|
|
||||||
highestUserWastedPercent: 0.02
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
# 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 jc21/nginx-full:node
|
||||||
|
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
ARG BUILD_VERSION
|
ARG BUILD_VERSION
|
||||||
@ -20,12 +20,12 @@ ENV SUPPRESS_NO_CONFIG_WARNING=1 \
|
|||||||
|
|
||||||
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 --no-install-recommends jq logrotate \
|
&& apt-get install -y certbot jq python3-pip \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
&& 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
|
||||||
@ -41,18 +41,11 @@ RUN yarn install
|
|||||||
COPY docker/rootfs /
|
COPY docker/rootfs /
|
||||||
|
|
||||||
# Remove frontend service not required for prod, dev nginx config as well
|
# Remove frontend service not required for prod, dev nginx config as well
|
||||||
RUN rm -rf /etc/services.d/frontend /etc/nginx/conf.d/dev.conf
|
RUN rm -rf /etc/services.d/frontend RUN rm -f /etc/nginx/conf.d/dev.conf
|
||||||
|
|
||||||
# Change permission of logrotate config file
|
|
||||||
RUN chmod 644 /etc/logrotate.d/nginx-proxy-manager
|
|
||||||
|
|
||||||
# fix for pip installs
|
|
||||||
# https://github.com/NginxProxyManager/nginx-proxy-manager/issues/1769
|
|
||||||
RUN pip uninstall --yes setuptools \
|
|
||||||
&& pip install "setuptools==58.0.0"
|
|
||||||
|
|
||||||
VOLUME [ "/data", "/etc/letsencrypt" ]
|
VOLUME [ "/data", "/etc/letsencrypt" ]
|
||||||
ENTRYPOINT [ "/init" ]
|
ENTRYPOINT [ "/init" ]
|
||||||
|
HEALTHCHECK --interval=5s --timeout=3s CMD /bin/check-health
|
||||||
|
|
||||||
LABEL org.label-schema.schema-version="1.0" \
|
LABEL org.label-schema.schema-version="1.0" \
|
||||||
org.label-schema.license="MIT" \
|
org.label-schema.license="MIT" \
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM jc21/nginx-full:certbot-node
|
FROM jc21/nginx-full:node
|
||||||
LABEL maintainer="Jamie Curnow <jc@jc21.com>"
|
LABEL maintainer="Jamie Curnow <jc@jc21.com>"
|
||||||
|
|
||||||
ENV S6_LOGGING=0 \
|
ENV S6_LOGGING=0 \
|
||||||
@ -7,7 +7,7 @@ ENV S6_LOGGING=0 \
|
|||||||
|
|
||||||
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 \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
@ -18,11 +18,11 @@ RUN cd /usr \
|
|||||||
|
|
||||||
COPY rootfs /
|
COPY rootfs /
|
||||||
RUN rm -f /etc/nginx/conf.d/production.conf
|
RUN rm -f /etc/nginx/conf.d/production.conf
|
||||||
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" ]
|
||||||
|
HEALTHCHECK --interval=5s --timeout=3s CMD /bin/check-health
|
||||||
|
@ -20,10 +20,6 @@ services:
|
|||||||
- 443
|
- 443
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "/bin/check-health"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 3s
|
|
||||||
|
|
||||||
fullstack-sqlite:
|
fullstack-sqlite:
|
||||||
image: ${IMAGE}:ci-${BUILD_NUMBER}
|
image: ${IMAGE}:ci-${BUILD_NUMBER}
|
||||||
@ -37,10 +33,6 @@ services:
|
|||||||
- 81
|
- 81
|
||||||
- 80
|
- 80
|
||||||
- 443
|
- 443
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "/bin/check-health"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 3s
|
|
||||||
|
|
||||||
db:
|
db:
|
||||||
image: jc21/mariadb-aria
|
image: jc21/mariadb-aria
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
# WARNING: This is a DEVELOPMENT docker-compose file, it should not be used for production.
|
# WARNING: This is a DEVELOPMENT docker-compose file, it should not be used for production.
|
||||||
version: "3.5"
|
version: "3"
|
||||||
services:
|
services:
|
||||||
|
|
||||||
npm:
|
npm:
|
||||||
image: nginxproxymanager:dev
|
image: nginxproxymanager:dev
|
||||||
container_name: npm_core
|
|
||||||
build:
|
build:
|
||||||
context: ./
|
context: ./
|
||||||
dockerfile: ./dev/Dockerfile
|
dockerfile: ./dev/Dockerfile
|
||||||
@ -36,9 +36,6 @@ services:
|
|||||||
|
|
||||||
db:
|
db:
|
||||||
image: jc21/mariadb-aria
|
image: jc21/mariadb-aria
|
||||||
container_name: npm_db
|
|
||||||
ports:
|
|
||||||
- 33306:3306
|
|
||||||
networks:
|
networks:
|
||||||
- nginx_proxy_manager
|
- nginx_proxy_manager
|
||||||
environment:
|
environment:
|
||||||
@ -49,14 +46,22 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- db_data:/var/lib/mysql
|
- db_data:/var/lib/mysql
|
||||||
|
|
||||||
|
swagger:
|
||||||
|
image: 'swaggerapi/swagger-ui:latest'
|
||||||
|
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
|
|
||||||
le_data:
|
le_data:
|
||||||
name: npm_le_data
|
|
||||||
db_data:
|
db_data:
|
||||||
name: npm_db_data
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
nginx_proxy_manager:
|
nginx_proxy_manager:
|
||||||
name: npm_network
|
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
#!/usr/bin/with-contenv bash
|
|
||||||
set -e
|
|
||||||
|
|
||||||
mkdir -p /data/logs
|
|
||||||
echo "Changing ownership of /data/logs to $(id -u):$(id -g)"
|
|
||||||
chown -R "$(id -u):$(id -g)" /data/logs
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
|||||||
# ref: https://github.com/linuxserver/docker-baseimage-alpine/blob/master/root/etc/cont-init.d/01-envfile
|
# 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
|
# in s6, environmental variables are written as text files for s6 to monitor
|
||||||
# search through full-path filenames for files ending in "__FILE"
|
# seach through full-path filenames for files ending in "__FILE"
|
||||||
for FILENAME in $(find /var/run/s6/container_environment/ | grep "__FILE$"); do
|
for FILENAME in $(find /var/run/s6/container_environment/ | grep "__FILE$"); do
|
||||||
echo "[secret-init] Evaluating ${FILENAME##*/} ..."
|
echo "[secret-init] Evaluating ${FILENAME##*/} ..."
|
||||||
|
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
text = True
|
text = True
|
||||||
non-interactive = True
|
non-interactive = True
|
||||||
|
authenticator = webroot
|
||||||
webroot-path = /data/letsencrypt-acme-challenge
|
webroot-path = /data/letsencrypt-acme-challenge
|
||||||
key-type = ecdsa
|
|
||||||
elliptic-curve = secp384r1
|
|
||||||
preferred-chain = ISRG Root X1
|
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
/data/logs/*_access.log /data/logs/*/access.log {
|
|
||||||
create 0644 root root
|
|
||||||
weekly
|
|
||||||
rotate 4
|
|
||||||
missingok
|
|
||||||
notifempty
|
|
||||||
compress
|
|
||||||
sharedscripts
|
|
||||||
postrotate
|
|
||||||
/bin/kill -USR1 `cat /run/nginx.pid 2>/dev/null` 2>/dev/null || true
|
|
||||||
endscript
|
|
||||||
}
|
|
||||||
|
|
||||||
/data/logs/*_error.log /data/logs/*/error.log {
|
|
||||||
create 0644 root root
|
|
||||||
weekly
|
|
||||||
rotate 10
|
|
||||||
missingok
|
|
||||||
notifempty
|
|
||||||
compress
|
|
||||||
sharedscripts
|
|
||||||
postrotate
|
|
||||||
/bin/kill -USR1 `cat /run/nginx.pid 2>/dev/null` 2>/dev/null || true
|
|
||||||
endscript
|
|
||||||
}
|
|
@ -8,11 +8,10 @@ server {
|
|||||||
set $port "80";
|
set $port "80";
|
||||||
|
|
||||||
server_name localhost-nginx-proxy-manager;
|
server_name localhost-nginx-proxy-manager;
|
||||||
access_log /data/logs/fallback_access.log standard;
|
access_log /data/logs/default.log standard;
|
||||||
error_log /data/logs/fallback_error.log warn;
|
error_log /dev/null crit;
|
||||||
include conf.d/include/assets.conf;
|
include conf.d/include/assets.conf;
|
||||||
include conf.d/include/block-exploits.conf;
|
include conf.d/include/block-exploits.conf;
|
||||||
include conf.d/include/letsencrypt-acme-challenge.conf;
|
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
index index.html;
|
index index.html;
|
||||||
@ -30,9 +29,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/default.log standard;
|
||||||
error_log /dev/null crit;
|
error_log /dev/null crit;
|
||||||
ssl_reject_handshake on;
|
ssl_certificate /data/nginx/dummycert.pem;
|
||||||
|
ssl_certificate_key /data/nginx/dummykey.pem;
|
||||||
|
include conf.d/include/ssl-ciphers.conf;
|
||||||
|
|
||||||
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
|
||||||
|
@ -5,7 +5,6 @@ location ^~ /.well-known/acme-challenge/ {
|
|||||||
# Since this is for letsencrypt authentication of a domain and they do not give IP ranges of their infrastructure
|
# Since this is for letsencrypt authentication of a domain and they do not give IP ranges of their infrastructure
|
||||||
# we need to open up access by turning off auth and IP ACL for this location.
|
# we need to open up access by turning off auth and IP ACL for this location.
|
||||||
auth_basic off;
|
auth_basic off;
|
||||||
auth_request off;
|
|
||||||
allow all;
|
allow all;
|
||||||
|
|
||||||
# Set correct content type. According to this:
|
# Set correct content type. According to this:
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -3,5 +3,7 @@ ssl_session_cache shared:SSL:50m;
|
|||||||
|
|
||||||
# intermediate configuration. tweak to your needs.
|
# intermediate configuration. tweak to your needs.
|
||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';
|
ssl_ciphers 'EECDH+AESGCM:AES256+EECDH:AES256+EDH:EDH+AESGCM:ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-
|
||||||
ssl_prefer_server_ciphers off;
|
ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AE
|
||||||
|
S128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES';
|
||||||
|
ssl_prefer_server_ciphers on;
|
||||||
|
@ -9,13 +9,13 @@ worker_processes auto;
|
|||||||
# Enables the use of JIT for regular expressions to speed-up their processing.
|
# Enables the use of JIT for regular expressions to speed-up their processing.
|
||||||
pcre_jit on;
|
pcre_jit on;
|
||||||
|
|
||||||
error_log /data/logs/fallback_error.log warn;
|
error_log /data/logs/error.log warn;
|
||||||
|
|
||||||
# Includes files with directives to load dynamic modules.
|
# Includes files with directives to load dynamic modules.
|
||||||
include /etc/nginx/modules/*.conf;
|
include /etc/nginx/modules/*.conf;
|
||||||
|
|
||||||
events {
|
events {
|
||||||
include /data/nginx/custom/events[.]conf;
|
worker_connections 1024;
|
||||||
}
|
}
|
||||||
|
|
||||||
http {
|
http {
|
||||||
@ -46,7 +46,8 @@ http {
|
|||||||
log_format proxy '[$time_local] $upstream_cache_status $upstream_status $status - $request_method $scheme $host "$request_uri" [Client $remote_addr] [Length $body_bytes_sent] [Gzip $gzip_ratio] [Sent-to $server] "$http_user_agent" "$http_referer"';
|
log_format proxy '[$time_local] $upstream_cache_status $upstream_status $status - $request_method $scheme $host "$request_uri" [Client $remote_addr] [Length $body_bytes_sent] [Gzip $gzip_ratio] [Sent-to $server] "$http_user_agent" "$http_referer"';
|
||||||
log_format standard '[$time_local] $status - $request_method $scheme $host "$request_uri" [Client $remote_addr] [Length $body_bytes_sent] [Gzip $gzip_ratio] "$http_user_agent" "$http_referer"';
|
log_format standard '[$time_local] $status - $request_method $scheme $host "$request_uri" [Client $remote_addr] [Length $body_bytes_sent] [Gzip $gzip_ratio] "$http_user_agent" "$http_referer"';
|
||||||
|
|
||||||
access_log /data/logs/fallback_access.log proxy;
|
|
||||||
|
access_log /data/logs/default.log proxy;
|
||||||
|
|
||||||
# Dynamically generated resolvers file
|
# Dynamically generated resolvers file
|
||||||
include /etc/nginx/conf.d/include/resolvers.conf;
|
include /etc/nginx/conf.d/include/resolvers.conf;
|
||||||
@ -57,11 +58,11 @@ http {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Real IP Determination
|
# Real IP Determination
|
||||||
|
# Docker subnet:
|
||||||
|
set_real_ip_from 172.0.0.0/8;
|
||||||
# Local subnets:
|
# Local subnets:
|
||||||
set_real_ip_from 10.0.0.0/8;
|
set_real_ip_from 10.0.0.0/8;
|
||||||
set_real_ip_from 172.16.0.0/12; # Includes Docker subnet
|
set_real_ip_from 192.0.0.0/8;
|
||||||
set_real_ip_from 192.168.0.0/16;
|
|
||||||
# NPM generated CDN ip ranges:
|
# NPM generated CDN ip ranges:
|
||||||
include conf.d/include/ip_ranges.conf;
|
include conf.d/include/ip_ranges.conf;
|
||||||
# always put the following 2 lines after ip subnets:
|
# always put the following 2 lines after ip subnets:
|
||||||
|
@ -1 +0,0 @@
|
|||||||
longrun
|
|
@ -1 +0,0 @@
|
|||||||
longrun
|
|
@ -1,7 +0,0 @@
|
|||||||
#!/command/with-contenv bash
|
|
||||||
# shellcheck shell=bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
echo "❯ Starting nginx ..."
|
|
||||||
exec nginx
|
|
@ -1 +0,0 @@
|
|||||||
longrun
|
|
@ -1,63 +0,0 @@
|
|||||||
#!/command/with-contenv bash
|
|
||||||
# shellcheck shell=bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
DATA_PATH=/data
|
|
||||||
|
|
||||||
# Ensure /data is mounted
|
|
||||||
if [ ! -d "$DATA_PATH" ]; then
|
|
||||||
echo '--------------------------------------'
|
|
||||||
echo "ERROR: $DATA_PATH is not mounted! Check your docker configuration."
|
|
||||||
echo '--------------------------------------'
|
|
||||||
/run/s6/basedir/bin/halt
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "❯ Checking folder structure ..."
|
|
||||||
|
|
||||||
# Create required folders
|
|
||||||
mkdir -p /tmp/nginx/body \
|
|
||||||
/run/nginx \
|
|
||||||
/var/log/nginx \
|
|
||||||
/data/nginx \
|
|
||||||
/data/custom_ssl \
|
|
||||||
/data/logs \
|
|
||||||
/data/access \
|
|
||||||
/data/nginx/default_host \
|
|
||||||
/data/nginx/default_www \
|
|
||||||
/data/nginx/proxy_host \
|
|
||||||
/data/nginx/redirection_host \
|
|
||||||
/data/nginx/stream \
|
|
||||||
/data/nginx/dead_host \
|
|
||||||
/data/nginx/temp \
|
|
||||||
/var/lib/nginx/cache/public \
|
|
||||||
/var/lib/nginx/cache/private \
|
|
||||||
/var/cache/nginx/proxy_temp \
|
|
||||||
/data/letsencrypt-acme-challenge
|
|
||||||
|
|
||||||
touch /var/log/nginx/error.log && chmod 777 /var/log/nginx/error.log && chmod -R 777 /var/cache/nginx
|
|
||||||
chown root /tmp/nginx
|
|
||||||
|
|
||||||
# Dynamically generate resolvers file, if resolver is IPv6, enclose in `[]`
|
|
||||||
# thanks @tfmm
|
|
||||||
if [ "$DISABLE_IPV6" == "true" ] || [ "$DISABLE_IPV6" == "on" ] || [ "$DISABLE_IPV6" == "1" ] || [ "$DISABLE_IPV6" == "yes" ];
|
|
||||||
then
|
|
||||||
echo resolver "$(awk 'BEGIN{ORS=" "} $1=="nameserver" { sub(/%.*$/,"",$2); print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf) ipv6=off valid=10s;" > /etc/nginx/conf.d/include/resolvers.conf
|
|
||||||
else
|
|
||||||
echo resolver "$(awk 'BEGIN{ORS=" "} $1=="nameserver" { sub(/%.*$/,"",$2); print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf) valid=10s;" > /etc/nginx/conf.d/include/resolvers.conf
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Handle IPV6 settings
|
|
||||||
/bin/handle-ipv6-setting /etc/nginx/conf.d
|
|
||||||
/bin/handle-ipv6-setting /data/nginx
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "-------------------------------------
|
|
||||||
_ _ ____ __ __
|
|
||||||
| \ | | _ \| \/ |
|
|
||||||
| \| | |_) | |\/| |
|
|
||||||
| |\ | __/| | | |
|
|
||||||
|_| \_|_| |_| |_|
|
|
||||||
-------------------------------------
|
|
||||||
"
|
|
@ -1 +0,0 @@
|
|||||||
oneshot
|
|
@ -1,2 +0,0 @@
|
|||||||
# shellcheck shell=bash
|
|
||||||
/etc/s6-overlay/s6-rc.d/prepare/script.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
|
||||||
|
|
@ -1,13 +1,9 @@
|
|||||||
#!/command/with-contenv bash
|
#!/usr/bin/with-contenv bash
|
||||||
# shellcheck shell=bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# This service is DEVELOPMENT only.
|
# This service is DEVELOPMENT only.
|
||||||
|
|
||||||
if [ "$DEVELOPMENT" == "true" ]; then
|
if [ "$DEVELOPMENT" == "true" ]; then
|
||||||
cd /app/frontend || exit 1
|
cd /app/frontend || exit 1
|
||||||
# If yarn install fails: add --verbose --network-concurrency 1
|
|
||||||
yarn install
|
yarn install
|
||||||
yarn watch
|
yarn watch
|
||||||
else
|
else
|
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
|
@ -1,12 +1,11 @@
|
|||||||
#!/command/with-contenv bash
|
#!/usr/bin/with-contenv bash
|
||||||
# shellcheck shell=bash
|
|
||||||
|
|
||||||
set -e
|
mkdir -p /data/letsencrypt-acme-challenge
|
||||||
|
|
||||||
|
cd /app || echo
|
||||||
|
|
||||||
echo "❯ Starting backend ..."
|
|
||||||
if [ "$DEVELOPMENT" == "true" ]; then
|
if [ "$DEVELOPMENT" == "true" ]; then
|
||||||
cd /app || exit 1
|
cd /app || exit 1
|
||||||
# If yarn install fails: add --verbose --network-concurrency 1
|
|
||||||
yarn install
|
yarn install
|
||||||
node --max_old_space_size=250 --abort_on_uncaught_exception node_modules/nodemon/bin/nodemon.js
|
node --max_old_space_size=250 --abort_on_uncaught_exception node_modules/nodemon/bin/nodemon.js
|
||||||
else
|
else
|
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=Nginx Proxy Manager/OU=Dummy Certificate/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
|
@ -16,7 +16,5 @@ alias h='cd ~;clear;'
|
|||||||
|
|
||||||
echo -e -n '\E[1;34m'
|
echo -e -n '\E[1;34m'
|
||||||
figlet -w 120 "NginxProxyManager"
|
figlet -w 120 "NginxProxyManager"
|
||||||
echo -e "\E[1;36mVersion \E[1;32m${NPM_BUILD_VERSION:-2.0.0-dev} (${NPM_BUILD_COMMIT:-dev}) ${NPM_BUILD_DATE:-0000-00-00}\E[1;36m, OpenResty \E[1;32m${OPENRESTY_VERSION:-unknown}\E[1;36m, ${ID:-debian} \E[1;32m${VERSION:-unknown}\E[1;36m, Certbot \E[1;32m$(certbot --version)\E[0m"
|
echo -e "\E[1;36mVersion \E[1;32m${NPM_BUILD_VERSION:-2.0.0-dev} (${NPM_BUILD_COMMIT:-dev}) ${NPM_BUILD_DATE:-0000-00-00}\E[1;36m, OpenResty \E[1;32m${OPENRESTY_VERSION:-unknown}\E[1;36m, Alpine \E[1;32m${VERSION_ID:-unknown}\E[1;36m, Kernel \E[1;32m$(uname -r)\E[0m"
|
||||||
echo -e -n '\E[1;34m'
|
echo
|
||||||
cat /built-for-arch
|
|
||||||
echo -e '\E[0m'
|
|
||||||
|
@ -37,3 +37,75 @@ footer: MIT Licensed | Copyright © 2016-present jc21.com
|
|||||||
<p>Configure other users to either view or manage their own hosts. Full access permissions are available.</p>
|
<p>Configure other users to either view or manage their own hosts. Full access permissions are available.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
### Quick Setup
|
||||||
|
|
||||||
|
1. Install Docker and Docker-Compose
|
||||||
|
|
||||||
|
- [Docker Install documentation](https://docs.docker.com/install/)
|
||||||
|
- [Docker-Compose Install documentation](https://docs.docker.com/compose/install/)
|
||||||
|
|
||||||
|
2. Create a docker-compose.yml file similar to this:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
version: '3'
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
image: 'jc21/nginx-proxy-manager:latest'
|
||||||
|
ports:
|
||||||
|
- '80:80'
|
||||||
|
- '81:81'
|
||||||
|
- '443:443'
|
||||||
|
environment:
|
||||||
|
DB_MYSQL_HOST: "db"
|
||||||
|
DB_MYSQL_PORT: 3306
|
||||||
|
DB_MYSQL_USER: "npm"
|
||||||
|
DB_MYSQL_PASSWORD: "npm"
|
||||||
|
DB_MYSQL_NAME: "npm"
|
||||||
|
volumes:
|
||||||
|
- ./data:/data
|
||||||
|
- ./letsencrypt:/etc/letsencrypt
|
||||||
|
db:
|
||||||
|
image: 'jc21/mariadb-aria:latest'
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: 'npm'
|
||||||
|
MYSQL_DATABASE: 'npm'
|
||||||
|
MYSQL_USER: 'npm'
|
||||||
|
MYSQL_PASSWORD: 'npm'
|
||||||
|
volumes:
|
||||||
|
- ./data/mysql:/var/lib/mysql
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Bring up your stack
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Log in to the Admin UI
|
||||||
|
|
||||||
|
When your docker container is running, connect to it on port `81` for the admin interface.
|
||||||
|
Sometimes this can take a little bit because of the entropy of keys.
|
||||||
|
|
||||||
|
[http://127.0.0.1:81](http://127.0.0.1:81)
|
||||||
|
|
||||||
|
Default Admin User:
|
||||||
|
|
||||||
|
```
|
||||||
|
Email: admin@example.com
|
||||||
|
Password: changeme
|
||||||
|
```
|
||||||
|
|
||||||
|
Immediately after logging in with this default user you will be asked to modify your details and change your password.
|
||||||
|
|
||||||
|
5. Upgrading to new versions
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose pull
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
This project will automatically update any databases or other requirements so you don't have to follow
|
||||||
|
any crazy instructions. These steps above will pull the latest updates and recreate the docker
|
||||||
|
containers.
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
# Advanced Configuration
|
# Advanced Configuration
|
||||||
|
|
||||||
## 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
|
||||||
host as NPM, here's a trick to secure things a bit better. By creating a custom Docker network,
|
host as NPM, here's a trick to secure things a bit better. By creating a custom docker network,
|
||||||
you don't need to publish ports for your upstream services to all of the Docker host's interfaces.
|
you don't need to publish ports for your upstream services to all of the docker host's interfaces.
|
||||||
|
|
||||||
Create a network, ie "scoobydoo":
|
Create a network, ie "scoobydoo":
|
||||||
|
|
||||||
@ -13,13 +13,13 @@ docker network create scoobydoo
|
|||||||
```
|
```
|
||||||
|
|
||||||
Then add the following to the `docker-compose.yml` file for both NPM and any other
|
Then add the following to the `docker-compose.yml` file for both NPM and any other
|
||||||
services running on this Docker host:
|
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:
|
||||||
@ -34,32 +34,20 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- './data:/data'
|
- './data:/data'
|
||||||
- '/var/run/docker.sock:/var/run/docker.sock'
|
- '/var/run/docker.sock:/var/run/docker.sock'
|
||||||
restart: unless-stopped
|
restart: always
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
default:
|
default:
|
||||||
external: true
|
external:
|
||||||
name: scoobydoo
|
name: scoobydoo
|
||||||
```
|
```
|
||||||
|
|
||||||
Now in the NPM UI you can create a proxy host with `portainer` as the hostname,
|
Now in the NPM UI you can create a proxy host with `portainer` as the hostname,
|
||||||
and port `9000` as the port. Even though this port isn't listed in the docker-compose
|
and port `9000` as the port. Even though this port isn't listed in the docker-compose
|
||||||
file, it's "exposed" by the Portainer Docker image for you and not available on
|
file, it's "exposed" by the portainer docker image for you and not available on
|
||||||
the Docker host outside of this Docker network. The service name is used as the
|
the docker host outside of this docker network. The service name is used as the
|
||||||
hostname, so make sure your service names are unique when using the same network.
|
hostname, so make sure your service names are unique when using the same network.
|
||||||
|
|
||||||
## Docker Healthcheck
|
|
||||||
|
|
||||||
The `Dockerfile` that builds this project does not include a `HEALTHCHECK` but you can opt in to this
|
|
||||||
feature by adding the following to the service in your `docker-compose.yml` file:
|
|
||||||
|
|
||||||
```yml
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "/bin/check-health"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 3s
|
|
||||||
```
|
|
||||||
|
|
||||||
## Docker Secrets
|
## Docker Secrets
|
||||||
|
|
||||||
This image supports the use of Docker secrets to import from file and keep sensitive usernames or passwords from being passed or preserved in plaintext.
|
This image supports the use of Docker secrets to import from file and keep sensitive usernames or passwords from being passed or preserved in plaintext.
|
||||||
@ -80,7 +68,7 @@ secrets:
|
|||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
image: 'jc21/nginx-proxy-manager:latest'
|
image: 'jc21/nginx-proxy-manager:latest'
|
||||||
restart: unless-stopped
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
# Public HTTP Port:
|
# Public HTTP Port:
|
||||||
- '80:80'
|
- '80:80'
|
||||||
@ -110,7 +98,7 @@ services:
|
|||||||
- db
|
- db
|
||||||
db:
|
db:
|
||||||
image: jc21/mariadb-aria
|
image: jc21/mariadb-aria
|
||||||
restart: unless-stopped
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
# MYSQL_ROOT_PASSWORD: "npm" # use secret instead
|
# MYSQL_ROOT_PASSWORD: "npm" # use secret instead
|
||||||
MYSQL_ROOT_PASSWORD__FILE: /run/secrets/DB_ROOT_PWD
|
MYSQL_ROOT_PASSWORD__FILE: /run/secrets/DB_ROOT_PWD
|
||||||
@ -128,7 +116,7 @@ services:
|
|||||||
|
|
||||||
## Disabling IPv6
|
## Disabling IPv6
|
||||||
|
|
||||||
On some Docker hosts IPv6 may not be enabled. In these cases, the following message may be seen in the log:
|
On some docker hosts IPv6 may not be enabled. In these cases, the following message may be seen in the log:
|
||||||
|
|
||||||
> Address family not supported by protocol
|
> Address family not supported by protocol
|
||||||
|
|
||||||
@ -151,7 +139,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
|
||||||
|
@ -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",
|
||||||
@ -143,7 +143,7 @@
|
|||||||
"css-select-base-adapter": "^0.1.1",
|
"css-select-base-adapter": "^0.1.1",
|
||||||
"css-tree": "^1.0.0-alpha.39",
|
"css-tree": "^1.0.0-alpha.39",
|
||||||
"css-unit-converter": "^1.1.2",
|
"css-unit-converter": "^1.1.2",
|
||||||
"css-what": "^5.0.1",
|
"css-what": "^3.3.0",
|
||||||
"cssesc": "^3.0.0",
|
"cssesc": "^3.0.0",
|
||||||
"cssnano": "^4.1.10",
|
"cssnano": "^4.1.10",
|
||||||
"cssnano-preset-default": "^4.0.7",
|
"cssnano-preset-default": "^4.0.7",
|
||||||
@ -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",
|
||||||
@ -500,7 +500,7 @@
|
|||||||
"pkg-up": "^3.1.0",
|
"pkg-up": "^3.1.0",
|
||||||
"portfinder": "^1.0.28",
|
"portfinder": "^1.0.28",
|
||||||
"posix-character-classes": "^1.0.0",
|
"posix-character-classes": "^1.0.0",
|
||||||
"postcss": "^8.2.10",
|
"postcss": "^7.0.32",
|
||||||
"postcss-calc": "^7.0.2",
|
"postcss-calc": "^7.0.2",
|
||||||
"postcss-colormin": "^4.0.3",
|
"postcss-colormin": "^4.0.3",
|
||||||
"postcss-convert-values": "^4.0.1",
|
"postcss-convert-values": "^4.0.1",
|
||||||
@ -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"
|
|
||||||
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,27 +10,39 @@ 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"
|
version: "3"
|
||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
image: 'jc21/nginx-proxy-manager:latest'
|
image: 'jc21/nginx-proxy-manager:latest'
|
||||||
restart: unless-stopped
|
restart: always
|
||||||
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'
|
||||||
# Add any other Stream port you want to expose
|
# Admin Web Port:
|
||||||
# - '21:21' # FTP
|
- '81:81'
|
||||||
environment:
|
environment:
|
||||||
|
# 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:
|
||||||
@ -76,10 +50,9 @@ 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: always
|
||||||
environment:
|
environment:
|
||||||
MYSQL_ROOT_PASSWORD: 'npm'
|
MYSQL_ROOT_PASSWORD: 'npm'
|
||||||
MYSQL_DATABASE: 'npm'
|
MYSQL_DATABASE: 'npm'
|
||||||
@ -89,13 +62,15 @@ services:
|
|||||||
- ./data/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
|
||||||
|
|
||||||
The docker images support the following architectures:
|
The docker images support the following architectures:
|
||||||
- amd64
|
- amd64
|
||||||
@ -107,14 +82,13 @@ 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.
|
|
||||||
|
|
||||||
## 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:
|
||||||
|
|
||||||
@ -125,7 +99,7 @@ After the app is running for the first time, the following will happen:
|
|||||||
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
|
||||||
|
|
||||||
```
|
```
|
||||||
Email: admin@example.com
|
Email: admin@example.com
|
||||||
@ -134,7 +108,7 @@ 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
|
### Configuration File
|
||||||
|
|
||||||
::: warning
|
::: warning
|
||||||
|
|
||||||
@ -155,8 +129,7 @@ Here's an example for `sqlite` configuration as it is generated from the environ
|
|||||||
"client": "sqlite3",
|
"client": "sqlite3",
|
||||||
"connection": {
|
"connection": {
|
||||||
"filename": "/data/database.sqlite"
|
"filename": "/data/database.sqlite"
|
||||||
},
|
}
|
||||||
"useNullAsDefault": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
2
docs/third-party/README.md
vendored
2
docs/third-party/README.md
vendored
@ -1,6 +1,6 @@
|
|||||||
# Third Party
|
# Third Party
|
||||||
|
|
||||||
As this software gains popularity it's common to see it integrated with other platforms. Please be aware that unless specifically mentioned in the documentation of those
|
As this software gains popularity it's common to see it integrated with other platforms. Please be aware that unless specifically mentioned in the documenation of those
|
||||||
integrations, they are *not supported* by me.
|
integrations, they are *not supported* by me.
|
||||||
|
|
||||||
Known integrations:
|
Known integrations:
|
||||||
|
629
docs/yarn.lock
629
docs/yarn.lock
File diff suppressed because it is too large
Load Diff
1
frontend/fonts
Symbolic link
1
frontend/fonts
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
./node_modules/tabler-ui/dist/assets/fonts
|
@ -1 +0,0 @@
|
|||||||
../node_modules/tabler-ui/dist/assets/fonts/feather
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user