Back in 2019 I wrote about how this blog works. A lot has changed since then — automated deploys, dev.to sync, AI-generated tweets. Time for an update.

Still Grav

The foundation hasn't changed. Grav CMS, flat-file, PHP 8.2, Markdown, no database. Plugin updates and security patches aren't something I think about — Grav just runs.

The blog theme extends quark and adds custom styles — alternating white/gray section backgrounds, a tag cloud with CSS custom properties, article cards. Pure vanilla JS.

The site is bilingual — Czech (*.cs.md) and English (*.en.md). Every article has both files side by side.

Deploy via Deployer + GitLab CI

This hasn't fundamentally changed since 2019, just gotten more solid. Deployer 7.3 handles the actual deploy — symlink strategy, shared dirs for cache, automatic cleanup of old releases.

GitLab CI orchestrates everything:

  1. Push to master triggers the pipeline
  2. Build image — Docker image with PHP 8.2 and Deployer (only when Dockerfile.ci changes)
  3. Deploydep deploy over SSH to biberle.cz
  4. Maintenance — dev.to sync and cache clearing

The SSH key is stored as a base64 CI variable, decoded at runtime. The entire deploy is fully automatic.

Automatic dev.to Sync

English articles automatically get published to dev.to. The sync script scans all English articles, matches them against existing dev.to articles via canonical_url, and creates or updates as needed. Stateless — the canonical URL serves as the key. Runs in CI after every deploy and also on a schedule (for future-dated articles that become publishable).

The reverse works too — importing dev.to articles into the local blog, downloading cover images, and setting the canonical_url back to the blog.

AI-Generated Tweets

This is the new addition. When a new article goes live, it automatically gets tweeted. But not with some generic "New blog post!" — the tweet text is generated by OpenAI (gpt-4o-mini) following my writing style guide.

Here's how it works:

  1. All English articles are scanned
  2. Already tweeted articles get skipped based on a tracked list
  3. For new articles, the title, excerpt, and tags are sent to OpenAI
  4. OpenAI generates a tweet (max 250 chars) matching my tone
  5. Tweet is posted via Twitter API v2 with OAuth 1.0a
  6. The article URL is recorded and committed back to git

The writing style guide lives in a single file, so there's one source of truth for both the AI and me.

Tweets only go out on weekdays at 9:00 AM (Europe/Prague), via a separate GitLab CI schedule with the TWITTER_POST=true variable.

What Hasn't Changed

Some things just work and there's no reason to touch them:

  • Grav is still perfect for a personal blog — fast, simple, minimal maintenance
  • Markdown + Git is the best workflow for writing technical articles
  • Deployer does exactly what it should and nothing more

What's Next

  • Umami analytics instead of Google Analytics (the template placeholder is already there)
  • Better cover images for articles
  • Maybe add an RSS feed for Czech articles

But mostly — write more. Automation is great, but it's useless without articles.

Previous Post Next Post

Related Posts