Skip to content

Instantly share code, notes, and snippets.

@tcaddy
Created May 7, 2026 04:19
Show Gist options
  • Select an option

  • Save tcaddy/9c3f203d4185571867d4e98abb6ab03f to your computer and use it in GitHub Desktop.

Select an option

Save tcaddy/9c3f203d4185571867d4e98abb6ab03f to your computer and use it in GitHub Desktop.

Composition: Renovate Story

Explainer video detailing the transition from manual homelab maintenance to an automated, self-hosted Renovate workflow.

Technical Context

  • Platform: K3s Cluster
  • Automation Engine: Self-hosted Renovate (CronJob)
  • Manifest Management: Static rendering via Kustomize/Helm
  • Rendering Framework: HyperFrames + GSAP

Comparative Metrics (April - May 2026)

Era Duration Updates Methodology
Before 60 Days 13 Manual PRs / Version Tracking
After 21 Days 27 Automated Cron / Rendered Manifests

Operational Procedures

All rendering tasks must be executed from the repository root to ensure correct volume mapping.

Automation Script

The render.sh script handles the full lifecycle:

./videos/renovate-story/render.sh

Manual Steps (for debugging)

  1. TTS Update: COMPOSITION=renovate-story docker compose run --rm generate-audio
  2. Timeline Check: COMPOSITION=renovate-story docker compose run --rm audio-timeline
  3. Draft Render: COMPOSITION=renovate-story docker compose run --rm render

Asset Metadata

  • Voice: bf_emma (British Female)
  • Primary Colors: Gemini-inspired Palette (Blue/Purple/Teal)
  • Total Duration: 66.0 seconds
{
"width": 1920,
"height": 1080,
"fps": 30,
"quality": "high"
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Renovate Story</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<style>
:root {
--bg: #050a14;
--text: #ffffff;
--gemini-blue: #4285f4;
--gemini-purple: #9b72f3;
--gemini-teal: #4db6ac;
--accent: #8ab4f8;
--card-bg: rgba(255, 255, 255, 0.05);
--success: #34a853;
}
body, html {
margin: 0;
padding: 0;
width: 1920px;
height: 1080px;
background-color: var(--bg);
color: var(--text);
font-family: 'Google Sans', 'Inter', sans-serif;
overflow: hidden;
}
#root {
width: 1920px;
height: 1080px;
position: relative;
background: linear-gradient(135deg, #050a14 0%, #0d1b3e 100%);
}
/* Gemini Gradient for accents */
.gemini-gradient {
background: linear-gradient(90deg, var(--gemini-blue), var(--gemini-purple), var(--gemini-teal));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.clip {
position: absolute;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
}
h1 {
font-size: 90px;
font-weight: 700;
margin: 0 0 40px 0;
letter-spacing: -1px;
}
p {
font-size: 48px;
max-width: 1300px;
line-height: 1.5;
color: #e8eaed;
margin: 0;
font-weight: 400;
}
.card-container {
display: flex;
gap: 40px;
margin-top: 60px;
justify-content: center;
}
.card {
background: var(--card-bg);
backdrop-filter: blur(20px);
padding: 50px;
border-radius: 32px;
border: 1px solid rgba(255,255,255,0.1);
width: 420px;
text-align: center;
box-shadow: 0 20px 50px rgba(0,0,0,0.5);
}
.card .title {
font-size: 32px;
font-weight: 500;
color: var(--accent);
margin-bottom: 24px;
text-transform: uppercase;
letter-spacing: 2px;
}
.card .value {
font-size: 80px;
font-weight: 800;
}
.icon {
font-size: 120px;
margin-bottom: 40px;
filter: drop-shadow(0 0 20px rgba(138, 180, 248, 0.4));
}
.code-block {
background: #000;
padding: 40px;
border-radius: 20px;
font-family: 'JetBrains Mono', 'Roboto Mono', monospace;
font-size: 26px;
color: #fff;
border: 1px solid var(--gemini-purple);
margin-top: 50px;
width: 900px;
text-align: left;
line-height: 1.6;
box-shadow: 0 0 40px rgba(155, 114, 243, 0.2);
}
.footer-credit {
position: absolute;
bottom: 60px;
font-size: 24px;
color: rgba(255,255,255,0.4);
display: flex;
align-items: center;
gap: 12px;
}
.sparkle {
color: var(--gemini-purple);
}
</style>
</head>
<body>
<div id="root" class="clip"
data-composition-id="renovate-story"
data-width="1920"
data-height="1080"
data-start="0"
data-duration="66"
data-track-index="0">
<!-- Audio Tracks -->
<audio id="audio1" class="clip" data-start="0.5" data-duration="11.8" data-track-index="1" src="audio/scene1-intro.wav"></audio>
<audio id="audio2" class="clip" data-start="14.0" data-duration="11.1" data-track-index="2" src="audio/scene2-manual.wav"></audio>
<audio id="audio3" class="clip" data-start="27.0" data-duration="12.3" data-track-index="3" src="audio/scene3-solution.wav"></audio>
<audio id="audio4" class="clip" data-start="41.0" data-duration="11.2" data-track-index="4" src="audio/scene4-automation.wav"></audio>
<audio id="audio5" class="clip" data-start="54.0" data-duration="11.4" data-track-index="5" src="audio/scene5-conclusion.wav"></audio>
<!-- Scene 1 Clips (0 - 13.5s) -->
<div id="s1-content" class="clip" data-start="0" data-duration="13.5" data-track-index="10">
<div class="icon" id="s1-icon">🏠</div>
<h1 id="s1-title" class="gemini-gradient">Homelab Maintenance</h1>
<p id="s1-p">The joy of self-hosting often comes with the burden of manual updates.</p>
</div>
<!-- Scene 2 Clips (13.5 - 26.5s) -->
<div id="s2-content" class="clip" data-start="13.5" data-duration="13" data-track-index="11">
<h1 id="s2-title">The Manual Era</h1>
<p id="s2-p">Tracking versions and creating pull requests manually is slow and error-prone.</p>
<div class="card-container" id="s2-cards">
<div class="card">
<div class="title">Updates / 2 Months</div>
<div class="value">13</div>
</div>
<div class="card">
<div class="title">Process</div>
<div class="value">Manual</div>
</div>
</div>
</div>
<!-- Scene 3 Clips (26.5 - 40.5s) -->
<div id="s3-content" class="clip" data-start="26.5" data-duration="14" data-track-index="12">
<h1 id="s3-title" class="gemini-gradient">Self-Hosted Renovate</h1>
<p id="s3-p">Deployed on K3s with ArgoCD, running a daily cron job to keep everything fresh.</p>
<div class="code-block" id="s3-code">
<span style="color: var(--gemini-purple)">schedule:</span> "0 1 * * *"<br>
<span style="color: var(--gemini-purple)">image:</span> renovate/renovate:43.168.5<br>
<span style="color: var(--gemini-purple)">env:</span> RENOVATE_APP_ID
</div>
</div>
<!-- Scene 4 Clips (40.5 - 53.5s) -->
<div id="s4-content" class="clip" data-start="40.5" data-duration="13" data-track-index="13">
<h1 id="s4-title">Beyond Simple PRs</h1>
<p id="s4-p">A custom wrapper script automates manifest rendering using Kustomize and Helm.</p>
<div class="code-block" id="s4-code">
<span style="color: #666"># bin/renovate-wrapper.sh</span><br>
install-tool kustomize 5.8.1<br>
install-tool helm 4.1.4<br>
./bin/render-manifests.sh
</div>
</div>
<!-- Scene 5 Clips (53.5 - 66s) -->
<div id="s5-content" class="clip" data-start="53.5" data-duration="12.5" data-track-index="14">
<div class="icon" id="s5-icon">🚀</div>
<h1 id="s5-title" class="gemini-gradient">Automated Success</h1>
<p id="s5-p">27 updates in just a few weeks. Secure, current, and completely hands-off.</p>
<div class="card-container" id="s5-cards">
<div class="card" style="border-color: var(--success)">
<div class="title" style="color: var(--success)">Updates / 3 Weeks</div>
<div class="value">27</div>
</div>
<div class="card" style="border-color: var(--gemini-blue)">
<div class="title" style="color: var(--gemini-blue)">Process</div>
<div class="value">Automated</div>
</div>
</div>
</div>
</div>
<script>
/**
* Composition: Renovate Story
* Framework: HyperFrames (HTML/CSS/GSAP)
* Management: Gemini CLI
*/
const tl = gsap.timeline();
const DURATION = 66;
// Required registration for HyperFrames discovery
window.__timelines = { "renovate-story": tl };
/* --- Scene 1: Homelab Maintenance (0 - 13.5s) --- */
tl.from("#s1-icon", { scale: 0, duration: 0.8, ease: "back.out(1.7)" }, 0.5);
tl.from("#s1-title", { opacity: 0, y: 50, duration: 0.8, ease: "power3.out" }, 1.0);
tl.from("#s1-p", { opacity: 0, y: 30, duration: 0.8, ease: "power3.out" }, 1.5);
tl.to("#s1-content", { opacity: 0, duration: 0.5 }, 13.0);
/* --- Scene 2: The Manual Era (13.5 - 26.5s) --- */
tl.from("#s2-title", { opacity: 0, x: -50, duration: 0.8 }, 14.5);
tl.from("#s2-p", { opacity: 0, x: 50, duration: 0.8 }, 15.0);
tl.from("#s2-cards", { opacity: 0, y: 100, duration: 0.8 }, 16.0);
tl.to("#s2-content", { opacity: 0, duration: 0.5 }, 26.0);
/* --- Scene 3: Self-Hosted Renovate (26.5 - 40.5s) --- */
tl.from("#s3-title", { opacity: 0, scale: 0.8, duration: 0.8 }, 27.5);
tl.from("#s3-p", { opacity: 0, y: 20, duration: 0.8 }, 28.0);
tl.from("#s3-code", { opacity: 0, width: 0, duration: 1, ease: "power4.out" }, 29.0);
tl.to("#s3-content", { opacity: 0, duration: 0.5 }, 40.0);
/* --- Scene 4: Beyond Simple PRs (40.5 - 53.5s) --- */
tl.from("#s4-title", { opacity: 0, y: -30, duration: 0.8 }, 41.5);
tl.from("#s4-p", { opacity: 0, y: 30, duration: 0.8 }, 42.0);
tl.from("#s4-code", { opacity: 0, x: -100, duration: 0.8 }, 43.0);
tl.to("#s4-content", { opacity: 0, duration: 0.5 }, 53.0);
/* --- Scene 5: Automated Success (53.5 - 66s) --- */
tl.from("#s5-icon", { rotate: 360, scale: 0, duration: 1 }, 54.5);
tl.from("#s5-title", { opacity: 0, y: -50, duration: 0.8 }, 55.0);
tl.from("#s5-p", { opacity: 0, duration: 0.8 }, 55.5);
tl.from("#s5-cards", { opacity: 0, scale: 0.5, duration: 0.8, ease: "back.out" }, 56.5);
// Enforce total duration
tl.set({}, {}, DURATION);
</script>
</body>
</html>
{
"title": "Renovate Story",
"description": "How self-hosted Renovate keeps my homelab up to date.",
"category": "infrastructure"
}
#!/usr/bin/env bash
# Renovate Story: Render Pipeline
# Orchestrates image building, audio generation, and final MP4 rendering.
# Managed by Gemini CLI
set -euo pipefail
# Configuration
COMPOSITION="renovate-story"
DOCKER_DIR="videos"
COMPOSE_FILE="${DOCKER_DIR}/docker-compose.yml"
IMAGE_TAG="22.14.0-bookworm"
export COMPOSITION
# 1. Environment Validation
if [[ ! -d "${DOCKER_DIR}" ]]; then
echo "Error: Directory ${DOCKER_DIR} not found. Run from project root."
exit 1
fi
# 2. Local Image Construction
echo "--- Initializing Build Phase ---"
docker build --load -t "hyperframes-render:${IMAGE_TAG}" -f "${DOCKER_DIR}/Dockerfile" "${DOCKER_DIR}/"
docker build --load -t "hyperframes-tts:${IMAGE_TAG}" -f "${DOCKER_DIR}/Dockerfile.tts" "${DOCKER_DIR}/"
# 3. Audio Generation
echo "--- Synchronizing Audio Assets ---"
docker compose -f "${COMPOSE_FILE}" run --rm generate-audio
# 4. Timeline Calculation
echo "--- Validating Composition Timeline ---"
docker compose -f "${COMPOSE_FILE}" run --rm audio-timeline
# 5. Final Render
echo "--- Executing Video Render ---"
docker compose -f "${COMPOSE_FILE}" run --rm render
echo "--- Pipeline Complete ---"
echo "Artifact: videos/output/${COMPOSITION}.mp4"
@tcaddy
Copy link
Copy Markdown
Author

tcaddy commented May 7, 2026

renovate-story.mp4

@tcaddy
Copy link
Copy Markdown
Author

tcaddy commented May 7, 2026

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment