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.22
...
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.22-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
|
|
||||||
|
2
docker/rootfs/etc/cont-finish.d/.gitignore
vendored
Normal file
2
docker/rootfs/etc/cont-finish.d/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
3
docker/rootfs/etc/cont-init.d/.gitignore
vendored
Normal file
3
docker/rootfs/etc/cont-init.d/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
||||||
|
!*.sh
|
29
docker/rootfs/etc/cont-init.d/01_s6-secret-init.sh
Normal file
29
docker/rootfs/etc/cont-init.d/01_s6-secret-init.sh
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#!/usr/bin/with-contenv bash
|
||||||
|
# ref: https://github.com/linuxserver/docker-baseimage-alpine/blob/master/root/etc/cont-init.d/01-envfile
|
||||||
|
|
||||||
|
# in s6, environmental variables are written as text files for s6 to monitor
|
||||||
|
# seach through full-path filenames for files ending in "__FILE"
|
||||||
|
for FILENAME in $(find /var/run/s6/container_environment/ | grep "__FILE$"); do
|
||||||
|
echo "[secret-init] Evaluating ${FILENAME##*/} ..."
|
||||||
|
|
||||||
|
# set SECRETFILE to the contents of the full-path textfile
|
||||||
|
SECRETFILE=$(cat ${FILENAME})
|
||||||
|
# SECRETFILE=${FILENAME}
|
||||||
|
# echo "[secret-init] Set SECRETFILE to ${SECRETFILE}" # DEBUG - rm for prod!
|
||||||
|
|
||||||
|
# if SECRETFILE exists / is not null
|
||||||
|
if [[ -f ${SECRETFILE} ]]; then
|
||||||
|
# strip the appended "__FILE" from environmental variable name ...
|
||||||
|
STRIPFILE=$(echo ${FILENAME} | sed "s/__FILE//g")
|
||||||
|
# echo "[secret-init] Set STRIPFILE to ${STRIPFILE}" # DEBUG - rm for prod!
|
||||||
|
|
||||||
|
# ... and set value to contents of secretfile
|
||||||
|
# since s6 uses text files, this is effectively "export ..."
|
||||||
|
printf $(cat ${SECRETFILE}) > ${STRIPFILE}
|
||||||
|
# echo "[secret-init] Set ${STRIPFILE##*/} to $(cat ${STRIPFILE})" # DEBUG - rm for prod!"
|
||||||
|
echo "[secret-init] Success! ${STRIPFILE##*/} set from ${FILENAME##*/}"
|
||||||
|
|
||||||
|
else
|
||||||
|
echo "[secret-init] cannot find secret in ${FILENAME}"
|
||||||
|
fi
|
||||||
|
done
|
2
docker/rootfs/etc/fix-attrs.d/.gitignore
vendored
Normal file
2
docker/rootfs/etc/fix-attrs.d/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
@ -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,93 +0,0 @@
|
|||||||
#!/command/with-contenv bash
|
|
||||||
# shellcheck shell=bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
DATA_PATH=/data
|
|
||||||
|
|
||||||
# Ensure /data is mounted
|
|
||||||
if [ ! -d "$DATA_PATH" ]; then
|
|
||||||
echo '--------------------------------------'
|
|
||||||
echo "ERROR: $DATA_PATH is not mounted! Check your docker configuration."
|
|
||||||
echo '--------------------------------------'
|
|
||||||
/run/s6/basedir/bin/halt
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "❯ Checking folder structure ..."
|
|
||||||
|
|
||||||
# Create required folders
|
|
||||||
mkdir -p /tmp/nginx/body \
|
|
||||||
/run/nginx \
|
|
||||||
/var/log/nginx \
|
|
||||||
/data/nginx \
|
|
||||||
/data/custom_ssl \
|
|
||||||
/data/logs \
|
|
||||||
/data/access \
|
|
||||||
/data/nginx/default_host \
|
|
||||||
/data/nginx/default_www \
|
|
||||||
/data/nginx/proxy_host \
|
|
||||||
/data/nginx/redirection_host \
|
|
||||||
/data/nginx/stream \
|
|
||||||
/data/nginx/dead_host \
|
|
||||||
/data/nginx/temp \
|
|
||||||
/var/lib/nginx/cache/public \
|
|
||||||
/var/lib/nginx/cache/private \
|
|
||||||
/var/cache/nginx/proxy_temp \
|
|
||||||
/data/letsencrypt-acme-challenge
|
|
||||||
|
|
||||||
touch /var/log/nginx/error.log && chmod 777 /var/log/nginx/error.log && chmod -R 777 /var/cache/nginx
|
|
||||||
chown root /tmp/nginx
|
|
||||||
|
|
||||||
# Dynamically generate resolvers file, if resolver is IPv6, enclose in `[]`
|
|
||||||
# thanks @tfmm
|
|
||||||
if [ "$DISABLE_IPV6" == "true" ] || [ "$DISABLE_IPV6" == "on" ] || [ "$DISABLE_IPV6" == "1" ] || [ "$DISABLE_IPV6" == "yes" ];
|
|
||||||
then
|
|
||||||
echo resolver "$(awk 'BEGIN{ORS=" "} $1=="nameserver" { sub(/%.*$/,"",$2); print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf) ipv6=off valid=10s;" > /etc/nginx/conf.d/include/resolvers.conf
|
|
||||||
else
|
|
||||||
echo resolver "$(awk 'BEGIN{ORS=" "} $1=="nameserver" { sub(/%.*$/,"",$2); print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf) valid=10s;" > /etc/nginx/conf.d/include/resolvers.conf
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Changing ownership of /data/logs to $(id -u):$(id -g)"
|
|
||||||
chown -R "$(id -u):$(id -g)" /data/logs
|
|
||||||
|
|
||||||
# Handle IPV6 settings
|
|
||||||
/bin/handle-ipv6-setting /etc/nginx/conf.d
|
|
||||||
/bin/handle-ipv6-setting /data/nginx
|
|
||||||
|
|
||||||
# ref: https://github.com/linuxserver/docker-baseimage-alpine/blob/master/root/etc/cont-init.d/01-envfile
|
|
||||||
|
|
||||||
# in s6, environmental variables are written as text files for s6 to monitor
|
|
||||||
# search through full-path filenames for files ending in "__FILE"
|
|
||||||
echo "❯ Secrets-init ..."
|
|
||||||
for FILENAME in $(find /var/run/s6/container_environment/ | grep "__FILE$"); do
|
|
||||||
echo "[secret-init] Evaluating ${FILENAME##*/} ..."
|
|
||||||
|
|
||||||
# set SECRETFILE to the contents of the full-path textfile
|
|
||||||
SECRETFILE=$(cat "${FILENAME}")
|
|
||||||
# if SECRETFILE exists / is not null
|
|
||||||
if [[ -f "${SECRETFILE}" ]]; then
|
|
||||||
# strip the appended "__FILE" from environmental variable name ...
|
|
||||||
STRIPFILE=$(echo "${FILENAME}" | sed "s/__FILE//g")
|
|
||||||
# echo "[secret-init] Set STRIPFILE to ${STRIPFILE}" # DEBUG - rm for prod!
|
|
||||||
|
|
||||||
# ... and set value to contents of secretfile
|
|
||||||
# since s6 uses text files, this is effectively "export ..."
|
|
||||||
printf $(cat "${SECRETFILE}") > "${STRIPFILE}"
|
|
||||||
# echo "[secret-init] Set ${STRIPFILE##*/} to $(cat ${STRIPFILE})" # DEBUG - rm for prod!"
|
|
||||||
echo "[secret-init] Success! ${STRIPFILE##*/} set from ${FILENAME##*/}"
|
|
||||||
|
|
||||||
else
|
|
||||||
echo "[secret-init] cannot find secret in ${FILENAME}"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "-------------------------------------
|
|
||||||
_ _ ____ __ __
|
|
||||||
| \ | | _ \| \/ |
|
|
||||||
| \| | |_) | |\/| |
|
|
||||||
| |\ | __/| | | |
|
|
||||||
|_| \_|_| |_| |_|
|
|
||||||
-------------------------------------
|
|
||||||
"
|
|
@ -1 +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
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user