POSSE personal site platform — Publish (on your) Own Site, Syndicate Elsewhere.
- Editing: Django admin app (deployed to
admin.phildini.net) - Public site: Static HTML on GitHub Pages (
phildini.net) - Syndication: Automatically posts to Bluesky and Mastodon on publish
┌──────────────────────┐ ┌──────────────────────┐
│ admin.phildini.net │ │ phildini.net │
│ Django (Fly.io) │ │ GitHub Pages (static) │
│ │ │ │
│ Write/edit posts ────┼────>│ Homepage + blog feed │
│ Syndicate to socials │ │ Individual posts │
│ Build static site ───┼────>│ Archive + RSS │
│ Push to gh-pages │ │ Webmentions │
└──────────────────────┘ └──────────────────────┘
- Python 3.12+
- A GitHub account (for repo and Pages)
- A Bluesky account (for syndication)
- A Mastodon account (for syndication)
- A Fly.io account (for hosting the admin app)
- A domain you control (e.g.,
phildini.net)
uv venv && source .venv/bin/activate && uv pip install -r requirements.txt
python manage.py migrate
python manage.py createsuperuser
python manage.py runserverVisit http://localhost:8000/admin/ and log in.
- Push the repo to GitHub
- Go to repo Settings → Pages → Source: Deploy from branch
- Branch:
gh-pages, folder:/ (root) - The
.github/workflows/pages.ymlwill deploy on every push togh-pages - Later, set your custom domain in the Pages settings tab (after DNS below)
These are your two domains — set them up at your DNS provider:
| Record | Type | Value |
|---|---|---|
phildini.net |
CNAME | <your-username>.github.io |
admin.phildini.net |
CNAME | comitatus.fly.dev (get this after fly launch below) |
GitHub Pages will use the CNAME file already in the gh-pages branch.
# Install flyctl
curl -fsSL https://fly.io/install.sh | sh
# Log in
fly auth login
# Launch the app (use the existing fly.toml)
fly launch --no-deploy
# Attach a persistent volume for SQLite
fly volumes create data --region sjc --size 1
# Set all secrets (see Step 4-7 for where values come from)
fly secrets set \
DJANGO_SECRET_KEY=$(python3 -c "import secrets; print(secrets.token_urlsafe(50))") \
SITE_DOMAIN=phildini.net \
DJANGO_ALLOWED_HOSTS=admin.phildini.net,comitatus.fly.dev \
BLUESKY_USERNAME=your-handle.bsky.social \
BLUESKY_PASSWORD='xxx' \
MASTODON_ACCESS_TOKEN='xxx' \
MASTODON_BASE_URL=https://mastodon.social \
WEBMENTION_IO_TOKEN='xxx' \
GITHUB_DEPLOY_KEY='xxx' \
GITHUB_REPO_URL=git@github.com:phildini/comitatus.git
# Deploy
fly deployAfter deploy, note the Fly app hostname:
fly status
# Look for "Hostname: comitatus.fly.dev"The Django admin app needs permission to push the generated static site to the gh-pages branch.
# Generate a deploy key (on your local machine)
ssh-keygen -t ed25519 -C "comitatus-deploy" -f ~/.ssh/comitatus_deployThis creates two files:
~/.ssh/comitatus_deploy— private key (put this in Fly secrets asGITHUB_DEPLOY_KEY)~/.ssh/comitatus_deploy.pub— public key (goes to GitHub)
Add the public key to GitHub:
- Go to https://github.com/phildini/comitatus/settings/keys
- Click Add deploy key
- Title:
comitatus-deploy - Key: paste the contents of
~/.ssh/comitatus_deploy.pub - Check Allow write access (required so Django can push to
gh-pages)
Set the private key as a Fly secret (one line, with newlines):
fly secrets set GITHUB_DEPLOY_KEY="$(cat ~/.ssh/comitatus_deploy)"- Log in to Bluesky in a browser
- Go to Settings → App Passwords
- Click Add App Password
- Name it
comitatus - Copy the generated password (looks like
xxxx-xxxx-xxxx-xxxx) - Set as Fly secrets:
fly secrets set BLUESKY_USERNAME=your-handle.bsky.social fly secrets set BLUESKY_PASSWORD='xxxx-xxxx-xxxx-xxxx'
Note: Use your full handle (e.g., phildini.bsky.social), not just @phildini.
- Log in to your Mastodon instance (e.g.,
mastodon.social) - Go to Preferences → Development (URL:
https://mastodon.social/settings/applications) - Click New Application
- Application name:
comitatus - Scopes: enable
write:statusesandwrite:mediaonly - Click Submit
- Copy the Your access token string (long random string)
- Set as Fly secrets:
fly secrets set MASTODON_ACCESS_TOKEN='your-access-token' fly secrets set MASTODON_BASE_URL=https://mastodon.social
- Go to https://webmention.io and sign in with IndieAuth (your domain)
- Add your domain (
phildini.net) - Copy the API token from the settings page
- Set as Fly secret:
fly secrets set WEBMENTION_IO_TOKEN='your-token'
The <link rel="webmention"> tags are already in the site templates. Webmentions are fetched at build time and embedded in post pages.
# Push the workflow file from your machine (needs workflow scope)
git add .github/workflows/fly-deploy.yml
git commit -m "Add Fly.io deploy workflow"
git push origin main
# Or deploy manually
fly deploy
# Verify
fly logs- Visit
https://admin.phildini.net— you should see the Django admin login - Log in with the superuser you created
- Create a test post with Is published checked
- Check
https://phildini.net— the post should appear - Check Bluesky and Mastodon — the post should be syndicated
- Go to
admin.phildini.netand log in - Click "Posts" → "Add Post"
- Choose Long-form or Micro-post
- For long-form: add a title and body (Markdown)
- For micro-posts: write short text, optionally attach an image
- Check Is published to publish immediately
- Or set Published at to a future date for scheduled publishing
When you save a post with Is published checked:
- Post is saved to the database
- Syndicated to Bluesky and Mastodon (with image attachment for micro-posts)
- Static site is generated to
site/ site/is pushed to thegh-pagesbranch- GitHub Pages serves the updated site at
phildini.net
Set a future published_at date and leave Is published unchecked.
The publish_scheduled management command (run via Fly cron) will publish
it automatically when the time comes.
In the Posts list view, you can:
- Resyndicate selected posts — re-post to Bluesky and Mastodon
- Rebuild and deploy the static site — manually trigger a rebuild
- Templates:
templates/site/— HTML templates rendered by Django - CSS:
static/css/site.css— overrides for Pico.css - Homepage bio: Edit
templates/site/home.html - Now page: Edit
templates/site/now.html
| Layer | Technology |
|---|---|
| Framework | Django 5.x |
| Database | SQLite (Fly Volume) |
| Admin host | Fly.io |
| Static host | GitHub Pages |
| CSS | Pico.css |
| Bluesky | atproto |
| Mastodon | Mastodon.py |
| Markdown | Python-Markdown |
BSD 3-Clause. See LICENSE.