From 23651e0e2eb3cbc29804c6c54650226d852a3503 Mon Sep 17 00:00:00 2001 From: Frank Agerholm Date: Sun, 18 Jan 2026 19:11:46 +0100 Subject: [PATCH 1/2] Download Let's Encrypt CA-Certificates --- Dockerfile | 8 +++ bin/fetch_letsencrypt_ca_certs.py | 109 ++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100755 bin/fetch_letsencrypt_ca_certs.py diff --git a/Dockerfile b/Dockerfile index 561e2bd..ea8ac0d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,9 @@ +FROM docker.io/library/python:3.11-alpine AS cabuilder +RUN apk add --no-cache ca-certificates +ADD bin/fetch_letsencrypt_ca_certs.py /usr/local/bin/fetch_letsencrypt_ca_certs.py +RUN /usr/local/bin/fetch_letsencrypt_ca_certs.py /usr/local/share/ca-certificates/ +RUN update-ca-certificates + FROM docker.io/library/python:3.11-alpine RUN apk --no-cache add sshpass openssh-client lftp nodejs npm; mkdir -p /opt/app @@ -5,9 +11,11 @@ WORKDIR /opt/app ADD requirements.txt /root/ ADD Dockerfile /root/ ADD bin/*.sh /usr/local/bin/ +COPY --from=cabuilder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt # ignore "Running pip as root" warning ENV PIP_ROOT_USER_ACTION=ignore +ENV REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt RUN chmod 755 /usr/local/bin/*.sh && pip install -r /root/requirements.txt && pip freeze && rm -Rf "$(pip cache dir)" diff --git a/bin/fetch_letsencrypt_ca_certs.py b/bin/fetch_letsencrypt_ca_certs.py new file mode 100755 index 0000000..85eb8f3 --- /dev/null +++ b/bin/fetch_letsencrypt_ca_certs.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 + +import os +import sys +import requests +from html.parser import HTMLParser +from urllib.parse import urljoin + +LETSENCRYPT_CERT_PAGE = "https://letsencrypt.org/certificates/" +DEFAULT_TARGET_DIR = "./letsencrypt-ca" + + +class PemLinkParser(HTMLParser): + def __init__(self): + super().__init__() + self.pem_links = set() + + def handle_starttag(self, tag, attrs): + if tag.lower() != "a": + return + for attr, value in attrs: + if attr == "href" and value.lower().endswith(".pem"): + self.pem_links.add(value) + + +def fetch_certificate_page(): + resp = requests.get(LETSENCRYPT_CERT_PAGE, timeout=15) + resp.raise_for_status() + return resp.text + + +def extract_pem_links(html): + parser = PemLinkParser() + parser.feed(html) + return sorted( + urljoin(LETSENCRYPT_CERT_PAGE, link) + for link in parser.pem_links + ) + + +def download_and_store(url, target_dir): + base_name = os.path.basename(url) + # Zielname = .crt statt .pem + target_name = os.path.splitext(base_name)[0] + ".crt" + target_path = os.path.join(target_dir, target_name) + + if os.path.exists(target_path): + print(f"[=] Bereits vorhanden: {target_name}") + return target_name + + print(f"[+] Lade herunter: {base_name}") + resp = requests.get(url, timeout=15) + resp.raise_for_status() + + if b"BEGIN CERTIFICATE" not in resp.content: + print(f"[!] Ungültiges Zertifikat: {base_name}") + return None + + # Direkt als .crt speichern (PEM-Inhalt) + with open(target_path, "wb") as f: + f.write(resp.content) + + return target_name + + +def list_local_crt_files(target_dir): + return { + f for f in os.listdir(target_dir) + if f.lower().endswith(".crt") + } + + +def main(): + target_dir = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_TARGET_DIR + os.makedirs(target_dir, exist_ok=True) + + print(f"[*] Zielverzeichnis: {os.path.abspath(target_dir)}") + print("[*] Format: PEM-Inhalt mit .crt-Endung") + + html = fetch_certificate_page() + pem_urls = extract_pem_links(html) + + # Remote-Dateinamen im .crt-Format + remote_files = { + os.path.splitext(os.path.basename(url))[0] + ".crt" + for url in pem_urls + } + + for url in pem_urls: + download_and_store(url, target_dir) + + local_files = list_local_crt_files(target_dir) + orphaned = sorted(local_files - remote_files) + + print("\n" + "=" * 60) + if orphaned: + print("[!] Lokal vorhanden, aber nicht mehr gelistet:") + for f in orphaned: + print(f" - {f}") + else: + print("[✓] Keine veralteten Dateien gefunden") + print("=" * 60) + print("[✓] Fertig.") + + +if __name__ == "__main__": + main() + + From 8333305c55d521fd2d2628204d40869d86cd56cf Mon Sep 17 00:00:00 2001 From: Frank Agerholm Date: Sun, 18 Jan 2026 19:31:23 +0100 Subject: [PATCH 2/2] =?UTF-8?q?Gitea=20Workflow=20eingef=C3=BCgt.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/container.yml | 43 ++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .gitea/workflows/container.yml diff --git a/.gitea/workflows/container.yml b/.gitea/workflows/container.yml new file mode 100644 index 0000000..b6d0758 --- /dev/null +++ b/.gitea/workflows/container.yml @@ -0,0 +1,43 @@ +name: Build and Push Docker Image + +on: + push: + branches: [ main ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Login to Registry + uses: docker/login-action@v2 + with: + registry: ${GITHUB_SERVER_URL} + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_PASSWORD }} + + - name: Build Docker Image + env: + BRANCH_NAME: ${{ github.ref_name }} + SHORT_HASH: ${{ github.sha }} + run: | + # Build the image with the commit hash tag + docker build --build-arg BUILD_IDENTIFIER=${SHORT_HASH:0:5} -t ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}:${BRANCH_NAME}-${SHORT_HASH:0:5} . + + # Tag the same image as "latest" + docker tag ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}:${BRANCH_NAME}-${SHORT_HASH:0:5} ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}:${BRANCH_NAME}-latest + + - name: Push Docker Images + env: + BRANCH_NAME: ${{ github.ref_name }} + SHORT_HASH: ${{ github.sha }} + run: | + docker push ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}:${BRANCH_NAME}-${SHORT_HASH:0:5} + docker push ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}:${BRANCH_NAME}-latest + + - name: Log out from registry + if: always() + run: docker logout ${GITHUB_SERVER_URL} +