diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 9860702e0..6ec6a7c91 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -6,15 +6,30 @@ labels: bug assignees: '' --- + **Checklist** - 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? -- If having problems with Lets Encrypt, have you made absolutely sure your site is accessible from outside of your network? + - Yes / No +- Have you searched for similar issues (both open and closed)? + - Yes / No **Describe the bug** -- 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** + + **To Reproduce** Steps to reproduce the behavior: @@ -23,14 +38,18 @@ Steps to reproduce the behavior: 3. Scroll down to '....' 4. See error + **Expected behavior** -A clear and concise description of what you expected to happen. + + **Screenshots** -If applicable, add screenshots to help explain your problem. + + **Operating System** -- Please specify if using a Rpi, Mac, orchestration tool or any other setups that might affect the reproduction of this error. + + **Additional context** -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. + diff --git a/.github/ISSUE_TEMPLATE/dns_challenge_request.md b/.github/ISSUE_TEMPLATE/dns_challenge_request.md new file mode 100644 index 000000000..0a00f00f8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/dns_challenge_request.md @@ -0,0 +1,18 @@ +--- +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?** + + + +**Have you checked if a certbot plugin exists?** + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 11fc491ef..cf5b0f772 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -7,14 +7,26 @@ assignees: '' --- + + **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 [...] + + **Describe the solution you'd like** -A clear and concise description of what you want to happen. + + **Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. + + **Additional context** -Add any other context or screenshots about the feature request here. + diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000..f859b1278 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,21 @@ +name: 'Close stale issues and PRs' +on: + schedule: + - cron: '30 1 * * *' + workflow_dispatch: + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v9 + with: + stale-issue-label: 'stale' + stale-pr-label: 'stale' + stale-issue-message: 'Issue is now considered stale. If you want to keep it open, please comment :+1:' + stale-pr-message: 'PR is now considered stale. If you want to keep it open, please comment :+1:' + close-issue-message: 'Issue was closed due to inactivity.' + close-pr-message: 'PR was closed due to inactivity.' + days-before-stale: 182 + days-before-close: 365 + operations-per-run: 50 diff --git a/.gitignore b/.gitignore index deb3fb55c..fbb8167e7 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,8 @@ .idea ._* .vscode - +certbot-help.txt +test/node_modules +*/node_modules +docker/dev/dnsrouter-config.json.tmp +docker/dev/resolv.conf diff --git a/.jenkins/config.json b/.jenkins/config.json deleted file mode 100644 index 19ad2237f..000000000 --- a/.jenkins/config.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "database": { - "engine": "mysql", - "host": "db", - "name": "npm", - "user": "npm", - "password": "npm", - "port": 3306 - } -} \ No newline at end of file diff --git a/.version b/.version index 585940699..e4643748f 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -2.2.3 +2.12.6 diff --git a/Jenkinsfile b/Jenkinsfile index 6cac8fe12..af913c2e0 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,3 +1,9 @@ +import groovy.transform.Field + +@Field +def shOutput = "" +def buildxPushTags = "" + pipeline { agent { label 'docker-multiarch' @@ -5,16 +11,15 @@ pipeline { options { buildDiscarder(logRotator(numToKeepStr: '5')) disableConcurrentBuilds() + ansiColor('xterm') } environment { - IMAGE = "nginx-proxy-manager" + IMAGE = 'nginx-proxy-manager' BUILD_VERSION = getVersion() - MAJOR_VERSION = "2" - BRANCH_LOWER = "${BRANCH_NAME.toLowerCase().replaceAll('/', '-')}" - COMPOSE_PROJECT_NAME = "npm_${BRANCH_LOWER}_${BUILD_NUMBER}" - COMPOSE_FILE = 'docker/docker-compose.ci.yml' + MAJOR_VERSION = '2' + BRANCH_LOWER = "${BRANCH_NAME.toLowerCase().replaceAll('\\\\', '-').replaceAll('/', '-').replaceAll('\\.', '-')}" + BUILDX_NAME = "npm_${BRANCH_LOWER}_${BUILD_NUMBER}" COMPOSE_INTERACTIVE_NO_CLI = 1 - BUILDX_NAME = "${COMPOSE_PROJECT_NAME}" } stages { stage('Environment') { @@ -25,7 +30,7 @@ pipeline { } steps { script { - env.BUILDX_PUSH_TAGS = "-t docker.io/jc21/${IMAGE}:${BUILD_VERSION} -t docker.io/jc21/${IMAGE}:${MAJOR_VERSION} -t docker.io/jc21/${IMAGE}:latest" + buildxPushTags = "-t docker.io/jc21/${IMAGE}:${BUILD_VERSION} -t docker.io/jc21/${IMAGE}:${MAJOR_VERSION} -t docker.io/jc21/${IMAGE}:latest" } } } @@ -38,7 +43,7 @@ pipeline { steps { script { // Defaults to the Branch name, which is applies to all branches AND pr's - env.BUILDX_PUSH_TAGS = "-t docker.io/jc21/${IMAGE}:github-${BRANCH_LOWER}" + buildxPushTags = "-t docker.io/nginxproxymanager/${IMAGE}-dev:${BRANCH_LOWER}" } } } @@ -51,151 +56,204 @@ pipeline { sh 'sed -i -E "s/(version-)[0-9]+\\.[0-9]+\\.[0-9]+(-green)/\\1${BUILD_VERSION}\\2/" README.md' } } + stage('Docker Login') { + steps { + withCredentials([usernamePassword(credentialsId: 'jc21-dockerhub', passwordVariable: 'dpass', usernameVariable: 'duser')]) { + sh 'docker login -u "${duser}" -p "${dpass}"' + } + } + } } } - stage('Frontend') { - steps { - ansiColor('xterm') { - sh './scripts/frontend-build' + stage('Builds') { + parallel { + stage('Project') { + steps { + script { + // 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 { + sh 'rm -f ${WORKSPACE}/tmp-sh-build' + } + failure { + npmGithubPrComment("CI Error:\n\n```\n${shOutput}\n```", true) + } + } + } + stage('Docs') { + steps { + dir(path: 'docs') { + sh 'yarn install' + sh 'yarn build' + } + } } } } - stage('Backend') { - steps { - ansiColor('xterm') { - echo 'Checking Syntax ...' - // See: https://github.com/yarnpkg/yarn/issues/3254 - sh '''docker run --rm \\ - -v "$(pwd)/backend:/app" \\ - -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('Test Sqlite') { + environment { + COMPOSE_PROJECT_NAME = "npm_${BRANCH_LOWER}_${BUILD_NUMBER}_sqlite" + COMPOSE_FILE = 'docker/docker-compose.ci.yml:docker/docker-compose.ci.sqlite.yml' + } + when { + not { + equals expected: 'UNSTABLE', actual: currentBuild.result } } - } - stage('Test') { steps { - ansiColor('xterm') { - // Bring up a stack - sh 'docker-compose up -d fullstack' - sh './scripts/wait-healthy $(docker-compose ps -q fullstack) 120' - - // Run tests - sh 'rm -rf test/results' - sh 'docker-compose up cypress' - // Get results - sh 'docker cp -L "$(docker-compose ps -q cypress):/results" test/' - } + sh 'rm -rf ./test/results/junit/*' + sh './scripts/ci/fulltest-cypress' } post { always { // Dumps to analyze later - sh 'mkdir -p debug' - sh 'docker-compose logs fullstack | gzip > debug/docker_fullstack.log.gz' - sh 'docker-compose logs db | gzip > debug/docker_db.log.gz' - // Cypress videos and screenshot artifacts + sh 'mkdir -p debug/sqlite' + sh 'docker logs $(docker-compose ps --all -q fullstack) > debug/sqlite/docker_fullstack.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q stepca) > debug/sqlite/docker_stepca.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q pdns) > debug/sqlite/docker_pdns.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q pdns-db) > debug/sqlite/docker_pdns-db.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q dnsrouter) > debug/sqlite/docker_dnsrouter.log 2>&1' + junit 'test/results/junit/*' + sh 'docker-compose down --remove-orphans --volumes -t 30 || true' + } + unstable { dir(path: 'test/results') { - archiveArtifacts allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml' + archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml') } - junit 'test/results/junit/*' } } } - stage('Docs') { + stage('Test Mysql') { + environment { + COMPOSE_PROJECT_NAME = "npm_${BRANCH_LOWER}_${BUILD_NUMBER}_mysql" + COMPOSE_FILE = 'docker/docker-compose.ci.yml:docker/docker-compose.ci.mysql.yml' + } when { not { equals expected: 'UNSTABLE', actual: currentBuild.result } } steps { - ansiColor('xterm') { - dir(path: 'docs') { - sh 'yarn install' - sh 'yarn build' - } - - dir(path: 'docs/.vuepress/dist') { - sh 'tar -czf ../../docs.tgz *' + sh 'rm -rf ./test/results/junit/*' + sh './scripts/ci/fulltest-cypress' + } + post { + always { + // Dumps to analyze later + sh 'mkdir -p debug/mysql' + sh 'docker logs $(docker-compose ps --all -q fullstack) > debug/mysql/docker_fullstack.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q stepca) > debug/mysql/docker_stepca.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q pdns) > debug/mysql/docker_pdns.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q pdns-db) > debug/mysql/docker_pdns-db.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q dnsrouter) > debug/mysql/docker_dnsrouter.log 2>&1' + junit 'test/results/junit/*' + sh 'docker-compose down --remove-orphans --volumes -t 30 || true' + } + unstable { + dir(path: 'test/results') { + archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml') } - - archiveArtifacts(artifacts: 'docs/docs.tgz', allowEmptyArchive: false) } } } - stage('MultiArch Build') { + stage('Test Postgres') { + environment { + COMPOSE_PROJECT_NAME = "npm_${BRANCH_LOWER}_${BUILD_NUMBER}_postgres" + COMPOSE_FILE = 'docker/docker-compose.ci.yml:docker/docker-compose.ci.postgres.yml' + } when { not { equals expected: 'UNSTABLE', actual: currentBuild.result } } steps { - ansiColor('xterm') { - withCredentials([usernamePassword(credentialsId: 'jc21-dockerhub', passwordVariable: 'dpass', usernameVariable: 'duser')]) { - sh "docker login -u '${duser}' -p '${dpass}'" - // Buildx with push - sh "./scripts/buildx --push ${BUILDX_PUSH_TAGS}" + sh 'rm -rf ./test/results/junit/*' + sh './scripts/ci/fulltest-cypress' + } + post { + always { + // Dumps to analyze later + sh 'mkdir -p debug/postgres' + sh 'docker logs $(docker-compose ps --all -q fullstack) > debug/postgres/docker_fullstack.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q stepca) > debug/postgres/docker_stepca.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q pdns) > debug/postgres/docker_pdns.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q pdns-db) > debug/postgres/docker_pdns-db.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q dnsrouter) > debug/postgres/docker_dnsrouter.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q db-postgres) > debug/postgres/docker_db-postgres.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q authentik) > debug/postgres/docker_authentik.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q authentik-redis) > debug/postgres/docker_authentik-redis.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q authentik-ldap) > debug/postgres/docker_authentik-ldap.log 2>&1' + + junit 'test/results/junit/*' + sh 'docker-compose down --remove-orphans --volumes -t 30 || true' + } + unstable { + dir(path: 'test/results') { + archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml') } } } } - stage('Docs Deploy') { + stage('MultiArch Build') { when { - allOf { - branch 'master' - not { - equals expected: 'UNSTABLE', actual: currentBuild.result - } + not { + equals expected: 'UNSTABLE', actual: currentBuild.result } } steps { - 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 '/*' - """ - } + sh "./scripts/buildx --push ${buildxPushTags}" } } - stage('PR Comment') { - when { - allOf { - changeRequest() - not { - equals expected: 'UNSTABLE', actual: currentBuild.result + stage('Docs / Comment') { + parallel { + stage('Docs Job') { + when { + allOf { + branch pattern: "^(develop|master)\$", comparator: "REGEXP" + not { + equals expected: 'UNSTABLE', actual: currentBuild.result + } + } + } + steps { + build wait: false, job: 'nginx-proxy-manager-docs', parameters: [string(name: 'docs_branch', value: "$BRANCH_NAME")] } } - } - steps { - ansiColor('xterm') { - script { - 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}`") + stage('PR Comment') { + when { + allOf { + changeRequest() + not { + equals expected: 'UNSTABLE', actual: currentBuild.result + } + } + } + steps { + script { + npmGithubPrComment("""Docker Image for build ${BUILD_NUMBER} is available on [DockerHub](https://cloud.docker.com/repository/docker/nginxproxymanager/${IMAGE}-dev): +``` +nginxproxymanager/${IMAGE}-dev:${BRANCH_LOWER} +``` + +> [!NOTE] +> Ensure you backup your NPM instance before testing this image! Especially if there are database changes. +> This is a different docker image namespace than the official image. + +> [!WARNING] +> Changes and additions to DNS Providers require verification by at least 2 members of the community! +""", true) + } } } } @@ -203,23 +261,15 @@ pipeline { } post { always { - sh 'docker-compose down --rmi all --remove-orphans --volumes -t 30' sh 'echo Reverting ownership' - sh 'docker run --rm -v $(pwd):/data ${DOCKER_CI_TOOLS} chown -R $(id -u):$(id -g) /data' - } - success { - juxtapose event: 'success' - sh 'figlet "SUCCESS"' + sh 'docker run --rm -v "$(pwd):/data" jc21/ci-tools chown -R "$(id -u):$(id -g)" /data' + printResult(true) } failure { - archiveArtifacts(artifacts: 'debug/**.*', allowEmptyArchive: true) - juxtapose event: 'failure' - sh 'figlet "FAILURE"' + archiveArtifacts(artifacts: 'debug/**/*.*', allowEmptyArchive: true) } unstable { - archiveArtifacts(artifacts: 'debug/**.*', allowEmptyArchive: true) - juxtapose event: 'unstable' - sh 'figlet "UNSTABLE"' + archiveArtifacts(artifacts: 'debug/**/*.*', allowEmptyArchive: true) } } } diff --git a/README.md b/README.md index 6099b86cf..2116a55ae 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,25 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
- Expose web services on your network · - Free SSL with Let's Encrypt · - Designed with security in mind · - Perfect for home networks -
-Expose your private network Web services and get connected anywhere.
-Based on Tabler, the interface is a pleasure to use. Configuring a server has never been so fun.
-Built in Let’s Encrypt support allows you to secure your Web services at no cost to you. The certificates even renew themselves!
-Built as a Docker Image, Nginx Proxy Manager only requires a database.
-Configure other users to either view or manage their own hosts. Full access permissions are available.
-+ IP Address Whitelist/Blacklist via + + Nginx HTTP Access + +
allow
and deny
directives will be applied in the order they are defined.