# 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 from `migrations/schema.sql`. ## Configuration Environment variables: - `JWT_SECRET` (required): Signing key for JWT tokens. - `DATABASE_PATH` (optional): SQLite file path; defaults to `/app/data/shagram.db` in the container. - `WS_ALLOWED_ORIGINS` (required for WebSocket): Comma-separated list of allowed `Origin` values 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` (requires `Authorization: Bearer `) → `{ "username": "..." }` - `GET /api/rooms` → `{ "rooms": [...] }` - `GET /api/messages/:room` → `{ "messages": [...] }` (last 50 messages) ## WebSocket Endpoint: - `ws(s):///ws/?token=` Client sends JSON: ```json { "text": "hello" } ``` Server broadcasts plain text messages: - `alice: hello` Security notes: - WebSocket requires a JWT token (`?token=...`) and validates the `Origin` header against `WS_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. ```bash export JWT_SECRET=change-me export DATABASE_PATH=./shagram.db export WS_ALLOWED_ORIGINS=http://localhost:8080 go run ./cmd/server ``` Open: - http://localhost:8080 ## Quickstart (Docker Compose + Nginx TLS) Prerequisites: Docker + Docker Compose. 1) Generate a self-signed TLS certificate (dev/demo only): See `deploy/shagram/nginx/certs/README.md`. 2) Create `deploy/shagram/.env`: ```text JWT_SECRET=change-me WS_ALLOWED_ORIGINS=https://localhost Optional: override the app image (e.g., from your registry) APP_IMAGE=//shagram: ``` 3) Start: ```bash cd deploy/shagram docker compose up -d --build ``` Open: - https://localhost ## 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).