Skip to content

nwwatson/prose

Repository files navigation

Prose

A self-hosted blogging platform built with Ruby on Rails 8.1 and the Solid stack. Prose runs entirely on SQLite — no Redis, Postgres, or external services required.

Features

  • Writing & Editing — Rich text with Lexxy, autosave, post scheduling, featured posts
  • AI Assistant — Chat (proofread, critique, brainstorm), SEO/social metadata generation, featured image generation (Gemini/OpenAI), streaming responses
  • MCP ServerModel Context Protocol endpoint for managing posts, categories, tags, and assets from Claude Desktop, Claude Code, or any MCP client
  • Content Organization — Categories, tags with searchable combo box and inline creation
  • Reader Engagement — Comments with threading and moderation, loves, subscriber magic-link auth, email notifications
  • Social Embeds — X/Twitter and YouTube via oEmbed
  • Analytics — Dashboard with view tracking, subscriber growth, post engagement
  • Custom Pages — Static pages with rich text editor, top-level URLs (e.g. /about), optional navigation menu integration
  • Customization — 30+ Google Fonts, adjustable typography, live preview
  • Internationalization — Full i18n support with English and Spanish included; site-wide locale setting
  • SEO — Slugged URLs, meta descriptions, RSS feed, XML sitemap

Tech Stack

  • Ruby 3.4.4 / Rails 8.1
  • SQLite3 for all persistence
  • Solid Queue — database-backed background jobs
  • Solid Cache — database-backed caching
  • Solid Cable — database-backed WebSockets
  • RubyLLM — unified AI interface (Claude, Gemini, OpenAI)
  • Hotwire (Turbo + Stimulus) — SPA-like interactivity
  • Tailwind CSS — utility-first styling
  • Propshaft — asset pipeline
  • ImportMap — JavaScript modules without bundling
  • Kamal — Docker-based deployment

Getting Started

Prerequisites

  • Ruby 3.4.4
  • SQLite3

Setup

bin/setup              # Install dependencies, prepare database, start server
bin/setup --skip-server # Setup without starting the server

Development

bin/dev                # Start dev server (Puma + Tailwind watcher) on port 3000

Visit http://localhost:3000/admin/setup to create your admin account.

Testing

bin/rails test         # Run all tests
bin/rails test:system  # Run system (browser) tests

Linting and Security

bin/ci                 # Run full CI locally (setup, lint, security, tests)
bin/rubocop            # Ruby linting
bin/brakeman --quiet --no-pager --exit-on-warn --exit-on-error  # Security scan
bin/bundler-audit      # Gem vulnerability audit
bin/importmap audit    # JS dependency audit

MCP Integration

Prose exposes a Model Context Protocol server at /mcp, allowing AI assistants to manage your blog programmatically. See docs/mcp_setup.md for the full setup guide.

Quick Start

  1. Generate an API token from Admin > System > API Tokens

  2. Connect your client:

    Claude Code:

    claude mcp add prose --transport streamable-http https://your-domain.com/mcp \
      --header "Authorization: Bearer prose_YOUR_TOKEN"

    Claude Desktop — add to claude_desktop_config.json:

    {
      "mcpServers": {
        "prose": {
          "url": "https://your-domain.com/mcp",
          "headers": { "Authorization": "Bearer prose_YOUR_TOKEN" }
        }
      }
    }

Available Tools

Post management (list_posts, get_post, create_post, update_post, delete_post, publish_post, schedule_post, unpublish_post), site info (get_site_info, list_categories, list_tags, create_tag), and assets (upload_asset, set_featured_image).

Contributing

This project uses GitHub Flow. The master branch is always deployable — all work happens on feature branches and is merged via pull request.

Workflow

  1. Create a branch from master:
    git checkout master && git pull
    git checkout -b my-feature-branch
  2. Make your changes and verify they pass all checks:
    bin/rails test         # All unit tests must pass
    bin/rubocop            # All linting must pass
  3. Commit, push, and open a pull request:
    git push -u origin my-feature-branch
    gh pr create

PR Requirements

  • All unit tests pass (bin/rails test)
  • No RuboCop offenses (bin/rubocop)
  • Security scans clean (bin/brakeman --quiet --no-pager --exit-on-warn --exit-on-error)
  • One logical change per PR

Deployment

Prose deploys as a Docker container via Kamal. SQLite databases are persisted through a Docker volume mount. Solid Queue runs in-process with Puma.

Prerequisites

  • A VPS or dedicated server with Docker installed (Ubuntu 22.04+ recommended)
  • A domain name pointed at your server's IP address
  • SSH access to the server as root (or a user with Docker privileges)
  • A container registry account (Docker Hub, GitHub Container Registry, etc.) — or use a local registry

Optional:

  • SMTP credentials for email delivery (subscriber notifications, magic links)
  • S3-compatible storage for file uploads (AWS S3, DigitalOcean Spaces, Cloudflare R2, MinIO)

Step 1: Configure config/deploy.yml

Edit the deployment configuration for your environment:

# Set your server IP
servers:
  web:
    - YOUR_SERVER_IP

# Enable SSL with Let's Encrypt (uncomment and set your domain)
proxy:
  ssl: true
  host: yourdomain.com

# Configure your container registry
registry:
  server: ghcr.io          # or hub.docker.com, registry.digitalocean.com
  username: your-username
  password:
    - KAMAL_REGISTRY_PASSWORD

# Set your domain and any optional services
env:
  clear:
    SOLID_QUEUE_IN_PUMA: true
    APP_HOST: yourdomain.com
    # SMTP_ADDRESS: smtp.example.com
    # SMTP_PORT: 587
    # SMTP_USERNAME: your-username
    # SMTP_FROM: noreply@yourdomain.com
    # ACTIVE_STORAGE_SERVICE: amazon

Step 2: Generate Secrets

Generate the required production secrets:

bin/rails prose:generate_secrets

Save the output to .kamal/.env (this file is gitignored):

# .kamal/.env
SECRET_KEY_BASE=<generated value>
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=<generated value>
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=<generated value>
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=<generated value>

Then update .kamal/secrets to source them:

source .kamal/.env

Warning: These secrets encrypt your AI API keys and other sensitive data. If lost, encrypted data becomes unrecoverable. Back them up securely. Do not change SECRET_KEY_BASE after deployment — it is used for IP anonymization in analytics.

Step 3: Configure Container Registry

If using GitHub Container Registry:

# Add to .kamal/.env
KAMAL_REGISTRY_PASSWORD=ghp_your_github_token

# Uncomment in .kamal/secrets
KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD

Step 4: Deploy

kamal setup    # First deploy — provisions server, pushes image, starts containers

Step 5: Post-Deployment

  1. Visit https://yourdomain.com/admin/setup to create your admin account
  2. Configure your site name and settings in Admin > System > Site Settings
  3. Add AI API keys (Anthropic, OpenAI, Google) in site settings to enable AI features

Optional: Email (SMTP)

Set these environment variables in config/deploy.yml under env.clear (and SMTP_PASSWORD is already in env.secret):

env:
  clear:
    SMTP_ADDRESS: smtp.example.com
    SMTP_PORT: 587
    SMTP_USERNAME: your-username
    SMTP_FROM: noreply@yourdomain.com

Optional: S3-Compatible Storage

For file uploads stored in S3 instead of local disk:

  1. Uncomment gem "aws-sdk-s3" in the Gemfile and run bundle install
  2. Set environment variables:
env:
  clear:
    ACTIVE_STORAGE_SERVICE: amazon
    AWS_REGION: us-east-1
    AWS_BUCKET: your-bucket-name
    # AWS_ENDPOINT: https://your-endpoint.com  # For S3-compatible services
  secret:
    - AWS_ACCESS_KEY_ID
    - AWS_SECRET_ACCESS_KEY

Optional: Passkey Authentication (WebAuthn)

Admin users can register passkeys (Touch ID, Face ID, security keys) for passwordless sign-in. Passkeys work out of the box in development. For production, set the origin and relying party ID to match your domain:

env:
  clear:
    WEBAUTHN_ORIGIN: https://yourdomain.com
    WEBAUTHN_RP_ID: yourdomain.com

Manage passkeys at /admin/passkeys after signing in.

Environment Variables Reference

Variable Required Default Description
SECRET_KEY_BASE Yes Rails secret key for sessions and signed cookies
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY Yes Encrypts sensitive model attributes (AI API keys)
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY Yes Deterministic encryption for queryable fields
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT Yes Salt for encryption key derivation
APP_HOST No example.com Your domain name (enables host authorization)
RAILS_ASSUME_SSL No true Set to false if not using SSL
SOLID_QUEUE_IN_PUMA No true Run background jobs in the web process
ACTIVE_STORAGE_SERVICE No local Storage backend: local or amazon
SMTP_ADDRESS No SMTP server address (enables email delivery)
SMTP_PORT No 587 SMTP server port
SMTP_USERNAME No SMTP authentication username
SMTP_PASSWORD No SMTP authentication password
SMTP_FROM No noreply@example.com Default sender email address
SMTP_DOMAIN No APP_HOST HELO domain for SMTP
SMTP_AUTHENTICATION No plain SMTP auth method (plain, login, cram_md5)
WEB_CONCURRENCY No 1 Number of Puma worker processes
JOB_CONCURRENCY No 1 Number of Solid Queue worker threads
WEBAUTHN_ORIGIN No http://localhost:3000 Full origin URL for passkey/WebAuthn verification
WEBAUTHN_RP_ID No localhost Relying Party ID for passkeys (usually your domain)
RAILS_LOG_LEVEL No info Log verbosity (debug, info, warn, error)

Updating

kamal deploy   # Build, push, and deploy the latest code

Useful Kamal Commands

kamal console  # Open Rails console on the server
kamal shell    # Open bash shell in the container
kamal logs     # Tail application logs
kamal details  # Show running containers and health status

License

This project is licensed under the MIT License. See LICENSE for details.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors