# Table of Contents

1. [Overview](#overview)
2. [Application Bootstrapper](#application-bootstrapper)
3. [Admin CLI](#admin-cli)
4. [Production Safety](#production-safety)
5. [Health Endpoint](#health-endpoint)
6. [Docker](#docker)
7. [Docker Compose](#docker-compose)
8. [GitLab CI/CD](#gitlab-cicd)
9. [Operations](#operations)

---

## Overview

Deployment and operations guide including Docker, CI/CD, and admin CLI.

---

## Application Bootstrapper

Single binary with command dispatch:

```bash
./messaging-be              # Run as daemon (default)
./messaging-be server       # Explicit daemon
./messaging-be migrate      # Run migrations (default: up)
./messaging-be migrate up   # Run migrations up
./messaging-be migrate down 1  # Rollback 1 migration
./messaging-be version      # Show version
```

**main.go:**

```go
package main

import (
    "fmt"
    "os"

    "messaging-be/cmd/admin"
    "messaging-be/cmd/server"
    "messaging-be/pkg/buildinfo"
)

func main() {
    if len(os.Args) < 2 {
        if err := server.Run([]string{}); err != nil {
            fmt.Fprintf(os.Stderr, "Error: %v\n", err)
            os.Exit(1)
        }
        return
    }

    cmdName := os.Args[1]
    args := os.Args[2:]

    switch cmdName {
    case "server":
        if err := server.Run(args); err != nil {
            fmt.Fprintf(os.Stderr, "Error: %v\n", err)
            os.Exit(1)
        }
    case "migrate":
        if err := admin.Migrate(args); err != nil {
            fmt.Fprintf(os.Stderr, "Error: %v\n", err)
            os.Exit(1)
        }
    case "version", "-v", "--version":
        fmt.Printf("messaging-be version %s (commit: %s)\n",
            buildinfo.Version, buildinfo.Commit)
    default:
        fmt.Printf("Unknown command: %s\n", cmdName)
        printHelp()
        os.Exit(1)
    }
}

func printHelp() {
    fmt.Println("Usage: messaging-be [command]")
    fmt.Println()
    fmt.Println("Commands:")
    fmt.Println("  server          Run HTTP server (default)")
    fmt.Println("  migrate [cmd]   Database migrations (default: up)")
    fmt.Println("  version         Show version")
}
```

---

## Admin CLI

### Migrate Command

```go
package admin

import (
    "fmt"
    "os"
    "strconv"

    "messaging-be/cmd"
    "messaging-be/config"

    "github.com/golang-migrate/migrate/v4"
    _ "github.com/golang-migrate/migrate/v4/database/postgres"
    _ "github.com/golang-migrate/migrate/v4/source/file"
)

func Migrate(args []string) error {
    cfg, err := cmd.GetConfig()
    if err != nil {
        return fmt.Errorf("config: %w", err)
    }

    subcommand := "up"
    if len(args) > 0 {
        subcommand = args[0]
    }

    switch subcommand {
    case "up":
        return runMigrateUp(cfg)
    case "down":
        if cfg.App.Env == "production" {
            return fmt.Errorf("migrate down is disabled in production")
        }
        steps := 1
        if len(args) > 1 {
            steps, _ = strconv.Atoi(args[1])
        }
        return runMigrateDown(cfg, steps)
    default:
        return fmt.Errorf("unknown migrate command: %s", subcommand)
    }
}

func runMigrateUp(cfg *config.Config) error {
    m, err := migrate.New(
        "file://db/migrations/persistence",
        cfg.Persistence.DSN,
    )
    if err != nil {
        return err
    }
    defer m.Close()

    if err := m.Up(); err != nil && err != migrate.ErrNoChange {
        return err
    }
    fmt.Println("Migrations applied successfully")
    return nil
}

func runMigrateDown(cfg *config.Config, steps int) error {
    m, err := migrate.New(
        "file://db/migrations/persistence",
        cfg.Persistence.DSN,
    )
    if err != nil {
        return err
    }
    defer m.Close()

    if err := m.Steps(-steps); err != nil {
        return err
    }
    fmt.Printf("Rolled back %d migration(s)\n", steps)
    return nil
}
```

### User Commands

```go
func UserCommand(args []string) error {
    if len(args) < 1 {
        return fmt.Errorf("usage: messaging-be user <list|deactivate|activate>")
    }

    subcommand := args[0]

    switch subcommand {
    case "list":
        return listUsers()
    case "deactivate":
        if len(args) < 2 {
            return fmt.Errorf("usage: messaging-be user deactivate <nip>")
        }
        return deactivateUser(args[1])
    case "activate":
        if len(args) < 2 {
            return fmt.Errorf("usage: messaging-be user activate <nip>")
        }
        return activateUser(args[1])
    default:
        return fmt.Errorf("unknown user command: %s", subcommand)
    }
}
```

---

## Production Safety

**Migrate down is disabled in production:**

```go
if cfg.App.Env == "production" {
    return fmt.Errorf("migrate down is disabled in production")
}
```

**Environment detection:**

```go
// APP_ENV values:
// - "local" - local development
// - "staging" - staging environment
// - "production" - production environment
```

---

## Health Endpoint

Returns version and service health:

```go
r.GET("/healthz", func(c *gin.Context) {
    dbHealthy := checkDatabase(provider.PersistencePool)
    cacheHealthy := checkCache(provider.CachePool)

    allHealthy := dbHealthy && cacheHealthy
    status := "ok"
    if !allHealthy {
        status = "degraded"
    }

    response := gin.H{
        "status":  status,
        "version": buildinfo.Version,
        "commit":  buildinfo.Commit,
        "checks": gin.H{
            "database": dbHealthy,
            "cache":    cacheHealthy,
        },
    }

    if allHealthy {
        c.JSON(200, response)
    } else {
        c.JSON(503, response)
    }
})
```

**Response (healthy):**

```json
{
  "status": "ok",
  "version": "1.0.0",
  "commit": "abc123",
  "checks": {
    "database": true,
    "cache": true
  }
}
```

**Response (degraded):**

```json
{
  "status": "degraded",
  "version": "1.0.0",
  "commit": "abc123",
  "checks": {
    "database": true,
    "cache": false
  }
}
```

---

## Docker

### Dockerfile

```dockerfile
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build \
    -ldflags "-s -w -X messaging-be/pkg/buildinfo.Version=$VERSION -X messaging-be/pkg/buildinfo.Commit=$COMMIT" \
    -o messaging-be .

FROM alpine:3.19
RUN apk --no-cache add ca-certificates tzdata
WORKDIR /app
COPY --from=builder /app/messaging-be .
COPY --from=builder /app/db/migrations ./db/migrations
EXPOSE 8080
CMD ["./messaging-be"]
```

---

## Docker Compose

```yaml
version: '3.8'

services:
  app:
    build: .
    env_file: .env
    ports:
      - "8080:8080"
    environment:
      PERSISTENCE_DSN: ${PERSISTENCE_DSN}
      KEYCLOAK_JWKS_URL: ${KEYCLOAK_JWKS_URL}
      KEYCLOAK_CLIENT_ID: ${KEYCLOAK_CLIENT_ID}
```

---

## GitLab CI/CD

```yaml
stages:
  - build
  - test
  - release
  - deploy

variables:
  IMAGE_NAME: $CI_REGISTRY_IMAGE/messaging-be

build:
  stage: build
  image: golang:1.21-alpine
  before_script:
    - apk add git make
  script:
    - make build
  artifacts:
    paths:
      - bin/

test:
  stage: test
  image: golang:1.21-alpine
  services:
    - postgres:15-alpine
  variables:
    POSTGRES_DB: test
    POSTGRES_USER: test
    POSTGRES_PASSWORD: test
  before_script:
    - apk add git make
    - go install github.com/golang-migrate/migrate/v4/cmd/migrate@latest
  script:
    - migrate -database "$TEST_DATABASE_URL" -path db/migrations/persistence up
    - go test -race -coverprofile=coverage.out ./...
    - go tool cover -func=coverage.out
  coverage: '/total:\s+\(statements\)\s+(\d+\.\d+)%/'

release:
  stage: release
  image: docker:24.0-cli
  services:
    - docker:24.0-dind
  script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - docker build -t $IMAGE_NAME:$CI_COMMIT_SHA -t $IMAGE_NAME:latest .
    - docker push $IMAGE_NAME:$CI_COMMIT_SHA
    - docker push $IMAGE_NAME:latest
  rules:
    - main

deploy-production:
  stage: deploy
  image: bitnami/kubectl:latest
  environment:
    name: production
  script:
    - kubectl set image deployment/messaging-be server=$IMAGE_NAME:$CI_COMMIT_SHA
    - kubectl rollout status deployment/messaging-be
  rules:
    - main
  when: manual
```

---

## Operations

### Build

```bash
make build
```

### Run Server

```bash
./bin/messaging-be
```

### Run Migrations

```bash
./bin/messaging-be migrate      # Default: up
./bin/messaging-be migrate up
./bin/messaging-be migrate down 1
```

### Docker Operations

```bash
# Build and run
docker build -t messaging-be .
docker run -p 8080:8080 messaging-be

# Run migrations in container
docker exec <container> ./messaging-be migrate

# View logs
docker logs -f <container>
```

### Health Check

```bash
curl http://localhost:8080/healthz
```
