- TypeScript 93.4%
- CSS 6.4%
- Makefile 0.1%
|
All checks were successful
Web Release / Build and publish Docker image (push) Successful in 3m40s
Reviewed-on: #5 |
||
|---|---|---|
| .forgejo/workflows | ||
| .github | ||
| docs | ||
| src | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| .slugkit-site.json | ||
| Dockerfile | ||
| Makefile | ||
| package-lock.json | ||
| package.json | ||
| Procfile.dev | ||
| README.md | ||
| tsconfig.json | ||
| vitest.config.ts | ||
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 totruefor local development so magic links are written to logs instead of sent by email. Set tofalsein production and configure SMTP.
Required when AUTH_DEV_MODE=false
SMTP_HOST- SMTP server hostname.SMTP_PORT- SMTP server port. Defaults to587when unset.SMTP_SECURE- set totruefor implicit TLS, usually port465; usefalsefor STARTTLS/submission on port587.SMTP_FROM_EMAIL- sender email address used for magic-link messages.SMTP_FROM_NAME- optional sender display name.SMTP_USERNAMEandSMTP_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 to0.0.0.0in.env.exampleso the dev site can be reached from another computer on the same LAN.APP_PORT- local Hono app port. Defaults to3000when unset.
Media storage settings
S3_ENDPOINT- S3-compatible storage endpoint.S3_REGION- S3 region value. Garage-compatible local storage can usegarage.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 totruefor Garage-style local S3 endpoints.S3_PUBLIC_URL- public URL prefix for media objects./mediauses 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. Keepfalseuntil 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 ashttps://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.
Navigation and footer links
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.tsor persisted site config uses the same canonical publicurl.ACTIVITYPUB_PUBLIC_ORIGINis 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/actorget 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 doctorpasses 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 aslug loginAPI 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.
Related Slugkit references
- 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.