cmc/cleberg.net

My personal web garden & blog.

clone: git clone https://gitbay.org/cmc/cleberg.net.git

fc8a4e57555ac0064a85c0598ccf20a5bda38e9d

signed_unknown_key

author: Christian Cleberg <hello@cleberg.net> · 2026-02-12T06:23:06Z
committer: <noreply@github.com>

test github actions for docker build and site deploy (#1)

* test github actions for docker build and site deploy

* fix typo in codeQL

* test actions on this branch

* fix typo in actions path

* update pkg tag

* update command for sh syntax

* attempt to fix build-job path

* only run docker-build when relevant files are modified

* update pkg tag

* debug failing upload

* try again to fix artifact build path

* try again to fix artifact build path

* upgrade codeql to v4

* optimize deployment to reduce building site twice

* tweak deployment steps

* remove deployment method options

* try heredoc for ssh config file

* try heredoc for ssh config file

* move deployment step out of python entirely for github actions

* fix rsync options

* last tweak to polish off github workflows
 .github/FUNDING.yml                |   1 +
 .github/dependabot.yml             |   6 +++
 .github/workflows/codeql.yml       |  46 ++++++++++++++++
 .github/workflows/deploy.yml       |  59 ++++++++++++++++++++
 .github/workflows/docker-build.yml |  47 ++++++++++++++++
 .github/workflows/ruff.yml         |  42 +++++++++++++++
 .gitlab-ci.yml                     |  34 ------------
 build.py                           | 107 ++++++++++++++++---------------------
 8 files changed, 247 insertions(+), 95 deletions(-)

diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..4b822e5
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+github: [ccleberg]
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..6483b1d
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,6 @@
+version: 2
+updates:
+  - package-ecosystem: "uv"
+    directory: "/"
+    schedule:
+      interval: "weekly"
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644
index 0000000..f857dda
--- /dev/null
+++ b/.github/workflows/codeql.yml
@@ -0,0 +1,46 @@
+name: "CodeQL Advanced"
+
+on:
+  push:
+    branches: [ "main" ]
+  pull_request:
+    branches: [ "main" ]
+
+jobs:
+  analyze:
+    name: Analyze (${{ matrix.language }})
+    runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
+    permissions:
+      security-events: write
+      packages: read
+      actions: read
+      contents: read
+
+    strategy:
+      fail-fast: false
+      matrix:
+        include:
+        - language: python
+          build-mode: none
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v4
+    - name: Initialize CodeQL
+      uses: github/codeql-action/init@v4
+      with:
+        languages: ${{ matrix.language }}
+        build-mode: ${{ matrix.build-mode }}
+    - if: matrix.build-mode == 'manual'
+      shell: bash
+      run: |
+        echo 'If you are using a "manual" build mode for one or more of the' \
+          'languages you are analyzing, replace this with the commands to build' \
+          'your code, for example:'
+        echo '  make bootstrap'
+        echo '  make release'
+        exit 1
+
+    - name: Perform CodeQL Analysis
+      uses: github/codeql-action/analyze@v4
+      with:
+        category: "/language:${{matrix.language}}"
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
new file mode 100644
index 0000000..85eeff7
--- /dev/null
+++ b/.github/workflows/deploy.yml
@@ -0,0 +1,59 @@
+name: Build and Deploy
+
+on:
+  push:
+    branches: [ "main" ]
+  pull_request:
+    branches: [ "main" ]
+
+jobs:
+  build-job:
+    runs-on: ubuntu-latest
+    container:
+      image: ghcr.io/ccleberg/cleberg.net:github-actions
+
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+
+      - name: Run Build
+        run: |
+          echo "Environment is ready. Running build..."
+          echo "r" | ENV=prod uv run build.py
+
+      - name: Upload Build Artifacts
+        uses: actions/upload-artifact@v4
+        with:
+          name: build-output
+          path: ${{ github.workspace }}/.build/
+          include-hidden-files: true
+
+  deploy-job:
+    runs-on: ubuntu-latest
+    needs: build-job
+    environment: production
+    container:
+      image: ghcr.io/ccleberg/cleberg.net:github-actions
+
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+
+      - name: Download Build Artifacts
+        uses: actions/download-artifact@v4
+        with:
+          name: build-output
+          path: ${{ github.workspace }}/.build/
+
+      - name: Setup SSH and Deploy
+        env:
+          SERVER_IP: ${{ secrets.SERVER_IP }}
+          SERVER_USER: ${{ secrets.SERVER_USER }}
+          SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
+        run: |
+          eval $(ssh-agent -s)
+          echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
+          rsync -avz --delete \
+            -e "ssh -p 2169 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
+            .build/ \
+            $SERVER_USER@$SERVER_IP:/var/www/cleberg.net/
\ No newline at end of file
diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml
new file mode 100644
index 0000000..6172cc4
--- /dev/null
+++ b/.github/workflows/docker-build.yml
@@ -0,0 +1,47 @@
+name: Build and Push Docker Image
+
+on:
+  push:
+    branches: [ "main" ]
+    paths:
+      - 'Dockerfile'
+      - 'requirements.txt'
+      - '.github/workflows/docker-build.yml'
+  pull_request:
+    branches: [ "main" ]
+    paths:
+      - 'Dockerfile'
+      - 'requirements.txt'
+      - '.github/workflows/docker-build.yml'
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    permissions:
+      contents: read
+      packages: write
+
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v4
+
+      - name: Log in to GHCR
+        uses: docker/login-action@v3
+        with:
+          registry: ghcr.io
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: Extract metadata
+        id: meta
+        uses: docker/metadata-action@v5
+        with:
+          images: ghcr.io/${{ github.repository }}
+
+      - name: Build and push
+        uses: docker/build-push-action@v5
+        with:
+          context: .
+          push: true
+          tags: ${{ steps.meta.outputs.tags }}
+          labels: ${{ steps.meta.outputs.labels }}
diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml
new file mode 100644
index 0000000..1c0fbea
--- /dev/null
+++ b/.github/workflows/ruff.yml
@@ -0,0 +1,42 @@
+name: Ruff
+
+on:
+  push:
+    branches: [ "main" ]
+    paths:
+      - '**.py'
+  pull_request:
+    branches: [ "main" ]
+    paths:
+      - '**.py'
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        python-version: ["3.x"]
+    steps:
+    - uses: actions/checkout@v4
+    - name: Set up Python ${{ matrix.python-version }}
+      uses: actions/setup-python@v5
+      with:
+        python-version: ${{ matrix.python-version }}
+        ref: ${{ github.event.pull_request.head.ref }}
+    - name: Install dependencies
+      run: |
+        python -m pip install --upgrade pip
+        pip install pandas dash plotly.express
+    - name: Install Ruff
+      uses: astral-sh/ruff-action@v3.2.2
+    - name: Ruff Actions
+      run: |
+        ruff check --fix
+        ruff format
+    - name: Add and Commit
+      uses: EndBug/add-and-commit@v9
+      env:
+        GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+      with:
+        default_author: github_actions
+        pathspec_error_handling: ignore
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
deleted file mode 100644
index e6a1137..0000000
--- a/.gitlab-ci.yml
+++ /dev/null
@@ -1,34 +0,0 @@
-image: registry.gitlab.com/ccleberg/cleberg-net:latest
-
-stages:
-  - build
-  - deploy
-
-build-job:
-  stage: build
-  script:
-    - echo "Environment is ready. Running build..."
-    - ENV=prod SKIP_DEPLOY=true uv run build.py <<< "r"
-  artifacts:
-    paths:
-      - .build/
-
-deploy-job:
-  stage: deploy
-  environment: production
-  dependencies:
-    - build-job
-  script:
-    - mkdir -p $HOME/.ssh
-    - |
-      echo "Host homelab-remote
-          HostName $SERVER_IP
-          User $SERVER_USER
-          Port 2169
-          StrictHostKeyChecking no
-          UserKnownHostsFile /dev/null" > $HOME/.ssh/config
-    - chmod 600 $HOME/.ssh/config
-    - eval $(ssh-agent -s)
-    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
-    - echo "Deploying via Python script..."
-    - ENV=prod uv run build.py <<< "r"
diff --git a/build.py b/build.py
index 7b19123..44b77d0 100644
--- a/build.py
+++ b/build.py
@@ -86,9 +86,7 @@ def update_index_html(html_snippet, template_path="./.build/index.html"):
         end_line_start = end_index
 
     # Construct the new content
-    new_content = (
-        content[:insert_start] + indented_snippet + content[end_line_start:]
-    )
+    new_content = content[:insert_start] + indented_snippet + content[end_line_start:]
 
     # Write back to index.html
     with open(template_path, "w", encoding="utf-8") as f:
@@ -190,9 +188,7 @@ def get_recent_posts_html(content_dir="./content/blog", num_posts=3):
         lines.append(
             f'\t\t<time datetime="{post["date_str"]}">{post["date_full"]}</time>'
         )
-        lines.append(
-            f'\t\t\t<a href="/blog/{post["slug"]}.html">{post["title"]}</a>'
-        )
+        lines.append(f'\t\t\t<a href="/blog/{post["slug"]}.html">{post["title"]}</a>')
         lines.append("\t</div>")
 
     return "\n".join(lines)
@@ -267,7 +263,9 @@ def run_emacs_publish(dev_mode=True):
     if annoying_file.exists():
         os.remove(annoying_file)
     else:
-        print("Warning: .build/cleberg-net.html not found, but Emacs exited successfully.")
+        print(
+            "Warning: .build/cleberg-net.html not found, but Emacs exited successfully."
+        )
 
 
 def generate_sitemap(build_dir=".build", base_url="https://cleberg.net"):
@@ -290,9 +288,9 @@ def generate_sitemap(build_dir=".build", base_url="https://cleberg.net"):
                 loc = f"{base_url}{url_path}"
 
                 # Last modified time
-                lastmod = datetime.fromtimestamp(
-                    os.path.getmtime(full_path)
-                ).strftime("%Y-%m-%d")
+                lastmod = datetime.fromtimestamp(os.path.getmtime(full_path)).strftime(
+                    "%Y-%m-%d"
+                )
 
                 sitemap_entries.append(f"""  <url>
     <loc>{loc}</loc>
@@ -308,9 +306,7 @@ def generate_sitemap(build_dir=".build", base_url="https://cleberg.net"):
     sitemap_path = os.path.join(build_dir, "sitemap.xml")
     with open(sitemap_path, "w", encoding="utf-8") as f:
         f.write(sitemap_xml)
-    print(
-        f"Sitemap generated at {sitemap_path} with {len(sitemap_entries)} entries."
-    )
+    print(f"Sitemap generated at {sitemap_path} with {len(sitemap_entries)} entries.")
 
 
 def deploy_to_server(build_dir, server):
@@ -333,9 +329,7 @@ def start_dev_server(build_dir):
     os.chdir(build_dir)
     # This will run until interrupted (Ctrl+C)
     try:
-        subprocess.run(
-            [sys.executable, "-m", "http.server", "8000"], check=True
-        )
+        subprocess.run([sys.executable, "-m", "http.server", "8000"], check=True)
     except KeyboardInterrupt:
         print("\nDevelopment server stopped.")
     except subprocess.CalledProcessError as e:
@@ -352,66 +346,57 @@ def main():
     css_min = theme_dir / "styles.min.css"
 
     env = os.environ.get("ENV", "").casefold()
-    skip_deploy = os.environ.get("SKIP_DEPLOY", "").casefold() == "true"
-    
-    if env == "prod":
-        print("Environment: Production")
-        method = prompt("Publishing on remote or LAN? [r|l] ").lower()
-        if method == "r":
-            homelab_server = "homelab-remote"
-        elif method == "l":
-            homelab_server = "homelab"
-        else:
-            print("Invalid input. Assuming LAN (homelab)")
-            homelab_server = "homelab"
+    deploy = os.environ.get("DEPLOY", "").casefold() == "true"
 
-        # Remove previous build
-        remove_build_directory(build_dir)
+    if deploy:
+        print("Deploying to production...")
+        deploy_to_server(build_dir, "homelab-remote")
+        return
+    else:
+        if env == "prod":
+            print("Environment: Production")
 
-        # Minify CSS
-        minify_css(css_src, css_min)
+            # Remove previous build
+            remove_build_directory(build_dir)
 
-        # Run publishing script (silenced output)
-        run_emacs_publish(dev_mode=False)
+            # Minify CSS
+            minify_css(css_src, css_min)
 
-        # Update index page with latest posts
-        update_index_html(html_snippet)
+            # Run publishing script (silenced output)
+            run_emacs_publish(dev_mode=False)
 
-        # Minify index page
-        minify_html("./.build/index.html", "./.build/index.html")
+            # Update index page with latest posts
+            update_index_html(html_snippet)
 
-        # Generate sitemap
-        generate_sitemap()
+            # Minify index page
+            minify_html("./.build/index.html", "./.build/index.html")
 
-        # Deploy changes
-        if not skip_deploy:
-            deploy_to_server(build_dir, homelab_server)
-        else:
-            print("SKIP_DEPLOY is true. Skipping rsync step.")
+            # Generate sitemap
+            generate_sitemap()
 
-    else:
-        print("Environment: Development")
+        else:
+            print("Environment: Development")
 
-        # Remove previous build
-        remove_build_directory(build_dir)
+            # Remove previous build
+            remove_build_directory(build_dir)
 
-        # Minify CSS
-        minify_css(css_src, css_min)
+            # Minify CSS
+            minify_css(css_src, css_min)
 
-        # Run publishing script (with console output)
-        run_emacs_publish(dev_mode=True)
+            # Run publishing script (with console output)
+            run_emacs_publish(dev_mode=True)
 
-        # Update index page with latest posts
-        update_index_html(html_snippet)
+            # Update index page with latest posts
+            update_index_html(html_snippet)
 
-        # Minify index page
-        minify_html("./.build/index.html", "./.build/index.html")
+            # Minify index page
+            minify_html("./.build/index.html", "./.build/index.html")
 
-        # Generate sitemap
-        generate_sitemap()
+            # Generate sitemap
+            generate_sitemap()
 
-        # Launch development web server
-        start_dev_server(build_dir)
+            # Launch development web server
+            start_dev_server(build_dir)
 
 
 if __name__ == "__main__":