6.0 KiB
Shagram — Go WebSocket Chat | DevOps Pet Project
Shagram is a small real-time chat application written in Go.
This is primarily a DevOps-focused pet project: the app itself is intentionally simple, while the main goal is to practice CI/CD, containerization, reverse proxy/TLS, and operating self-hosted infrastructure.
Public repo note: I intentionally do not publish any real server URLs, IP addresses, credentials, or registry endpoints here.
What it does
- Multi-room chat via WebSockets (
/ws/:room) with message broadcast. - Message history persisted in SQLite and available through an HTTP API.
- Minimal browser UI served from
./static.
DevOps skills demonstrated
- Cloud: Deployed and operated on a VPS in Yandex Cloud (self-managed infrastructure).
- Docker: Multi-stage image build for a Go service; small runtime image.
- Docker Compose: Stack orchestration for the app (Go service + Nginx) and for CI infrastructure (Jenkins controller + agent).
- Nginx: Reverse proxy configuration for HTTP + WebSocket (Upgrade/Connection headers) and TLS termination.
- TLS: Self-signed certificates for dev/demo environments; production note to use a trusted CA (e.g., Let’s Encrypt).
- CI/CD with Jenkins: Pipeline that builds a Docker image, tags it, pushes it to a registry, and deploys via Docker Compose (deploy gated to
main). - GitHub → Jenkins automation: GitHub repository webhook triggers Jenkins builds on push/changes.
- Private registry (Harbor): Self-hosted registry for storing and distributing built images.
- Jenkins agent architecture: Dedicated inbound Docker agent for builds, with Docker socket mounting for Docker-based workloads (security caveat applies).
- Ops automation: One-command restart/update script (
restart-stacks.sh) that pulls and recreates Shagram, Jenkins, and Harbor stacks.
Roadmap
- Kubernetes: Migrate deployment from Docker Compose to Kubernetes (manifests/Helm), add Ingress + cert-manager, and prepare the app for future scaling (multi-replica WebSocket strategy and persistent storage).
Application stack
- Go + Gin (HTTP API and routing) (
cmd/server). - WebSockets: Gorilla WebSocket (
internal/api,internal/websocket). - Auth: JWT access tokens (
internal/auth); login endpoint issues tokens; WebSocket uses?token=.... - Storage: SQLite (
internal/db) initialized frommigrations/schema.sql.
Configuration
Environment variables:
JWT_SECRET(required): Signing key for JWT tokens.DATABASE_PATH(optional): SQLite file path; defaults to/app/data/shagram.dbin the container.WS_ALLOWED_ORIGINS(required for WebSocket): Comma-separated list of allowedOriginvalues for browser WebSocket connections.APP_IMAGE(optional, deployment): Docker image reference used by Compose to deploy a prebuilt image.
HTTP API
Server listens on :8080.
POST /api/auth/login→{ "access_token": "..." }GET /api/me(requiresAuthorization: Bearer <token>) →{ "username": "..." }GET /api/rooms→{ "rooms": [...] }GET /api/messages/:room→{ "messages": [...] }(last 50 messages)
WebSocket
Endpoint:
ws(s)://<host>/ws/<room>?token=<access_token>
Client sends JSON:
{ "text": "hello" }
Server broadcasts plain text messages:
alice: hello
Security notes:
- WebSocket requires a JWT token (
?token=...) and validates theOriginheader againstWS_ALLOWED_ORIGINS. - This project is still a learning lab; for production you would additionally harden auth, rate limits, and request validation.
Data model (SQLite)
Schema is created on startup from migrations/schema.sql and includes:
rooms(id, name)messages(id, room_id, user, text, created_at)
Quickstart (local, without Docker)
Prerequisites: Go toolchain.
export JWT_SECRET=change-me
export DATABASE_PATH=./shagram.db
export WS_ALLOWED_ORIGINS=http://localhost:8080
go run ./cmd/server
Open:
Quickstart (Docker Compose + Nginx TLS)
Prerequisites: Docker + Docker Compose.
-
Generate a self-signed TLS certificate (dev/demo only):
Seedeploy/shagram/nginx/certs/README.md. -
Create
deploy/shagram/.env:
JWT_SECRET=change-me
WS_ALLOWED_ORIGINS=https://localhost
Optional: override the app image (e.g., from your registry)
APP_IMAGE=<your-registry>/<project>/shagram:<tag>
- Start:
cd deploy/shagram
docker compose up -d --build
Open:
CI/CD overview (Jenkins + Registry)
- Jenkins pipeline builds a Docker image from this repository, tags it, pushes it to a registry, and deploys the updated stack with Docker Compose (deploy gated to
main). - Jenkins is triggered by a GitHub repository webhook (push/changes).
- Jenkins setup instructions:
infra/jenkins/README.md.
Ops: restart / update all stacks
On the host, restart-stacks.sh can be used to pull and recreate:
- Shagram stack (app + nginx)
- Jenkins stack (controller + agent)
- Harbor stack (registry)
Repository structure
cmd/server/— Gin router, endpoints, wiring.internal/auth/— JWT issuing/parsing + Gin auth middleware.internal/api/— WebSocket handler (token validation + origin check + DB persistence + broadcast).internal/websocket/— In-memory hub/rooms and broadcast logic.internal/db/— SQLite connection + schema bootstrap.migrations/— SQL schema.static/— Minimal web UI.deploy/shagram/— Docker Compose + Nginx config/certs.infra/jenkins/— Jenkins controller/agent Compose setup and docs.
Notes / trade-offs
- The WebSocket hub is in-memory, so the app is intended to run as a single instance (no horizontal scaling).
- Jenkins Docker agent mounts
/var/run/docker.sock, which provides high-level control of the Docker host; use only in trusted environments. - Self-signed TLS is for development/demo; production should use a trusted CA (e.g., Let’s Encrypt).