diff --git a/Jenkins/.gitignore b/Jenkins/.gitignore
new file mode 100644
index 00000000..5f1e4d07
--- /dev/null
+++ b/Jenkins/.gitignore
@@ -0,0 +1 @@
+.generated
diff --git a/Jenkins/Jenkinsfile b/Jenkins/Jenkinsfile
index 34332d46..167a6ba9 100644
--- a/Jenkins/Jenkinsfile
+++ b/Jenkins/Jenkinsfile
@@ -6,7 +6,7 @@ pipeline {
}
agent {
dockerfile {
- filename 'Jenkins/agent/Dockerfile'
+ filename 'Jenkins/jenkins-agent/Dockerfile'
args """--user root --network ${params.AGENT_NETWORK}
--volume /var/run/docker.sock:/var/run/docker.sock
--memory=8g --cpus=${params.AGENT_CPUS}"""
diff --git a/Jenkins/Makefile b/Jenkins/Makefile
index 4beb5ebd..d891ac8a 100644
--- a/Jenkins/Makefile
+++ b/Jenkins/Makefile
@@ -1,54 +1,169 @@
-DOCKER := docker
+include .env
+export
+
SOCKET := /var/run/docker.sock
VOLUME := jenkins_home
-.PHONY: build run bash init-pw unprotected protected start stop rm purge
+CERTBOT_CONF := $(PWD)/.generated/certbot/lib/conf
+CERTBOT_WWW := $(PWD)/.generated/certbot/lib/www
+CERTBOT_LOG := $(PWD)/.generated/certbot/log
+NGINX_LOG := $(PWD)/.generated/certbot/nginx/log
-# building the Jenkins image
+.PHONY: provision \
+ build run bash init-pw unprotected protected start stop rm purge \
+ nginx-prepare nginx-proxy nginx-start nginx-letsencrypt-init nginx-letsencrypt-timer nginx-restart nginx-stop
+
+## lists all documented targets
+help:
+ @awk '/^##/ {sub(/^## /, "", $$0); desc=$$0; next} /^[a-zA-Z0-9][^:]*:/ { \
+ print "\033[1m" $$1 "\033[0m"; \
+ print " " desc "\n" \
+ }' $(MAKEFILE_LIST)
+
+## initially, run this once to provision te nginx
+provision: nginx-prepare nginx-letsencrypt-init nginx-letsencrypt-timer nginx-start build start
+
+## removes all generated files
+clean: nginx-stop stop
+ rm -rf .generated/
+
+## builds the Jenkins image
build:
- $(DOCKER) build -t jenkins-docker .
+ docker build -t jenkins-docker .
-# initially running the Jenkins container
+## manually running the Jenkins container
run:
- $(DOCKER) run --detach \
+ docker run --detach \
--dns 8.8.8.8 \
--network bridge \
- --publish 8080:8080 --publish 50000:50000 \
+ --publish 8090:8080 --publish 50000:50000 \
--volume $(SOCKET):/var/run/docker.sock \
--volume $(VOLUME):/var/jenkins_home \
--restart unless-stopped \
--name jenkins jenkins-docker
-# (re-) starts the Jenkins container
+## manually starts the Jenkins container (again)
start:
- $(DOCKER) start jenkins
+ docker start jenkins
-# opens a bash within the Jenkins container
+## opens a bash within the Jenkins container
bash:
- $(DOCKER) exec -it jenkins bash
+ docker exec -it jenkins bash
-# prints the inital password of a newly setup Jenkins
+## prints the initial password of a newly setup Jenkins
init-pw:
- $(DOCKER) exec -it jenkins cat /var/jenkins_home/secrets/initialAdminPassword
+ docker exec -it jenkins sh -c '\
+ while [ ! -f /var/jenkins_home/secrets/initialAdminPassword ]; do \
+ sleep 1; \
+ done; \
+ cat /var/jenkins_home/secrets/initialAdminPassword \
+ '
-# disables security for the Jenkins, allows login without credentials
+## disables security for the Jenkins => allows login to Jenkins without credentials
unprotected:
docker exec -it jenkins sed -i 's|true|false|' /var/jenkins_home/config.xml
docker exec -it jenkins grep useSecurity /var/jenkins_home/config.xml
-# enables security for the Jenkins, requires login with credentials
+## enables security for the Jenkins => Jenkins requires login with credentials
protected:
docker exec -it jenkins sed -i 's|true|true|' /var/jenkins_home/config.xml
docker exec -it jenkins grep useSecurity /var/jenkins_home/config.xml
-# stops the Jenkins container
+## stops the Jenkins container
stop:
- $(DOCKER) stop jenkins
+ docker stop jenkins
-# removes the Jenkins container
+## removes the Jenkins container
rm: stop
- $(DOCKER) rm jenkins
+ docker rm jenkins
-# purges the Jenkins volume (finally deletes the configuration)
+## purges the Jenkins volume (finally deletes the configuration)
purge: rm
- $(DOCKER) volume rm $(VOLUME)
+ docker volume rm $(VOLUME)
+
+# (internal) generates the files for nginx-proxy and certbot
+nginx-prepare:
+ mkdir -p $(CERTBOT_WWW) $(CERTBOT_LOG) $(CERTBOT_CONF)/live/$(SERVER_NAME) $(NGINX_LOG)
+ chmod 755 $(CERTBOT_WWW) $(CERTBOT_LOG) $(CERTBOT_CONF)/live/$(SERVER_NAME) $(NGINX_LOG)
+ sed -e 's/%SERVER_NAME/$(SERVER_NAME)/g' .generated/nginx.conf
+ cp nginx-proxy/options-ssl-nginx.conf $(CERTBOT_CONF)/options-ssl-nginx.conf
+ chmod 644 $(CERTBOT_CONF)/options-ssl-nginx.conf
+ test -f $(CERTBOT_CONF)/ssl-dhparams.pem || curl -o $(CERTBOT_CONF)/ssl-dhparams.pem \
+ https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem
+ chmod 644 $(CERTBOT_CONF)/ssl-dhparams.pem
+ openssl req -x509 -nodes -newkey rsa:2048 \
+ -keyout $(CERTBOT_CONF)/live/$(SERVER_NAME)/privkey.pem \
+ -out /$(CERTBOT_CONF)/live/$(SERVER_NAME)/fullchain.pem \
+ -subj "/CN=dummy"
+
+## opens a bash within the Nginx-proxy container
+nginx-bash:
+ docker exec -it nginx bash
+
+# (internal) fetches an initial certificate from letsencrypt
+nginx-letsencrypt-init: nginx-start
+ # wait for nginx actually running (could be improved)
+ @sleep 5
+ # delete the previous (dummy) config to avoid file creation with suffix -0001 etc.
+ rm -rf $(CERTBOT_CONF)/etc/letsencrypt/live/$(SERVER_NAME) \
+ $(CERTBOT_CONF)/etc/letsencrypt/archive/$(SERVER_NAME) \
+ $(CERTBOT_CONF)/etc/letsencrypt/renewal/$(SERVER_NAME).conf
+ # request the certificate via letsencrypt
+ docker run --rm \
+ -v $(CERTBOT_CONF):/etc/letsencrypt \
+ -v $(CERTBOT_WWW):/var/www/certbot \
+ -v $(CERTBOT_LOG):/var/log/letsencrypt \
+ certbot/certbot \
+ certonly --webroot --webroot-path /var/www/certbot \
+ --email $(EMAIL) --cert-name $(SERVER_NAME) \
+ -d $(SERVER_NAME) --rsa-key-size 4096 \
+ --agree-tos --force-renewal
+ # restart nginx
+ docker stop nginx || true
+ docker start nginx
+
+## opens a shell in the letsencrypt certbot
+nginx-letsencrypt-sh:
+ docker run -it --rm \
+ -v $(CERTBOT_CONF):/etc/letsencrypt \
+ -v $(CERTBOT_WWW):/var/www/certbot \
+ -v $(CERTBOT_LOG):/var/log/letsencrypt \
+ --entrypoint /bin/sh \
+ certbot/certbot
+
+# (internal) installs the letsencrypt certbot timer for automatic renewal
+nginx-letsencrypt-timer:
+ @mkdir -p $(HOME)/.config/systemd/user
+ @cp nginx-proxy/nginx-letsencrypt-renew.timer $(HOME)/.config/systemd/user/nginx-letsencrypt-renew.timer
+ @cp nginx-proxy/nginx-letsencrypt-renew.service $(HOME)/.config/systemd/user/nginx-letsencrypt-renew.service
+ systemctl --user daemon-reload
+ systemctl --user enable --now nginx-letsencrypt-renew.timer
+
+## renews the cert, if already renewable - this is also called from the timer
+nginx-letsencrypt-renew:
+ docker run --rm \
+ -v $(CERTBOT_CONF):/etc/letsencrypt \
+ -v $(CERTBOT_WWW):/var/www/certbot \
+ -v $(CERTBOT_LOG):/var/log/letsencrypt \
+ certbot/certbot renew -q
+
+## starts the nginx proxy server
+nginx-start: nginx-stop
+ docker run -d --name nginx \
+ --publish 8080:80 \
+ --publish 8443:443 \
+ --network bridge \
+ -v $(CERTBOT_CONF):/etc/letsencrypt \
+ -v $(CERTBOT_WWW):/var/www/certbot \
+ -v $(NGINX_LOG):/var/log/nginx \
+ -v $(PWD)/.generated/nginx.conf:/etc/nginx/nginx.conf \
+ nginx
+
+## restarts the nginx proxy server
+nginx-restart: nginx-stop nginx-start
+
+## stops the nginx proxy server
+nginx-stop:
+ docker stop nginx || true
+ docker rm nginx || true
+
diff --git a/Jenkins/README.md b/Jenkins/README.md
new file mode 100644
index 00000000..9b5422c9
--- /dev/null
+++ b/Jenkins/README.md
@@ -0,0 +1,16 @@
+# Jenkins Build+Test-Pipeline with NGINX HTTPS-Proxy and Letsencrypt
+
+The scripts work in a Hostsharing Managed Docker environment.
+
+Requires a .env file like this in the current directory:
+
+```
+SERVER_NAME=jenkins.example.org
+EMAIL=contact@example.org
+```
+
+Then run `make provision` to initialize everything.
+
+Run `make help` for more information.
+
+WARNING: Provisioning does not really work yet, needs some manual restarts.
diff --git a/Jenkins/agent/Dockerfile b/Jenkins/jenkins-agent/Dockerfile
similarity index 100%
rename from Jenkins/agent/Dockerfile
rename to Jenkins/jenkins-agent/Dockerfile
diff --git a/Jenkins/nginx-proxy/nginx-letsencrypt-renew.service b/Jenkins/nginx-proxy/nginx-letsencrypt-renew.service
new file mode 100644
index 00000000..a327a7c5
--- /dev/null
+++ b/Jenkins/nginx-proxy/nginx-letsencrypt-renew.service
@@ -0,0 +1,8 @@
+[Unit]
+Description=Renew Let's Encrypt certs via Make
+
+[Service]
+Type=oneshot
+WorkingDirectory=%h/hs.hsadmin.ng/Jenkins
+ExecStart=/usr/bin/make nginx-letsencrypt-renew
+
diff --git a/Jenkins/nginx-proxy/nginx-letsencrypt-renew.timer b/Jenkins/nginx-proxy/nginx-letsencrypt-renew.timer
new file mode 100644
index 00000000..cc02c9bb
--- /dev/null
+++ b/Jenkins/nginx-proxy/nginx-letsencrypt-renew.timer
@@ -0,0 +1,11 @@
+[Unit]
+Description=Run cert renew Make target at ~5 AM daily
+
+[Timer]
+OnCalendar=05:05
+RandomizedDelaySec=20m
+Persistent=true
+
+[Install]
+WantedBy=timers.target
+
diff --git a/Jenkins/nginx-proxy/nginx.conf b/Jenkins/nginx-proxy/nginx.conf
new file mode 100644
index 00000000..bf0a6864
--- /dev/null
+++ b/Jenkins/nginx-proxy/nginx.conf
@@ -0,0 +1,40 @@
+events {}
+
+http {
+ server {
+ listen 80;
+ server_name %SERVER_NAME;
+
+ # directly answer initial certbot request
+ location /.well-known/acme-challenge/ {
+ root /var/www/certbot;
+ }
+
+ # forward all other HTTP-requests to HTTPS
+ location / {
+ return 301 https://$host$request_uri;
+ }
+ }
+
+ server {
+ listen 443 ssl;
+ server_name %SERVER_NAME;
+
+ ssl_certificate /etc/letsencrypt/live/%SERVER_NAME/fullchain.pem;
+ ssl_certificate_key /etc/letsencrypt/live/%SERVER_NAME/privkey.pem;
+ include /etc/letsencrypt/options-ssl-nginx.conf;
+
+ location /.well-known/acme-challenge/ {
+ root /var/www/certbot;
+ }
+
+ location / {
+ proxy_pass http://%SERVER_NAME:8090;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ }
+ }
+}
+
diff --git a/Jenkins/nginx-proxy/options-ssl-nginx.conf b/Jenkins/nginx-proxy/options-ssl-nginx.conf
new file mode 100644
index 00000000..35296eea
--- /dev/null
+++ b/Jenkins/nginx-proxy/options-ssl-nginx.conf
@@ -0,0 +1,7 @@
+ssl_session_cache shared:le_nginx_SSL:1m;
+ssl_session_timeout 1440m;
+ssl_protocols TLSv1.2 TLSv1.3;
+ssl_prefer_server_ciphers off;
+
+ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:...";
+ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;