Commit fc8a4e5755

fc8a4e57555ac0064a85c0598ccf20a5bda38e9d

parent: aecb7e8bc6

Unregistered key

cmc <hello@cleberg.net> · 2026-02-12 06:23 UTC
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

Layout: unified · split

.github/FUNDING.yml added +1
@@ -0,0 +1 @@
1github: [ccleberg]
.github/dependabot.yml added +6
@@ -0,0 +1,6 @@
1version: 2
2updates:
3 - package-ecosystem: "uv"
4 directory: "/"
5 schedule:
6 interval: "weekly"
.github/workflows/codeql.yml added +46
@@ -0,0 +1,46 @@
1name: "CodeQL Advanced"
2
3on:
4 push:
5 branches: [ "main" ]
6 pull_request:
7 branches: [ "main" ]
8
9jobs:
10 analyze:
11 name: Analyze (${{ matrix.language }})
12 runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
13 permissions:
14 security-events: write
15 packages: read
16 actions: read
17 contents: read
18
19 strategy:
20 fail-fast: false
21 matrix:
22 include:
23 - language: python
24 build-mode: none
25 steps:
26 - name: Checkout repository
27 uses: actions/checkout@v4
28 - name: Initialize CodeQL
29 uses: github/codeql-action/init@v4
30 with:
31 languages: ${{ matrix.language }}
32 build-mode: ${{ matrix.build-mode }}
33 - if: matrix.build-mode == 'manual'
34 shell: bash
35 run: |
36 echo 'If you are using a "manual" build mode for one or more of the' \
37 'languages you are analyzing, replace this with the commands to build' \
38 'your code, for example:'
39 echo ' make bootstrap'
40 echo ' make release'
41 exit 1
42
43 - name: Perform CodeQL Analysis
44 uses: github/codeql-action/analyze@v4
45 with:
46 category: "/language:${{matrix.language}}"
.github/workflows/deploy.yml added +59
@@ -0,0 +1,59 @@
1name: Build and Deploy
2
3on:
4 push:
5 branches: [ "main" ]
6 pull_request:
7 branches: [ "main" ]
8
9jobs:
10 build-job:
11 runs-on: ubuntu-latest
12 container:
13 image: ghcr.io/ccleberg/cleberg.net:github-actions
14
15 steps:
16 - name: Checkout code
17 uses: actions/checkout@v4
18
19 - name: Run Build
20 run: |
21 echo "Environment is ready. Running build..."
22 echo "r" | ENV=prod uv run build.py
23
24 - name: Upload Build Artifacts
25 uses: actions/upload-artifact@v4
26 with:
27 name: build-output
28 path: ${{ github.workspace }}/.build/
29 include-hidden-files: true
30
31 deploy-job:
32 runs-on: ubuntu-latest
33 needs: build-job
34 environment: production
35 container:
36 image: ghcr.io/ccleberg/cleberg.net:github-actions
37
38 steps:
39 - name: Checkout code
40 uses: actions/checkout@v4
41
42 - name: Download Build Artifacts
43 uses: actions/download-artifact@v4
44 with:
45 name: build-output
46 path: ${{ github.workspace }}/.build/
47
48 - name: Setup SSH and Deploy
49 env:
50 SERVER_IP: ${{ secrets.SERVER_IP }}
51 SERVER_USER: ${{ secrets.SERVER_USER }}
52 SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
53 run: |
54 eval $(ssh-agent -s)
55 echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
56 rsync -avz --delete \
57 -e "ssh -p 2169 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
58 .build/ \
59 $SERVER_USER@$SERVER_IP:/var/www/cleberg.net/\ No newline at end of file
.github/workflows/docker-build.yml added +47
@@ -0,0 +1,47 @@
1name: Build and Push Docker Image
2
3on:
4 push:
5 branches: [ "main" ]
6 paths:
7 - 'Dockerfile'
8 - 'requirements.txt'
9 - '.github/workflows/docker-build.yml'
10 pull_request:
11 branches: [ "main" ]
12 paths:
13 - 'Dockerfile'
14 - 'requirements.txt'
15 - '.github/workflows/docker-build.yml'
16
17jobs:
18 build:
19 runs-on: ubuntu-latest
20 permissions:
21 contents: read
22 packages: write
23
24 steps:
25 - name: Checkout repository
26 uses: actions/checkout@v4
27
28 - name: Log in to GHCR
29 uses: docker/login-action@v3
30 with:
31 registry: ghcr.io
32 username: ${{ github.actor }}
33 password: ${{ secrets.GITHUB_TOKEN }}
34
35 - name: Extract metadata
36 id: meta
37 uses: docker/metadata-action@v5
38 with:
39 images: ghcr.io/${{ github.repository }}
40
41 - name: Build and push
42 uses: docker/build-push-action@v5
43 with:
44 context: .
45 push: true
46 tags: ${{ steps.meta.outputs.tags }}
47 labels: ${{ steps.meta.outputs.labels }}
.github/workflows/ruff.yml added +42
@@ -0,0 +1,42 @@
1name: Ruff
2
3on:
4 push:
5 branches: [ "main" ]
6 paths:
7 - '**.py'
8 pull_request:
9 branches: [ "main" ]
10 paths:
11 - '**.py'
12
13jobs:
14 build:
15 runs-on: ubuntu-latest
16 strategy:
17 matrix:
18 python-version: ["3.x"]
19 steps:
20 - uses: actions/checkout@v4
21 - name: Set up Python ${{ matrix.python-version }}
22 uses: actions/setup-python@v5
23 with:
24 python-version: ${{ matrix.python-version }}
25 ref: ${{ github.event.pull_request.head.ref }}
26 - name: Install dependencies
27 run: |
28 python -m pip install --upgrade pip
29 pip install pandas dash plotly.express
30 - name: Install Ruff
31 uses: astral-sh/ruff-action@v3.2.2
32 - name: Ruff Actions
33 run: |
34 ruff check --fix
35 ruff format
36 - name: Add and Commit
37 uses: EndBug/add-and-commit@v9
38 env:
39 GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
40 with:
41 default_author: github_actions
42 pathspec_error_handling: ignore
.gitlab-ci.yml deleted −34
@@ -1,34 +0,0 @@
1image: registry.gitlab.com/ccleberg/cleberg-net:latest
2
3stages:
4 - build
5 - deploy
6
7build-job:
8 stage: build
9 script:
10 - echo "Environment is ready. Running build..."
11 - ENV=prod SKIP_DEPLOY=true uv run build.py <<< "r"
12 artifacts:
13 paths:
14 - .build/
15
16deploy-job:
17 stage: deploy
18 environment: production
19 dependencies:
20 - build-job
21 script:
22 - mkdir -p $HOME/.ssh
23 - |
24 echo "Host homelab-remote
25 HostName $SERVER_IP
26 User $SERVER_USER
27 Port 2169
28 StrictHostKeyChecking no
29 UserKnownHostsFile /dev/null" > $HOME/.ssh/config
30 - chmod 600 $HOME/.ssh/config
31 - eval $(ssh-agent -s)
32 - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
33 - echo "Deploying via Python script..."
34 - ENV=prod uv run build.py <<< "r"
build.py +46 −61
@@ -86,9 +86,7 @@ def update_index_html(html_snippet, template_path="./.build/index.html"):
86 end_line_start = end_index 86 end_line_start = end_index
87 87
88 # Construct the new content 88 # Construct the new content
89 new_content = ( 89 new_content = content[:insert_start] + indented_snippet + content[end_line_start:]
90 content[:insert_start] + indented_snippet + content[end_line_start:]
91 )
92 90
93 # Write back to index.html 91 # Write back to index.html
94 with open(template_path, "w", encoding="utf-8") as f: 92 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):
190 lines.append( 188 lines.append(
191 f'\t\t<time datetime="{post["date_str"]}">{post["date_full"]}</time>' 189 f'\t\t<time datetime="{post["date_str"]}">{post["date_full"]}</time>'
192 ) 190 )
193 lines.append( 191 lines.append(f'\t\t\t<a href="/blog/{post["slug"]}.html">{post["title"]}</a>')
194 f'\t\t\t<a href="/blog/{post["slug"]}.html">{post["title"]}</a>'
195 )
196 lines.append("\t</div>") 192 lines.append("\t</div>")
197 193
198 return "\n".join(lines) 194 return "\n".join(lines)
@@ -267,7 +263,9 @@ def run_emacs_publish(dev_mode=True):
267 if annoying_file.exists(): 263 if annoying_file.exists():
268 os.remove(annoying_file) 264 os.remove(annoying_file)
269 else: 265 else:
270 print("Warning: .build/cleberg-net.html not found, but Emacs exited successfully.") 266 print(
267 "Warning: .build/cleberg-net.html not found, but Emacs exited successfully."
268 )
271 269
272 270
273def generate_sitemap(build_dir=".build", base_url="https://cleberg.net"): 271def 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"):
290 loc = f"{base_url}{url_path}" 288 loc = f"{base_url}{url_path}"
291 289
292 # Last modified time 290 # Last modified time
293 lastmod = datetime.fromtimestamp( 291 lastmod = datetime.fromtimestamp(os.path.getmtime(full_path)).strftime(
294 os.path.getmtime(full_path) 292 "%Y-%m-%d"
295 ).strftime("%Y-%m-%d") 293 )
296 294
297 sitemap_entries.append(f""" <url> 295 sitemap_entries.append(f""" <url>
298 <loc>{loc}</loc> 296 <loc>{loc}</loc>
@@ -308,9 +306,7 @@ def generate_sitemap(build_dir=".build", base_url="https://cleberg.net"):
308 sitemap_path = os.path.join(build_dir, "sitemap.xml") 306 sitemap_path = os.path.join(build_dir, "sitemap.xml")
309 with open(sitemap_path, "w", encoding="utf-8") as f: 307 with open(sitemap_path, "w", encoding="utf-8") as f:
310 f.write(sitemap_xml) 308 f.write(sitemap_xml)
311 print( 309 print(f"Sitemap generated at {sitemap_path} with {len(sitemap_entries)} entries.")
312 f"Sitemap generated at {sitemap_path} with {len(sitemap_entries)} entries."
313 )
314 310
315 311
316def deploy_to_server(build_dir, server): 312def deploy_to_server(build_dir, server):
@@ -333,9 +329,7 @@ def start_dev_server(build_dir):
333 os.chdir(build_dir) 329 os.chdir(build_dir)
334 # This will run until interrupted (Ctrl+C) 330 # This will run until interrupted (Ctrl+C)
335 try: 331 try:
336 subprocess.run( 332 subprocess.run([sys.executable, "-m", "http.server", "8000"], check=True)
337 [sys.executable, "-m", "http.server", "8000"], check=True
338 )
339 except KeyboardInterrupt: 333 except KeyboardInterrupt:
340 print("\nDevelopment server stopped.") 334 print("\nDevelopment server stopped.")
341 except subprocess.CalledProcessError as e: 335 except subprocess.CalledProcessError as e:
@@ -352,66 +346,57 @@ def main():
352 css_min = theme_dir / "styles.min.css" 346 css_min = theme_dir / "styles.min.css"
353 347
354 env = os.environ.get("ENV", "").casefold() 348 env = os.environ.get("ENV", "").casefold()
355 skip_deploy = os.environ.get("SKIP_DEPLOY", "").casefold() == "true" 349 deploy = os.environ.get("DEPLOY", "").casefold() == "true"
356
357 if env == "prod":
358 print("Environment: Production")
359 method = prompt("Publishing on remote or LAN? [r|l] ").lower()
360 if method == "r":
361 homelab_server = "homelab-remote"
362 elif method == "l":
363 homelab_server = "homelab"
364 else:
365 print("Invalid input. Assuming LAN (homelab)")
366 homelab_server = "homelab"
367 350
368 # Remove previous build 351 if deploy:
369 remove_build_directory(build_dir) 352 print("Deploying to production...")
353 deploy_to_server(build_dir, "homelab-remote")
354 return
355 else:
356 if env == "prod":
357 print("Environment: Production")
370 358
371 # Minify CSS 359 # Remove previous build
372 minify_css(css_src, css_min) 360 remove_build_directory(build_dir)
373 361
374 # Run publishing script (silenced output) 362 # Minify CSS
375 run_emacs_publish(dev_mode=False) 363 minify_css(css_src, css_min)
376 364
377 # Update index page with latest posts 365 # Run publishing script (silenced output)
378 update_index_html(html_snippet) 366 run_emacs_publish(dev_mode=False)
379 367
380 # Minify index page 368 # Update index page with latest posts
381 minify_html("./.build/index.html", "./.build/index.html") 369 update_index_html(html_snippet)
382 370
383 # Generate sitemap 371 # Minify index page
384 generate_sitemap() 372 minify_html("./.build/index.html", "./.build/index.html")
385 373
386 # Deploy changes 374 # Generate sitemap
387 if not skip_deploy: 375 generate_sitemap()
388 deploy_to_server(build_dir, homelab_server)
389 else:
390 print("SKIP_DEPLOY is true. Skipping rsync step.")
391 376
392 else: 377 else:
393 print("Environment: Development") 378 print("Environment: Development")
394 379
395 # Remove previous build 380 # Remove previous build
396 remove_build_directory(build_dir) 381 remove_build_directory(build_dir)
397 382
398 # Minify CSS 383 # Minify CSS
399 minify_css(css_src, css_min) 384 minify_css(css_src, css_min)
400 385
401 # Run publishing script (with console output) 386 # Run publishing script (with console output)
402 run_emacs_publish(dev_mode=True) 387 run_emacs_publish(dev_mode=True)
403 388
404 # Update index page with latest posts 389 # Update index page with latest posts
405 update_index_html(html_snippet) 390 update_index_html(html_snippet)
406 391
407 # Minify index page 392 # Minify index page
408 minify_html("./.build/index.html", "./.build/index.html") 393 minify_html("./.build/index.html", "./.build/index.html")
409 394
410 # Generate sitemap 395 # Generate sitemap
411 generate_sitemap() 396 generate_sitemap()
412 397
413 # Launch development web server 398 # Launch development web server
414 start_dev_server(build_dir) 399 start_dev_server(build_dir)
415 400
416 401
417if __name__ == "__main__": 402if __name__ == "__main__":