Slugkit-generated web app for slugkit.com
  • TypeScript 93.4%
  • CSS 6.4%
  • Makefile 0.1%
Find a file
Erik Craddock 52cbbeca21
All checks were successful
Web Release / Build and publish Docker image (push) Successful in 3m40s
Merge pull request 'Fix runtime TSX JSX config' (#5) from fix-runtime-tsx-tsconfig into main
Reviewed-on: #5
2026-06-24 23:09:00 -05:00
.forgejo/workflows Remove temporary runner verification workflow 2026-06-24 20:56:00 -05:00
.github Configure actionlint for Forgejo runner labels 2026-06-24 20:57:33 -05:00
docs Verify Docker-capable Forgejo runner 2026-06-24 20:55:32 -05:00
src Initial slugkit web app 2026-06-24 16:27:32 -05:00
.dockerignore Initial slugkit web app 2026-06-24 16:27:32 -05:00
.env.example Initial slugkit web app 2026-06-24 16:27:32 -05:00
.gitignore Initial slugkit web app 2026-06-24 16:27:32 -05:00
.slugkit-site.json Initial slugkit web app 2026-06-24 16:27:32 -05:00
Dockerfile Fix runtime TSX JSX config 2026-06-24 23:08:23 -05:00
Makefile Initial slugkit web app 2026-06-24 16:27:32 -05:00
package-lock.json Fix runtime TSX JSX config 2026-06-24 23:08:23 -05:00
package.json Fix runtime TSX JSX config 2026-06-24 23:08:23 -05:00
Procfile.dev Initial slugkit web app 2026-06-24 16:27:32 -05:00
README.md Add Forgejo web release workflow 2026-06-24 20:35:58 -05:00
tsconfig.json Initial slugkit web app 2026-06-24 16:27:32 -05:00
vitest.config.ts Initial slugkit web app 2026-06-24 16:27:32 -05:00

Slugkit

This is a standalone Slugkit-compatible website generated from the Slugkit template. This site owns its code and can customize routes, templates, styles, assets, and deployment freely while preserving the Slugkit API contract that the slug CLI uses.

Quick start from slug init

Create a standalone site from the Slugkit CLI:

slug init ./my-site --name my-site --site-title "My Site"
cd ./my-site

Install the generated site's dependencies:

npm install

Copy the example environment file and edit it for local development:

cp .env.example .env

Run database migrations:

npm run db:migrate
npm run db:status

Install Overmind if it is not already available, then start the local dev environment:

make dev

This starts the app and Tailwind CSS watcher together using Procfile.dev. Use make dev-logs to connect to the running processes, make dev-status to check them, and make dev-stop to stop them. Open the local site at http://localhost:3000. Open the settings UI at http://localhost:3000/settings, sign in with the email configured by ADMIN_EMAIL, and use the logged magic link from the dev server output when AUTH_DEV_MODE=true. When AUTH_DEV_MODE=false, the site sends the magic link through the configured SMTP server instead.

Create an API key and connect slug

After signing in, open http://localhost:3000/settings/api-keys and create an API key for CLI/API access. Copy the key immediately; it is only shown once.

Configure the CLI with the local API base URL and API key:

slug config set api-base-url http://localhost:3000/api/v1
slug config set api-key <copied-api-key>
slug doctor

Alternatively, run the browser-assisted login flow and paste the generated key when prompted:

slug login http://localhost:3000/api/v1
slug doctor

slug doctor checks the API base URL, /health, /meta, /openapi.json, and API key authentication when a key is configured.

Docker deployment

Generated sites include a standalone Dockerfile and .dockerignore for building and running the website as a container image. The image builds the Tailwind CSS output, type-checks the site, installs production dependencies, exposes port 3000, and runs npm start.

Build and run the image locally:

docker build -t my-site:local .
docker run --rm -p 3000:3000 \
  --env-file .env \
  -e DATABASE_PATH=/app/data/slugkit.sqlite \
  -v my-site-data:/app/data \
  my-site:local

Run migrations before starting a new deployment or after updating the image. For a one-off migration against the same SQLite volume:

docker run --rm \
  --env-file .env \
  -e DATABASE_PATH=/app/data/slugkit.sqlite \
  -v my-site-data:/app/data \
  my-site:local npm run db:migrate

Forgejo web releases for slugkit.com

This repository includes a Forgejo Actions release workflow at .forgejo/workflows/web-release.yml. It publishes the Docker Hub image evcraddock/slugkit-com for linux/amd64 and linux/arm64 when a web-v<major>.<minor>.<patch> tag, such as web-v1.2.3, is pushed to forge.caradoc.com.

Required Forgejo repository secrets are DOCKER_USERNAME, DOCKER_PASSWORD, and FORGE_CA_CERT_B64. Use a Docker Hub access token for DOCKER_PASSWORD when possible. See docs/web-release.md for the full release process and validation steps.

Publish an image with GitHub Actions

Add registry credentials as repository secrets before enabling deployment. For GitHub Container Registry, GITHUB_TOKEN can publish to ghcr.io when package permissions allow it. For another registry, add secrets such as REGISTRY_USERNAME and REGISTRY_PASSWORD and adjust the login step.

Create .github/workflows/publish-image.yml in your generated site repository:

name: Publish image

on:
  push:
    branches: [main]
  workflow_dispatch:

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
      - uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - uses: docker/metadata-action@v5
        id: meta
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=ref,event=branch
            type=sha
      - uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

The published image will be available as ghcr.io/<owner>/<repo>:main and with a commit SHA tag. If you publish to Docker Hub or another registry, change REGISTRY, IMAGE_NAME, and the login credentials to match that provider.

Host the published image

Any host that can run a container can run the site. Configure the host to expose container port 3000, inject production environment variables, and mount persistent storage at /app/data when using SQLite. At minimum, production deployments usually set DATABASE_PATH=/app/data/slugkit.sqlite, ADMIN_EMAIL, AUTH_DEV_MODE=false, SMTP variables, and public site/federation variables such as ACTIVITYPUB_PUBLIC_ORIGIN when ActivityPub is enabled.

Example host command using a published image:

docker volume create my-site-data
docker run -d --name my-site \
  --restart unless-stopped \
  -p 3000:3000 \
  --env-file /etc/my-site.env \
  -e DATABASE_PATH=/app/data/slugkit.sqlite \
  -v my-site-data:/app/data \
  ghcr.io/<owner>/<repo>:main

Keep SQLite data on a persistent volume or bind mount. For media uploads, prefer S3-compatible storage in production by configuring S3_ENDPOINT, S3_BUCKET, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, and S3_PUBLIC_URL; otherwise any local file or database-backed data must be covered by your host backup strategy. Put secrets in the host environment or deployment secret store, not in the image.

Environment variables

Copy .env.example to .env before running the site directly. Keep secrets in .env or deployment secrets, not in src/config/site.ts.

Required for a useful local site

  • DATABASE_PATH - SQLite database path used by migration and runtime commands.
  • ADMIN_EMAIL - initial site-owner email allowed to sign in and administer the site.
  • AUTH_DEV_MODE - set to true for local development so magic links are written to logs instead of sent by email. Set to false in production and configure SMTP.

Required when AUTH_DEV_MODE=false

  • SMTP_HOST - SMTP server hostname.
  • SMTP_PORT - SMTP server port. Defaults to 587 when unset.
  • SMTP_SECURE - set to true for implicit TLS, usually port 465; use false for STARTTLS/submission on port 587.
  • SMTP_FROM_EMAIL - sender email address used for magic-link messages.
  • SMTP_FROM_NAME - optional sender display name.
  • SMTP_USERNAME and SMTP_PASSWORD - optional SMTP authentication credentials. Configure both together when your SMTP provider requires authentication.

Optional local runtime settings

  • APP_HOST - local Hono bind address. Defaults to 0.0.0.0 in .env.example so the dev site can be reached from another computer on the same LAN.
  • APP_PORT - local Hono app port. Defaults to 3000 when unset.

Media storage settings

  • S3_ENDPOINT - S3-compatible storage endpoint.
  • S3_REGION - S3 region value. Garage-compatible local storage can use garage.
  • S3_BUCKET - bucket name for uploaded media.
  • S3_ACCESS_KEY_ID - S3 access key ID.
  • S3_SECRET_ACCESS_KEY - S3 secret access key.
  • S3_FORCE_PATH_STYLE - set to true for Garage-style local S3 endpoints.
  • S3_PUBLIC_URL - public URL prefix for media objects. /media uses the app's same-origin media route.
  • MEDIA_MAX_UPLOAD_BYTES - upload size limit in bytes.
  • MEDIA_ALLOWED_MIME_TYPES - comma-separated allow-list for uploaded media MIME types.

Configure these values for your own local or production S3-compatible service before uploading media.

ActivityPub settings

  • ACTIVITYPUB_ENABLED - enables federation behavior at runtime. Keep false until the site has a stable public HTTPS origin, actor identity, and signing keys.
  • ACTIVITYPUB_PUBLIC_ORIGIN - optional canonical public origin for ActivityPub URLs. Set this to the production origin, such as https://example.com, when it differs from the public site URL stored in config.

Site customization checklist

Identity and public copy

Edit src/config/site.ts first. Required first-pass values include name, url, tagline, description, homepage.intro, homepage.body, navigation, and footerLinks.

The slug site config show and slug site config set <field> <value> commands can manage supported runtime site configuration fields through the API, including name, url, tagline, description, homepage.intro, and homepage.body.

Theme, layout, and assets

Edit Tailwind utility classes in src/templates/public.tsx and shared Tailwind input in src/styles/tailwind.css to change the public website look and feel. src/styles/public.css is generated by npm run css:build or npm run css:watch and is served at runtime. Replace files in src/assets/, such as slugkit-logo.png, to customize public images. Slugkit intentionally keeps visual styling out of site config.

Update navigation and footerLinks in src/config/site.ts for public site navigation. Keep these links focused on public pages and public resources. Settings navigation is generated by the app based on the signed-in user's access.

Media storage

Configure the S3-compatible media variables before relying on media uploads. For local development, same-origin /media URLs are convenient. For production, configure durable object storage and a stable public media URL.

ActivityPub actor and domain

Sign in to /settings, then open /settings/actor to configure the primary ActivityPub actor username, display name, summary, avatar/banner metadata, social accounts, and signing keys. The primary actor selected in settings is the local public author/publisher shown on posts.

ActivityPub prerequisites

Federation requires a stable HTTPS domain. Do not enable ACTIVITYPUB_ENABLED=true for a temporary local URL unless you understand the federation consequences.

Before enabling federation, verify this checklist:

  • Public origin is stable and HTTPS, such as https://example.com.
  • src/config/site.ts or persisted site config uses the same canonical public url.
  • ACTIVITYPUB_PUBLIC_ORIGIN is unset when the site URL is already canonical, or set to the canonical HTTPS origin when needed.
  • Primary actor has a stable username.
  • Primary actor has display metadata, including display name and summary.
  • Primary actor has signing key material. New actors created in /settings/actor get persisted keys automatically; the manual key action is available for older actors missing keys.
  • WebFinger route /.well-known/webfinger?resource=acct:<username>@<domain> resolves for the actor.
  • Actor route /users/<username> returns the ActivityPub actor document.
  • slug doctor passes API compatibility checks after CLI configuration.

Useful local diagnostics:

curl "http://localhost:3000/.well-known/webfinger?resource=acct:slug@localhost:3000"
curl -H "Accept: application/activity+json" http://localhost:3000/users/slug
slug doctor

Local development

Install Overmind if it is not already available, then run the app and Tailwind watcher together from this site directory:

make dev

The generated Procfile.dev runs npm run dev for the Hono app and npm run css:watch for Tailwind. Use the non-workspace commands shown in the quick start section for migrations and local checks.

Database

Run migrations from this site directory:

npm run db:migrate
npm run db:status

The migration system creates and updates the SQLite database configured by DATABASE_PATH. The site falls back to file defaults in src/config/site.ts when no persisted site_config row exists.

ActivityPub actor identity and signing keys are stored in activitypub_actors, editable actor profile fields are stored in activitypub_actor_settings, and the activitypub_primary_actor singleton row selects the primary/default actor. Federation enabled/disabled state is runtime configuration, not actor identity persistence.

Post creditContactIds and creditedContacts refer to contact records credited for external, source, or linked-content context. They are not the local post author or publisher; the template site's local publishing identity is the primary ActivityPub actor.

Routes

Public website routes:

  • GET /.well-known/webfinger - ActivityPub WebFinger discovery for the configured actor.
  • GET /users/:identifier - ActivityPub actor document for the configured actor.
  • GET / - public homepage with published posts.
  • GET /posts/:slug - public post detail page.
  • GET /tags - public tags index.
  • GET /tags/:slug - public tag detail page.
  • GET /contacts - public contacts index.
  • GET /contacts/:id - public contact detail page.
  • GET /sources - public sources index.
  • GET /sources/:id - public source detail page.
  • GET /feed - public feed page.
  • GET /feed.xml - public RSS feed.
  • GET /styles/public.css - public website stylesheet.
  • GET /assets/slugkit-logo.png - public logo image.
  • GET /health - JSON health check.

Settings routes:

  • GET /login - settings magic-link login form.
  • POST /login - request a settings magic link.
  • GET /login/verify - verify a magic link and create a site-user session.
  • GET /settings - protected general settings page.
  • GET /settings/passkeys - protected passkey management page.
  • GET /settings/api-keys - protected API key list and creation form.
  • POST /settings/api-keys - protected API key creation action.
  • POST /settings/api-keys/:id/revoke - protected API key revocation action.
  • GET /settings/users - admin-only site-user management page.
  • GET /settings/actor - admin-only ActivityPub actor settings page.
  • GET /cli/auth - protected browser flow for generating a slug login API key.
  • POST /logout - delete the site-user session.

API routes:

  • GET /api/v1/auth/check - bearer API key authentication check.
  • GET /api/v1/site-config - authenticated read path for supported site configuration.
  • PATCH /api/v1/site-config - authenticated update path for supported site configuration.
  • GET /api/v1/posts - authenticated post listing.
  • POST /api/v1/posts - authenticated draft post creation.
  • GET /api/v1/posts/:slug - authenticated post lookup by slug.
  • PUT /api/v1/posts/:slug - authenticated post update by slug.
  • DELETE /api/v1/posts/:slug - authenticated post deletion.
  • POST /api/v1/posts/:slug/publish - authenticated explicit post publishing.
  • POST /api/v1/posts/:slug/unpublish - authenticated explicit post unpublishing.
  • GET /api/v1/health - Slugkit API health check.
  • GET /api/v1/meta - Slugkit API bootstrap metadata for clients.
  • GET /api/v1/openapi.json - OpenAPI 3.1 document for the Slugkit API.
  • GET /api/v1/docs - Swagger UI for the Slugkit API.

Public website routes stay separate from /api/v1 management routes.

Structure

  • src/routes/ - public website route registration.
  • src/api/ - API route registration, OpenAPI document, response helpers, and auth middleware scaffolding.
  • src/templates/ - HTML rendering helpers.
  • src/services/ - application services.
  • src/db/ - database schema, migrations, and query helpers.
  • src/auth/ - admin auth, passkeys, and API key helpers.
  • src/federation/ - ActivityPub and social protocol implementation.
  • src/styles/ - public website CSS customization files.
  • src/assets/ - public website images and other static assets.
  • Slugkit API documentation is available from a running site at /api/v1/docs.
  • The CLI compatibility check is slug doctor.
  • The generated site marker is .slugkit-site.json.