mdhost
A simple, single-binary Go web application for hosting and sharing markdown files.
Features
- Create, view, edit, and delete markdown files via web interface
- Stable file IDs - unique identifiers don't change when files are edited
- Syntax highlighting for code blocks (bash, go, python, etc.)
- Optional authentication - anonymous users can create files, registered users can claim them
- Access control - edit/delete only your own files, public/private visibility toggle
- Responsive UI - mobile-friendly CSS layout
- Persistent storage - all data (users, sessions, file metadata) survives server restarts
Quick Start
Build
go build -o mdhost .
Run
./mdhost
The server will start on http://localhost:8080.
Usage
- List files: Visit
/to see all public markdown files - Add a file: Click "add" in the nav bar, enter title and content, submit
- View a file: Click any file name in the list
- Edit a file: Click the
[edit]link next to a file, or visit/edit?id=<file-id> - Delete a file: Click the
[delete]button (only visible for files you own)
Authentication
Anonymous Use
You can create and edit files without an account. Files created anonymously are associated with your browser session (via cookie).
Register
Click "register" in the nav bar to create an account. After registering:
- Your existing anonymous files are automatically claimed and linked to your account
- You can edit/delete all files created in your session
Login/Logout
- Click "login" to authenticate with existing credentials
- Click "logout" to end your session
Claiming Files
Files created while anonymous are automatically claimed when you register or login from the same browser.
File Visibility
- Public files: Appear in the public list, visible to all users
- Private files: Only accessible via direct URL, don't appear in public list
- Toggle visibility using the "Listed in public list" checkbox when editing
Configuration
The following variables can be modified at the top of main.go:
var (
postsDir = "./posts" // Directory for markdown files
dataDir = "./data" // Directory for persistent data (data.json)
port = ":8080" // Server port
)
var ListFormat = "ul" // List rendering: "ul" (bullet), "ol" (numbered), "plain"
Data Storage
- Markdown files: Stored in
./posts/directory - Metadata: Stored in
./data/data.json(users, sessions, file metadata) - All data persists across server restarts
ID Generation
File IDs are generated as the first 12 characters of a SHA1 hash:
SHA1(filename + creation_timestamp)[:12]
The creation timestamp is stored in the file's CreatedAt field and persists across restarts, ensuring stable IDs.
Security
- Passwords are hashed using bcrypt
- Sessions use HTTP-only cookies with 1-year expiry
- File ownership is enforced (users can only edit/delete their own files)
- Private files are hidden from public listings but accessible via direct URL
API Endpoints
| Method | Path | Description |
|---|---|---|
| GET | / |
List all public files |
| GET | /home |
Homepage with README |
| GET | /view/{id} |
View rendered markdown |
| GET | /add |
Form to create new file |
| POST | /create |
Create new file |
| GET | /edit |
Select file to edit |
| GET | /edit?id={id} |
Edit specific file |
| POST | /update |
Save file edits |
| POST | /delete |
Delete a file |
| GET/POST | /register |
Create user account |
| GET/POST | /login |
Authenticate |
| GET | /logout |
Clear session |
| GET | /claim |
Claim anonymous files (auto-handled in register) |
Docker
A pre-built Docker image is available from gitea.scu.si/florian.walther/mdhost:latest.
Build the Image
docker build -t mdhost .
Run with Docker
The data and posts directories are designed to be bind-mounted from the host filesystem for persistence:
# Create host directories for persistent data
mkdir -p ~/mdhost-data ~/mdhost-posts
# Run the container with bind mounts
docker run -d \
--name mdhost \
-p 8080:8080 \
-v ~/mdhost-posts:/posts \
-v ~/mdhost-data:/data \
mdhost
The application will be available at http://localhost:8080.
/posts- Markdown files are stored here (bind-mounted from host)/data- Metadata (users, sessions, file metadata) is stored here asdata.json(bind-mounted from host)
Docker Compose
Create a docker-compose.yml:
version: '3.8'
services:
mdhost:
build: .
ports:
- "8080:8080"
volumes:
- ./posts:/posts
- ./data:/data
restart: unless-stopped
Then run:
docker compose up -d
Dependencies
- Go 1.26+
github.com/yuin/goldmarkv1.8.2github.com/yuin/goldmark-highlightingv0.0.0-20220208100518-594be1970594github.com/alecthomas/chroma/v2v2.24.1golang.org/x/crypto/bcryptv0.50.0
License
MIT License