Major overhaul

- Docker buildx support in CI
- Cypress API Testing in CI
- Restructured folder layout (insert clean face meme)
- Added Swagger documentation and validate API against that (to be completed)
- Use common base image for all supported archs, which includes updated nginx with ipv6 support
- Updated certbot and changes required for it
- Large amount of Hosts names will wrap in UI
- Updated packages for frontend
- Version bump 2.1.0
This commit is contained in:
Jamie Curnow
2020-02-07 15:22:23 +10:00
parent 41d95660e7
commit 4dbee0fea7
403 changed files with 15855 additions and 1269 deletions

View File

@@ -1,12 +0,0 @@
{
"presets": [
["env", {
"targets": {
"browsers": ["Chrome >= 65"]
},
"debug": false,
"modules": false,
"useBuiltIns": "usage"
}]
]
}

11
.gitignore vendored
View File

@@ -1,14 +1,5 @@
.DS_Store .DS_Store
.idea .idea
._* ._*
node_modules .vscode
core*
config/development.json
dist
webpack_stats.html
data/*
yarn-error.log
yarn.lock
tmp
certbot.log

10
.jenkins/config.json Normal file
View File

@@ -0,0 +1,10 @@
{
"database": {
"engine": "mysql",
"host": "db",
"name": "npm",
"user": "npm",
"password": "npm",
"port": 3306
}
}

1
.version Normal file
View File

@@ -0,0 +1 @@
2.1.0

View File

@@ -1,39 +0,0 @@
FROM jc21/nginx-proxy-manager-base:latest
MAINTAINER Jamie Curnow <jc@jc21.com>
LABEL maintainer="Jamie Curnow <jc@jc21.com>"
ENV SUPPRESS_NO_CONFIG_WARNING=1
ENV S6_FIX_ATTRS_HIDDEN=1
RUN echo "fs.file-max = 65535" > /etc/sysctl.conf
# Nginx, Node and required packages should already be installed from the base image
# root filesystem
COPY rootfs /
# s6 overlay
RUN curl -L -o /tmp/s6-overlay-amd64.tar.gz "https://github.com/just-containers/s6-overlay/releases/download/v1.21.4.0/s6-overlay-amd64.tar.gz" \
&& tar xzf /tmp/s6-overlay-amd64.tar.gz -C /
# App
ENV NODE_ENV=production
ADD dist /app/dist
ADD node_modules /app/node_modules
ADD src/backend /app/src/backend
ADD package.json /app/package.json
ADD knexfile.js /app/knexfile.js
# Volumes
VOLUME [ "/data", "/etc/letsencrypt" ]
CMD [ "/init" ]
# Ports
EXPOSE 80
EXPOSE 81
EXPOSE 443
EXPOSE 9876
HEALTHCHECK --interval=15s --timeout=3s CMD curl -f http://localhost:9876/health || exit 1

View File

@@ -1,38 +0,0 @@
FROM jc21/nginx-proxy-manager-base:arm64
MAINTAINER Jamie Curnow <jc@jc21.com>
LABEL maintainer="Jamie Curnow <jc@jc21.com>"
ENV SUPPRESS_NO_CONFIG_WARNING=1
ENV S6_FIX_ATTRS_HIDDEN=1
RUN echo "fs.file-max = 65535" > /etc/sysctl.conf
# Nginx, Node and required packages should already be installed from the base image
# root filesystem
COPY rootfs /
# s6 overlay
RUN curl -L -o /tmp/s6-overlay-aarch64.tar.gz "https://github.com/just-containers/s6-overlay/releases/download/v1.21.8.0/s6-overlay-aarch64.tar.gz" \
&& tar xzf /tmp/s6-overlay-aarch64.tar.gz -C /
# App
ENV NODE_ENV=production
ADD dist /app/dist
ADD node_modules /app/node_modules
ADD src/backend /app/src/backend
ADD package.json /app/package.json
ADD knexfile.js /app/knexfile.js
# Volumes
VOLUME [ "/data", "/etc/letsencrypt" ]
CMD [ "/init" ]
# Ports
EXPOSE 80
EXPOSE 81
EXPOSE 443
EXPOSE 9876
HEALTHCHECK --interval=15s --timeout=3s CMD curl -f http://localhost:9876/health || exit 1

View File

@@ -1,38 +0,0 @@
FROM jc21/nginx-proxy-manager-base:armv6
MAINTAINER Jamie Curnow <jc@jc21.com>
LABEL maintainer="Jamie Curnow <jc@jc21.com>"
ENV SUPPRESS_NO_CONFIG_WARNING=1
ENV S6_FIX_ATTRS_HIDDEN=1
RUN echo "fs.file-max = 65535" > /etc/sysctl.conf
# Nginx, Node and required packages should already be installed from the base image
# root filesystem
COPY rootfs /
# s6 overlay
RUN curl -L -o /tmp/s6-overlay-arm.tar.gz "https://github.com/just-containers/s6-overlay/releases/download/v1.21.8.0/s6-overlay-arm.tar.gz" \
&& tar xzf /tmp/s6-overlay-arm.tar.gz -C /
# App
ENV NODE_ENV=production
ADD dist /app/dist
ADD node_modules /app/node_modules
ADD src/backend /app/src/backend
ADD package.json /app/package.json
ADD knexfile.js /app/knexfile.js
# Volumes
VOLUME [ "/data", "/etc/letsencrypt" ]
CMD [ "/init" ]
# Ports
EXPOSE 80
EXPOSE 81
EXPOSE 443
EXPOSE 9876
HEALTHCHECK --interval=15s --timeout=3s CMD curl -f http://localhost:9876/health || exit 1

View File

@@ -1,38 +0,0 @@
FROM jc21/nginx-proxy-manager-base:armhf
MAINTAINER Jamie Curnow <jc@jc21.com>
LABEL maintainer="Jamie Curnow <jc@jc21.com>"
ENV SUPPRESS_NO_CONFIG_WARNING=1
ENV S6_FIX_ATTRS_HIDDEN=1
RUN echo "fs.file-max = 65535" > /etc/sysctl.conf
# Nginx, Node and required packages should already be installed from the base image
# root filesystem
COPY rootfs /
# s6 overlay
RUN curl -L -o /tmp/s6-overlay-armhf.tar.gz "https://github.com/just-containers/s6-overlay/releases/download/v1.21.4.0/s6-overlay-armhf.tar.gz" \
&& tar xzf /tmp/s6-overlay-armhf.tar.gz -C /
# App
ENV NODE_ENV=production
ADD dist /app/dist
ADD node_modules /app/node_modules
ADD src/backend /app/src/backend
ADD package.json /app/package.json
ADD knexfile.js /app/knexfile.js
# Volumes
VOLUME [ "/data", "/etc/letsencrypt" ]
CMD [ "/init" ]
# Ports
EXPOSE 80
EXPOSE 81
EXPOSE 443
EXPOSE 9876
HEALTHCHECK --interval=15s --timeout=3s CMD curl -f http://localhost:9876/health || exit 1

503
Jenkinsfile vendored
View File

@@ -1,364 +1,151 @@
pipeline { pipeline {
options { agent {
buildDiscarder(logRotator(numToKeepStr: '10')) label 'docker-multiarch'
disableConcurrentBuilds() }
} options {
agent any buildDiscarder(logRotator(numToKeepStr: '5'))
environment { disableConcurrentBuilds()
IMAGE = "nginx-proxy-manager" }
BASE_IMAGE = "jc21/${IMAGE}-base" environment {
TEMP_IMAGE = "${IMAGE}-build_${BUILD_NUMBER}" IMAGE = "nginx-proxy-manager"
TAG_VERSION = getPackageVersion() BUILD_VERSION = getVersion()
MAJOR_VERSION = "2" MAJOR_VERSION = "2"
BRANCH_LOWER = "${BRANCH_NAME.toLowerCase()}" COMPOSE_PROJECT_NAME = "npm_${GIT_BRANCH}_${BUILD_NUMBER}"
// Architectures: COMPOSE_FILE = 'docker/docker-compose.ci.yml'
AMD64_TAG = "amd64" COMPOSE_INTERACTIVE_NO_CLI = 1
ARMV6_TAG = "armv6l" BUILDX_NAME = "${COMPOSE_PROJECT_NAME}"
ARMV7_TAG = "armv7l" BRANCH_LOWER = "${BRANCH_NAME.toLowerCase()}"
ARM64_TAG = "arm64"
}
stages {
stage('Build PR') {
when {
changeRequest()
}
steps {
ansiColor('xterm') {
// Codebase
sh 'docker run --rm -v $(pwd):/app -w /app ${BASE_IMAGE} yarn install'
sh 'docker run --rm -v $(pwd):/app -w /app ${BASE_IMAGE} npm run-script build'
sh 'docker run --rm -v $(pwd):/data ${DOCKER_CI_TOOLS} rm -rf node_modules'
sh 'docker run --rm -v $(pwd):/app -w /app ${BASE_IMAGE} yarn install --prod'
sh 'docker run --rm -v $(pwd):/data ${DOCKER_CI_TOOLS} node-prune'
// Docker Build // Defaults to the Branch name, which is applies to all branches AND pr's
sh 'docker build --pull --no-cache --squash --compress -t ${TEMP_IMAGE}-${AMD64_TAG} .' BUILDX_PUSH_TAGS = "-t docker.io/jc21/${IMAGE}:github-${BRANCH_LOWER}"
}
stages {
stage('Environment') {
parallel {
stage('Master') {
when {
branch 'master'
}
steps {
script {
env.BUILDX_PUSH_TAGS = "-t docker.io/jc21/${IMAGE}:${BUILD_VERSION} -t docker.io/jc21/${IMAGE}:${MAJOR_VERSION}"
}
}
}
}
}
stage('Frontend') {
steps {
ansiColor('xterm') {
sh './scripts/frontend-build'
}
}
}
stage('Backend') {
steps {
ansiColor('xterm') {
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') {
steps {
ansiColor('xterm') {
// Bring up a stack
sh 'docker-compose up -d fullstack'
sh './scripts/wait-healthy $(docker-compose ps -q fullstack) 120'
// Dockerhub // Run tests
sh 'docker tag ${TEMP_IMAGE}-${AMD64_TAG} docker.io/jc21/${IMAGE}:github-${BRANCH_LOWER}-${AMD64_TAG}' sh 'rm -rf test/results'
withCredentials([usernamePassword(credentialsId: 'jc21-dockerhub', passwordVariable: 'dpass', usernameVariable: 'duser')]) { sh 'docker-compose up cypress'
sh "docker login -u '${duser}' -p '${dpass}'" // Get results
sh 'docker push docker.io/jc21/${IMAGE}:github-${BRANCH_LOWER}-${AMD64_TAG}' sh 'docker cp -L "$(docker-compose ps -q cypress):/results" test/'
} }
}
sh 'docker rmi ${TEMP_IMAGE}-${AMD64_TAG}' post {
always {
script { junit 'test/results/junit/*'
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}-${AMD64_TAG}`") // Cypress videos and screenshot artifacts
} dir(path: 'test/results') {
} archiveArtifacts allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml'
} }
} // Dumps to analyze later
stage('Build Develop') { sh 'mkdir -p debug'
when { sh 'docker-compose logs fullstack | gzip > debug/docker_fullstack.log.gz'
branch 'develop' }
} }
steps { }
ansiColor('xterm') { stage('MultiArch Build') {
// Codebase when {
sh 'docker run --rm -v $(pwd):/app -w /app ${BASE_IMAGE} yarn install' not {
sh 'docker run --rm -v $(pwd):/app -w /app ${BASE_IMAGE} npm run-script build' equals expected: 'UNSTABLE', actual: currentBuild.result
sh 'docker run --rm -v $(pwd):/data ${DOCKER_CI_TOOLS} rm -rf node_modules' }
sh 'docker run --rm -v $(pwd):/app -w /app ${BASE_IMAGE} yarn install --prod' }
sh 'docker run --rm -v $(pwd):/data ${DOCKER_CI_TOOLS} node-prune' steps {
ansiColor('xterm') {
// Docker Build withCredentials([usernamePassword(credentialsId: 'jc21-dockerhub', passwordVariable: 'dpass', usernameVariable: 'duser')]) {
sh 'docker build --pull --no-cache --squash --compress -t ${TEMP_IMAGE}-${AMD64_TAG} .' sh "docker login -u '${duser}' -p '${dpass}'"
// Buildx with push
// Dockerhub sh "./scripts/buildx --push ${BUILDX_PUSH_TAGS}"
sh 'docker tag ${TEMP_IMAGE}-${AMD64_TAG} docker.io/jc21/${IMAGE}:develop-${AMD64_TAG}' }
withCredentials([usernamePassword(credentialsId: 'jc21-dockerhub', passwordVariable: 'dpass', usernameVariable: 'duser')]) { }
sh "docker login -u '${duser}' -p '${dpass}'" }
sh 'docker push docker.io/jc21/${IMAGE}:develop-${AMD64_TAG}' }
} stage('PR Comment') {
when {
sh 'docker rmi ${TEMP_IMAGE}-${AMD64_TAG}' allOf {
} changeRequest()
} not {
} equals expected: 'UNSTABLE', actual: currentBuild.result
stage('Build Master') { }
when { }
branch 'master' }
} steps {
parallel { ansiColor('xterm') {
// ======================== script {
// amd64 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('amd64') { }
agent { }
label 'amd64' }
} }
steps { post {
ansiColor('xterm') { always {
// Codebase sh 'docker-compose down --rmi all --remove-orphans --volumes -t 30'
sh 'docker run --rm -v $(pwd):/app -w /app ${BASE_IMAGE} yarn install' sh 'echo Reverting ownership'
sh 'docker run --rm -v $(pwd):/app -w /app ${BASE_IMAGE} npm run-script build' sh 'docker run --rm -v $(pwd):/data ${DOCKER_CI_TOOLS} chown -R $(id -u):$(id -g) /data'
sh 'docker run --rm -v $(pwd):/data ${DOCKER_CI_TOOLS} rm -rf node_modules' }
sh 'docker run --rm -v $(pwd):/app -w /app ${BASE_IMAGE} yarn install --prod' success {
sh 'docker run --rm -v $(pwd):/data ${DOCKER_CI_TOOLS} node-prune' juxtapose event: 'success'
sh 'figlet "SUCCESS"'
// Docker Build }
sh 'docker build --pull --no-cache --squash --compress -t ${TEMP_IMAGE}-${AMD64_TAG} .' failure {
juxtapose event: 'failure'
// Dockerhub sh 'figlet "FAILURE"'
sh 'docker tag ${TEMP_IMAGE}-${AMD64_TAG} docker.io/jc21/${IMAGE}:${TAG_VERSION}-${AMD64_TAG}' }
sh 'docker tag ${TEMP_IMAGE}-${AMD64_TAG} docker.io/jc21/${IMAGE}:${MAJOR_VERSION}-${AMD64_TAG}' unstable {
sh 'docker tag ${TEMP_IMAGE}-${AMD64_TAG} docker.io/jc21/${IMAGE}:latest-${AMD64_TAG}' archiveArtifacts(artifacts: 'debug/**.*', allowEmptyArchive: true)
juxtapose event: 'unstable'
withCredentials([usernamePassword(credentialsId: 'jc21-dockerhub', passwordVariable: 'dpass', usernameVariable: 'duser')]) { sh 'figlet "UNSTABLE"'
sh "docker login -u '${duser}' -p '${dpass}'" }
sh 'docker push docker.io/jc21/${IMAGE}:${TAG_VERSION}-${AMD64_TAG}' }
sh 'docker push docker.io/jc21/${IMAGE}:${MAJOR_VERSION}-${AMD64_TAG}'
sh 'docker push docker.io/jc21/${IMAGE}:latest-${AMD64_TAG}'
}
sh 'docker rmi ${TEMP_IMAGE}-${AMD64_TAG}'
}
}
}
// ========================
// arm64
// ========================
stage('arm64') {
agent {
label 'arm64'
}
steps {
ansiColor('xterm') {
// Codebase
sh 'docker run --rm -v $(pwd):/app -w /app ${BASE_IMAGE} yarn install'
sh 'docker run --rm -v $(pwd):/app -w /app ${BASE_IMAGE} npm run-script build'
sh 'docker run --rm -v $(pwd):/data ${DOCKER_CI_TOOLS} rm -rf node_modules'
sh 'docker run --rm -v $(pwd):/app -w /app ${BASE_IMAGE} yarn install --prod'
// Docker Build
sh 'docker build --pull --no-cache --squash --compress -t ${TEMP_IMAGE}-${ARM64_TAG} -f Dockerfile.${ARM64_TAG} .'
// Dockerhub
sh 'docker tag ${TEMP_IMAGE}-${ARM64_TAG} docker.io/jc21/${IMAGE}:${TAG_VERSION}-${ARM64_TAG}'
sh 'docker tag ${TEMP_IMAGE}-${ARM64_TAG} docker.io/jc21/${IMAGE}:${MAJOR_VERSION}-${ARM64_TAG}'
sh 'docker tag ${TEMP_IMAGE}-${ARM64_TAG} docker.io/jc21/${IMAGE}:latest-${ARM64_TAG}'
withCredentials([usernamePassword(credentialsId: 'jc21-dockerhub', passwordVariable: 'dpass', usernameVariable: 'duser')]) {
sh "docker login -u '${duser}' -p '${dpass}'"
sh 'docker push docker.io/jc21/${IMAGE}:${TAG_VERSION}-${ARM64_TAG}'
sh 'docker push docker.io/jc21/${IMAGE}:${MAJOR_VERSION}-${ARM64_TAG}'
sh 'docker push docker.io/jc21/${IMAGE}:latest-${ARM64_TAG}'
}
sh 'docker rmi ${TEMP_IMAGE}-${ARM64_TAG}'
}
}
}
// ========================
// armv7l
// ========================
stage('armv7l') {
agent {
label 'armv7l'
}
steps {
ansiColor('xterm') {
// Codebase
sh 'docker run --rm -v $(pwd):/app -w /app ${BASE_IMAGE} yarn install'
sh 'docker run --rm -v $(pwd):/app -w /app ${BASE_IMAGE} npm run-script build'
sh 'docker run --rm -v $(pwd):/data ${DOCKER_CI_TOOLS} rm -rf node_modules'
sh 'docker run --rm -v $(pwd):/app -w /app ${BASE_IMAGE} yarn install --prod'
// Docker Build
sh 'docker build --pull --no-cache --squash --compress -t ${TEMP_IMAGE}-${ARMV7_TAG} -f Dockerfile.${ARMV7_TAG} .'
// Dockerhub
sh 'docker tag ${TEMP_IMAGE}-${ARMV7_TAG} docker.io/jc21/${IMAGE}:${TAG_VERSION}-${ARMV7_TAG}'
sh 'docker tag ${TEMP_IMAGE}-${ARMV7_TAG} docker.io/jc21/${IMAGE}:${MAJOR_VERSION}-${ARMV7_TAG}'
sh 'docker tag ${TEMP_IMAGE}-${ARMV7_TAG} docker.io/jc21/${IMAGE}:latest-${ARMV7_TAG}'
withCredentials([usernamePassword(credentialsId: 'jc21-dockerhub', passwordVariable: 'dpass', usernameVariable: 'duser')]) {
sh "docker login -u '${duser}' -p '${dpass}'"
sh 'docker push docker.io/jc21/${IMAGE}:${TAG_VERSION}-${ARMV7_TAG}'
sh 'docker push docker.io/jc21/${IMAGE}:${MAJOR_VERSION}-${ARMV7_TAG}'
sh 'docker push docker.io/jc21/${IMAGE}:latest-${ARMV7_TAG}'
}
sh 'docker rmi ${TEMP_IMAGE}-${ARMV7_TAG}'
}
}
}
// ========================
// armv6l - Disabled for the time being
// ========================
/*
stage('armv6l') {
agent {
label 'armv6l'
}
steps {
ansiColor('xterm') {
// Codebase
sh 'docker run --rm -v $(pwd):/app -w /app ${BASE_IMAGE} yarn install'
sh 'docker run --rm -v $(pwd):/app -w /app ${BASE_IMAGE} npm run-script build'
sh 'docker run --rm -v $(pwd):/data ${DOCKER_CI_TOOLS} rm -rf node_modules'
sh 'docker run --rm -v $(pwd):/app -w /app ${BASE_IMAGE} yarn install --prod'
// Docker Build
sh 'docker build --pull --no-cache --squash --compress -t ${TEMP_IMAGE}-${ARMV6_TAG} -f Dockerfile.${ARMV6_TAG} .'
// Dockerhub
sh 'docker tag ${TEMP_IMAGE}-${ARMV6_TAG} docker.io/jc21/${IMAGE}:${TAG_VERSION}-${ARMV6_TAG}'
sh 'docker tag ${TEMP_IMAGE}-${ARMV6_TAG} docker.io/jc21/${IMAGE}:${MAJOR_VERSION}-${ARMV6_TAG}'
sh 'docker tag ${TEMP_IMAGE}-${ARMV6_TAG} docker.io/jc21/${IMAGE}:latest-${ARMV6_TAG}'
withCredentials([usernamePassword(credentialsId: 'jc21-dockerhub', passwordVariable: 'dpass', usernameVariable: 'duser')]) {
sh "docker login -u '${duser}' -p '${dpass}'"
sh 'docker push docker.io/jc21/${IMAGE}:${TAG_VERSION}-${ARMV6_TAG}'
sh 'docker push docker.io/jc21/${IMAGE}:${MAJOR_VERSION}-${ARMV6_TAG}'
sh 'docker push docker.io/jc21/${IMAGE}:latest-${ARMV6_TAG}'
}
sh 'docker rmi ${TEMP_IMAGE}-${ARMV6_TAG}'
}
}
}
*/
}
}
// ========================
// latest manifest
// ========================
stage('Latest Manifest') {
when {
branch 'master'
}
steps {
ansiColor('xterm') {
// =======================
// latest
// =======================
sh 'docker pull jc21/${IMAGE}:latest-${AMD64_TAG}'
sh 'docker pull jc21/${IMAGE}:latest-${ARM64_TAG}'
sh 'docker pull jc21/${IMAGE}:latest-${ARMV7_TAG}'
//sh 'docker pull jc21/${IMAGE}:latest-${ARMV6_TAG}'
sh 'docker manifest push --purge jc21/${IMAGE}:latest || echo ""'
sh 'docker manifest create jc21/${IMAGE}:latest jc21/${IMAGE}:latest-${AMD64_TAG} jc21/${IMAGE}:latest-${ARM64_TAG} jc21/${IMAGE}:latest-${ARMV7_TAG}'
sh 'docker manifest annotate jc21/${IMAGE}:latest jc21/${IMAGE}:latest-${AMD64_TAG} --arch ${AMD64_TAG}'
sh 'docker manifest annotate jc21/${IMAGE}:latest jc21/${IMAGE}:latest-${ARM64_TAG} --os linux --arch ${ARM64_TAG}'
sh 'docker manifest annotate jc21/${IMAGE}:latest jc21/${IMAGE}:latest-${ARMV7_TAG} --os linux --arch arm --variant ${ARMV7_TAG}'
//sh 'docker manifest annotate jc21/${IMAGE}:latest jc21/${IMAGE}:latest-${ARMV6_TAG} --os linux --arch arm --variant ${ARMV6_TAG}'
sh 'docker manifest push --purge jc21/${IMAGE}:latest'
// =======================
// major version
// =======================
sh 'docker pull jc21/${IMAGE}:${MAJOR_VERSION}-${AMD64_TAG}'
sh 'docker pull jc21/${IMAGE}:${MAJOR_VERSION}-${ARM64_TAG}'
sh 'docker pull jc21/${IMAGE}:${MAJOR_VERSION}-${ARMV7_TAG}'
//sh 'docker pull jc21/${IMAGE}:${MAJOR_VERSION}-${ARMV6_TAG}'
sh 'docker manifest push --purge jc21/${IMAGE}:${MAJOR_VERSION} || echo ""'
sh 'docker manifest create jc21/${IMAGE}:${MAJOR_VERSION} jc21/${IMAGE}:${MAJOR_VERSION}-${AMD64_TAG} jc21/${IMAGE}:${MAJOR_VERSION}-${ARM64_TAG} jc21/${IMAGE}:${MAJOR_VERSION}-${ARMV7_TAG}'
sh 'docker manifest annotate jc21/${IMAGE}:${MAJOR_VERSION} jc21/${IMAGE}:${MAJOR_VERSION}-${AMD64_TAG} --arch ${AMD64_TAG}'
sh 'docker manifest annotate jc21/${IMAGE}:${MAJOR_VERSION} jc21/${IMAGE}:${MAJOR_VERSION}-${ARM64_TAG} --os linux --arch ${ARM64_TAG}'
sh 'docker manifest annotate jc21/${IMAGE}:${MAJOR_VERSION} jc21/${IMAGE}:${MAJOR_VERSION}-${ARMV7_TAG} --os linux --arch arm --variant ${ARMV7_TAG}'
//sh 'docker manifest annotate jc21/${IMAGE}:${MAJOR_VERSION} jc21/${IMAGE}:${MAJOR_VERSION}-${ARMV6_TAG} --os linux --arch arm --variant ${ARMV6_TAG}'
// =======================
// version
// =======================
sh 'docker pull jc21/${IMAGE}:${TAG_VERSION}-${AMD64_TAG}'
sh 'docker pull jc21/${IMAGE}:${TAG_VERSION}-${ARM64_TAG}'
sh 'docker pull jc21/${IMAGE}:${TAG_VERSION}-${ARMV7_TAG}'
//sh 'docker pull jc21/${IMAGE}:${TAG_VERSION}-${ARMV6_TAG}'
sh 'docker manifest push --purge jc21/${IMAGE}:${TAG_VERSION} || echo ""'
sh 'docker manifest create jc21/${IMAGE}:${TAG_VERSION} jc21/${IMAGE}:${TAG_VERSION}-${AMD64_TAG} jc21/${IMAGE}:${TAG_VERSION}-${ARM64_TAG} jc21/${IMAGE}:${TAG_VERSION}-${ARMV7_TAG}'
sh 'docker manifest annotate jc21/${IMAGE}:${TAG_VERSION} jc21/${IMAGE}:${TAG_VERSION}-${AMD64_TAG} --arch ${AMD64_TAG}'
sh 'docker manifest annotate jc21/${IMAGE}:${TAG_VERSION} jc21/${IMAGE}:${TAG_VERSION}-${ARM64_TAG} --os linux --arch ${ARM64_TAG}'
sh 'docker manifest annotate jc21/${IMAGE}:${TAG_VERSION} jc21/${IMAGE}:${TAG_VERSION}-${ARMV7_TAG} --os linux --arch arm --variant ${ARMV7_TAG}'
//sh 'docker manifest annotate jc21/${IMAGE}:${TAG_VERSION} jc21/${IMAGE}:${TAG_VERSION}-${ARMV6_TAG} --os linux --arch arm --variant ${ARMV6_TAG}'
}
}
}
// ========================
// develop
// ========================
stage('Develop Manifest') {
when {
branch 'develop'
}
steps {
ansiColor('xterm') {
sh 'docker pull jc21/${IMAGE}:develop-${AMD64_TAG}'
//sh 'docker pull jc21/${IMAGE}:develop-${ARM64_TAG}'
//sh 'docker pull jc21/${IMAGE}:develop-${ARMV7_TAG}'
//sh 'docker pull jc21/${IMAGE}:${TAG_VERSION}-${ARMV6_TAG}'
sh 'docker manifest push --purge jc21/${IMAGE}:develop || :'
sh 'docker manifest create jc21/${IMAGE}:develop jc21/${IMAGE}:develop-${AMD64_TAG}'
sh 'docker manifest annotate jc21/${IMAGE}:develop jc21/${IMAGE}:develop-${AMD64_TAG} --arch ${AMD64_TAG}'
//sh 'docker manifest annotate jc21/${IMAGE}:develop jc21/${IMAGE}:develop-${ARM64_TAG} --os linux --arch ${ARM64_TAG}'
//sh 'docker manifest annotate jc21/${IMAGE}:develop jc21/${IMAGE}:develop-${ARMV7_TAG} --os linux --arch arm --variant ${ARMV7_TAG}'
//sh 'docker manifest annotate jc21/${IMAGE}:develop jc21/${IMAGE}:develop-${ARMV6_TAG} --os linux --arch arm --variant ${ARMV6_TAG}'
}
}
}
// ========================
// cleanup
// ========================
stage('Latest Cleanup') {
when {
branch 'master'
}
steps {
ansiColor('xterm') {
sh 'docker rmi jc21/${IMAGE}:latest jc21/${IMAGE}:latest-${AMD64_TAG} jc21/${IMAGE}:latest-${ARM64_TAG} jc21/${IMAGE}:latest-${ARMV7_TAG} || echo ""'
sh 'docker rmi jc21/${IMAGE}:${MAJOR_VERSION}-${AMD64_TAG} jc21/${IMAGE}:${MAJOR_VERSION}-${ARM64_TAG} jc21/${IMAGE}:${MAJOR_VERSION}-${ARMV7_TAG} || echo ""'
sh 'docker rmi jc21/${IMAGE}:${TAG_VERSION}-${AMD64_TAG} jc21/${IMAGE}:${TAG_VERSION}-${ARM64_TAG} jc21/${IMAGE}:${TAG_VERSION}-${ARMV7_TAG} || echo ""'
}
}
}
stage('Develop Cleanup') {
when {
branch 'develop'
}
steps {
ansiColor('xterm') {
sh 'docker rmi jc21/${IMAGE}:develop jc21/${IMAGE}:develop-${AMD64_TAG} || echo ""'
}
}
}
stage('PR Cleanup') {
when {
changeRequest()
}
steps {
ansiColor('xterm') {
sh 'docker rmi jc21/${IMAGE}:github-${BRANCH_LOWER}-${AMD64_TAG} || echo ""'
}
}
}
}
post {
success {
juxtapose event: 'success'
sh 'figlet "SUCCESS"'
}
failure {
juxtapose event: 'failure'
sh 'figlet "FAILURE"'
}
always {
sh 'echo Reverting ownership'
sh 'docker run --rm -v $(pwd):/data ${DOCKER_CI_TOOLS} chown -R $(id -u):$(id -g) /data'
}
}
} }
def getPackageVersion() { def getVersion() {
ver = sh(script: 'docker run --rm -v $(pwd):/data ${DOCKER_CI_TOOLS} bash -c "cat /data/package.json|jq -r \'.version\'"', returnStdout: true) ver = sh(script: 'cat .version', returnStdout: true)
return ver.trim() return ver.trim()
}
def getCommit() {
ver = sh(script: 'git log -n 1 --format=%h', returnStdout: true)
return ver.trim()
} }

View File

@@ -6,6 +6,8 @@
![Stars](https://img.shields.io/docker/stars/jc21/nginx-proxy-manager.svg?style=for-the-badge) ![Stars](https://img.shields.io/docker/stars/jc21/nginx-proxy-manager.svg?style=for-the-badge)
![Pulls](https://img.shields.io/docker/pulls/jc21/nginx-proxy-manager.svg?style=for-the-badge) ![Pulls](https://img.shields.io/docker/pulls/jc21/nginx-proxy-manager.svg?style=for-the-badge)
[![Build Status](https://ci.nginxproxymanager.jc21.com/buildStatus/icon?job=nginx-proxy-manager%2Fmaster&style=flat-square)](https://ci.nginxproxymanager.jc21.com/job/nginx-proxy-manager/job/master/)
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.

6
backend/.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
config/development.json
data/*
yarn-error.log
tmp
certbot.log
node_modules

View File

@@ -29,10 +29,6 @@ if (process.env.NODE_ENV !== 'production') {
app.set('json spaces', 2); app.set('json spaces', 2);
} }
// set the view engine to ejs
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, '/views'));
// CORS for everything // CORS for everything
app.use(require('./lib/express/cors')); app.use(require('./lib/express/cors'));
@@ -56,19 +52,8 @@ app.use(function (req, res, next) {
next(); next();
}); });
// ATTACH JWT value - FOR ANY RATE LIMITERS and JWT DECODE
app.use(require('./lib/express/jwt')()); app.use(require('./lib/express/jwt')());
app.use('/', require('./routes/api/main'));
/**
* Routes
*/
app.use('/assets', express.static('dist/assets'));
app.use('/css', express.static('dist/css'));
app.use('/fonts', express.static('dist/fonts'));
app.use('/images', express.static('dist/images'));
app.use('/js', express.static('dist/js'));
app.use('/api', require('./routes/api/main'));
app.use('/', require('./routes/main'));
// production error handler // production error handler
// no stacktraces leaked to user // no stacktraces leaked to user
@@ -91,8 +76,8 @@ 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') { if (process.env.NODE_ENV === 'development') {
log.warn(err.stack); log.debug(err.stack);
} else { } else if (typeof err.public == 'undefined' || !err.public) {
log.warn(err.message); log.warn(err.message);
} }
} }

1254
backend/doc/api.swagger.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -23,8 +23,8 @@ function appStart () {
internalCertificate.initTimer(); internalCertificate.initTimer();
internalIpRanges.initTimer(); internalIpRanges.initTimer();
const server = app.listen(81, () => { const server = app.listen(3000, () => {
logger.info('PID ' + process.pid + ' listening on port 81 ...'); logger.info('Backend PID ' + process.pid + ' listening on port 3000 ...');
process.on('SIGTERM', () => { process.on('SIGTERM', () => {
logger.info('PID ' + process.pid + ' received SIGTERM'); logger.info('PID ' + process.pid + ' received SIGTERM');

View File

@@ -12,6 +12,7 @@ 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 certbot_command = '/usr/bin/certbot'; const certbot_command = '/usr/bin/certbot';
const le_config = '/etc/letsencrypt.ini';
function omissions() { function omissions() {
return ['is_deleted']; return ['is_deleted'];
@@ -27,6 +28,8 @@ const internalCertificate = {
initTimer: () => { initTimer: () => {
logger.info('Let\'s Encrypt Renewal Timer initialized'); logger.info('Let\'s Encrypt Renewal Timer initialized');
internalCertificate.interval = setInterval(internalCertificate.processExpiringHosts, internalCertificate.interval_timeout); internalCertificate.interval = setInterval(internalCertificate.processExpiringHosts, internalCertificate.interval_timeout);
// And do this now as well
internalCertificate.processExpiringHosts();
}, },
/** /**
@@ -37,9 +40,17 @@ const internalCertificate = {
internalCertificate.interval_processing = true; internalCertificate.interval_processing = true;
logger.info('Renewing SSL certs close to expiry...'); logger.info('Renewing SSL certs close to expiry...');
return utils.exec(certbot_command + ' renew -q ' + (le_staging ? '--staging' : '')) let cmd = certbot_command + ' renew --non-interactive --quiet ' +
'--config "' + le_config + '" ' +
'--preferred-challenges "dns,http" ' +
'--disable-hook-validation ' +
(le_staging ? '--staging' : '');
return utils.exec(cmd)
.then(result => { .then(result => {
logger.info(result); if (result) {
logger.info('Renew Result: ' + result);
}
return internalNginx.reload() return internalNginx.reload()
.then(() => { .then(() => {
@@ -716,10 +727,14 @@ const internalCertificate = {
requestLetsEncryptSsl: certificate => { requestLetsEncryptSsl: certificate => {
logger.info('Requesting Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', ')); logger.info('Requesting Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
let cmd = certbot_command + ' certonly --cert-name "npm-' + certificate.id + '" --agree-tos ' + let cmd = certbot_command + ' certonly --non-interactive ' +
'--config "' + le_config + '" ' +
'--cert-name "npm-' + certificate.id + '" ' +
'--agree-tos ' +
'--email "' + certificate.meta.letsencrypt_email + '" ' + '--email "' + certificate.meta.letsencrypt_email + '" ' +
'--preferred-challenges "dns,http" ' + '--preferred-challenges "dns,http" ' +
'-n -a webroot -d "' + certificate.domain_names.join(',') + '" ' + '--webroot ' +
'--domains "' + certificate.domain_names.join(',') + '" ' +
(le_staging ? '--staging' : ''); (le_staging ? '--staging' : '');
if (debug_mode) { if (debug_mode) {
@@ -782,7 +797,12 @@ 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(', '));
let cmd = certbot_command + ' renew -n --force-renewal --disable-hook-validation --cert-name "npm-' + certificate.id + '" ' + (le_staging ? '--staging' : ''); let cmd = certbot_command + ' renew --non-interactive ' +
'--config "' + le_config + '" ' +
'--cert-name "npm-' + certificate.id + '" ' +
'--preferred-challenges "dns,http" ' +
'--disable-hook-validation ' +
(le_staging ? '--staging' : '');
if (debug_mode) { if (debug_mode) {
logger.info('Command:', cmd); logger.info('Command:', cmd);
@@ -803,29 +823,24 @@ 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(', '));
let revoke_cmd = certbot_command + ' revoke --cert-path "/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem" ' + (le_staging ? '--staging' : ''); let cmd = certbot_command + ' revoke --non-interactive ' +
let delete_cmd = certbot_command + ' delete --cert-name "npm-' + certificate.id + '" ' + (le_staging ? '--staging' : ''); '--config "' + le_config + '" ' +
'--cert-path "/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem" ' +
'--delete-after-revoke ' +
(le_staging ? '--staging' : '');
if (debug_mode) { if (debug_mode) {
logger.info('Command:', revoke_cmd); logger.info('Command:', cmd);
} }
return utils.exec(revoke_cmd) return utils.exec(cmd)
.then((result) => { .then((result) => {
if (debug_mode) {
logger.info('Command:', cmd);
}
logger.info(result); logger.info(result);
return result; return result;
}) })
.then(() => {
if (debug_mode) {
logger.info('Command:', delete_cmd);
}
return utils.exec(delete_cmd)
.then((result) => {
logger.info(result);
return result;
})
})
.catch(err => { .catch(err => {
if (debug_mode) { if (debug_mode) {
logger.error(err.message); logger.error(err.message);

View File

@@ -61,7 +61,7 @@ module.exports = {
}, },
scope: [data.scope] scope: [data.scope]
}, { }, {
expiresIn: expiry.unix() expires: expiry.unix()
}) })
.then(signed => { .then(signed => {
return { return {

View File

@@ -3,8 +3,8 @@ module.exports = {
client: 'mysql', client: 'mysql',
migrations: { migrations: {
tableName: 'migrations', tableName: 'migrations',
stub: 'src/backend/lib/migrate_template.js', stub: 'lib/migrate_template.js',
directory: 'src/backend/migrations' directory: 'migrations'
} }
}, },
@@ -12,8 +12,8 @@ module.exports = {
client: 'mysql', client: 'mysql',
migrations: { migrations: {
tableName: 'migrations', tableName: 'migrations',
stub: 'src/backend/lib/migrate_template.js', stub: 'lib/migrate_template.js',
directory: 'src/backend/migrations' directory: 'migrations'
} }
} }
}; };

View File

@@ -36,7 +36,7 @@ function apiValidator (schema, payload/*, description*/) {
} }
apiValidator.loadSchemas = parser apiValidator.loadSchemas = parser
.dereference(path.resolve('src/backend/schema/index.json')) .dereference(path.resolve('schema/index.json'))
.then(schema => { .then(schema => {
ajv.addSchema(schema); ajv.addSchema(schema);
return schema; return schema;

View File

@@ -8,7 +8,7 @@ module.exports = {
logger.info('Current database version:', version); logger.info('Current database version:', version);
return db.migrate.latest({ return db.migrate.latest({
tableName: 'migrations', tableName: 'migrations',
directory: 'src/backend/migrations' directory: 'migrations'
}); });
}); });
} }

Some files were not shown because too many files have changed in this diff Show More