TASK-012: Initialize Chirp project — design spec, directory structure, README

- Add chirp-design-spec.md (857-line full technical specification)
- Create Go server directory structure (cmd/, internal/*, deploy/, tests/)
- Create PWA frontend directories (web/templates, web/static, web/sw)
- Write README with architecture overview and stack table

Infrastructure status as of 2026-05-31:
- GoToSocial v0.21.2 running on :8085
- PostgreSQL 16, Redis, Meilisearch all configured
- Nginx SSL termination for chirp.myfacefeed.com
This commit is contained in:
2026-05-31 01:28:55 -04:00
parent 6d851b968b
commit 865b20f0b4
20 changed files with 926 additions and 2 deletions
+857
View File
@@ -0,0 +1,857 @@
# Chirp — Full Technical Design Specification
# chirp.myfacefeed.com
# Last updated: 2026-05-30
---
## 1. SYSTEM OVERVIEW — END TO END FLOWCHART
```
USER (browser / mobile)
│ HTTPS
┌─────────────────────────────────────────────────────┐
│ K3s Ingress (176.9.93.253) │
│ chirp.myfacefeed.com │
└─────────────────────┬───────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ CHIRP GO WEB SERVER │
│ (K3s pod, mff namespace) │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────┐ │
│ │ Landing Page │ │ PWA Shell │ │ Admin │ │
│ │ / │ │ /app/* │ │ /admin │ │
│ └──────────────┘ └──────────────┘ └──────────┘ │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ API PROXY LAYER │ │
│ │ /api/v1/* → GoToSocial pass-through │ │
│ │ /api/v2/* → Chirp custom extensions │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ WEBSOCKET HUB (Go) │ │
│ │ Real-time: timeline, notifications, DMs │ │
│ │ Bridges GoToSocial streaming + custom events│ │
│ └──────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ SCHEDULER (Go) │ │
│ │ Post scheduling, recurring jobs │ │
│ │ Backed by Redis job queue │ │
│ └──────────────────────────────────────────────┘ │
└──────────┬──────────────┬──────────────┬────────────┘
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ GOTOSOCIAL │ │ MEILISEARCH │ │ REDIS │
│ (Go, K3s) │ │ (Rust, K3s) │ │ (K3s) │
│ │ │ │ │ │
│ ActivityPub │ │ Full-text │ │ Sessions │
│ Federation │ │ search idx │ │ Job queue │
│ REST API │ │ posts,users │ │ Rate limit │
│ WebSockets │ │ hashtags │ │ Cache │
│ Web Push │ └──────────────┘ └──────────────┘
│ OAuth2/OIDC │
└──────┬───────┘
├──────────────────────────┐
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ POSTGRESQL │ │ MINIO │
│ (K3s) │ │ s3.mff.com │
│ │ │ │
│ GTS schema │ │ chirp-media │
│ chirp_* │ │ bucket │
│ schema │ │ (images, │
│ extensions │ │ video, │
└──────────────┘ │ audio) │
└──────────────┘
┌─────────────────────────┤
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ MEDIAMTX │ │ ZITADEL │
│ stream.mff │ │ auth.mff │
│ │ │ │
│ Live video │ │ SSO / OIDC │
│ RTMP/HLS │ │ Identity │
│ WebRTC │ │ provider │
└──────────────┘ └──────────────┘
┌──────────────────────────────────────────┐
│ FEDIVERSE (Internet) │
│ Mastodon, Misskey, Pleroma, Pixelfed │
│ Any ActivityPub server worldwide │
└──────────────────────────────────────────┘
```
---
## 2. COMPONENTS — ROLES AND RESPONSIBILITIES
### GoToSocial (backend engine)
- ActivityPub server — handles all federation with the Fediverse
- Mastodon-compatible REST API (`/api/v1/`)
- WebSocket streaming (`wss://chirp.myfacefeed.com/api/v1/streaming`)
- Web Push notifications (v0.18+)
- OAuth2 + OIDC (integrates with Zitadel at auth.myfacefeed.com)
- Media storage via MinIO S3 (`chirp-media` bucket)
- PostgreSQL backend
### Chirp Go Web Server (custom build)
- Serves landing page + PWA shell
- API proxy: pass `/api/v1/*` through to GoToSocial
- Custom API extensions at `/api/v2/*` for missing features
- WebSocket hub bridging GoToSocial streaming + custom events
- Post scheduler (Redis-backed)
- Meilisearch integration for full-text search
- Service worker + web manifest (makes it a PWA)
- Push notification subscription management
### Meilisearch (search)
- Full-text search across posts, users, hashtags
- Indexes synced from GoToSocial via webhook + polling
- Go client: `github.com/meilisearch/meilisearch-go`
- RAM: practical ~200-400MB for small-medium instance
### Redis
- Session store for Chirp Go server
- Job queue for post scheduler
- Rate limiting
- Cache for expensive GoToSocial API responses
- Pub/sub for WebSocket hub fan-out
### PostgreSQL (shared instance, separate schemas)
- `gotosocial` schema — owned by GoToSocial, never touch directly
- `chirp` schema — owned by Chirp Go server, all custom extensions
- Same DB host, separate connection users
### MinIO (existing at s3.myfacefeed.com)
- New bucket: `chirp-media`
- GoToSocial configured to use it directly
- CDN-fronted via cdn.myfacefeed.com for serving media
---
## 3. DATABASE DESIGN
### GoToSocial (do not modify — reference only)
GoToSocial manages its own schema via internal migrations.
Key tables it owns (read-only for Chirp):
```
accounts — user profiles, actor info
statuses — posts
follows — follow relationships
notifications — notification records
media_attachments — uploaded media references
tokens — OAuth tokens
applications — registered OAuth apps
```
### Chirp Schema Extensions (chirp.*)
All custom tables live in the `chirp` PostgreSQL schema.
Chirp Go server connects as user `chirp_app` with access only to `chirp.*`.
```sql
-- ============================================================
-- BOOKMARKS
-- GoToSocial has /bookmark endpoint but no persistent store
-- We store the mapping here
-- ============================================================
CREATE TABLE chirp.bookmarks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
account_id TEXT NOT NULL, -- GoToSocial account ID
status_id TEXT NOT NULL, -- GoToSocial status ID
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(account_id, status_id)
);
-- ============================================================
-- LISTS
-- GoToSocial has list endpoints — we extend with metadata
-- ============================================================
CREATE TABLE chirp.lists (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
account_id TEXT NOT NULL,
title TEXT NOT NULL,
replies_policy TEXT DEFAULT 'list', -- list, followed, none
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE chirp.list_members (
list_id UUID REFERENCES chirp.lists(id) ON DELETE CASCADE,
account_id TEXT NOT NULL, -- member's GoToSocial account ID
added_at TIMESTAMPTZ DEFAULT NOW(),
PRIMARY KEY (list_id, account_id)
);
-- ============================================================
-- SCHEDULED POSTS
-- ============================================================
CREATE TABLE chirp.scheduled_posts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
account_id TEXT NOT NULL,
scheduled_at TIMESTAMPTZ NOT NULL,
status_text TEXT NOT NULL,
visibility TEXT DEFAULT 'public',
content_warning TEXT,
media_ids TEXT[], -- GoToSocial media attachment IDs
poll JSONB, -- poll options if applicable
in_reply_to_id TEXT,
language TEXT DEFAULT 'en',
sensitive BOOLEAN DEFAULT FALSE,
created_at TIMESTAMPTZ DEFAULT NOW(),
dispatched_at TIMESTAMPTZ, -- set when sent to GoToSocial
failed_at TIMESTAMPTZ,
error TEXT
);
-- ============================================================
-- CONVERSATION MUTES
-- ============================================================
CREATE TABLE chirp.conversation_mutes (
account_id TEXT NOT NULL,
status_id TEXT NOT NULL, -- root status of conversation
muted_at TIMESTAMPTZ DEFAULT NOW(),
PRIMARY KEY (account_id, status_id)
);
-- ============================================================
-- SEARCH INDEX SYNC TRACKING
-- Tracks what has been indexed in Meilisearch
-- ============================================================
CREATE TABLE chirp.search_sync (
status_id TEXT PRIMARY KEY,
indexed_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ,
deleted_at TIMESTAMPTZ
);
-- ============================================================
-- PUSH NOTIFICATION SUBSCRIPTIONS
-- Web Push subscriptions per account/device
-- ============================================================
CREATE TABLE chirp.push_subscriptions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
account_id TEXT NOT NULL,
endpoint TEXT NOT NULL UNIQUE,
p256dh_key TEXT NOT NULL,
auth_key TEXT NOT NULL,
alert_follow BOOLEAN DEFAULT TRUE,
alert_mention BOOLEAN DEFAULT TRUE,
alert_reblog BOOLEAN DEFAULT TRUE,
alert_poll BOOLEAN DEFAULT TRUE,
alert_status BOOLEAN DEFAULT FALSE,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- ============================================================
-- RATE LIMITING OVERRIDES (per account)
-- ============================================================
CREATE TABLE chirp.rate_limit_overrides (
account_id TEXT PRIMARY KEY,
requests_per_min INT DEFAULT 300,
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- indexes
CREATE INDEX ON chirp.bookmarks(account_id);
CREATE INDEX ON chirp.scheduled_posts(scheduled_at) WHERE dispatched_at IS NULL;
CREATE INDEX ON chirp.conversation_mutes(account_id);
CREATE INDEX ON chirp.push_subscriptions(account_id);
```
---
## 4. MISSING GOTOSOCIAL FEATURES — HOW CHIRP HANDLES EACH
| Missing Feature | Where Handled | Approach |
|----------------|---------------|----------|
| Full-text search | Chirp Go + Meilisearch | Index all public posts + users. Custom `/api/v2/search` endpoint |
| Post scheduling | Chirp Go + Redis + chirp.scheduled_posts | Store in DB, Redis job fires at scheduled_at, POSTs to GoToSocial |
| Conversation muting | Chirp Go + chirp.conversation_mutes | Client-side filter on timeline; Go server filters WebSocket stream |
| Federated hashtag search | Chirp Go | Query multiple known instances via their public APIs, merge results |
| Shared/imported block lists | Chirp Go admin | Fetch known block lists (e.g. Oliphant, Seirdy), push to GoToSocial admin API |
| Video streaming | MediaMTX (already running) | stream.myfacefeed.com — RTMP ingest, HLS/WebRTC playback, link in post |
| Web signup flow | Chirp Go | Custom signup page POSTs to GoToSocial admin API to create account |
| Account import/export | Chirp Go | Export: query GoToSocial for follows/blocks, serialize CSV. Import: re-follow via API |
| Follow/block list import | Chirp Go | Parse Mastodon-format CSV, batch follow/block via GoToSocial API |
| Hashtag following | Chirp Go + chirp schema | Track in DB, filter WebSocket stream to inject matching posts |
| Trending (local+federated) | Chirp Go | Aggregate hashtag counts from GoToSocial + query remote instances |
---
## 5. FULL API SHEET
### 5A. GoToSocial Pass-Through (Chirp proxies these unchanged)
```
ACCOUNTS
GET /api/v1/accounts/verify_credentials
PATCH /api/v1/accounts/update_credentials
GET /api/v1/accounts/:id
GET /api/v1/accounts/:id/statuses
GET /api/v1/accounts/:id/followers
GET /api/v1/accounts/:id/following
POST /api/v1/accounts/:id/follow
POST /api/v1/accounts/:id/unfollow
POST /api/v1/accounts/:id/block
POST /api/v1/accounts/:id/unblock
POST /api/v1/accounts/:id/mute
POST /api/v1/accounts/:id/unmute
GET /api/v1/accounts/relationships
GET /api/v1/accounts/search
POST /api/v1/accounts/:id/note
STATUSES
POST /api/v1/statuses (create post)
GET /api/v1/statuses/:id
DELETE /api/v1/statuses/:id
PUT /api/v1/statuses/:id (edit post)
GET /api/v1/statuses/:id/context (thread view)
GET /api/v1/statuses/:id/reblogged_by
GET /api/v1/statuses/:id/favourited_by
POST /api/v1/statuses/:id/favourite
POST /api/v1/statuses/:id/unfavourite
POST /api/v1/statuses/:id/reblog
POST /api/v1/statuses/:id/unreblog
POST /api/v1/statuses/:id/bookmark
POST /api/v1/statuses/:id/unbookmark
POST /api/v1/statuses/:id/pin
POST /api/v1/statuses/:id/unpin
GET /api/v1/statuses/:id/history (edit history)
TIMELINES
GET /api/v1/timelines/home
GET /api/v1/timelines/public
GET /api/v1/timelines/tag/:hashtag
GET /api/v1/timelines/list/:id
NOTIFICATIONS
GET /api/v1/notifications
GET /api/v1/notifications/:id
POST /api/v1/notifications/clear
POST /api/v1/notifications/:id/dismiss
MEDIA
POST /api/v2/media (upload attachment)
GET /api/v1/media/:id
PUT /api/v1/media/:id (update description)
STREAMING (WebSocket)
GET /api/v1/streaming?stream=user
GET /api/v1/streaming?stream=public
GET /api/v1/streaming?stream=public:local
GET /api/v1/streaming?stream=hashtag&tag=name
GET /api/v1/streaming?stream=list&list=id
GET /api/v1/streaming?stream=direct
FILTERS
GET /api/v2/filters
POST /api/v2/filters
GET /api/v2/filters/:id
PUT /api/v2/filters/:id
DELETE /api/v2/filters/:id
LISTS
GET /api/v1/lists
POST /api/v1/lists
GET /api/v1/lists/:id
PUT /api/v1/lists/:id
DELETE /api/v1/lists/:id
GET /api/v1/lists/:id/accounts
POST /api/v1/lists/:id/accounts
DELETE /api/v1/lists/:id/accounts
POLLS
GET /api/v1/polls/:id
POST /api/v1/polls/:id/votes
INSTANCE
GET /api/v1/instance
GET /api/v2/instance
GET /api/v1/instance/peers
GET /api/v1/instance/rules
OAUTH
POST /api/v1/apps
GET /oauth/authorize
POST /oauth/token
POST /oauth/revoke
TRENDS
GET /api/v1/trends/tags
GET /api/v1/trends/statuses
GET /api/v1/trends/links
FOLLOW REQUESTS
GET /api/v1/follow_requests
POST /api/v1/follow_requests/:id/authorize
POST /api/v1/follow_requests/:id/reject
ADMIN (GoToSocial admin API)
GET /api/v1/admin/accounts
POST /api/v1/admin/accounts/:id/action
GET /api/v1/admin/reports
GET /api/v1/admin/domain_allows
POST /api/v1/admin/domain_allows
GET /api/v1/admin/domain_blocks
POST /api/v1/admin/domain_blocks
```
### 5B. Chirp Custom API Extensions (/api/v2/chirp/*)
```
SEARCH (Meilisearch-backed)
GET /api/v2/chirp/search
?q=query&type=accounts|statuses|hashtags
&federated=true|false
&from=username
&before=date&after=date
&limit=20&offset=0
SCHEDULED POSTS
GET /api/v2/chirp/scheduled_statuses
POST /api/v2/chirp/scheduled_statuses
body: { text, scheduled_at, visibility, cw, media_ids, poll }
GET /api/v2/chirp/scheduled_statuses/:id
PUT /api/v2/chirp/scheduled_statuses/:id
DELETE /api/v2/chirp/scheduled_statuses/:id
CONVERSATION MUTES
POST /api/v2/chirp/statuses/:id/mute_conversation
POST /api/v2/chirp/statuses/:id/unmute_conversation
GET /api/v2/chirp/muted_conversations
ACCOUNT IMPORT / EXPORT
GET /api/v2/chirp/export/follows → CSV download
GET /api/v2/chirp/export/blocks → CSV download
GET /api/v2/chirp/export/mutes → CSV download
POST /api/v2/chirp/import/follows → CSV upload, batch follow
POST /api/v2/chirp/import/blocks → CSV upload, batch block
BLOCK LIST SUBSCRIPTIONS (shared block lists)
GET /api/v2/chirp/admin/blocklist_subscriptions
POST /api/v2/chirp/admin/blocklist_subscriptions
body: { url, description }
DELETE /api/v2/chirp/admin/blocklist_subscriptions/:id
POST /api/v2/chirp/admin/blocklist_subscriptions/:id/sync
FEDERATED HASHTAG SEARCH
GET /api/v2/chirp/federated/tag/:hashtag
Queries known peer instances, merges + deduplicates results
TRENDING (enhanced)
GET /api/v2/chirp/trends/federated Merges trends from peer instances
WEB PUSH (Chirp-managed subscriptions)
POST /api/v2/chirp/push/subscription
body: { endpoint, keys: { p256dh, auth }, alerts: {...} }
PUT /api/v2/chirp/push/subscription
GET /api/v2/chirp/push/subscription
DELETE /api/v2/chirp/push/subscription
LIVE VIDEO (MediaMTX integration)
POST /api/v2/chirp/live/start Creates stream key, returns RTMP URL
DELETE /api/v2/chirp/live/:stream_key Ends stream
GET /api/v2/chirp/live/:stream_key HLS playback URL + viewer count
INSTANCE ADMIN (Chirp layer)
GET /api/v2/chirp/admin/stats
GET /api/v2/chirp/admin/search_index Meilisearch index stats
POST /api/v2/chirp/admin/search_index/rebuild
```
---
## 6. SEARCH — MEILISEARCH IMPLEMENTATION
### Index Design
```
Index: chirp_statuses
Searchable fields: content, account_username, account_display_name, tags
Filterable: account_id, visibility, language, created_at, tags
Sortable: created_at, favourites_count, reblogs_count
Index: chirp_accounts
Searchable fields: username, display_name, note (bio), fields
Filterable: instance, discoverable
Sortable: followers_count
Index: chirp_tags
Searchable fields: name
Sortable: uses_week, uses_total
```
### Sync Strategy
```
1. Initial index: bulk fetch from GoToSocial admin API, batch insert
2. Real-time sync: subscribe to GoToSocial WebSocket stream
- on Create → index new status
- on Delete → remove from index
- on Update → re-index status
3. Chirp Go indexer runs as goroutine, channels for queue
4. Meilisearch tasks are async — track task ID in chirp.search_sync
5. Re-index job runs nightly to catch any gaps
```
### Go Client
```go
import "github.com/meilisearch/meilisearch-go"
client := meilisearch.New("http://meilisearch:7700",
meilisearch.WithAPIKey("MEILI_MASTER_KEY"))
index := client.Index("chirp_statuses")
index.AddDocuments(docs, "id")
index.Search("golang", &meilisearch.SearchRequest{
Limit: 20,
Filter: "visibility = public",
})
```
---
## 7. STREAMING — WEBSOCKET ARCHITECTURE
GoToSocial provides native WebSocket streaming at `/api/v1/streaming`.
Chirp Go server bridges it and adds custom event types.
```
User Browser (WebSocket)
Chirp Go WebSocket Hub
┌────┴─────────────────────────────┐
│ │
▼ ▼
GoToSocial Streaming Chirp Custom Events
/api/v1/streaming - scheduled_post_sent
(user, public, hashtag, list) - conversation_muted
- search_result_push
- live_stream_started
```
### Hub Design (Go)
```
Hub struct {
clients map[*Client]bool
broadcast chan Event
register chan *Client
unregister chan *Client
rooms map[string]map[*Client]bool // room = stream type
}
```
- One goroutine per GoToSocial WebSocket connection
- Redis pub/sub for fan-out across multiple Chirp pod replicas
- Events: `update`, `notification`, `delete`, `filters_changed` + Chirp custom types
---
## 8. POST SCHEDULER — REDIS JOB QUEUE
```
POST /api/v2/chirp/scheduled_statuses
1. Store in chirp.scheduled_posts (PostgreSQL)
2. Push job to Redis sorted set (score = unix timestamp of scheduled_at)
Chirp Scheduler Goroutine (runs every 30 seconds)
ZRANGEBYSCORE chirp:scheduled 0 now()
For each due job:
1. POST to GoToSocial /api/v1/statuses with stored content
2. Mark chirp.scheduled_posts.dispatched_at = now()
3. Remove from Redis sorted set
4. On failure: set failed_at, store error, retry up to 3x
```
---
## 9. ACTIVITYPUB — WHAT GOTOSOCIAL HANDLES
GoToSocial manages all ActivityPub automatically. No custom AP code needed in Chirp.
| Activity | Who sends | Who receives |
|----------|-----------|-------------|
| Create (Note) | GoToSocial | Remote servers |
| Delete | GoToSocial | Remote servers |
| Announce (boost) | GoToSocial | Remote servers |
| Like | GoToSocial | Remote servers |
| Follow/Unfollow | GoToSocial | Remote servers |
| Accept/Reject | GoToSocial | Remote servers |
| Update (profile/post) | GoToSocial | Remote servers |
| Move (migration) | GoToSocial | Remote servers |
| Flag (report) | GoToSocial | Remote servers |
Federation endpoints GoToSocial exposes (do not intercept):
```
GET /.well-known/webfinger
GET /.well-known/nodeinfo
GET /nodeinfo/2.0
GET /users/:username (Actor object)
GET /users/:username/inbox (AP inbox)
POST /users/:username/inbox (receive AP activities)
GET /users/:username/outbox
GET /users/:username/followers
GET /users/:username/following
```
---
## 10. OAUTH / AUTHENTICATION FLOW
GoToSocial supports OIDC — connect to Zitadel (already at auth.myfacefeed.com):
```
User clicks Login
Chirp Go → redirect to Zitadel (auth.myfacefeed.com)
▼ (user authenticates with Zitadel)
Zitadel → callback to GoToSocial OIDC callback
GoToSocial issues OAuth token
Chirp Go stores token in Redis session
User is logged in — token used for all API calls
```
Benefits:
- SSO across entire MFF platform (same Zitadel login as Nextcloud, Mattermost, etc.)
- No separate password for Chirp
- Chirp Go never sees raw passwords
---
## 11. PWA SPECIFICS
### Web Manifest (/manifest.json)
```json
{
"name": "Chirp",
"short_name": "Chirp",
"start_url": "/app",
"display": "standalone",
"background_color": "#0f172a",
"theme_color": "#2563eb",
"icons": [
{ "src": "/icons/192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/icons/512.png", "sizes": "512x512", "type": "image/png" },
{ "src": "/icons/512-maskable.png", "sizes": "512x512",
"type": "image/png", "purpose": "maskable" }
],
"shortcuts": [
{ "name": "Home", "url": "/app/home" },
{ "name": "Notifications", "url": "/app/notifications" },
{ "name": "Compose", "url": "/app/compose" }
]
}
```
### Service Worker Capabilities
- Cache shell (app HTML/CSS/JS) for offline load
- Cache recent timeline posts for offline reading
- Background sync: queue posts written offline, send when back online
- Push notifications: receive Web Push, display even when app closed
- Share target: receive shared URLs from other apps
---
## 12. GO CODE STRUCTURE
```
chirp/
├── cmd/
│ └── chirp/
│ └── main.go entry point
├── internal/
│ ├── api/
│ │ ├── proxy.go GoToSocial API pass-through
│ │ ├── search.go /api/v2/chirp/search handlers
│ │ ├── scheduler.go scheduled posts handlers
│ │ ├── mutes.go conversation mute handlers
│ │ ├── push.go Web Push handlers
│ │ ├── live.go MediaMTX live video handlers
│ │ ├── import_export.go CSV import/export
│ │ └── admin.go admin endpoints
│ │
│ ├── ws/
│ │ ├── hub.go WebSocket hub
│ │ ├── client.go per-connection client
│ │ └── bridge.go GoToSocial stream bridge
│ │
│ ├── search/
│ │ ├── meilisearch.go search client wrapper
│ │ ├── indexer.go real-time indexing goroutine
│ │ └── sync.go nightly re-index job
│ │
│ ├── scheduler/
│ │ ├── scheduler.go Redis job runner
│ │ └── dispatcher.go posts to GoToSocial
│ │
│ ├── db/
│ │ ├── db.go PostgreSQL connection pool
│ │ ├── bookmarks.go
│ │ ├── lists.go
│ │ ├── scheduled.go
│ │ ├── mutes.go
│ │ └── push.go
│ │
│ ├── gts/
│ │ └── client.go GoToSocial API client (typed)
│ │
│ ├── push/
│ │ └── webpush.go Web Push sender (VAPID)
│ │
│ └── config/
│ └── config.go env-based config
├── web/
│ ├── static/
│ │ ├── sw.js service worker
│ │ ├── manifest.json
│ │ ├── app.js PWA app bundle
│ │ └── app.css
│ └── templates/
│ ├── landing.html public landing page
│ └── app.html PWA shell (single page)
├── migrations/
│ └── *.sql chirp schema migrations
├── Dockerfile (minimal — scratch or alpine)
├── go.mod
└── go.sum
```
---
## 13. K3S DEPLOYMENT
```yaml
# 3 pods: gotosocial, chirp, meilisearch
# Shared: postgresql (existing), redis (existing), minio (existing)
Deployments:
gotosocial 1 replica ~350MB RAM chirp-media S3 bucket
chirp 2 replicas ~100MB RAM (Go is lean)
meilisearch 1 replica ~300MB RAM PVC for index data
Services:
gotosocial ClusterIP :8080
chirp ClusterIP :3000
meilisearch ClusterIP :7700 (internal only — never exposed)
Ingress:
chirp.myfacefeed.com → chirp :3000
Total new RAM: ~750MB-1GB
(vs Mastodon which would need 2-4GB)
```
---
## 14. BUILD ORDER (Implementation Sequence)
```
Phase 1 — Backend up
1. GoToSocial K3s deployment + MinIO bucket + PostgreSQL DB
2. Zitadel OIDC config for GoToSocial
3. chirp schema migrations
4. Meilisearch deployment + initial index
Phase 2 — Chirp Go server core
5. API proxy (pass-through to GoToSocial)
6. WebSocket hub + GoToSocial stream bridge
7. Session management (Redis)
8. OAuth flow with Zitadel
Phase 3 — PWA frontend
9. Landing page (chirp.myfacefeed.com public view)
10. App shell + service worker + manifest
11. Timeline view (home, public, local)
12. Compose post, reply, boost, favourite
13. Profile pages
14. Notifications
Phase 4 — Custom features (missing from GoToSocial)
15. Search (Meilisearch integration)
16. Post scheduling (Redis scheduler)
17. Conversation muting
18. Import/export follows/blocks
19. Shared block lists
20. Live video integration (MediaMTX)
Phase 5 — Polish
21. Push notifications (Web Push + VAPID)
22. Offline support (service worker caching)
23. PWA install prompt
24. Mobile responsive polish
25. Admin panel
```
---
## 15. ENVIRONMENT CONFIG (Chirp Go Server)
```bash
# GoToSocial
GTS_HOST=gotosocial:8080
GTS_ADMIN_TOKEN=<admin token>
# PostgreSQL (chirp schema)
CHIRP_DB_HOST=postgresql:5432
CHIRP_DB_NAME=chirp
CHIRP_DB_USER=chirp_app
CHIRP_DB_PASS=<password>
# Redis
REDIS_HOST=redis:6379
REDIS_PASS=<password>
# Meilisearch
MEILI_HOST=http://meilisearch:7700
MEILI_KEY=<master key>
# OAuth / Zitadel
OIDC_ISSUER=https://auth.myfacefeed.com
OIDC_CLIENT_ID=chirp
OIDC_CLIENT_SECRET=<secret>
# Web Push (VAPID keys — generate once)
VAPID_PUBLIC_KEY=<key>
VAPID_PRIVATE_KEY=<key>
VAPID_SUBJECT=mailto:admin@myfacefeed.com
# MinIO
S3_ENDPOINT=s3.myfacefeed.com
S3_BUCKET=chirp-media
S3_ACCESS_KEY=<key>
S3_SECRET_KEY=<key>
# MediaMTX
MEDIAMTX_API=http://mediamtx:9997
MEDIAMTX_RTMP_HOST=rtmp://stream.myfacefeed.com
# App
CHIRP_HOST=https://chirp.myfacefeed.com
CHIRP_PORT=3000
SESSION_SECRET=<random 64 bytes>
```