소스 정보
- 저장소
- themadorg/bedrud
- 최근 소스 활동
- 2026년 7월 9일 05:23
- 감지된 SKILL.md 언어
- 영어
- 스타
- 7
- 포크
- 2
설치 방법
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
소스 파일 검토
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
메뉴
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/themadorg/bedrud --skill bedrud-server명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
SKILL.md 표시 중
Operational tooling — cobra CLI surface, Linux installer, user/room management, TLS certs, config/settings, invite tokens.
Skill router — classify task → load correct sub-skill.
Create a GitHub issue on themadorg/bedrud using .github ISSUE_TEMPLATE forms and gh CLI. Sets Issue Type (Bug/Feature/Task), Priority, surface labels, research, screenshots. Triggers: create issue, open issue, file bug, feature request, GitHub issue, report bug, /create-bedrud-issue.
SOC 직업 분류 기준
| name | bedrud-server |
| description | Full Go backend code map. Load for any server/API/auth/DB/model/handler work. |
| license | Apache License |
Go module bedrud. Root: server/. Fiber HTTP + GORM + embedded LiveKit + job queue.
Leaf skills (deeper detail when needed):
| Skill | Scope |
|---|---|
bedrud-http | Entrypoints, bootstrap, handlers, routes, lkutil |
bedrud-auth | AuthService, JWT, middleware, rate limits |
bedrud-data | Models, repos, migrations, testutil |
bedrud-jobs | Queue, scheduler, cleanup, recording service/store, chat storage |
bedrud-realtime | Embedded LiveKit binary lifecycle |
bedrud-ops-cli | Cobra CLI, installer, usercli/roomcli, utils |
bedrud-email-cerberus | Email templates + send_email branding |
cmd/bedrud/main.go — Production CLIThin wrapper → cli.Execute(version) (Cobra in internal/cli/). Legacy flags still work (--run, --livekit, --version).
| Command | Calls | Purpose |
|---|---|---|
run / server | server.Run(configPath, version) | Full app (API + optional embedded LK + SPA) |
livekit / --livekit | livekit.RunLiveKit(configPath) | Standalone LK binary |
install / uninstall | install.LinuxInstall / LinuxUninstall | Linux installer (systemd/OpenRC/SysV) |
user * | usercli.* | create/delete/promote/demote/list/info/password/enable/disable |
room * | roomcli.* | list/info/close/suspend/reactivate/kick |
config / settings / invite-token / cert / db | respective CLI | Ops tooling |
version | Version string |
Config: --config, BEDRUD_CONFIG, or CONFIG_PATH. Management cmds default /etc/bedrud/config.yaml.
cmd/server/main.go — Dev API (Air)No Cobra. Wires subsystems + routes + Swagger/Scalar + SPA. Minor drift vs prod (GET /admin/stats still registered here; prod uses overview). Prefer internal/server/server.go as source of truth.
Health: GET /api/health · Ready: GET /api/ready. Docs: /api/swagger/*, /api/scalar. Disable: DISABLE_API_DOCS.
ui.go — Frontend embed//go:embed all:frontend → UI embed.FS. make build copies apps/web/build/* → server/frontend/.
server/internal/)| Package | Role |
|---|---|
server/ | Prod bootstrap Run(configPath, version) |
handlers/ | HTTP handlers (auth, room, users, admin, recording, webhook, cert, queue) |
auth/ | AuthService, JWT, session store, challenge store, email canonicalize |
middleware/ | Protected, RBAC, CSRF bearer-for-mutations, guest reject, email-verify, rate limit, recordings gate |
models/ | GORM models + admin/overview DTOs |
repository/ | Data access (user, room, passkey, settings, invite, prefs, verification, webhook, recording) |
database/ | GORM init (SQLite/Postgres), AutoMigrate + PG FKs |
queue/ | Job enqueue/worker + 8 handlers + email templates |
scheduler/ | gocron: idle rooms, guests, unverified, tokens, queue cleanup, cert, recording retention |
services/ | RoomCleanupService, RecordingService |
storage/ | Chat upload, avatar, recording store |
livekit/ | Embedded LK binary + temp YAML + process mgmt |
lkutil/ | RoomService/Egress clients, AuthContext, system messages |
cli/ | Cobra command surface |
clioutput/ | --json envelopes |
usercli/ / roomcli/ | CLI domain ops |
install/ | LinuxInstall / LinuxUninstall |
utils/ | TLS certs, SMTP, keys, safeio, net |
testutil/ | In-memory SQLite + LiveKit mocks |
templates/ | Legacy HTML (login/index) — SPA usually embedded |
internal/server/server.go)Run(configPath, version) error sequence:
room, user, recording, …)recordingRepo live; recStore/recCfg currently nil until store wired)Init + hydrate ban set from inactive users*)/livekit when internal/api routes + static uploads + SPAhttpPortRecording bootstrap gap (important): handler, service, repo, store, queue types, and webhook processing are implemented, but prod server.Run still has commented blocks for:
NewRecordingStore / egress client / RecordingService / RecordingHandlerprocess_recording, recording_delete/api/rooms/:id/... and admin recording routesWire same pattern as integration tests when enabling. Jobs enqueued without a registered handler → permanent fail.
All API under Fiber group /api (@BasePath /api).
| Area | Prefix | Notes |
|---|---|---|
| Room lifecycle + moderation | /api/room/... (singular) | create, join, list, :roomId/* |
| Recording | /api/rooms/:id/... (plural) | SHIPPED code; routes still commented in bootstrap |
| Auth | /api/auth/... | register, login, passkeys, verify, preferences, OAuth |
| Admin | /api/admin/... | superadmin group |
| LiveKit webhook | POST /api/livekit/webhook | LK JWT, not app JWT |
| Static | /uploads/chat/* (auth), /uploads/avatars/* (public) | |
| Embedded LK proxy | /livekit/* | internal only |
Admin group middleware: Protected → RequireEmailVerified → RequireAccess(superadmin).
/api/health /api/ready
/api/auth/* register, login, guest-login, refresh, logout
me, me/avatar, password, verify*, forgot/reset
passkey/*, preferences, settings (public)
:provider/login|callback
/api/room/* create, join, guest-join, refresh-token, list, archived
:roomId/{kick,mute,ban,video,promote,demote,chat,deafen,
undeafen,ask,spotlight,screenshare,presence,
participant,stage,settings,chat/upload}
DELETE :roomId → 202 room_delete
/api/rooms/:id/recording* (IMPLEMENTED; bootstrap routes commented)
/api/admin/* users*, rooms*, settings*, invite-tokens, webhooks*
overview, queue, cert-info, online-count, livekit/stats
recordings* (IMPLEMENTED; bootstrap commented)
/api/livekit/webhook
/api/cert
/uploads/chat/* /uploads/avatars/*
/livekit/* (internal LK reverse proxy)
| Handler | Route |
|---|---|
BringToStage | POST /api/room/:roomId/stage/:identity/bring |
RemoveFromStage | POST /api/room/:roomId/stage/:identity/remove |
AdminGenerateToken | POST /api/admin/rooms/:roomId/token |
Repo stage helpers (BringToStage/RemoveFromStage on participants) exist; HTTP layer returns 501.
Do not call email, webhook, or recording “stubs” — they are implemented at handler/service/queue layer.
internal/handlers/)| File | Responsibility |
|---|---|
auth.go | OAuth begin/callback (goth) |
auth_handler.go | Local auth, passkeys, profile/avatar, verify, password reset |
room.go | Room lifecycle, moderation, chat upload, admin room ops |
recording_handler.go | Start/stop/list/get/wait/clear + admin list/bulk delete → RecordingService |
users.go | Admin users, bulk, force-logout, verify, async delete |
admin_handler.go | Settings, invite tokens, webhooks, test email, validate |
preferences_handler.go | User JSON prefs ≤4KB |
admin_overview.go | Aggregated KPIs / health |
admin_queue.go | Queue stats + email diagnostics |
cert_handler.go | Cert PEM download + admin cert-info |
livekit_webhook.go | participant_left, room_finished, egress_started, egress_ended |
cooldown.go / errors.go / room_auth.go / models.go | Shared helpers + DTOs |
| Method | Path | Notes |
|---|---|---|
| POST | /auth/register, /login, /guest-login, /refresh | Rate limited |
| POST | /auth/logout | Protected |
| GET/PUT | /auth/me | Protected + email verified |
| POST/DELETE | /auth/me/avatar | Avatar upload |
| PUT | /auth/password | Change password |
| POST/GET | /auth/verify, /verify/status, /verify/resend | Email verification |
| POST | /auth/forgot-password, /reset-password | Reset flow |
| POST | /auth/passkey/* | Register/login/signup begin+finish |
| GET/PUT | /auth/preferences | Prefs |
| GET | /auth/settings | Public flags (incl. recordingsEnabled) |
| GET | /auth/:provider/login|callback | OAuth |
/room)| Method | Path | Purpose |
|---|---|---|
| POST | /room/create | Create LK + DB |
| POST | /room/join, /guest-join | Join + LK token |
| POST | /room/refresh-token | Refresh LK JWT |
| GET | /room/list, /archived | User rooms / archives |
| POST | /room/:roomId/{kick,mute,ban,...}/:identity | Moderation |
| GET | /room/:roomId/presence | Presence (API rate limit) |
| PUT | /room/:roomId/settings | Settings (incl. recordingsAllowed) |
| DELETE | /room/:roomId | 202 · room_delete (archive, Purge=false) |
| POST | /room/:roomId/chat/upload | Chat image |
/rooms — code ready)| Method | Path | Purpose |
|---|---|---|
| POST | /rooms/:id/recording/start|stop | Egress control · RecordingsEnabled |
| GET | /rooms/:id/recordings, .../:rid, .../:rid/wait | List/get/long-poll |
| DELETE | /rooms/:id/recordings, .../:recordingId | Clear |
| GET | /admin/recordings | Admin list |
| POST | /admin/recordings/bulk/delete | 202 · recording_delete |
Users: list, recent, bulk ban/promote/delete, status, accesses, force-logout, password, detail, sessions, verify, delete.
Rooms admin: list, events, close/suspend/reactivate/update, participants, bulk, livekit stats, online-count.
Settings/webhooks/invite-tokens/overview/queue/cert as in route map.
internal/auth/ + middleware/)Password: HashPassword / VerifyPassword — bcrypt(sha256(password)) with legacy plain-bcrypt fallback.
Flows: Register, Login (email-verify gate), GuestLogin, refresh rotate (RotateRefreshToken), Logout (block refresh + revoke access), profile/avatar/email/password, passkeys (WebAuthn), OAuth Init/ReloadProviders.
jwt.go)GenerateTokenPair); purpose tokens: email_verify, password_reset| Fn | Behavior |
|---|---|
Protected | Bearer or access_token cookie → claims in Locals("user"); ban → 403 |
RequireAccess | Hierarchical: superadmin > admin > moderator > user |
RequireBearerForMutations | CSRF: mutations need Authorization header |
RejectGuest | Block guest provider |
RequireEmailVerified | Always DB EmailVerifiedAt (never trust JWT claim) |
Auth/Resend/Guest/APIRateLimiter | Per-IP Fiber limiters |
RecordingsEnabled | 403 if system flag off |
internal/models/)| Model | Table / notes |
|---|---|
User | email+provider unique; Accesses text[]; EmailVerifiedAt; PasswordChangedAt; refresh stored as SHA-256 |
Room | Settings embedded (settings_* incl. RecordingsAllowed, IsPersistent); LastActivityAt; soft DeletedAt archive; active-name unique partial index |
RoomParticipant / RoomPermissions | Stage, moderator, ban, chat block flags |
Passkey | WebAuthn credentials |
BlockedRefreshToken | Hashed refresh blocklist |
SystemSettings | Singleton ID=1 — registration, OAuth, server, LK, CORS, chat, email, recordings, limits (runtime overlay via GetEffectiveSettings) |
InviteToken | Gated registration |
UserPreferences | JSON blob PK=userID |
ChatUpload | Room/user upload metadata (no dedicated repo) |
Job | Queue rows: pending/active/done/failed |
VerificationEvent | sent/resent/success/failed/admin_force/email_change |
Webhook | Outbound subscriptions; events: room.created/ended, participant.joined, recording.completed, ping |
Recording | LiveKit egress lifecycle: pending→started→processing→completed|failed|deleting |
QueueStats / overview DTOs | Admin API shapes (not tables) |
Relationships: User→Passkeys/BlockedTokens/Prefs/Participants; Room→Participants/Permissions/ChatUploads/Recordings; Job & Webhook standalone; SystemSettings singleton.
internal/repository/)| Repo | Highlights |
|---|---|
UserRepository | Upsert, hashed refresh CAS, cascade delete, admin filters/batch, guest/unverified cleanup |
RoomRepository | Create/join capacity, stage/mod flags, soft/hard delete, admin filters, KPIs/trends |
PasskeyRepository | CRUD + counter advance |
SettingsRepository | Get/Save/GetEffectiveSettings |
InviteTokenRepository | Create/list/mark used/delete |
UserPreferencesRepository | Get/Upsert/Delete |
VerificationEventRepository | Record + recent by user |
WebhookRepository | CRUD, ListActive(event), secret rotate |
RecordingRepository | Optimistic status transitions, list/admin, stale/retention queries |
No SQL migration dir — schema via database.RunMigrations() AutoMigrate + Postgres FKs. Skip: BEDRUD_SKIP_MIGRATE=1.
internal/queue/) — 8 job typesWorker polls jobs. Postgres SKIP LOCKED; SQLite two-step + MaxOpenConns(1). Default poll 500ms, concurrency 1, maxAttempts 3, backoff 2^n * 5s cap 1h. Depth cap 10000.
| Type | Payload | Handler | Status |
|---|---|---|---|
user_delete | UserID, Email, RoomIDs | NewUserDeleteHandler | Registered |
room_delete | RoomID, msgs, Purge | archive (false) or cascade (true) | Registered |
room_suspend | RoomID | SuspendRoom | Registered |
chat_upload_s3 | base64 + meta | Store + tracker | Registered |
send_email | To, Subject, TemplateName, Data | SMTP + Cerberus templates | Registered |
dispatch_webhook | URL, Event, Body, Secret | HMAC POST (soft-fail) | Registered |
process_recording | egress/file meta | Download → RecordingStore → complete + webhooks | Implemented; bootstrap commented |
recording_delete | RecordingID, RoomID, RoomName | Hard-delete DB row | Implemented; bootstrap commented |
handler_stubs.go is an index comment only — no stub handlers remain.
Email templates: welcome, verify_email, password_reset, password_changed, room_invite (registered, not yet enqueued by handlers), generic. Branding from config + SystemSettings.
RoomCleanupService| Fn | Behavior |
|---|---|
CascadeDeleteRoom | Stop egress → system msg → LK delete → chat cleanup → delete recordings → HardDeleteRoom |
ArchiveRoom | Soft-delete; keeps recordings |
SuspendRoom | Idle + disconnect; clears recording rows |
DeleteUserRooms | Cascade each owned room |
RecordingService (SHIPPED)Gates: system RecordingsEnabled → room exists → room RecordingsAllowed → max per room → no active.
StartRecording → RoomCompositeEgress MP4 AudioOnly=true.
StopRecording → StopEgress only; webhook egress_ended owns processing enqueue.
Lifecycle: pending → started → (webhook) processing → process_recording → completed|failed.
| Component | Role |
|---|---|
ChatUploadStore | disk/hybrid/inline/s3 (SigV4) |
ChatUploadTracker | Record + DeleteByRoom + quotas |
Avatar | ./data/uploads/avatars, max 2MB |
RecordingStore | Disk or S3; key recordings/{user}/{room}/{id}-….mp4 |
internal/scheduler/)| Cadence | Task |
|---|---|
| 1 min | Expired rooms + idle LK empty rooms (skip persistent) |
| Weekly 03:00 | Delete old guests (7d) |
| Daily 03:30 | Unverified local/passkey accounts |
| Hourly | Blocked refresh tokens + prune revoked access JWTs |
| Daily 03:00 | Queue cleanup (done 7d / failed 30d); stale recordings 7d |
| Daily 09:00 | Self-signed cert renew if ≤30d |
CleanupIntervalHours | Recording retention on archived rooms (needs recStore) |
internal/livekit/)bin/livekit-server (placeholder OK for build; real binary for run)StartInternalServer / RunLiveKit; skip if LIVEKIT_MANAGED=truehttp://localhost:<httpPort>/api/livekit/webhook/livekit reverse-proxy when internalinternal/lkutil/)NewClient (RoomService), NewEgressClient, AuthContext, SendSystemMessage (+ deletedIdentity variant).
handlers/livekit_webhook.go)Verifies LK JWT. Handles participant/room lifecycle + egress → may enqueue process_recording. External LK: configure webhook URL manually to /api/livekit/webhook.
internal/cli/, usercli/, roomcli/, install/)Cobra root: run, livekit, install/uninstall, cert, user, room, config, settings, invite-token, db, version.
--json via clioutput. Installer: LinuxInstall — multi-init (systemd/OpenRC/SysV/container), secrets, /etc/bedrud, /var/lib/bedrud, optional ACME/self-signed/external LK.
usercli: promote/demote/create/delete/list/info/password/active with full cascade delete via cleanup service.
roomcli: list/info/close/suspend/reactivate/kick.
Utils: TLS multi-algo certs, SMTP, LiveKit keypair, SafeCreate, OutboundIP.
cmd/bedrud → internal/cli
├─ server.Run → handlers → repository → models
│ → auth → repository
│ → middleware → auth
│ → queue → services / storage / email
│ → scheduler → repository + livekit + auth prune
│ → services (cleanup, recording)
│ → storage (chat, avatar, recording)
│ → livekit (embed process)
│ → lkutil (RoomService / Egress)
├─ livekit.RunLiveKit
├─ install.LinuxInstall
├─ usercli / roomcli → repository + services + storage + lkutil
└─ utils / database / clioutput
cmd/server → same wiring inline (dev; Swagger)
| Area | Keys / env |
|---|---|
| Server | host, port, httpPort, TLS, ACME, behindProxy, trustedProxies |
| Auth | JWTSecret, TokenDuration, RequireEmailVerification, OAuth, passkey TTL |
| Database | type sqlite|postgres, path/DSN, pool |
| LiveKit | host, internalHost, apiKey/secret (≥32 embedded), external, nodeIP, configPath |
| Queue | pollInterval, maxAttempts, concurrency |
| SMTP + template branding | |
| Chat | uploads backend/disk/s3/inlineMax |
| Recording | maxFileSizeMB, storageDir, maxPerRoom, retentionHours |
| RateLimit | auth/guest/api/resend max + window |
Runtime overrides: many via SystemSettings + GetEffectiveSettings.
/api/room/*; recording is /api/rooms/:id/*.DeletedAt); hard delete via HardDeleteRoom / purge jobs. No AdminDeleteRoom name.idx_rooms_active_name); idle/archived can reuse names.EmailVerifiedAt is client-facing only.MaxOpenConns(1)); pending jobs stuck → check DB/worker map.internal/cli), not manual arg switch in main.LinuxInstall, not Debian-only.make init.cd server && go vet ./...
cd server && go build ./...
cd server && go test -v -count=1 ./...
# race (CI): go test -race ./...
Swagger regen: make swagger-gen (swag CLI). UI: /api/swagger, /api/scalar.