diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 00000000..f859b127 --- /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 08462849..fbb8167e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,7 @@ ._* .vscode certbot-help.txt +test/node_modules +*/node_modules +docker/dev/dnsrouter-config.json.tmp +docker/dev/resolv.conf diff --git a/.version b/.version index 8bcbcd5c..22e3b6b0 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -2.9.8 \ No newline at end of file +2.11.3 diff --git a/Jenkinsfile b/Jenkinsfile index 3161a254..1d8680a6 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,3 +1,9 @@ +import groovy.transform.Field + +@Field +def shOutput = "" +def buildxPushTags = "" + pipeline { agent { label 'docker-multiarch' @@ -8,14 +14,12 @@ pipeline { 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') { @@ -26,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" } } } @@ -39,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/jc21/${IMAGE}:github-${BRANCH_LOWER}" } } } @@ -54,105 +58,96 @@ pipeline { } } } - stage('Frontend') { - steps { - sh './scripts/frontend-build' - } - } - stage('Backend') { - steps { - echo 'Checking Syntax ...' - // See: https://github.com/yarnpkg/yarn/issues/3254 - 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') { - steps { - // Bring up a stack - sh 'docker-compose up -d fullstack-sqlite' - sh './scripts/wait-healthy $(docker-compose ps -q fullstack-sqlite) 120' - - // Run tests - sh 'rm -rf test/results' - sh 'docker-compose up cypress-sqlite' - // Get results - sh 'docker cp -L "$(docker-compose ps -q cypress-sqlite):/test/results" test/' - } - post { - always { - // Dumps to analyze later - sh 'mkdir -p debug' - sh 'docker-compose logs fullstack-sqlite | gzip > debug/docker_fullstack_sqlite.log.gz' - sh 'docker-compose logs db | gzip > debug/docker_db.log.gz' - // Cypress videos and screenshot artifacts - dir(path: 'test/results') { - archiveArtifacts allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml' + 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' + } } - junit 'test/results/junit/*' } } } - stage('Integration Tests Mysql') { - steps { - // Bring up a stack - sh 'docker-compose up -d fullstack-mysql' - sh './scripts/wait-healthy $(docker-compose ps -q fullstack-mysql) 120' - - // Run tests - sh 'rm -rf test/results' - sh 'docker-compose up cypress-mysql' - // Get results - sh 'docker cp -L "$(docker-compose ps -q cypress-mysql):/test/results" test/' + 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' } - post { - always { - // Dumps to analyze later - sh 'mkdir -p debug' - sh 'docker-compose logs fullstack-mysql | gzip > debug/docker_fullstack_mysql.log.gz' - sh 'docker-compose logs db | gzip > debug/docker_db.log.gz' - // Cypress videos and screenshot artifacts - dir(path: 'test/results') { - archiveArtifacts allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml' - } - junit 'test/results/junit/*' - } - } - } - stage('Docs') { when { not { equals expected: 'UNSTABLE', actual: currentBuild.result } } steps { - dir(path: 'docs') { - sh 'yarn install' - sh 'yarn build' + sh 'rm -rf ./test/results/junit/*' + sh './scripts/ci/fulltest-cypress' + } + post { + always { + // Dumps to analyze later + sh 'mkdir -p debug/sqlite' + sh 'docker logs $(docker-compose ps --all -q fullstack) > debug/sqlite/docker_fullstack.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q stepca) > debug/sqlite/docker_stepca.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q pdns) > debug/sqlite/docker_pdns.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q pdns-db) > debug/sqlite/docker_pdns-db.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q dnsrouter) > debug/sqlite/docker_dnsrouter.log 2>&1' + junit 'test/results/junit/*' + sh 'docker-compose down --remove-orphans --volumes -t 30 || true' } - - dir(path: 'docs/.vuepress/dist') { - sh 'tar -czf ../../docs.tgz *' + } + } + stage('Test Mysql') { + environment { + COMPOSE_PROJECT_NAME = "npm_${BRANCH_LOWER}_${BUILD_NUMBER}_mysql" + COMPOSE_FILE = 'docker/docker-compose.ci.yml:docker/docker-compose.ci.mysql.yml' + } + when { + not { + equals expected: 'UNSTABLE', actual: currentBuild.result + } + } + steps { + sh 'rm -rf ./test/results/junit/*' + sh './scripts/ci/fulltest-cypress' + } + post { + always { + // Dumps to analyze later + sh 'mkdir -p debug/mysql' + sh 'docker logs $(docker-compose ps --all -q fullstack) > debug/mysql/docker_fullstack.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q stepca) > debug/mysql/docker_stepca.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q pdns) > debug/mysql/docker_pdns.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q pdns-db) > debug/mysql/docker_pdns-db.log 2>&1' + sh 'docker logs $(docker-compose ps --all -q dnsrouter) > debug/mysql/docker_dnsrouter.log 2>&1' + junit 'test/results/junit/*' + sh 'docker-compose down --remove-orphans --volumes -t 30 || true' } - - archiveArtifacts(artifacts: 'docs/docs.tgz', allowEmptyArchive: false) } } stage('MultiArch Build') { @@ -163,78 +158,60 @@ pipeline { } steps { withCredentials([usernamePassword(credentialsId: 'jc21-dockerhub', passwordVariable: 'dpass', usernameVariable: 'duser')]) { - // Docker Login - sh "docker login -u '${duser}' -p '${dpass}'" - // Buildx with push from cache - sh "./scripts/buildx --push ${BUILDX_PUSH_TAGS}" + sh 'docker login -u "${duser}" -p "${dpass}"' + sh "./scripts/buildx --push ${buildxPushTags}" } } } - stage('Docs Deploy') { - when { - allOf { - branch 'master' - 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 { - 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') { - when { - allOf { - changeRequest() - not { - equals expected: 'UNSTABLE', actual: currentBuild.result + 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/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) + } } - } - } - steps { - script { - def comment = pullRequest.comment("This is an automated message from CI:\n\nDocker Image for build ${BUILD_NUMBER} is available on [DockerHub](https://cloud.docker.com/repository/docker/jc21/${IMAGE}) as `jc21/${IMAGE}:github-${BRANCH_LOWER}`\n\n**Note:** ensure you backup your NPM instance before testing this PR image! Especially if this PR contains database changes.") } } } } post { always { - sh 'docker-compose down --rmi all --remove-orphans --volumes -t 30' sh 'echo Reverting ownership' - sh 'docker run --rm -v $(pwd):/data jc21/ci-tools chown -R $(id -u):$(id -g) /data' + sh 'docker run --rm -v "$(pwd):/data" jc21/ci-tools chown -R "$(id -u):$(id -g)" /data' } success { juxtapose event: 'success' sh 'figlet "SUCCESS"' } failure { - archiveArtifacts(artifacts: 'debug/**.*', allowEmptyArchive: true) + archiveArtifacts(artifacts: 'debug/**/*.*', allowEmptyArchive: true) juxtapose event: 'failure' sh 'figlet "FAILURE"' } unstable { - archiveArtifacts(artifacts: 'debug/**.*', allowEmptyArchive: true) + archiveArtifacts(artifacts: 'debug/**/*.*', allowEmptyArchive: true) juxtapose event: 'unstable' sh 'figlet "UNSTABLE"' } diff --git a/README.md b/README.md index 3665eb92..55a986d1 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,13 @@
This project comes as a pre-built docker image that enables you to easily forward to your websites @@ -28,7 +19,7 @@ running at home or otherwise, including free SSL, without having to know too muc ## Project Goal -I created this project to fill a personal need to provide users with a easy way to accomplish reverse +I created this project to fill a personal need to provide users with an easy way to accomplish reverse proxying hosts with SSL termination and it had to be so easy that a monkey could do it. This goal hasn't changed. While there might be advanced options they are optional and the project should be as simple as possible so that the barrier for entry here is low. @@ -65,40 +56,29 @@ I won't go in to too much detail here but here are the basics for someone new to 2. Create a docker-compose.yml file similar to this: ```yml -version: '3' services: app: - image: 'jc21/nginx-proxy-manager:latest' + image: 'docker.io/jc21/nginx-proxy-manager:latest' restart: unless-stopped 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' - restart: unless-stopped - 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 +This is the bare minimum configuration required. See the [documentation](https://nginxproxymanager.com/setup/) for more. + +3. Bring up your stack by running ```bash docker-compose up -d + +# If using docker-compose-plugin +docker compose up -d + ``` 4. Log in to the Admin UI @@ -117,373 +97,24 @@ Password: changeme Immediately after logging in with this default user you will be asked to modify your details and change your password. -## Contributors +## Contributing -Special thanks to the following contributors: +All are welcome to create pull requests for this project, against the `develop` branch. Official releases are created from the `master` branch. - - -- 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.
-