mdserve

A tiny Markdown server for people who keep their notes in Git.

mdserve lets you browse a directory of Markdown files over HTTP. No database. No Electron. No proprietary format. Just files.

Interesting bits

Why?

Most note-taking software wants to own your notes.

mdserve is the opposite:

Features

Philosophy

mdserve is not trying to replace your editor. It's a thin HTTP layer over your Markdown repository — if your notes already live in Git, mdserve lets you access them anywhere without introducing another database, sync service, or proprietary workspace.

Build

Dependencies

On Debian/Ubuntu:

sudo apt install libmd4c-dev libssl-dev

Compile

make

Usage

./mdserve [listen-url] [root-dir]

Examples

# Serve ./md on port 8080
./mdserve

# Serve ./notes on port 8080
./mdserve http://0.0.0.0:8080 ./notes

# Bind to a specific interface
./mdserve http://100.64.0.12:8080 /home/user/notes

Remote Markdown

/remote/https%3A%2F%2Fraw.githubusercontent.com%2Fuser%2Frepo%2Fmain%2FREADME.md

Only http:// and https:// URLs ending in .md are allowed. Raw IP addresses and private/reserved IP ranges are blocked.

Static site generation

mdbuild renders the same tree to plain HTML for hosting where you can't run a daemon (shared hosting, object storage, GitHub Pages). It shares the render core with mdserve but links neither mongoose nor OpenSSL.

make mdbuild
./mdbuild [src-dir] [out-dir]     # defaults: ./md  ./site
make site                         # same thing

Publishing is opt-in

A .md file is only emitted if its YAML frontmatter says so:

---
publish: true
title: Optional title for directory listings
---

# Your note

Files without the flag are skipped. mdserve ignores the flag entirely — browsing locally shows everything — but both binaries strip the frontmatter block before rendering, so it never leaks into the output.

Non-Markdown files (images, attachments) are copied unconditionally, since a published note that links to an image needs that image to exist. Don't put anything in the tree you wouldn't publish as a raw file.

URL layout

Output uses directory-style URLs: md/notes/rust.md → site/notes/rust/index.html, served as /notes/rust/. Because that puts every page one directory deeper than its source, relative .md links are rewritten to root-absolute paths (other.md → /notes/other/). The generated site therefore has to live at the root of its domain, not under a subpath.

index.md becomes the directory's page. Directories without a published index.md get a generated listing using the same markup as the server's.

A .htaccess (Apache ErrorDocument, Options -Indexes) and a rendered 404.html are written to the output root.

Deploying

./deploy-static.sh            # build + rsync to floreria:domains/notes.krr.cl/public_html
./deploy-static.sh --dry-run  # show what would change

Override REMOTE, DOMAIN, SRC, OUT via environment. The sync uses --delete, so the remote docroot is owned entirely by the script — unpublishing a note removes it from the server, and hand-edited remote files get wiped.

Project structure

.
├── main.c          # HTTP server, routing, directory listing, remote fetch
├── mdbuild.c       # Static site generator (no mongoose, no TLS)
├── src/
│   ├── md.c        # Markdown → HTML (md4c wrapper)
│   ├── frontmatter.c  # YAML frontmatter scanner (publish flag, title)
│   └── membuf.c    # Growable memory buffer
├── include/
│   ├── md.h
│   ├── frontmatter.h
│   └── membuf.h
├── head.html       # HTML <head> with styles, KaTeX, open <body>
├── tail.html       # Closing </body></html>
├── 404.html        # 404 page content
├── mongoose.c      # Vendored mongoose 7.22 (MIT)
├── mongoose.h
├── Makefile
├── deploy.sh       # git-archive deploy of the server to gcp1
├── deploy-static.sh   # build + rsync the static site to shared hosting
└── md/             # Default root for Markdown files
    └── index.md

Customisation

Why not Obsidian / Notion?

Because plain files, Git history, no vendor lock-in, no Electron, no cloud account, complete control over your data.

License

GNU General Public License v2. See LICENSE.

Contains vendored mongoose 7.22 (MIT license).