17 Commits

Author SHA1 Message Date
72619e58b5 fix(deps): update tailwindcss monorepo to v4
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build was killed
2025-11-13 02:12:30 +00:00
9086f7833d feat: add plausible
All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
2025-08-11 10:48:36 +02:00
cc5b165822 chore: small corrections
All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
2025-08-05 22:53:55 +02:00
0cf84ffe0e feat: change ports
All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
2025-08-05 22:48:27 +02:00
61e98064f6 feat: use styles absolute
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-31 12:18:20 +02:00
f35fbe21c2 feat: set port
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-31 12:14:44 +02:00
47dfec5e26 feat: add more logging
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-31 12:13:07 +02:00
e3056382d3 feat: serve for everything
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-31 12:07:58 +02:00
a24a726a9b faet: remove encode
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-31 12:05:07 +02:00
ccfcd42d1e feat: move to caddy
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-31 12:04:16 +02:00
592afe4914 feat: move to serve 2025-07-31 12:04:02 +02:00
bf39c5f42f feat: caddy
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-31 11:59:35 +02:00
f575351335 missed 'v'
Some checks failed
continuous-integration/drone/push Build is failing
2025-07-31 11:57:04 +02:00
b1a13dad9b feat: download new zola
Some checks failed
continuous-integration/drone/push Build is failing
2025-07-31 11:55:57 +02:00
97986b1bef feat: with stuff
Some checks failed
continuous-integration/drone/push Build is failing
2025-07-31 11:43:39 +02:00
3274120012 feat: add git password
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-31 11:24:57 +02:00
ec9bc7eed8 feat: add cargo.toml
Some checks failed
continuous-integration/drone/push Build is failing
2025-07-31 11:16:31 +02:00
11 changed files with 3118 additions and 565 deletions

View File

@@ -70,6 +70,9 @@ steps:
CUDDLE_SECRETS_PROVIDER: 1password CUDDLE_SECRETS_PROVIDER: 1password
CUDDLE_ONE_PASSWORD_DOT_ENV: ".env.ci" CUDDLE_ONE_PASSWORD_DOT_ENV: ".env.ci"
CUDDLE_SSH_AGENT: "true" CUDDLE_SSH_AGENT: "true"
GIT_USERNAME: "kjuulh"
GIT_PASSWORD:
from_secret: git_password
CI_PREFIX: "/mnt/ci/ci" CI_PREFIX: "/mnt/ci/ci"
CUDDLE_PLEASE_TOKEN: CUDDLE_PLEASE_TOKEN:
from_secret: cuddle_please_token from_secret: cuddle_please_token
@@ -123,8 +126,11 @@ steps:
CUDDLE_SECRETS_PROVIDER: 1password CUDDLE_SECRETS_PROVIDER: 1password
CUDDLE_ONE_PASSWORD_DOT_ENV: ".env.ci" CUDDLE_ONE_PASSWORD_DOT_ENV: ".env.ci"
CUDDLE_SSH_AGENT: "true" CUDDLE_SSH_AGENT: "true"
GIT_USERNAME: "kjuulh"
GIT_PASSWORD: GIT_PASSWORD:
from_secret: git_password from_secret: git_password
SSH_KEY:
from_secret: gitea_id_ed25519
CI_PREFIX: "/mnt/ci/ci" CI_PREFIX: "/mnt/ci/ci"
DOCKER_HOST: "tcp://192.168.1.155:2376" DOCKER_HOST: "tcp://192.168.1.155:2376"
CUDDLE_PLEASE_TOKEN: CUDDLE_PLEASE_TOKEN:

2574
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

3
Cargo.toml Normal file
View File

@@ -0,0 +1,3 @@
[workspace]
members = ["ci/"]
resolver = "2"

84
README.md Normal file
View File

@@ -0,0 +1,84 @@
# Kasper Hermansen's Blog
A personal blog built with Zola (static site generator) and deployed using containerized CI/CD with Dagger.
## Prerequisites
- **mise** - Development environment manager
- **Rust** - For running Dagger CI
- **Docker** - For containerization
## Development
### Quick Start
```bash
# Install dependencies and start dev server
mise run dev
```
This will:
- Start Zola dev server on http://localhost:8000
- Watch and compile Tailwind CSS changes
- Show posts in progress
### Available Commands
```bash
mise run dev # Start development server with live reload
mise run ci:pr # Run CI build for pull requests
mise run ci:main # Run full CI pipeline (build + deploy)
```
## Deployment
The blog uses a fully automated CI/CD pipeline built with Dagger (Rust SDK).
### Manual Deployment
```bash
# Build and deploy to production
(cd ci && cargo build)
./ci/target/debug/ci main
```
This will:
1. Compile Tailwind CSS styles
2. Build static site with Zola
3. Package with Caddy web server
4. Push Docker image to registry
5. Update deployment configuration in git
### Automated Deployment
Pushing to `main` branch triggers automatic deployment via CI.
## Project Structure
```
├── content/posts/ # Blog posts (Markdown with YAML frontmatter)
├── templates/ # Zola templates (Tera)
├── static/ # Static assets
├── styles/ # Tailwind CSS source
├── ci/ # Dagger CI/CD pipeline (Rust)
├── deployment/ # Caddy server configuration
├── config.toml # Zola configuration
└── mise.toml # Development tasks
```
## Adding Content
Create a new post in `content/posts/`:
```markdown
+++
title = "Your Post Title"
description = "Brief description"
date = 2025-01-01
draft = false
[taxonomies]
tags = ["rust", "development"]
+++
Your content here...
```

View File

@@ -1,13 +1,14 @@
use cuddle_ci::{Context, MainAction, PullRequestAction}; use cuddle_ci::{Context, MainAction, PullRequestAction};
use dagger_sdk::{ use dagger_sdk::{Container, HostDirectoryOptsBuilder, PortForward, ServiceUpOptsBuilder};
Container, HostDirectoryOptsBuilder, PortForward, QueryContainerOptsBuilder,
ServiceUpOptsBuilder,
};
const UPDATE_DEPLOYMENT_IMAGE: &str = "docker.io/kasperhermansen/update-deployment:1690401410"; const UPDATE_DEPLOYMENT_IMAGE: &str = "docker.io/kasperhermansen/update-deployment:1690401410";
const ZOLA_VERSION: &str = "0.17.2-1"; const CADDY_IMAGE: &str = "caddy:2.10.0";
const DEBIAN_EDITION: &str = "bullseye";
const ZOLA_PLATFORM: &str = "x86_64";
const ZOLA_VERSION: &str = "v0.21.0";
const DEBIAN_EDITION: &str = "bookworm";
const DEBIAN_PLATFORM: &str = "amd64"; const DEBIAN_PLATFORM: &str = "amd64";
#[derive(Clone)] #[derive(Clone)]
@@ -24,7 +25,7 @@ impl BlogComponent {
.up_opts( .up_opts(
ServiceUpOptsBuilder::default() ServiceUpOptsBuilder::default()
.ports(vec![PortForward { .ports(vec![PortForward {
backend: 8000, backend: 80,
frontend: 8000, frontend: 8000,
protocol: dagger_sdk::NetworkProtocol::Tcp, protocol: dagger_sdk::NetworkProtocol::Tcp,
}]) }])
@@ -64,7 +65,7 @@ async fn build(client: dagger_sdk::Query) -> eyre::Result<(i64, Container)> {
.build()?, .build()?,
); );
let github_zola_download: String = format!("https://github.com/barnumbirr/zola-debian/releases/download/v{ZOLA_VERSION}/zola_{ZOLA_VERSION}_{DEBIAN_PLATFORM}_{DEBIAN_EDITION}.deb"); // let github_zola_download: String = format!("https://github.com/barnumbirr/zola-debian/releases/download/v{ZOLA_VERSION}/zola_{ZOLA_VERSION}_{DEBIAN_PLATFORM}_{DEBIAN_EDITION}.deb");
let node_cache = client.cache_volume("node_cache"); let node_cache = client.cache_volume("node_cache");
let debian_cache = client.cache_volume("debian_cache"); let debian_cache = client.cache_volume("debian_cache");
@@ -72,14 +73,16 @@ async fn build(client: dagger_sdk::Query) -> eyre::Result<(i64, Container)> {
.container() .container()
.from("node:16") .from("node:16")
.with_workdir("/app") .with_workdir("/app")
.with_directory(".", src.id().await?) .with_directory(".", src.clone())
.with_mounted_cache("node_modules", node_cache.id().await?) .with_mounted_cache("node_modules", node_cache)
.with_exec(vec!["yarn"]) .with_exec(vec!["yarn"])
.with_exec(vec!["yarn", "compile"]) .with_exec(vec!["yarn", "compile"])
.file("static/styles/styles.css"); .file("static/styles/styles.css");
let tag = chrono::Utc::now().timestamp(); let tag = chrono::Utc::now().timestamp();
let zola_download_link = format!("https://github.com/getzola/zola/releases/download/{ZOLA_VERSION}/zola-{ZOLA_VERSION}-{ZOLA_PLATFORM}-unknown-linux-gnu.tar.gz");
let dist_dir = client let dist_dir = client
.container_opts( .container_opts(
dagger_sdk::QueryContainerOptsBuilder::default() dagger_sdk::QueryContainerOptsBuilder::default()
@@ -87,29 +90,32 @@ async fn build(client: dagger_sdk::Query) -> eyre::Result<(i64, Container)> {
.build()?, .build()?,
) )
.from(format!("debian:{DEBIAN_EDITION}")) .from(format!("debian:{DEBIAN_EDITION}"))
.with_exec(vec!["apt", "update"]) // .with_exec(vec!["apt", "update"])
.with_exec(vec!["apt", "install", "wget", "-y"]) // .with_exec(vec!["apt", "install", "wget", "-y"])
.with_workdir("/mnt") .with_workdir("/mnt")
.with_mounted_cache("/mnt", debian_cache.id().await?) .with_file("/mnt/zola.tar.gz", client.http(zola_download_link))
.with_exec(vec!["wget", &github_zola_download]) .with_exec(vec!["tar", "-xvf", "/mnt/zola.tar.gz"])
.with_exec(vec![ .with_exec(vec!["mv", "/mnt/zola", "/usr/local/bin/zola"])
"dpkg", // .with_mounted_cache("/mnt", debian_cache)
"-i", // .with_exec(vec!["wget", &github_zola_download])
format!("zola_{ZOLA_VERSION}_{DEBIAN_PLATFORM}_{DEBIAN_EDITION}.deb").as_str(), // .with_exec(vec![
]) // "dpkg",
// "-i",
// format!("zola_{ZOLA_VERSION}_{DEBIAN_PLATFORM}_{DEBIAN_EDITION}.deb").as_str(),
// ])
.with_workdir("/app") .with_workdir("/app")
.with_directory(".", src.id().await?) .with_directory(".", src)
.with_file("static/styles/styles.css", styles_file.id().await?) .with_file("static/styles/styles.css", styles_file)
.with_exec(vec!["zola", "build"]) .with_exec(vec!["zola", "build"])
.directory("public"); .directory("public");
let caddy_file = client.host().directory("deployment").file("Caddyfile"); let caddy_file = client.host().directory("deployment").file("Caddyfile");
let dep_image = client let dep_image = client
.container_opts(QueryContainerOptsBuilder::default().build().unwrap()) .container()
.from("caddy") .from(CADDY_IMAGE)
.with_directory("/usr/share/caddy", dist_dir.id().await.unwrap()) .with_directory("/srv", dist_dir)
.with_file("/etc/caddy/Caddyfile", caddy_file.id().await.unwrap()); .with_file("/etc/caddy/Caddyfile", caddy_file);
Ok((tag, dep_image)) Ok((tag, dep_image))
} }

View File

@@ -1,8 +1,13 @@
{ {
debug debug
log default {
output stdout
level DEBUG
}
} }
http://blog.kasperhermansen.com { :80 {
root * /usr/share/caddy root * /srv
file_server file_server
} }

View File

@@ -3,7 +3,7 @@ dir = "./ci"
run = "cargo build" run = "cargo build"
[tasks."ci:run"] [tasks."ci:run"]
run = "./ci/target/debug/ci" run = "./target/debug/ci"
[tasks."ci:main"] [tasks."ci:main"]
depends = ["ci:build"] depends = ["ci:build"]

View File

@@ -14,10 +14,10 @@
}, },
"dependencies": { "dependencies": {
"@tailwindcss/typography": "^0.5.9", "@tailwindcss/typography": "^0.5.9",
"tailwindcss": "^3.3.1" "tailwindcss": "^4.0.0"
}, },
"devDependencies": { "devDependencies": {
"@catppuccin/tailwindcss": "^0.1.1", "@catppuccin/tailwindcss": "^0.1.1",
"@tailwindcss/cli": "^0.1.2" "@tailwindcss/cli": "^4.0.0"
} }
} }

View File

@@ -12,7 +12,8 @@
{{ post_macros::styles() }} {{ post_macros::styles() }}
<script defer data-domain="blog.kasperhermansen.com" src="https://plausible.front.kjuulh.io/js/script.js"></script> <script defer data-domain="blog.kasperhermansen.com" src="https://plausible.front.kjuulh.io/js/script.file-downloads.hash.outbound-links.pageview-props.tagged-events.js"></script>
<script>window.plausible = window.plausible || function() { (window.plausible.q = window.plausible.q || []).push(arguments) }</script>
<meta name="description" content="Kasper Hermansen's blog is a hub of insights on platform engineering. "> <meta name="description" content="Kasper Hermansen's blog is a hub of insights on platform engineering. ">
<meta name="keywords" content="Kasper Hermansen, kjuulh, blog, technology, platform engineering, cloud native"> <meta name="keywords" content="Kasper Hermansen, kjuulh, blog, technology, platform engineering, cloud native">

View File

@@ -36,5 +36,5 @@
{% endmacro list_posts %} {% endmacro list_posts %}
{% macro styles() %} {% macro styles() %}
<link rel="stylesheet" href="{{ get_url(path="styles/styles.css") | safe }}" /> <link rel="stylesheet" href="{{ get_url(path="/styles/styles.css") | safe }}" />
{% endmacro styles %} {% endmacro styles %}

940
yarn.lock

File diff suppressed because it is too large Load Diff