Skip to content

Coriakin/mdcmd

Repository files navigation

MDCMS - Markdown Content Management System

A lightweight, file-based CMS designed for serving markdown content with versioning support, perfect for fan fiction and content that evolves over time.

Features

  • Markdown-based content with YAML front matter
  • Version control for pages (v1, v2, v3, etc.)
  • HTTPS-only secure serving
  • Admin dashboard with visitor analytics
  • Theme system with multiple CSS themes
  • Concurrent-safe analytics with batched writes
  • Static asset serving for images and media
  • Docker-ready with minimal footprint
  • Zero database - everything stored in files

Quick Start

1. Install Dependencies

npm install

2. Generate SSL Certificates

For development, create self-signed certificates:

mkdir ssl
openssl req -x509 -newkey rsa:4096 -keyout ssl/key.pem -out ssl/cert.pem -days 365 -nodes

For production, use real certificates from Let's Encrypt or your certificate authority.

3. Configure the Application

Copy the example configuration and edit it:

cp config.json.example config.json

Then edit config.json:

{
  "port": 3000,
  "host": "0.0.0.0",
  "admin": {
    "password": "your-secure-password-here",
    "path": "/admin"
  },
  "ssl": {
    "cert": "./ssl/cert.pem",
    "key": "./ssl/key.pem"
  },
  "defaultTheme": "default"
}

⚠️ Important: Change the admin password before running in production!

4. Create Your First Content

Create a file content/hello.md:

---
title: "Hello World"
theme: "default"
description: "My first page"
---

# Hello World

This is my first page on MDCMS!

## Features

- Easy markdown editing
- Version support
- Beautiful themes

5. Start the Server

npm start

Visit https://localhost:3000/hello to see your page!

Content Management

Creating Pages

Pages are markdown files in the content/ directory:

  • content/about.mdhttps://yoursite.com/about
  • content/my-story.mdhttps://yoursite.com/my-story

Page Versioning

Create multiple versions of the same page:

  • content/my-story.md → Version 1 (original)
  • content/my-story.v2.md → Version 2
  • content/my-story.v3.md → Version 3

URL Access:

  • /my-story → Latest version (automatic)
  • /my-story/v1 → Specific version 1
  • /my-story/v2 → Specific version 2

Version Navigation:

  • Version links always use explicit routes (/story/v1, /story/v2, etc.)
  • Clicking "v1" will take you to /story/v1, not the base URL
  • Latest version shows "(latest)" label but still uses explicit route

Front Matter Options

---
title: "Page Title"          # Optional, defaults to filename
theme: "dark"               # Optional: default, dark, minimal
description: "SEO description"
published: true             # Optional, defaults to true
public-versions: true       # Optional, show version navigation (defaults to true)
date: "2025-01-15"         # Optional
author: "Author Name"       # Optional
tags: ["fiction", "drama"]  # Optional
---

Version Navigation Control

  • public-versions: true (default) - Shows version navigation links
  • public-versions: false - Hides version navigation, useful for drafts or private versions

Adding Images and Assets

Create a directory for pages with assets:

content/
├── my-story.md           # Version 1
├── my-story.v2.md        # Version 2
└── my-story/             # Assets directory
    ├── cover.jpg         # Shared across versions
    ├── chapter1.png      # Version-specific
    └── assets/
        └── diagram.svg

Reference images in markdown:

![Cover Image](./cover.jpg)
![Chapter 1](./chapter1.png)

Obsidian-Style Image Embedding

MDCMS also supports Obsidian-style image embedding for easier content migration:

![['my-story/cover.jpg']]
![['assets/diagram.png']]

These will be automatically converted to standard markdown image syntax with proper URL encoding for spaces and special characters.

Admin Interface

Accessing Admin

  1. Go to https://yoursite.com/admin
  2. Enter the password from config.json
  3. View analytics and manage content

Admin Features

  • Visitor Statistics: Total, daily, weekly, monthly visits
  • Popular Pages: Most visited content with version breakdown
  • Recent Activity: Latest visitor information
  • Page Management: List all pages with version hierarchy
  • URL Copying: One-click copy of page URLs (always latest version)

Analytics Data

All visitor data is stored in analytics.json with:

  • Timestamp and page visited
  • Version accessed
  • Anonymized visitor info (hashed IP)
  • Referrer information

Themes

Four built-in themes are available:

  • default - Clean, GitHub-inspired design
  • dark - Dark mode with syntax highlighting
  • minimal - Typography-focused, minimal design
  • ember-sanctum - Rich, warm theme with ember-like styling

Using Themes

Set theme in front matter:

---
theme: "dark"
---

Or set a global default in config.json:

{
  "defaultTheme": "minimal"
}

Custom Themes

Create new CSS files in themes/ directory:

/* themes/custom.css */
body {
  /* Your custom styles */
}

Reference in front matter:

---
theme: "custom"
---

Deployment

Docker Deployment

Create Dockerfile:

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]

Create docker-compose.yml:

version: '3.8'
services:
  mdcms:
    build: .
    ports:
      - "3000:3000"  # Change to "443:3000" for production HTTPS
    volumes:
      # Content and themes directories
      - ./content:/app/content
      - ./themes:/app/themes
      # Configuration files (create from examples)
      - ./config.json:/app/config.json
      - ./analytics.json:/app/analytics.json
      # SSL certificates
      - ./ssl:/app/ssl
    environment:
      - NODE_ENV=production
    restart: unless-stopped
    container_name: mdcms-app
    
    # Health check to ensure the service is running
    healthcheck:
      test: ["CMD", "node", "-e", "require('https').get('https://localhost:3000/', {rejectUnauthorized: false}, (res) => process.exit(res.statusCode === 404 ? 0 : 1)).on('error', () => process.exit(1))"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s

Before running Docker:

# Copy configuration template
cp config.json.example config.json
# Edit config.json with your settings
# Ensure SSL certificates exist in ssl/ directory

Run with:

docker-compose up -d

Traditional Server Deployment

  1. Install Node.js 18+ on your server
  2. Copy all files to your server
  3. Install dependencies: npm install --production
  4. Configure SSL certificates
  5. Update config.json with production settings
  6. Run with a process manager like PM2:
npm install -g pm2
pm2 start server.js --name mdcms
pm2 save
pm2 startup

File Structure

mdcms/
├── server.js           # Main application
├── package.json        # Dependencies
├── config.json        # Configuration
├── analytics.json     # Visitor data (auto-created)
├── CLAUDE.md          # Implementation notes
├── README.md          # This file
├── content/           # Your markdown content
│   ├── page.md        # Single version page
│   ├── story.md       # Story version 1
│   ├── story.v2.md    # Story version 2
│   └── story/         # Story assets
│       └── image.jpg
├── themes/            # CSS themes
│   ├── default.css
│   ├── dark.css
│   ├── minimal.css
│   └── ember-sanctum.css
└── ssl/              # HTTPS certificates
    ├── cert.pem
    └── key.pem

Configuration Reference

config.json

{
  "port": 3000,                    // Server port
  "host": "0.0.0.0",              // Bind address
  "admin": {
    "password": "secure-password",  // Admin login password
    "path": "/admin"               // Admin URL path
  },
  "ssl": {
    "cert": "./ssl/cert.pem",      // SSL certificate path
    "key": "./ssl/key.pem"         // SSL private key path
  },
  "defaultTheme": "default"        // Default theme name
}

Security Features

  • HTTPS-only - No HTTP support
  • Secure sessions - HTTPOnly, Secure cookies
  • Session timeout - 1 hour automatic expiry
  • File restrictions - No direct access to .md or .json files
  • Path validation - Prevents directory traversal
  • Anonymous analytics - IP addresses are hashed

Performance

  • Concurrent-safe analytics with batched writes every 10 seconds
  • Atomic file operations prevent data corruption
  • Static asset caching with proper headers
  • Minimal dependencies for small footprint
  • Graceful shutdown ensures no data loss

Troubleshooting

SSL Certificate Issues

# Generate self-signed certificates
mkdir ssl
openssl req -x509 -newkey rsa:4096 -keyout ssl/key.pem -out ssl/cert.pem -days 365 -nodes

Permission Issues

Ensure the application has read/write permissions:

chmod 755 content/
chmod 644 content/*.md
chmod 666 analytics.json

Port Already in Use

Change the port in config.json or find the conflicting process:

lsof -i :3000

Analytics Not Working

Check file permissions and disk space:

ls -la analytics.json
df -h

Development

Adding Features

The codebase is organized into classes:

  • Analytics - Visitor tracking and statistics
  • ContentManager - Page and version management
  • Server - HTTP server and routing

Testing

Create test content and verify:

  1. Page rendering works correctly
  2. Versioning system functions
  3. Admin authentication works
  4. Analytics data is collected
  5. Themes apply properly

License

MIT License - Feel free to use and modify for your projects.

Support

  • Check CLAUDE.md for implementation details
  • Review server logs for error messages
  • Ensure all file paths and permissions are correct
  • Verify SSL certificates are valid and accessible

About

markdown cms for minimalistic sharing of pages online with minimal footprint

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors