Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .docker/Dockerfile.backend.dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
FROM golang:1.25-alpine

RUN go install github.com/air-verse/air@latest

WORKDIR /app
9 changes: 9 additions & 0 deletions .docker/Dockerfile.frontend.dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM node:20-alpine

WORKDIR /app

COPY package*.json ./

RUN npm install

CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]
69 changes: 69 additions & 0 deletions .docker/docker-compose.dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
services:
frontend:
build:
context: ..
dockerfile: .docker/Dockerfile.frontend.dev
ports:
- "5173:5173"
environment:
- NODE_ENV=development
- BACKEND_URL=http://backend:3001
volumes:
- ..:/app
- /app/node_modules
depends_on:
backend:
condition: service_healthy

backend:
build:
context: ..
dockerfile: .docker/Dockerfile.backend.dev
working_dir: /app
command: air
ports:
- "3001:3001"
environment:
- DB_HOST=postgres
- DB_PORT=5432
- DB_NAME=crossview
- DB_USER=postgres
- DB_PASSWORD=password
- SESSION_SECRET=dev-secret-change-in-production
- CORS_ORIGIN=http://localhost:5173
- KUBECONFIG=/root/.kube/config
- KUBE_SERVER=${KUBE_SERVER:-}
- KUBE_INSECURE_SKIP_TLS_VERIFY=${KUBE_INSECURE_SKIP_TLS_VERIFY:-}
volumes:
- ../crossview-go-server:/app
- go-cache:/root/go/pkg/mod
- ~/.kube/config:/root/.kube/config:ro
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3001/api/health"]
interval: 5s
timeout: 5s
retries: 12
start_period: 15s
depends_on:
postgres:
condition: service_healthy

postgres:
image: postgres:17
environment:
- POSTGRES_DB=crossview
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=password
ports:
- "5432:5432"
volumes:
- postgres-dev-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5

volumes:
go-cache:
postgres-dev-data:
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ dist-ssr
# Configuration files (contain sensitive info)
config/database.json
config/config.yaml
.env
13 changes: 13 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.PHONY: help dev dev-down

ENV_FILE := $(wildcard .env)
COMPOSE := docker compose $(if $(ENV_FILE),--env-file .env,) -f .docker/docker-compose.dev.yml

help: ## Show available commands
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'

dev: ## Start dev environment in detached mode (frontend + backend + postgres)
$(COMPOSE) up --build -d

dev-down: ## Stop dev environment
$(COMPOSE) down
46 changes: 46 additions & 0 deletions crossview-go-server/.air.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"

[build]
args_bin = ["app:serve"]
bin = "./tmp/server"
cmd = "go build -o ./tmp/server ./main.go"
delay = 1000
exclude_dir = ["vendor", "tmp", "testdata"]
exclude_file = []
exclude_regex = ["_test\\.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "tpl", "tmpl", "html"]
include_file = []
kill_delay = "0s"
log = "build-errors.log"
poll = false
poll_interval = 0
post_cmd = []
pre_cmd = []
rerun = false
rerun_delay = 500
send_interrupt = false
stop_on_error = false

[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"

[log]
main_only = false
time = false

[misc]
clean_on_exit = false

[screen]
clear_on_rebuild = false
keep_scroll = true
1 change: 1 addition & 0 deletions crossview-go-server/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
tmp/
39 changes: 24 additions & 15 deletions crossview-go-server/lib/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,23 @@ import (
)

type Env struct {
ServerPort string `mapstructure:"SERVER_PORT"`
Environment string `mapstructure:"ENV"`
LogOutput string `mapstructure:"LOG_OUTPUT"`
LogLevel string `mapstructure:"LOG_LEVEL"`
DBUsername string `mapstructure:"DB_USER"`
DBPassword string `mapstructure:"DB_PASS"`
DBHost string `mapstructure:"DB_HOST"`
DBPort string `mapstructure:"DB_PORT"`
DBName string `mapstructure:"DB_NAME"`
SessionSecret string `mapstructure:"SESSION_SECRET"`
CORSOrigin string `mapstructure:"CORS_ORIGIN"`
AuthMode string `mapstructure:"AUTH_MODE"`
AuthTrustedHeader string `mapstructure:"AUTH_TRUSTED_HEADER"`
AuthCreateUsers bool `mapstructure:"AUTH_CREATE_USERS"`
AuthDefaultRole string `mapstructure:"AUTH_DEFAULT_ROLE"`
ServerPort string `mapstructure:"SERVER_PORT"`
Environment string `mapstructure:"ENV"`
LogOutput string `mapstructure:"LOG_OUTPUT"`
LogLevel string `mapstructure:"LOG_LEVEL"`
DBUsername string `mapstructure:"DB_USER"`
DBPassword string `mapstructure:"DB_PASS"`
DBHost string `mapstructure:"DB_HOST"`
DBPort string `mapstructure:"DB_PORT"`
DBName string `mapstructure:"DB_NAME"`
SessionSecret string `mapstructure:"SESSION_SECRET"`
CORSOrigin string `mapstructure:"CORS_ORIGIN"`
AuthMode string `mapstructure:"AUTH_MODE"`
AuthTrustedHeader string `mapstructure:"AUTH_TRUSTED_HEADER"`
AuthCreateUsers bool `mapstructure:"AUTH_CREATE_USERS"`
AuthDefaultRole string `mapstructure:"AUTH_DEFAULT_ROLE"`
KubeServer string `mapstructure:"KUBE_SERVER"`
KubeInsecureSkipTLSVerify bool `mapstructure:"KUBE_INSECURE_SKIP_TLS_VERIFY"`
}

func NewEnv() Env {
Expand Down Expand Up @@ -89,6 +91,13 @@ func NewEnv() Env {
getConfigValue("server.auth.header.trustedHeader", viper.GetString("AUTH_TRUSTED_HEADER"), "X-Auth-User")))
env.AuthDefaultRole = getEnvOrDefault("AUTH_DEFAULT_ROLE",
getConfigValue("server.auth.header.defaultRole", viper.GetString("AUTH_DEFAULT_ROLE"), "viewer"))
env.KubeServer = getEnvOrDefault("KUBE_SERVER",
getConfigValue("kube.server", viper.GetString("KUBE_SERVER"), ""))
if v := os.Getenv("KUBE_INSECURE_SKIP_TLS_VERIFY"); v != "" {
env.KubeInsecureSkipTLSVerify = v == "true" || v == "1"
} else if viper.IsSet("kube.insecureSkipTLSVerify") {
env.KubeInsecureSkipTLSVerify = viper.GetBool("kube.insecureSkipTLSVerify")
}
if v := os.Getenv("AUTH_CREATE_USERS"); v != "" {
env.AuthCreateUsers = v == "true" || v == "1"
} else if viper.IsSet("server.auth.header.createUsers") {
Expand Down
7 changes: 6 additions & 1 deletion crossview-go-server/services/kubernetes_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,12 @@ func (k *KubernetesService) SetContext(ctxName string) error {
k.kubeConfig.CurrentContext = ctxName
k.currentContext = ctxName

restConfig, err = clientcmd.NewDefaultClientConfig(*k.kubeConfig, &clientcmd.ConfigOverrides{}).ClientConfig()
overrides := &clientcmd.ConfigOverrides{}
if k.env.KubeServer != "" {
overrides.ClusterInfo.Server = k.env.KubeServer
}
overrides.ClusterInfo.InsecureSkipTLSVerify = k.env.KubeInsecureSkipTLSVerify
restConfig, err = clientcmd.NewDefaultClientConfig(*k.kubeConfig, overrides).ClientConfig()
if err != nil {
k.failedContexts[targetContext] = true
return fmt.Errorf("failed to create rest config: %w", err)
Expand Down
40 changes: 39 additions & 1 deletion docs/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,46 @@ Thank you for your interest in contributing to Crossview. This guide will help y

### Development Setup

#### Option 1 — Docker (recommended)

The fastest way to get a full local environment (frontend + backend + PostgreSQL) running with hot reload is via Docker Compose. Make sure you have **Docker** and **Docker Compose** installed.

```bash
make dev # build and start all services in detached mode
make dev-down # stop and remove the containers
```

Services started:

| Service | URL / Port | Notes |
| ---------- | --------------------- | -------------------------------------------------------- |
| Frontend | http://localhost:5173 | Vite dev server with HMR |
| Backend | http://localhost:3001 | Go + [Air](https://github.com/air-verse/air) live reload |
| PostgreSQL | localhost:5432 | DB: `crossview`, user: `postgres` |

The backend uses [Air](https://github.com/air-verse/air) (configured in `crossview-go-server/.air.toml`) and rebuilds automatically on every `.go` file change. The frontend uses Vite's built-in HMR. Both source trees are mounted as volumes, so no rebuild of the Docker image is needed during development.

> **Kubeconfig:** Your host `~/.kube/config` is mounted read-only into the backend container at `/root/.kube/config`. Make sure this file exists and contains a valid context before running `make dev`.
>
> **kind clusters:** The Kubernetes API server address in your kubeconfig (e.g. `https://127.0.0.1:<port>`) resolves to the container itself, not your host. Create a `.env` file at the project root (it is gitignored) with the following variables:
>
> ```dotenv
> # Replace <port> with the port shown in your kubeconfig for the kind cluster.
> # host.docker.internal resolves to the host machine on macOS and Windows.
> # On Linux, use the Docker bridge IP (172.17.0.1) or add "127.0.0.1 host.docker.internal" to /etc/hosts.
> KUBE_SERVER=https://host.docker.internal:<port>
>
> # kind uses a self-signed certificate that does not include host.docker.internal as a SAN,
> # so TLS verification must be disabled when overriding the server address.
> KUBE_INSECURE_SKIP_TLS_VERIFY=true
> ```
>
> The `.env` file is automatically picked up by `make dev` and passed to Docker Compose via `--env-file`.

#### Option 2 — Manual

- **Frontend:** Node.js 20+, `npm install`, `npm run dev`
- **Backend:** Go 1.24+, `cd crossview-go-server && go run main.go app:serve`
- **Backend:** Go 1.25+, `cd crossview-go-server && go run main.go app:serve`
- **Config:** Copy `config/examples/config.yaml.example` to `config/config.yaml` and adjust as needed.

See [Getting Started](GETTING_STARTED.md) and [Configuration](CONFIGURATION.md) for full details.
Expand Down
2 changes: 1 addition & 1 deletion vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default defineConfig({
server: {
proxy: {
'/api': {
target: viteConfig?.server?.proxy?.api?.target || 'http://localhost:3001',
target: process.env.BACKEND_URL || viteConfig?.server?.proxy?.api?.target || 'http://localhost:3001',
changeOrigin: viteConfig?.server?.proxy?.api?.changeOrigin !== false,
},
},
Expand Down