The Problem

Maintaining a static blog that automatically enriches posts with related‑content data and search indexes can become brittle when the CI pipelines do not actually run tests, use mutable action versions, or expose overly‑privileged tokens. The repo also ships large binary blobs that bloat every clone.

What This Does

The repository builds a Jekyll site under docs/ (see docs/_posts/, docs/assets/, docs/_layouts/). On every push that touches docs/_posts/** a workflow Generate Related Data recomputes a lightweight related‑post graph and updates docs/assets/search-index.json. A scheduled workflow runs every 30 minutes, loads pre‑computed embeddings from docs/_data/embeddings.npz, computes missing neighbours, and writes docs/_data/related.json and docs/_data/status.json. The final site is deployed to GitHub Pages by the workflow Deploy Jekyll site.

Key files:

  • .github/workflows/generate-related-min.yml – scheduled data generation.
  • .github/workflows/e2e-semantic.yml – example end‑to‑end test workflow (currently empty of test steps).
  • docs/vendor/bundle/.../Dockerfile – builds a Docker image used by the generation jobs.
  • Large data blobs in docs/_data/ (related.json, embeddings.f32) that exceed 5 MiB each.

How It Is Wired

Entry point: a push to main or the scheduled cron triggers the GitHub Actions defined in .github/workflows/*.yml.

Workflow flow (Generate Related Data):

  1. actions/checkout@v3 pulls the repo.
  2. docker/build-push-action (or a custom Docker step) builds the image defined in docs/vendor/bundle/.../Dockerfile.
  3. Inside the container a Ruby script (part of the Jekyll site or a custom plugin) reads docs/_data/embeddings.npz, computes neighbour vectors, and writes related.json and status.json.
  4. The workflow commits the updated JSON files back to the repo using git (the token from GITHUB_TOKEN).

Deploy Jekyll site workflow:

  1. Checkout.
  2. Set up Ruby (ruby/setup-ruby).
  3. Run bundle install and bundle exec jekyll build.
  4. Push the generated _site/ directory to the gh-pages environment via the built‑in Pages deployment action.

Permissions & token usage: All workflows rely on the default GITHUB_TOKEN. No explicit permissions: block is present, so the token inherits repository defaults (full write). The checkout step retains the token in .git/config, exposing it to any later npm/gem post‑install script.

Docker image: Dockerfile in docs/vendor/bundle/... starts from ruby:$RUBY_VERSION-slim and runs Jekyll commands as root, with no non‑root USER directive.

Data handling: Large files (related.json ≈ 20 MiB, embeddings.f32 ≈ 20 MiB) live directly in the Git history, causing every clone and CI checkout to transfer > 40 MiB of binary data.

How To Use It

# Clone the repo
git clone https://github.com/moses-y/repo_posts.git
cd repo_posts

# Install Ruby dependencies (requires Ruby 3.2)
bundle install --path vendor/bundle

# Build the site locally
bundle exec jekyll build --source docs --destination _site

# Serve locally for preview
bundle exec jekyll serve --source docs --destination _site --watch

No explicit configuration files (e.g., config.yml) are present; the site relies on Jekyll defaults and the docs/_config.yml that ships with the Minimal theme. To trigger the data‑generation pipeline locally you would need to run the same Docker image used in the workflow, but the repo does not provide a ready‑made CLI script.

Real‑World Use

A marketing team could host a curated blog where each article automatically shows “related posts” based on vector similarity. A nightly CI job updates the similarity graph without manual intervention, and the published site reflects the latest relationships immediately after each push.

# Example snippet that could be added to a downstream repo
on:
  schedule:
    - cron: "*/30 * * * *"

Code Health & Issues

  • High – Pin third‑party GitHub Actions to a commit SHA – .github/workflows/* uses stefanzweifel/git-auto-commit-action@v5. Replace @v5 with a 40‑character SHA and keep the version comment.
  • High – Make CI invoke the test suite it has – .github/workflows/* contain four test files but no run: bundle exec rake test (or similar). Add a test step to the existing workflow.
  • Medium – Declare least‑privilege permissions for GITHUB_TOKEN – .github/workflows/e2e-semantic.yml lacks a permissions: block. Add permissions: contents: read and widen only where needed.
  • Medium – Pin the container base image by digest – Dockerfile in docs/vendor/bundle/... uses ruby:$RUBY_VERSION-slim. Replace with ruby@sha256:<digest>.
  • Medium – Move large binaries to Git LFS or external storage – docs/_data/related.json and embeddings.f32 each exceed 5 MiB. Track them with LFS or fetch from object storage in a setup step.
  • Medium – Set persist-credentials: false on checkout – .github/workflows/e2e-semantic.yml keeps the token in .git/config. Add with: persist-credentials: false.
  • Medium – Add a non‑root USER to the image – Dockerfile lacks a USER directive. Create an unprivileged user and switch to it before CMD.
  • Low – Set timeout-minutes on workflow jobs – .github/workflows/e2e-semantic.yml defines no job timeout. Add a reasonable timeout-minutes: (e.g., 30).

Additional hygiene gaps: no LICENSE file, no README describing onboarding, no test suite executed by CI, and no Dockerfile at the repository root.

The Bottom Line

The repo delivers a functional Jekyll‑based blog with automated related‑post updates, but its CI pipelines are insecure and untested, and the repository is bloated with large binary assets. It is suitable for teams that need a quick static‑site showcase and are willing to tighten CI security, add proper testing, and offload large data files to LFS or external storage.