Compare commits
19 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
a545c66c3e |
|||
|
9d59f0b318 |
|||
|
78a76a5a3f |
|||
|
eada22fb21 |
|||
|
9c9850395f |
|||
|
4de4c398c6 |
|||
|
60231cb20d |
|||
|
dc3ab7e3c0 |
|||
|
943f488bbe |
|||
|
497468c8e9 |
|||
|
ecba05f9e7 |
|||
|
8333305c55 |
|||
|
23651e0e2e |
|||
|
8c13a92fa3 |
|||
|
9506e727ad |
|||
|
ce035d9186 |
|||
|
a85e2a090e |
|||
|
ce60b815c9 |
|||
|
c972a5982e |
7 changed files with 270 additions and 14 deletions
45
.gitea/workflows/container.yml
Normal file
45
.gitea/workflows/container.yml
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
name: Build and Push Docker Image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main, dev ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
env:
|
||||||
|
GIT_SSL_NO_VERIFY: true
|
||||||
|
|
||||||
|
- name: Login to Registry
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: ${{ vars.REGISTRY_HOST }}
|
||||||
|
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 ${{ vars.REGISTRY_HOST }}/${GITHUB_REPOSITORY}:${BRANCH_NAME}-${SHORT_HASH:0:5} .
|
||||||
|
|
||||||
|
# Tag the same image as "latest"
|
||||||
|
docker tag ${{ vars.REGISTRY_HOST }}/${GITHUB_REPOSITORY}:${BRANCH_NAME}-${SHORT_HASH:0:5} ${{ vars.REGISTRY_HOST }}/${GITHUB_REPOSITORY}:${BRANCH_NAME}-latest
|
||||||
|
|
||||||
|
- name: Push Docker Images
|
||||||
|
env:
|
||||||
|
BRANCH_NAME: ${{ github.ref_name }}
|
||||||
|
SHORT_HASH: ${{ github.sha }}
|
||||||
|
run: |
|
||||||
|
docker push ${{ vars.REGISTRY_HOST }}/${GITHUB_REPOSITORY}:${BRANCH_NAME}-${SHORT_HASH:0:5}
|
||||||
|
docker push ${{ vars.REGISTRY_HOST }}/${GITHUB_REPOSITORY}:${BRANCH_NAME}-latest
|
||||||
|
|
||||||
|
- name: Log out from registry
|
||||||
|
if: always()
|
||||||
|
run: docker logout ${{ vars.REGISTRY_HOST }}
|
||||||
|
|
||||||
|
|
@ -27,6 +27,8 @@ test:
|
||||||
stage: test
|
stage: test
|
||||||
image: "$CI_REGISTRY_IMAGE:job-$CI_PIPELINE_ID"
|
image: "$CI_REGISTRY_IMAGE:job-$CI_PIPELINE_ID"
|
||||||
script:
|
script:
|
||||||
|
- node --version
|
||||||
|
- npm --version
|
||||||
- python --version
|
- python --version
|
||||||
- nikola --version
|
- nikola --version
|
||||||
- pip --version
|
- pip --version
|
||||||
|
|
@ -37,8 +39,9 @@ deploy:
|
||||||
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
||||||
script:
|
script:
|
||||||
- docker pull $CI_REGISTRY_IMAGE:job-$CI_PIPELINE_ID
|
- docker pull $CI_REGISTRY_IMAGE:job-$CI_PIPELINE_ID
|
||||||
- docker tag $CI_REGISTRY_IMAGE:job-$CI_PIPELINE_ID $CI_REGISTRY_IMAGE:latest
|
- docker tag $CI_REGISTRY_IMAGE:job-$CI_PIPELINE_ID $CI_REGISTRY_IMAGE:${CI_COMMIT_REF_SLUG:-latest}
|
||||||
- docker push $CI_REGISTRY_IMAGE:latest
|
- echo "deploying $CI_REGISTRY_IMAGE:${CI_COMMIT_REF_SLUG:-latest}"
|
||||||
|
- docker push $CI_REGISTRY_IMAGE:${CI_COMMIT_REF_SLUG:-latest}
|
||||||
include:
|
include:
|
||||||
- template: Security/SAST.gitlab-ci.yml
|
- template: Security/SAST.gitlab-ci.yml
|
||||||
- template: Security/Container-Scanning.gitlab-ci.yml
|
- template: Security/Container-Scanning.gitlab-ci.yml
|
||||||
|
|
|
||||||
10
Dockerfile
10
Dockerfile
|
|
@ -1,13 +1,21 @@
|
||||||
|
FROM docker.io/library/alpine:3.23 AS cabuilder
|
||||||
|
RUN apk add --no-cache ca-certificates python3 py3-requests
|
||||||
|
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
|
FROM docker.io/library/python:3.11-alpine
|
||||||
|
|
||||||
RUN apk --no-cache add sshpass openssh-client lftp; mkdir -p /opt/app
|
RUN apk --no-cache add sshpass openssh-client lftp nodejs npm; mkdir -p /opt/app
|
||||||
WORKDIR /opt/app
|
WORKDIR /opt/app
|
||||||
ADD requirements.txt /root/
|
ADD requirements.txt /root/
|
||||||
ADD Dockerfile /root/
|
ADD Dockerfile /root/
|
||||||
ADD bin/*.sh /usr/local/bin/
|
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
|
# ignore "Running pip as root" warning
|
||||||
ENV PIP_ROOT_USER_ACTION=ignore
|
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)"
|
RUN chmod 755 /usr/local/bin/*.sh && pip install -r /root/requirements.txt && pip freeze && rm -Rf "$(pip cache dir)"
|
||||||
|
|
||||||
|
|
|
||||||
23
README.md
23
README.md
|
|
@ -1,6 +1,6 @@
|
||||||
# deployment-helper für Nikola based Websites
|
# deployment-helper für Nikola based Websites
|
||||||
|
|
||||||
# ENV-Variables
|
## ENV-Variables
|
||||||
|
|
||||||
* LFTP_PASSWORD (Deployment-Passwort for sftp-Upload)
|
* LFTP_PASSWORD (Deployment-Passwort for sftp-Upload)
|
||||||
* SSH_HOST (Deployment-Host for sftp-Upload)
|
* SSH_HOST (Deployment-Host for sftp-Upload)
|
||||||
|
|
@ -11,6 +11,27 @@
|
||||||
This image is based on the python:3.11-alpine image and includes additional
|
This image is based on the python:3.11-alpine image and includes additional
|
||||||
Python-packages for building nikola-sites.
|
Python-packages for building nikola-sites.
|
||||||
|
|
||||||
|
## Configure your project
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
| --------------------------- | --------------- | ---------------------------------------- |
|
||||||
|
| DEPLOY_HELPER_ENABLE_NIKOLA | true | Build Nikola-Website in `website/` |
|
||||||
|
| DEPLOY_HELPER_ENABLE_NODE | false | Enable nodejs Build in `node/` |
|
||||||
|
| DEPLOY_HELPER_CONTENT_DIR | website/output/ | Default Content-Directory for publishing |
|
||||||
|
|
||||||
|
|
||||||
|
Create a configuration file in your git-repo: `.deployment-helper`:
|
||||||
|
|
||||||
|
```
|
||||||
|
DEPLOY_HELPER_ENABLE_NIKOLA=false
|
||||||
|
DEPLOY_HELPER_ENABLE_NODE=true
|
||||||
|
DEPLOY_HELPER_CONTENT_DIR="node/.output/public/"
|
||||||
|
```
|
||||||
|
|
||||||
|
This example disables the niko build and enables node. The finished
|
||||||
|
website is copied from the `node/.output/public/` directory to the
|
||||||
|
target server.
|
||||||
|
|
||||||
## lftp for sftp-Uploads
|
## lftp for sftp-Uploads
|
||||||
|
|
||||||
This image includes `lftp` to upload files to a sftp-server and use
|
This image includes `lftp` to upload files to a sftp-server and use
|
||||||
|
|
|
||||||
68
bin/build.sh
68
bin/build.sh
|
|
@ -1,18 +1,76 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Default configurations:
|
||||||
|
DEPLOY_HELPER_ENABLE_NIKOLA=true
|
||||||
|
DEPLOY_HELPER_ENABLE_NODE=false
|
||||||
|
|
||||||
|
if test -f .deployment-helper
|
||||||
|
then
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. .deployment-helper
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if $DEPLOY_HELPER_ENABLE_NODE
|
||||||
|
then
|
||||||
|
if ! command -v node >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
echo "node not found" 1>&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! command -v npm >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
echo "npm not found" 1>&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! test -d node
|
||||||
|
then
|
||||||
|
echo "Node directory not found" 1>&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd node || { echo 'Directory "node" not found'; exit 1;}
|
||||||
|
if ! npm install
|
||||||
|
then
|
||||||
|
echo "Error installing node-dependencies" 1>&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if test -f nuxt.config.ts
|
||||||
|
then
|
||||||
|
# Build for nuxi based Sites
|
||||||
|
if ! npx nuxi build
|
||||||
|
then
|
||||||
|
echo "Error nuxi build" 1>&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! npx nuxi generate
|
||||||
|
then
|
||||||
|
echo "Error nuxi generate" 1>&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "No supportet node website type found" 1>&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
cd ..
|
||||||
|
fi
|
||||||
|
|
||||||
|
if $DEPLOY_HELPER_ENABLE_NIKOLA
|
||||||
|
then
|
||||||
if ! test -d website
|
if ! test -d website
|
||||||
then
|
then
|
||||||
echo "Website directory not found"
|
echo "Website directory not found" 1>&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! command -v nikola >/dev/null 2>&1
|
if ! command -v nikola >/dev/null 2>&1
|
||||||
then
|
then
|
||||||
echo "nikola command not found"
|
echo "nikola command not found" 1>&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
cd website || { echo 'Directory "website" not found'; exit 1;}
|
||||||
cd website
|
|
||||||
|
|
||||||
nikola build
|
nikola build
|
||||||
|
cd ..
|
||||||
|
fi
|
||||||
exit "$?"
|
exit "$?"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,16 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Default configurations:
|
||||||
|
DEPLOY_HELPER_CONTENT_DIR="website/output"
|
||||||
|
|
||||||
if ! test -d website/output
|
if test -f .deployment-helper
|
||||||
|
then
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. .deployment-helper
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
if ! test -d "${DEPLOY_HELPER_CONTENT_DIR}"
|
||||||
then
|
then
|
||||||
echo "Website-Build not found"
|
echo "Website-Build not found"
|
||||||
exit 1
|
exit 1
|
||||||
|
|
@ -16,6 +25,9 @@ then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
lftp -e 'set sftp:auto-confirm yes; mirror -R ./website/output/ ./web/; bye' --env-password -u ${SSH_USER} sftp://${SSH_HOST}
|
lftp -e "set sftp:auto-confirm yes; mirror -R ${DEPLOY_HELPER_CONTENT_DIR} ./web/; bye" \
|
||||||
|
--env-password \
|
||||||
|
-u ${SSH_USER} \
|
||||||
|
sftp://${SSH_HOST}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
109
bin/fetch_letsencrypt_ca_certs.py
Executable file
109
bin/fetch_letsencrypt_ca_certs.py
Executable file
|
|
@ -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()
|
||||||
|
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue