diff --git a/PORT_MAP.md b/PORT_MAP.md index a1725cb..40f6d0a 100644 --- a/PORT_MAP.md +++ b/PORT_MAP.md @@ -45,6 +45,8 @@ Rule of thumb: keep ports below 10000 free for stuff that reserves ports. | 22060 | pretalx | gunicorn | | 22070 | paperless-ng | gunicorn | | 22080 | netbox | gunicorn | +| 22100 | woodpecker-server | http | +| 22101 | woodpecker-server | gRPC | | 22999 | nginx | stub_status | | 22100 | ntfy | http | diff --git a/bundles/woodpecker-server/files/woodpecker-server.service b/bundles/woodpecker-server/files/woodpecker-server.service new file mode 100644 index 0000000..5520b49 --- /dev/null +++ b/bundles/woodpecker-server/files/woodpecker-server.service @@ -0,0 +1,19 @@ +[Unit] +Description=woodpecker ci +After=syslog.target +After=network.target +Requires=postgresql.service + +[Service] +RestartSec=2s +Type=simple +User=woodpecker +Group=woodpecker +ExecStart=/usr/local/bin/woodpecker-server +Restart=always +% for k, v in sorted(env.items()): +Environment=${k}=${v} +% endfor + +[Install] +WantedBy=multi-user.target diff --git a/bundles/woodpecker-server/items.py b/bundles/woodpecker-server/items.py new file mode 100644 index 0000000..cccbb8c --- /dev/null +++ b/bundles/woodpecker-server/items.py @@ -0,0 +1,35 @@ +version = node.metadata.get('woodpecker-server/version') + +actions['install_woodpecker-server'] = { + 'command': ' && '.join([ + f'wget -q -O/tmp/woodpecker-server.deb https://github.com/woodpecker-ci/woodpecker/releases/download/v{version}/woodpecker-server_{version}_amd64.deb', + 'dpkg -i /tmp/woodpecker-server.deb', + ]), + 'unless': f'''bash -c "[[ \"$(woodpecker-server --version | cut -d' ' -f3)\" == "{version}" ]]"''', + 'triggers': { + 'svc_systemd:woodpecker-server:restart', + }, +} + +files['/usr/local/lib/systemd/system/woodpecker-server.service'] = { + 'content_type': 'mako', + 'context': { + 'env': node.metadata.get('woodpecker-server/environment'), + }, + 'triggers': { + 'action:systemd-reload', + 'svc_systemd:woodpecker-server:restart', + }, +} + +svc_systemd['woodpecker-server'] = { + 'needs': { + 'action:install_woodpecker-server', + 'file:/usr/local/lib/systemd/system/woodpecker-server.service', + 'postgres_db:woodpecker', + 'postgres_role:woodpecker', + 'user:woodpecker', + }, +} + +users['woodpecker'] = {} diff --git a/bundles/woodpecker-server/metadata.py b/bundles/woodpecker-server/metadata.py new file mode 100644 index 0000000..b98c89a --- /dev/null +++ b/bundles/woodpecker-server/metadata.py @@ -0,0 +1,94 @@ +from bundlewrap.metadata import atomic + +defaults = { + 'postgresql': { + 'roles': { + 'woodpecker': { + 'password': repo.vault.password_for(f'{node.name} postgresql woodpecker'), + }, + }, + 'databases': { + 'woodpecker': { + 'owner': 'woodpecker', + }, + }, + }, + 'woodpecker-server': { + 'environment': { + 'WOODPECKER_AGENT_SECRET': repo.vault.password_for(f'{node.name} WOODPECKER_AGENT_SECRET'), + 'WOODPECKER_DATABASE_DATASOURCE': repo.vault.password_for(f'{node.name} postgresql woodpecker').format_into( + 'postgres://woodpecker:{}@localhost/woodpecker?sslmode=disable' + ), + 'WOODPECKER_DATABASE_DRIVER': 'postgres', + 'WOODPECKER_GRPC_ADDR': ':22101', + 'WOODPECKER_LOG_LEVEL': 'warn', + 'WOODPECKER_OPEN': 'true', + 'WOODPECKER_SERVER_ADDR': ':22100', + }, + }, +} + + +@metadata_reactor.provides( + 'nginx/vhosts/woodpecker-server', + 'woodpecker-server/environment/WOODPECKER_HOST', +) +def nginx(metadata): + if not node.has_bundle('nginx'): + raise DoNotRunAgain + + ssl = metadata.get('nginx/vhosts/woodpecker-server/ssl', 'letsencrypt') + domain = metadata.get('woodpecker-server/domain') + prefix = 'https' if ssl else 'http' + + return { + 'nginx': { + 'vhosts': { + 'woodpecker-server': { + 'domain': domain, + 'locations': { + '/': { + 'target': 'http://127.0.0.1:22100', + 'additional_config': { + 'proxy_redirect off', + 'chunked_transfer_encoding off', + }, + }, + '/metrics': { + 'return': 403, + }, + '/debug': { + 'return': 403, + }, + }, + 'website_check_path': '/do-login', + 'website_check_string': 'Woodpecker', + }, + }, + }, + 'woodpecker-server': { + 'environment': { + 'WOODPECKER_HOST': f'{prefix}://{domain}', + }, + }, + } + + +@metadata_reactor.provides( + 'firewall/port_rules', +) +def firewall(metadata): + port = metadata.get('woodpecker-server/environment/WOODPECKER_GRPC_ADDR')[1:] + agents = set() + + for node in repo.nodes: + if node.has_bundle('woodpecker-agent'): + agents.add(node.name) + + return { + 'firewall': { + 'port_rules': { + port: atomic(agents), + }, + }, + } diff --git a/nodes/rx300.py b/nodes/rx300.py index f5e1c71..1a6ed40 100644 --- a/nodes/rx300.py +++ b/nodes/rx300.py @@ -35,6 +35,7 @@ nodes['rx300'] = { 'travelynx', 'unbound', 'vmhost', + 'woodpecker-server', 'zfs', }, 'groups': { @@ -334,6 +335,7 @@ nodes['rx300'] = { 'netbox': {'ssl': '_.franzi.business'}, 'radicale': {'ssl': '_.franzi.business'}, 'travelynx': {'ssl': '_.franzi.business'}, + 'woodpecker-server': {'ssl': '_.franzi.business'}, 'daskritzelt-redirect': { 'domain': 'die-brontosaurier-waren-es.org', 'ssl': None, @@ -535,6 +537,16 @@ nodes['rx300'] = { 'enable_linger': True, }, }, + 'woodpecker-server': { + 'domain': 'woodpecker.franzi.business', + 'version': '0.15.5', + 'environment': { + 'WOODPECKER_GITEA': 'true', + 'WOODPECKER_GITEA_URL': 'https://git.franzi.business', + 'WOODPECKER_GITEA_CLIENT': vault.decrypt('encrypt$gAAAAABjpJJQkNyG2B2ThT5yrkGnrPoM33bVYNTyLcuaas4_7ewBRrDb-KO2-JIM895fdI6U6NO8wHQ3gKBxBBYUtt-xgbWW1j4iUrzyt7KhqswSNBIBFfce80UmQ5UuOHsaFPVyyd1W'), + 'WOODPECKER_GITEA_SECRET': vault.decrypt('encrypt$gAAAAABjpJJW95MaCPnK2ngkGf1DLBmV8Y_K6B0Dc8XBM4oN3sPHH54vFbKB1YLODepR-okpXUJGHxqlS7TkTlu4JylRINXiIh7OHRRDaTCkU_bfLSUDnc_VLgDmVULWH09fsveslKw5v1ssl-RBGJg16XXBz1Sq4g=='), + }, + }, 'zfs': { 'module_options': { 'zfs_arc_max_gb': 16,