Notizen zur Installation und dem Setup eines neuen Doku-Projekts¶
Bootstrap von Sphinx¶
~ $ virtualenv venv
created virtual environment CPython3.9.13.final.0-64 in 6768ms
...
~ $ . venv/bin/activate
(venv) ~ $ pip install Sphinx
Collecting Sphinx
...
Installing collected packages: s........
Successfully installed ...
WARNING: You are using pip version 22.0.4; however, version 22.2.2 is available.
You should consider upgrading via the '/home/LOCAL_USER/venv/bin/python -m pip install --upgrade pip' command.
(venv) ~ $ sphinx-quickstart MY_projekt
Welcome to the Sphinx 5.2.3 quickstart utility.
Please enter values for the following settings (just press Enter to
accept a default value, if one is given in brackets).
Selected root path:MY_projekt
You have two options for placing the build directory for Sphinx output.
Either, you use a directory "_build" within the root path, or you separate
"source" and "build" directories within the root path.
> Separate source and build directories (y/n) [n]: y
The project name will occur in several places in the built documentation.
> Project name: Mein Doku Projekt
Creating file /home/LOCAL_USER/my_projekt/source/conf.py.
Creating file /home/LOCAL_USER/my_projekt/source/index.rst.
Creating file /home/LOCAL_USER/my_projekt/Makefile.
Creating file /home/LOCAL_USER/my_projekt/make.bat.
Finished: An initial directory structure has been created.
You should now populate your master file /home/LOCAL_USER/my_projekt/source/index.rst and create other documentation
source files. Use the Makefile to build the docs, like so:
make builder
where "builder" is one of the supported builders, e.g. html, latex or linkcheck.
Vergleiche auch https://www.sphinx-doc.org/en/master/tutorial/index.html
Einchecken in Gitea¶
Ein Repository my_projekt anlegen
Einchecken:
~ $ cd MY_projekt
~/my_projekt $ git init
Initialized empty Git repository in /home/LOCAL_USER/my_projekt/.git/
~/my_projekt $ git add *
~/my_projekt $ git commit -m "initial commit"
[main (root-commit) 43a000b] initial commit
4 files changed, 102 insertions(+)
create mode 100644 Makefile
create mode 100644 make.bat
create mode 100644 source/conf.py
create mode 100644 source/index.rst
~/my_projekt $ git remote add origin https://MY_GIT_HOSTNAME/gitea/MY_GIT_USER/my_projekt.git
~/my_projekt $ git push origin main
Password for 'https://MY_GIT_USER@MY_GIT_HOSTNAME':
Enumerating objects: 7, done.
Counting objects: 100% (7/7), done.
Delta compression using up to 8 threads
Compressing objects: 100% (7/7), done.
Writing objects: 100% (7/7), 1.72 KiB | 1.72 MiB/s, done.
Total 7 (delta 0), reused 0 (delta 0), pack-reused 0
remote: . Processing 1 references
remote: Processed 1 references in total
To https://MY_GIT_HOSTNAME/gitea/MY_GIT_USER/my_projekt.git
* [new branch] main -> main
Konfigurieren des Webhook¶
Ziel ist es, dass bei einem Checkin per git in Gitea automatisch auf dem Webserver ein Checkout gemacht und ein Build gestartet wird (ähnlich wie ein Github-Runner).
Im Projekt¶
Im Projekt einen Folder webhook anlegen und zwei Dateien hinterlegen:
1#!/usr/bin/env python3
2
3# credits to: https://github.com/DavesCodeMusings/tea-runner
4# curl -X POST --data-binary '{}' -H "Content-Type: application/json" $URL
5
6"""
7 Run tasks based on webhooks configured in Gitea.
8
9 Command-line options:
10 --debug, -d Send more detailed log output to console.
11
12 Configuration file (config.ini) options:
13
14 [runner]
15 ALLOWED_IP_RANGE=xxx.xxx.xxx.xxx/mm
16 # Only respond to requests made from this range of IP addresses. Eg. 192.168.1.0/24
17 LISTEN_IP=xxx.xxx.xxx.xxx
18 # IP address for incoming requests. Defaults to 0.0.0.0 (Any).
19 LISTEN_PORT=xxxx
20 # TCP port number used for incoming requests. Defaults to 1706.
21
22 BASE_DIR=xxx
23 # Location of an existing repository
24"""
25
26GIT_BIN = '/usr/bin/git'
27MAKE_BIN = '/usr/bin/make'
28
29import logging
30from argparse import ArgumentParser
31from configparser import ConfigParser
32from flask import Flask, request, jsonify
33from werkzeug import utils
34from waitress import serve
35from tempfile import TemporaryDirectory
36from os import access, X_OK, chdir, path
37from sys import exit
38from subprocess import run, DEVNULL
39from os import path
40from ipaddress import ip_address, ip_network
41
42print("Tea Runner")
43
44# Debug is a command-line option, but most configuration comes from config.ini
45arg_parser = ArgumentParser()
46arg_parser.add_argument('-d', '--debug', action='store_true', help='display debugging output while running')
47args = arg_parser.parse_args()
48
49if args.debug:
50 logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.DEBUG)
51else:
52 logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.INFO)
53
54config = ConfigParser()
55config.read('config.ini')
56
57if not access(GIT_BIN, X_OK):
58 logging.error("git binary not found or not executable")
59 exit(1)
60
61if not access(MAKE_BIN, X_OK):
62 logging.error(f"{MAKE_BIN}: binary not found or not executable")
63 exit(1)
64
65
66def git_clone(src_url, dest_dir):
67 """
68 Clone a remote git repository into a local directory.
69 :src_url (string): HTTP(S) url used to clone the repo.
70 :dest_dir (string): Path to the local directory.
71 :returns (boolean): True if command returns success.
72 """
73
74 logging.info('git clone ' + src_url)
75 chdir(dest_dir)
76 clone_result = run([GIT_BIN, 'clone', src_url, '.'],
77 stdout=None if args.debug else DEVNULL, stderr=None if args.debug else DEVNULL)
78 return clone_result.returncode == 0
79
80
81def git_pull(src_url, dest_dir):
82 """
83 Pull a remote git repository into a local directory.
84 :src_url (string): HTTP(S) url used to clone the repo.
85 :dest_dir (string): Path to the local directory.
86 :returns (boolean): True if command returns success.
87 """
88
89 logging.info('git pull ' + src_url)
90 chdir(dest_dir)
91 clone_result = run([GIT_BIN, 'pull'],
92 stdout=None if args.debug else DEVNULL, stderr=None if args.debug else DEVNULL)
93 return clone_result.returncode == 0
94
95
96app = Flask(__name__)
97
98
99@app.before_request
100def check_authorized():
101 """
102 Only respond to requests from ALLOWED_IP_RANGE if it's configured in config.ini
103 """
104
105 if config.has_option('runner', 'ALLOWED_IP_RANGE'):
106 allowed_ip_range = ip_network(config['runner']['ALLOWED_IP_RANGE'])
107 requesting_ip = ip_address(request.remote_addr)
108 if requesting_ip not in allowed_ip_range:
109 logging.info('Dropping request from unauthorized host ' + request.remote_addr)
110 return jsonify(status='forbidden'), 403
111 else:
112 logging.info('Request from ' + request.remote_addr)
113
114
115@app.before_request
116def check_media_type():
117 """
118 Only respond requests with Content-Type header of application/json
119 """
120 if not request.headers.get('Content-Type').lower().startswith('application/json'):
121 logging.error('"Content-Type: application/json" header missing from request made by ' + request.remote_addr)
122 return jsonify(status='unsupported media type'), 415
123
124
125@app.route('/test', methods=['POST'])
126def test():
127 logging.debug('Content-Type: ' + request.headers.get('Content-Type'))
128 logging.debug(request.get_json(force=True))
129 return jsonify(status='success', sender=request.remote_addr)
130
131
132@app.route('/sphinx/build', methods=['POST'])
133def sphinx_build():
134 body = request.get_json()
135 logging.info('Working dir: ' + config.get('runner', 'BASE_DIR', fallback='/tmp'))
136 work_dir = config.get('runner', 'BASE_DIR', fallback='/tmp')
137 if git_pull(body['repository']['clone_url'], work_dir):
138 logging.info('sphinx build')
139 chdir(work_dir)
140 result = run([MAKE_BIN, 'html'],
141 stdout=None if args.debug else DEVNULL, stderr=None if args.debug else DEVNULL)
142 if result.returncode != 0:
143 return jsonify(status='sphinx build failed'), 500
144 else:
145 return jsonify(status='git pull failed'), 500
146
147 return jsonify(status='success')
148
149
150if __name__ == '__main__':
151 logging.info('Limiting requests to: ' + config.get('runner', 'ALLOWED_IP_RANGE', fallback='<any>'))
152 serve(app, host=config.get('runner', 'LISTEN_IP', fallback='0.0.0.0'),
153 port=config.getint('runner', 'LISTEN_PORT', fallback=1706))
[runner]
ALLOWED_IP_RANGE = 192.168.xx.xx/24
LISTEN_PORT = xxxx
BASE_DIR = /www/WEBSERVER_DIR/my_projekt
Bemerkung
Großen Dank an https://github.com/DavesCodeMusings/tea-runner! Das Projekt hat als Vorlage für meinen tea_runner gedient. An Stelle des Docker-Builds ist hier ein Sphinx-Build getreten.
Auf dem Server¶
manueller Checkout des Projekts in das WEBSERVER_DIR
Anlegen eines Python-Virtualenv / venv
Erzeugen eines ssh-Keys (z.B. id_gitrunner)
SSH-Command für git, um den Key einzustellen:
git config core.sshCommand "ssh -i ....../id_gitrunner "
Starten des Webhook-Servers (tea_runner.py):
cd /www/WEBSERVER_DIR/my_projekt/
source venv/bin/activate
cd webhook
nohup python tea_runner.py -d > ../logs/webhook.log 2>&1 &
Gitea konfigurieren¶
Im Gitea-Projekt unter Einstellungen > Webhooks einen neuen Webhook erstellen. URL ist die des gestarteten Tearunner-Dient auf dem Server