| Crates.io | riley-cms-core |
| lib.rs | riley-cms-core |
| version | 0.1.0 |
| created_at | 2026-01-24 00:18:08.497214+00 |
| updated_at | 2026-01-24 00:18:08.497214+00 |
| description | Core library for riley_cms - content parsing, S3 storage, in-memory caching |
| homepage | |
| repository | https://github.com/rileyleff/riley_cms |
| max_upload_size | |
| id | 2065766 |
| size | 198,187 |
A minimal, self-hosted headless CMS for personal blogs. Rust, no database, no GUI.
Git is your content database. S3/R2 is your asset database. riley_cms is the stateless glue that serves it.
cargo install riley-cms-cli
Or build from source:
git clone https://github.com/rileyleff/riley_cms
cd riley_cms
cargo build --release
riley_cms init my-blog
cd my-blog
This creates an example content structure:
my-blog/
├── riley_cms.toml
└── content/
├── hello-world/
│ ├── config.toml
│ └── content.mdx
└── my-series/
├── series.toml
├── part-one/
│ ├── config.toml
│ └── content.mdx
└── part-two/
├── config.toml
└── content.mdx
Edit riley_cms.toml:
[content]
repo_path = "."
content_dir = "content"
[storage]
backend = "s3"
bucket = "my-assets"
region = "auto"
endpoint = "https://xxx.r2.cloudflarestorage.com"
public_url_base = "https://assets.example.com"
[server]
host = "0.0.0.0"
port = 8080
cors_origins = ["https://mysite.com"]
riley_cms serve
Posts live in directories with config.toml + content.mdx:
content/
├── my-post/
│ ├── config.toml
│ └── content.mdx
└── my-series/
├── series.toml # Makes this a series
├── getting-started/
│ ├── config.toml
│ └── content.mdx
└── advanced-topics/
├── config.toml
└── content.mdx
title = "My Post Title"
subtitle = "Optional subtitle"
preview_text = "A short preview for listings..."
preview_image = "https://assets.example.com/preview.jpg"
tags = ["rust", "programming"]
goes_live_at = 2025-01-15T00:00:00Z # None = draft, future = scheduled
order = 1 # For series posts
title = "My Series"
description = "Learn something cool"
preview_image = "https://assets.example.com/series.jpg"
goes_live_at = 2025-01-15T00:00:00Z
| Endpoint | Description |
|---|---|
GET /posts |
List all live posts |
GET /posts/:slug |
Get a single post with content |
GET /posts/:slug/raw |
Get raw MDX content only |
GET /series |
List all live series |
GET /series/:slug |
Get series with ordered posts |
GET /assets |
List assets in bucket |
GET /health |
Health check |
* /git/{*path} |
Git Smart HTTP (requires Basic Auth) |
?include_drafts=true - Include unpublished posts (requires auth)?include_scheduled=true - Include future-dated posts (requires auth)?limit=N - Limit results (default: 50)?offset=N - Skip results for paginationriley_cms supports two authentication mechanisms:
For accessing drafts and scheduled content via the API:
curl -H "Authorization: Bearer your-api-token" \
"http://localhost:8080/posts?include_drafts=true"
Configure in riley_cms.toml:
[auth]
api_token = "env:API_TOKEN" # Read from environment variable
# or
api_token = "your-literal-token"
For pushing content via Git over HTTP:
git remote add origin http://git:your-token@localhost:8080/git
git push origin main
Configure in riley_cms.toml:
[auth]
git_token = "env:GIT_AUTH_TOKEN"
riley_cms can serve your content repository over HTTP, allowing you to push content updates directly to the server.
git_token in your config# On your local machine
git remote add cms http://git:your-token@your-server:8080/git
git push cms main
| Endpoint | Description |
|---|---|
GET /git/{*path} |
Git read operations (fetch/clone) |
POST /git/{*path} |
Git write operations (push) |
After a successful push, riley_cms automatically:
{
"posts": [
{
"slug": "my-post",
"title": "My Post Title",
"subtitle": null,
"preview_text": "A short preview...",
"preview_image": "https://assets.example.com/preview.jpg",
"tags": ["rust"],
"series_slug": null,
"goes_live_at": "2025-01-15T00:00:00Z"
}
],
"total": 42,
"limit": 50,
"offset": 0
}
riley_cms serve # Run the HTTP API
riley_cms init <path> # Initialize content structure
riley_cms upload <file> # Upload asset to S3/R2
riley_cms ls posts # List posts
riley_cms ls series # List series
riley_cms ls assets # List assets
riley_cms validate # Check content for errors
| Crate | Description |
|---|---|
riley-cms-core |
Core library - embed in your own apps |
riley-cms-api |
Axum HTTP server |
riley-cms-cli |
CLI binary |
use riley_cms_core::{RileyCms, resolve_config, ListOptions};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let config = resolve_config(None)?;
let riley_cms = RileyCms::from_config(config).await?;
let posts = riley_cms.list_posts(&ListOptions::default()).await?;
for post in posts.items {
println!("{}: {}", post.slug, post.title);
}
Ok(())
}
FROM rust:1.88 AS builder
WORKDIR /app
COPY . .
RUN cargo build --release
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y git ca-certificates && rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/target/release/riley_cms /usr/local/bin/
VOLUME /data
EXPOSE 8080
CMD ["riley_cms", "serve"]
services:
riley_cms:
build: .
volumes:
- riley_cms_data:/data
- ./riley_cms.toml:/etc/riley_cms/config.toml:ro
environment:
- GIT_AUTH_TOKEN=${GIT_AUTH_TOKEN}
- API_TOKEN=${API_TOKEN}
- AWS_ACCESS_KEY_ID=${R2_ACCESS_KEY_ID}
- AWS_SECRET_ACCESS_KEY=${R2_SECRET_ACCESS_KEY}
ports:
- "8080:8080"
restart: unless-stopped
volumes:
riley_cms_data:
Config is loaded from (first match wins):
--config <path> flagRILEY_CMS_CONFIG env varriley_cms.toml in current directoryriley_cms.toml~/.config/riley_cms/config.toml/etc/riley_cms/config.tomlSee riley_cms.example.toml for all options.
MIT