Rust-based static site generator that converts Markdown to HTML and PDF. Includes a CLI for content management and browser-side (WASM) decryption of password-protected entries.
Regular pages ship zero JavaScript: the day/night theme follows the OS
setting via pure CSS (prefers-color-scheme), and the WASM module is loaded
only on pages with encrypted content.
flowchart LR;
A[content in Markdown]-->B[static site generator];
C[HTML templates]-->B;
B-->D[index.html];
B-->E[PDF files];
Generate HTML and PDF files from Markdown sources:
cargo run --releaseCreate a new entry in in/entries/ and update in/junkyard.md:
cargo run --release -- add "Entry Title"Entry filename format: N-entry-title.md where N is auto-incremented.
Create a private entry that's not listed in junkyard.md:
cargo run --release -- add --shadow "Private Entry"Shadow entries:
- Stored in
in/entries/shadow/(independent numbering) - Output to
priv/entries/N.html(separate frompub/) - Accessible via
/priv/entries/N.htmlURLs - Not added to junkyard index
- Navigation links only to other shadow entries
Edit an existing entry by number or path:
# Edit public entry #5
cargo run --release -- edit 5
cargo run --release -- edit 5p
# Edit shadow entry #5
cargo run --release -- edit 5s
# Edit by full path
cargo run --release -- edit in/entries/5-title.mdEntry editing:
- Opens entry in
$EDITOR(defaults tovim) - Auto-decrypts locked entries (
.enc) to temporary file - Auto-re-encrypts after editing (preserves encryption)
- Temp files cleaned up automatically (RAII pattern)
- Single passphrase attempt (fail-fast security)
Password-protect an entry with AES-256-GCM encryption:
# Create and encrypt an entry
cargo run --release -- add "Private Research"
cargo run --release -- lock in/entries/7-private-research.md
# Encrypt shadow entry (double privacy: unlisted + encrypted)
cargo run --release -- add --shadow "Secret Notes"
cargo run --release -- lock in/entries/shadow/1-secret-notes.md
# Decrypt entry back to plaintext
cargo run --release -- lock --unlock in/entries/7-private-research.encEncryption details:
- Uses AES-256-GCM (authenticated encryption, tamper-proof)
- Argon2id key derivation (64MB memory, GPU-resistant)
- Source file:
.md→.enc(encrypted on disk) - Browser-side decryption via WASM (no server needed)
- Locked entries show blurred preview with unlock form
- Set passphrase:
export RONIO_LOCK_KEY="your-passphrase"(interactive no-echo prompt when unset; there is deliberately no CLI flag because flags are visible in the process list) - No passphrase is needed to build the site:
.encfiles are embedded as-is
Lockfile tracking:
- Locking an entry records it in
.ronio-locks(JSON, gitignored) - Tracks entry number, shadow flag, and creation timestamp
- Metadata only;
.encfiles themselves ARE committed to the repository
Markdown sources may embed duration markers that are expanded at build time
(used by cv.md):
{{work_period: start="2022-12", end="present"}} -> "2 years, 7 months"
{{work_period: start="2018-07", end="2021-11"}} -> "3 years, 4 months"
{{total_work_period}} -> sum of all markers, years only
{{total_work_period}} sums the markers of the current file, or falls back
to scanning in/cv.md when the current file has none.
ronio [COMMAND] # no command: build the site
Commands:
add [OPTIONS] <TITLE> Add a new blog entry
edit <TARGET> Edit existing entry (5p/5s/5 or full path)
lock [OPTIONS] <PATH> Encrypt/decrypt entry with AES-256-GCM
help Print help information
Options for add:
--shadow Create as shadow entry (private, not listed)
-h, --help Print help
Options for lock:
-u, --unlock Decrypt .enc file back to .md
-h, --help Print help
Global options:
-h, --help Print help (see subcommand help for details)
-V, --version Print versionRun cargo run --release -- help <COMMAND> for the long per-command help,
including environment variables (RONIO_LOCK_KEY, EDITOR).
in/
├── entries/ Blog entries (numbered: 1-title.md, 2-title.md, ...)
│ └── shadow/ Private entries (not in junkyard)
├── cv.md CV (→ root/cv.html + download/sbelokon.pdf)
├── index.md Cover page (→ root/index.html + download/cover.pdf)
└── junkyard.md Blog index page
pub/
├── entries/ Generated entry HTML (1.html, 2.html, ...)
└── junkyard.html Blog index HTML
priv/
└── entries/ Generated shadow entry HTML (1.html, 2.html, ...)
download/ Generated PDFs
web/pkg/ WASM module (locked entry decryption only)
404.html Custom 404 page
Every build also generates directory index stubs so hosting never exposes a directory listing:
pub/index.html,pub/entries/index.html: redirect to the junkyarddownload/index.html: redirects to the home pagepriv/entries/index.html: 404-style page (shadow entries stay unlisted)
The site has light and dark palettes defined as CSS variables in
css/main.css. The dark palette activates through a single
@media (prefers-color-scheme: dark) block, so pages follow the OS theme
with no JavaScript, no toggle button, and no flash of the wrong theme on
load. Matching <meta name="theme-color"> tags keep the browser chrome in
sync on mobile.
make siteBuilds WASM module, generates site, serves on http://localhost:8080.
wasm-pack build --target web --out-dir web/pkgmake cleanmake testCreates a "Test Entry", appends a marker line, and encrypts it with the
passphrase test. Clean up afterwards with make clean.
cargo check
cargo clippy
./pre-ci.sh # clippy + release build + wasm-pack build + tests (mirrors CI)Install pre-push hook to validate code quality before pushing:
./hooks/install-hooks.shPre-push hook runs automatically before git push and checks:
- Code formatting (
cargo fmt) - Linting (
cargo clippy) - Test suite (
cargo test)
To bypass hook (not recommended):
git push --no-verifyLarger refactors identified during the 2026-07 code review, deliberately not yet applied:
- Unify the duplicated AES-GCM/Argon2 decryption logic and parameter
constants between
src/crypto.rs(native) andsrc/lib.rs(WASM) - Unify the two markdown renderers: pulldown-cmark (build) vs the minimal
hand-rolled converter used for browser-decrypted content in
src/lib.rs - Split
Site::buildinto discovery/routing/rendering functions - Migrate
rand0.8 to 0.9 (thread_rng/distributionsare renamed there) - Make
lock/unlockatomic: write the output file and fsync before removing the source, so a crash cannot lose both - Consider Rust edition 2024
Implemented using Gihub workflows feature. Build stages:
flowchart LR;
A[provision VM/container]-->B[install Rust toolchains];
B-->C[checkout repository];
C-->D[build static pages];
C-->E[site availability test];
D-->F[pack artifacts];
F-->G[deploy artifacts];