From e3e32d4046b8c2f837f13841ddad3f341d91eef7 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sun, 22 Nov 2020 22:57:24 +0100 Subject: [PATCH] First version --- Dockerfile | 25 ++++++++++++ README.md | 66 ++++++++++++++++++++++++++++++++ docker-hook | 104 ++++++++++++++++++++++++++++++++++++++++++++++++++ entrypoint.sh | 8 ++++ nginx.conf | 25 ++++++++++++ update.sh | 6 +++ 6 files changed, 234 insertions(+) create mode 100644 Dockerfile create mode 100644 README.md create mode 100755 docker-hook create mode 100755 entrypoint.sh create mode 100644 nginx.conf create mode 100755 update.sh diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2b51b08 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,25 @@ +FROM python:3-alpine + +RUN apk --update add git nginx && \ + rm /var/cache/apk/* + +RUN pip install requests --no-cache-dir + +RUN mkdir /hook +RUN mkdir -p /var/www/html + +# Configure nginx +RUN mkdir /run/nginx +RUN ln -sf /dev/stdout /var/log/nginx/access.log && ln -sf /dev/stderr /var/log/nginx/error.log +COPY nginx.conf /etc/nginx/conf.d/mkdocs-server.conf +RUN rm /etc/nginx/conf.d/default.conf + +COPY ./entrypoint.sh /hook +COPY ./docker-hook /hook +COPY ./update.sh /hook + +WORKDIR /var/www/html + +ENTRYPOINT ["/hook/entrypoint.sh"] + +EXPOSE 80 diff --git a/README.md b/README.md new file mode 100644 index 0000000..5bf7f51 --- /dev/null +++ b/README.md @@ -0,0 +1,66 @@ +# Serveur Nginx statique auto-mis à jour via Git + +Cette image Docker permet la mise en place d'un serveur Nginx dont les sources sont un +site statique. + +Cependant, cette image offre l'avantage de mettre à jour automatiquement le site dès lors +que la branche `master` du dépôt Git est modifiée. + +## Configuration + +On désignera par `www.exemple.com` le site sur lequel vous souhaitez héberger votre site. +Sur votre dépôt Git, ajoutez un nouveau webhook déclenché à chaque événement `push` +(cela suffit normalement) vers l'adresse `https://www.exemple.com/trigger-ci.json`. + +Il vous suffit ensuite de lancer l'image Docker en renseignant la variable d'environnement +`MKDOCS_SERVER_GIT_URL` par l'adresse de clonage **HTTPS** du dépôt Git. Par exemple, pour +ce dépôt, il s'agit de l'adresse `https://gitlab.com/animath/si/mkdocs-server-from-git.git`. + +Un exemple : + +```bash +docker run -e NGINX_SERVER_GIT_URL=https://gitlab.com/me/mon-super-projet-nginx.git -p 8000:80 ynerant/nginx-server-from-git +``` + +Le résultat s'observe sur `http://localhost:8000`. + +Il est recommandé d'utiliser `docker-compose` : + +```yaml + nginx-static-server: + image: ynerant/nginx-server-from-git + ports: + - 8000:80 + environment: + - NGINX_SERVER_GIT_URL=https://gitlab.com/me/mon-super-projet.git +``` + +Si vous utilisez Traefik comme reverse-proxy : + +```yaml + nginx-static-server: + image: ynerant/nginx-server-from-git + restart: always + environment: + - NGINX_SERVER_GIT_URL=https://gitlab.com/me/mon-super-projet.git + labels: + - "traefik.enable=true" + - "traefik.http.routers.nginx-static-server.rule=Host(`www.exemple.com`)" + - "traefik.http.routers.nginx-static-server.entrypoints=websecure" + - "traefik.http.routers.nginx-static-server.tls.certresolver=mytlschallenge" +``` + +À vous bien sûr d'adapter la configuration comme bon vous semble. + +Enjoy :) + +## Fonctionnement + +L'image contient un serveur Nginx avec deux entrées : un serveur classique desservant +le dossier `/var/www/html` et une entrée écoutant sur `/trigger-ci.json` écrite en Python +afin de récupérer l'événement de `push` envoyé par le serveur Git. + +À chaque push, le script `update.sh` est exécuté, visant simplement à effectuer un +`git pull` afin de mettre à jour les fichiers. + +Le dossier `.git` n'est pas desservi pour des raisons de sécurité. diff --git a/docker-hook b/docker-hook new file mode 100755 index 0000000..aa472a2 --- /dev/null +++ b/docker-hook @@ -0,0 +1,104 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- + +"""Automatic Docker Deployment via Webhooks.""" + +import json +import os +from subprocess import Popen +from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter +from http.server import HTTPServer +from http.server import BaseHTTPRequestHandler +import sys +import logging +import requests + +logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', + level=logging.DEBUG, + stream=sys.stdout) + +class RequestHandler(BaseHTTPRequestHandler): + """A POST request handler which expects a token in its path.""" + + def do_POST(self): + try: + logging.info("Path: %s", self.path) + header_length = int(self.headers.get('content-length', "0")) + json_payload = self.rfile.read(header_length) + env = dict(os.environ) + json_params = {} + if len(json_payload) > 0: + json_params = json.loads(json_payload) + env.update(('REPOSITORY_' + var.upper(), str(val)) + for var, val in json_params['repository'].items()) + + logging.info("Start executing '%s'" % args.cmd) + try: + Popen(args.cmd, env=env).wait() + self.send_response(200, "OK") + if 'callback_url' in json_params: + # Make a callback to Docker Hub + data = {'state': 'success'} + headers = {'Content-type': 'application/json', + 'Accept': 'text/plain'} + requests.post(json_params['callback_url'], + data=json.dumps(data), + headers=headers) + except OSError as err: + self.send_response(500, "OSError") + logging.error("You probably didn't use 'sh ./script.sh'.") + logging.error(err) + if 'callback_url' in json_params: + # Make a callback to Docker Hub + data = {'state': 'failure', + 'description': str(err)} + headers = {'Content-type': 'application/json', + 'Accept': 'text/plain'} + requests.post(json_params['callback_url'], + data=json.dumps(data), + headers=headers) + self.end_headers() + except Exception as e: + logging.error(e) + + +def get_parser(): + """Get a command line parser for docker-hook.""" + parser = ArgumentParser(description=__doc__, + formatter_class=ArgumentDefaultsHelpFormatter) + + parser.add_argument("-t", "--token", + dest="token", + required=False, + help=("Secure auth token (can be choosen " + "arbitrarily)")) + parser.add_argument("-c", "--cmd", + dest="cmd", + required=True, + nargs="*", + help="Command to execute when triggered") + parser.add_argument("--addr", + dest="addr", + default="0.0.0.0", + help="address where it listens") + parser.add_argument("--port", + dest="port", + type=int, + default=8555, + metavar="PORT", + help="port where it listens") + return parser + + +def main(addr, port): + """Start a HTTPServer which waits for requests.""" + httpd = HTTPServer((addr, port), RequestHandler) + httpd.serve_forever() + +if __name__ == '__main__': + parser = get_parser() + if len(sys.argv) == 1: + parser.print_help() + sys.exit(1) + args = parser.parse_args() + main(args.addr, args.port) diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 0000000..56548c0 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +cd /var/www/html +[ -d .git ] || git clone ${NGINX_SERVER_GIT_URL} . + +git pull +nginx& +python /hook/docker-hook -c sh /hook/update.sh diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..02c534a --- /dev/null +++ b/nginx.conf @@ -0,0 +1,25 @@ +upstream trigger-ci { + server 127.0.0.1:8555; +} + +server { + listen 80; + server_name static-server; + root /var/www/html; + error_page 404 /404.html; + + location /trigger-ci.json { + proxy_pass http://trigger-ci; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $host; + proxy_redirect off; + } + + location /.git* { + return 404; + } + + location /README* { + return 404; + } +} diff --git a/update.sh b/update.sh new file mode 100755 index 0000000..467f974 --- /dev/null +++ b/update.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +old_pwd=$PWD +cd /var/www/html +git pull +export PWD=old_pwd