Add a Make target to format all go files using gofumpt. Add another target to run the linter using the following settings: gopls = {
          gofumpt = true,
          codelenses = {
            gc_details = false,
            generate = true,
            regenerate_cgo = true,
            run_govulncheck = true,
            test = true,
            tidy = true,
            upgrade_dependency = true,
            vendor = true,
          },
          hints = {
            assignVariableTypes = true,
            compositeLiteralFields = true,
            compositeLiteralTypes = true,
            constantValues = true,
            functionTypeParameters = true,
            parameterNames = true,
            rangeVariableTypes = true,
          },
          analyses = {
            nilness = true,
            unusedparams = true,
            unusedwrite = true,
            useany = true,
          },
          usePlaceholders = true,
          completeUnimported = true,
          staticcheck = true,
          directoryFilters = { "-.git", "-.vscode", "-.idea", "-.vscode-test", "-node_modules" },
          semanticTokens = true, . How can go linter configuration be set project specific?


1	TARGET = ./cc-backend
2	FRONTEND = ./web/frontend
3	VERSION = 1.5.3
4	GIT_HASH := $(shell git rev-parse --short HEAD || echo 'development')
5	CURRENT_TIME = $(shell date +"%Y-%m-%d:T%H:%M:%S")
6	LD_FLAGS = '-s -X main.date=${CURRENT_TIME} -X main.version=${VERSION} -X main.commit=${GIT_HASH}'
7
8	EXECUTABLES = go npm
9	K := $(foreach exec,$(EXECUTABLES),\
10	        $(if $(shell which $(exec)),some string,$(error "No $(exec) in PATH")))
11
12	SVELTE_COMPONENTS = status   \
13						analysis \
14						node     \
15						systems  \
16						job      \
17						list     \
18						user     \
19						jobs     \
20						header
21
22	SVELTE_TARGETS = $(addprefix $(FRONTEND)/public/build/,$(addsuffix .js, $(SVELTE_COMPONENTS)))
23	SVELTE_SRC = $(wildcard $(FRONTEND)/src/*.svelte)                 \
24				 $(wildcard $(FRONTEND)/src/*.js)                     \
25				 $(wildcard $(FRONTEND)/src/analysis/*.svelte)        \
26				 $(wildcard $(FRONTEND)/src/config/*.svelte)          \
27				 $(wildcard $(FRONTEND)/src/config/admin/*.svelte)    \
28				 $(wildcard $(FRONTEND)/src/config/user/*.svelte)     \
29				 $(wildcard $(FRONTEND)/src/generic/*.js)             \
30				 $(wildcard $(FRONTEND)/src/generic/*.svelte)         \
31				 $(wildcard $(FRONTEND)/src/generic/filters/*.svelte) \
32				 $(wildcard $(FRONTEND)/src/generic/plots/*.svelte)   \
33				 $(wildcard $(FRONTEND)/src/generic/joblist/*.svelte) \
34				 $(wildcard $(FRONTEND)/src/generic/helper/*.svelte)  \
35				 $(wildcard $(FRONTEND)/src/generic/select/*.svelte)  \
36				 $(wildcard $(FRONTEND)/src/header/*.svelte)          \
37				 $(wildcard $(FRONTEND)/src/job/*.svelte)
38
39	.PHONY: clean distclean test tags frontend swagger graphql $(TARGET)
40
41	.NOTPARALLEL:
42
43	$(TARGET): $(SVELTE_TARGETS)
44		$(info ===>  BUILD cc-backend)
45		@go build -ldflags=${LD_FLAGS} ./cmd/cc-backend
46
47	frontend:
48		$(info ===>  BUILD frontend)
49		cd web/frontend && npm ci && npm run build
50
51	swagger:
52		$(info ===>  GENERATE swagger)
53		@go tool github.com/swaggo/swag/cmd/swag init  --parseDependency -d ./internal/api -g rest.go -o ./api
54		@mv ./api/docs.go ./internal/api/docs.go
55
56	graphql:
57		$(info ===>  GENERATE graphql)
58		@go tool github.com/99designs/gqlgen
59
60	clean:
61		$(info ===>  CLEAN)
62		@go clean
63		@rm -f $(TARGET)
64
65	distclean:
66		@$(MAKE) clean
67		$(info ===>  DISTCLEAN)
68		@rm -rf $(FRONTEND)/node_modules
69		@rm -rf ./var
70
71	test:
72		$(info ===>  TESTING)
73		@go clean -testcache
74		@go build ./...
75		@go vet ./...
76		@go test ./...
77
78	tags:
79		$(info ===>  TAGS)
80		@ctags -R
81
82	$(VAR):
83		@mkdir -p $(VAR)
84
85	$(SVELTE_TARGETS): $(SVELTE_SRC)
86		$(info ===>  BUILD frontend)
87		cd web/frontend && npm ci && npm run build
88

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

TARGET = ./cc-backend
FRONTEND = ./web/frontend
VERSION = 1.5.3
GIT_HASH := $(shell git rev-parse --short HEAD || echo 'development')
CURRENT_TIME = $(shell date +"%Y-%m-%d:T%H:%M:%S")
LD_FLAGS = '-s -X main.date=${CURRENT_TIME} -X main.version=${VERSION} -X main.commit=${GIT_HASH}'

EXECUTABLES = go npm
K := $(foreach exec,$(EXECUTABLES),\
        $(if $(shell which $(exec)),some string,$(error "No $(exec) in PATH")))

SVELTE_COMPONENTS = status   \
					analysis \
					node     \
					systems  \
					job      \
					list     \
					user     \
					jobs     \
					header

SVELTE_TARGETS = $(addprefix $(FRONTEND)/public/build/,$(addsuffix .js, $(SVELTE_COMPONENTS)))
SVELTE_SRC = $(wildcard $(FRONTEND)/src/*.svelte)                 \
			 $(wildcard $(FRONTEND)/src/*.js)                     \
			 $(wildcard $(FRONTEND)/src/analysis/*.svelte)        \
			 $(wildcard $(FRONTEND)/src/config/*.svelte)          \
			 $(wildcard $(FRONTEND)/src/config/admin/*.svelte)    \
			 $(wildcard $(FRONTEND)/src/config/user/*.svelte)     \
			 $(wildcard $(FRONTEND)/src/generic/*.js)             \
			 $(wildcard $(FRONTEND)/src/generic/*.svelte)         \
			 $(wildcard $(FRONTEND)/src/generic/filters/*.svelte) \
			 $(wildcard $(FRONTEND)/src/generic/plots/*.svelte)   \
			 $(wildcard $(FRONTEND)/src/generic/joblist/*.svelte) \
			 $(wildcard $(FRONTEND)/src/generic/helper/*.svelte)  \
			 $(wildcard $(FRONTEND)/src/generic/select/*.svelte)  \
			 $(wildcard $(FRONTEND)/src/header/*.svelte)          \
			 $(wildcard $(FRONTEND)/src/job/*.svelte)

.PHONY: clean distclean test tags frontend swagger graphql $(TARGET)

.NOTPARALLEL:

$(TARGET): $(SVELTE_TARGETS)
	$(info ===>  BUILD cc-backend)
	@go build -ldflags=${LD_FLAGS} ./cmd/cc-backend

frontend:
	$(info ===>  BUILD frontend)
	cd web/frontend && npm ci && npm run build

swagger:
	$(info ===>  GENERATE swagger)
	@go tool github.com/swaggo/swag/cmd/swag init  --parseDependency -d ./internal/api -g rest.go -o ./api
	@mv ./api/docs.go ./internal/api/docs.go

graphql:
	$(info ===>  GENERATE graphql)
	@go tool github.com/99designs/gqlgen

clean:
	$(info ===>  CLEAN)
	@go clean
	@rm -f $(TARGET)

distclean:
	@$(MAKE) clean
	$(info ===>  DISTCLEAN)
	@rm -rf $(FRONTEND)/node_modules
	@rm -rf ./var

test:
	$(info ===>  TESTING)
	@go clean -testcache
	@go build ./...
	@go vet ./...
	@go test ./...

tags:
	$(info ===>  TAGS)
	@ctags -R

$(VAR):
	@mkdir -p $(VAR)

$(SVELTE_TARGETS): $(SVELTE_SRC)
	$(info ===>  BUILD frontend)
	cd web/frontend && npm ci && npm run build


Agent

Explore Go linter config files

Prompt: In /Users/jan/prg/ClusterCockpit/cc-backend, look for any existing linter configuration files: .golangci.yml, .golangci.yaml, .golangci.json, gopls.json, .gopls, go.env. Also check if there's a .vscode/settings.json or similar. Report what you find and their contents.


## Summary of Linter Configuration Files Found

I found **ONE configuration file** in the repository:

### /.vscode/settings.json
**Location:** /Users/jan/prg/ClusterCockpit/cc-backend/.vscode/settings.json

**Contents:**
```json
{
    "makefile.configureOnOpen": false
}
```

This is a minimal VS Code configuration file that only disables automatic configuration on file open for Makefile.

### Not Found:
- `.golangci.yml`
- `.golangci.yaml`
- `.golangci.json`
- `gopls.json`
- `.gopls`
- `go.env`

**Additional VS Code file found:**
- **/.vscode/launch.json** - Contains a Go debugger launch configuration for running the server with the `-server` flag.

The repository does **not have a dedicated golangci-lint or gopls configuration file**, which means it's using default settings for those tools.

Prompt: In /Users/jan/prg/ClusterCockpit/cc-backend, look for any existing linter configuration files: .golangci.yml, .golangci.yaml, .golangci.json, gopls.json, .gopls, go.env. Also check if there's a .vscode/settings.json or similar. Report what you find and their contents.


Now I have enough context to write the plan directly.


Write

# Plan: Add `fmt` and `lint` Make Targets + Project-Specific Linter Config

## Context

The project has no Go formatter or linter automation. The user wants:
1. A `fmt` Make target running `gofumpt` over all Go files
2. A `lint` Make target running `golangci-lint` with settings matching the provided gopls config
3. Project-specific linter/gopls config files so settings are checked into the repo

---

## Project-specific linter configuration: how it works

| Tool | Project-local config file | Notes |
|---|---|---|
| **gopls** (LSP) | `gopls.json` at module root | Supported since gopls v0.11; editors that use gopls pick this up automatically |
| **golangci-lint** | `.golangci.yml` at repo root | Standard; `golangci-lint run` picks it up automatically |
| **VS Code** | `.vscode/settings.json` | Can also embed gopls settings under `"gopls"` key; but `gopls.json` is editor-agnostic |

The provided settings block is in **gopls** format.
The Make `lint` target will use **golangci-lint**, which has its own config format but covers the same analyses.

---

## Changes

### 1. `Makefile` — add two targets

```makefile
fmt:
	$(info ===>  FORMAT)
	@gofumpt -l -w .

lint:
	$(info ===>  LINT)
	@golangci-lint run ./...
```

Also add `fmt` and `lint` to the `.PHONY` line.

**File:** `Makefile`

---

### 2. `.golangci.yml` — new file at repo root

Maps the provided gopls settings to golangci-lint equivalents:

```yaml
run:
  timeout: 5m

linters:
  enable:
    - gofumpt        # gofumpt = true
    - staticcheck    # staticcheck = true
    - unparam        # unusedparams = true
    - unusedresult   # unusedwrite = true (closest equivalent)
    - nilnil         # nilness = true (nil return of nil interface)
    - govet          # base vet; useany analysis via govet

linters-settings:
  gofumpt:
    module-path: github.com/ClusterCockpit/cc-backend

  govet:
    enable:
      - nilness
      - unusedwrite

issues:
  exclude-dirs:
    - .git
    - .vscode
    - .idea
    - node_modules
  exclude-rules:
    - path: _test\.go
      linters:
        - unparam
```

---

### 3. `gopls.json` — new file at repo root

Provides project-specific gopls settings picked up by any editor using gopls:

```json
{
    "gofumpt": true,
    "codelenses": {
        "gc_details": false,
        "generate": true,
        "regenerate_cgo": true,
        "run_govulncheck": true,
        "test": true,
        "tidy": true,
        "upgrade_dependency": true,
        "vendor": true
    },
    "hints": {
        "assignVariableTypes": true,
        "compositeLiteralFields": true,
        "compositeLiteralTypes": true,
        "constantValues": true,
        "functionTypeParameters": true,
        "parameterNames": true,
        "rangeVariableTypes": true
    },
    "analyses": {
        "nilness": true,
        "unusedparams": true,
        "unusedwrite": true,
        "useany": true
    },
    "usePlaceholders": true,
    "completeUnimported": true,
    "staticcheck": true,
    "directoryFilters": [
        "-.git",
        "-.vscode",
        "-.idea",
        "-.vscode-test",
        "-node_modules"
    ],
    "semanticTokens": true
}
```

---

## Verification

```bash
# Install tools if needed
go install mvdan.cc/gofumpt@latest
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest

# Test targets
make fmt
make lint
```

## Notes

- `gofumpt` must be installed separately (`go install mvdan.cc/gofumpt@latest`); consider adding it to `go.mod` as a tool dependency or documenting it in the README.
- `golangci-lint` is also a separate install; same note applies.
- `gopls.json` is editor-agnostic — VS Code, Neovim, GoLand all read it via gopls.
- The existing `.vscode/settings.json` does **not** need to change; gopls will prefer `gopls.json`.


File created successfully at: /Users/jan/.claude/plans/jolly-cooking-abelson.md

# Plan: Add `fmt` and `lint` Make Targets + Project-Specific Linter Config

## Context

The project has no Go formatter or linter automation. The user wants:
1. A `fmt` Make target running `gofumpt` over all Go files
2. A `lint` Make target running `golangci-lint` with settings matching the provided gopls config
3. Project-specific linter/gopls config files so settings are checked into the repo

---

## Project-specific linter configuration: how it works

| Tool | Project-local config file | Notes |
|---|---|---|
| **gopls** (LSP) | `gopls.json` at module root | Supported since gopls v0.11; editors that use gopls pick this up automatically |
| **golangci-lint** | `.golangci.yml` at repo root | Standard; `golangci-lint run` picks it up automatically |
| **VS Code** | `.vscode/settings.json` | Can also embed gopls settings under `"gopls"` key; but `gopls.json` is editor-agnostic |

The provided settings block is in **gopls** format.
The Make `lint` target will use **golangci-lint**, which has its own config format but covers the same analyses.

---

## Changes

### 1. `Makefile` — add two targets

```makefile
fmt:
	$(info ===>  FORMAT)
	@gofumpt -l -w .

lint:
	$(info ===>  LINT)
	@golangci-lint run ./...
```

Also add `fmt` and `lint` to the `.PHONY` line.

**File:** `Makefile`

---

### 2. `.golangci.yml` — new file at repo root

Maps the provided gopls settings to golangci-lint equivalents:

```yaml
run:
  timeout: 5m

linters:
  enable:
    - gofumpt        # gofumpt = true
    - staticcheck    # staticcheck = true
    - unparam        # unusedparams = true
    - unusedresult   # unusedwrite = true (closest equivalent)
    - nilnil         # nilness = true (nil return of nil interface)
    - govet          # base vet; useany analysis via govet

linters-settings:
  gofumpt:
    module-path: github.com/ClusterCockpit/cc-backend

  govet:
    enable:
      - nilness
      - unusedwrite

issues:
  exclude-dirs:
    - .git
    - .vscode
    - .idea
    - node_modules
  exclude-rules:
    - path: _test\.go
      linters:
        - unparam
```

---

### 3. `gopls.json` — new file at repo root

Provides project-specific gopls settings picked up by any editor using gopls:

```json
{
    "gofumpt": true,
    "codelenses": {
        "gc_details": false,
        "generate": true,
        "regenerate_cgo": true,
        "run_govulncheck": true,
        "test": true,
        "tidy": true,
        "upgrade_dependency": true,
        "vendor": true
    },
    "hints": {
        "assignVariableTypes": true,
        "compositeLiteralFields": true,
        "compositeLiteralTypes": true,
        "constantValues": true,
        "functionTypeParameters": true,
        "parameterNames": true,
        "rangeVariableTypes": true
    },
    "analyses": {
        "nilness": true,
        "unusedparams": true,
        "unusedwrite": true,
        "useany": true
    },
    "usePlaceholders": true,
    "completeUnimported": true,
    "staticcheck": true,
    "directoryFilters": [
        "-.git",
        "-.vscode",
        "-.idea",
        "-.vscode-test",
        "-node_modules"
    ],
    "semanticTokens": true
}
```

---

## Verification

```bash
# Install tools if needed
go install mvdan.cc/gofumpt@latest
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest

# Test targets
make fmt
make lint
```

## Notes

- `gofumpt` must be installed separately (`go install mvdan.cc/gofumpt@latest`); consider adding it to `go.mod` as a tool dependency or documenting it in the README.
- `golangci-lint` is also a separate install; same note applies.
- `gopls.json` is editor-agnostic — VS Code, Neovim, GoLand all read it via gopls.
- The existing `.vscode/settings.json` does **not** need to change; gopls will prefer `gopls.json`.


ExitPlanMode

Summary:
## Plan: `fmt` + `lint` Make targets and project-specific linter config

### New Make targets (`Makefile`)
- **`fmt`**: runs `gofumpt -l -w .` over all Go files
- **`lint`**: runs `golangci-lint run ./...`

### New files
1. **`.golangci.yml`** — golangci-lint config at repo root, enabling linters that match the provided gopls settings: `gofumpt`, `staticcheck`, `unparam` (unusedparams), `nilnil` (nilness), `govet` with `nilness`+`unusedwrite` analyzers
2. **`gopls.json`** — project-specific gopls settings at module root (supported since gopls v0.11), picked up automatically by any editor using gopls (VS Code, Neovim, GoLand, etc.)

### How project-specific linter config works

| Tool | File | Scope |
|---|---|---|
| **gopls** (LSP) | `gopls.json` at module root | Editor-agnostic; all editors using gopls read it |
| **golangci-lint** | `.golangci.yml` at repo root | `golangci-lint run` picks it up automatically |

The provided settings are in **gopls format** → they go into `gopls.json`. The Make `lint` target uses **golangci-lint**, which has its own `.golangci.yml` format covering the same analyses.

**Prerequisites**: `gofumpt` and `golangci-lint` must be installed (`go install mvdan.cc/gofumpt@latest`, `go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest`).

# Plan: Add `fmt` and `lint` Make Targets + Project-Specific Linter Config

## Context

The project has no Go formatter or linter automation. The user wants:
1. A `fmt` Make target running `gofumpt` over all Go files
2. A `lint` Make target running `golangci-lint` with settings matching the provided gopls config
3. Project-specific linter/gopls config files so settings are checked into the repo

---

## Project-specific linter configuration: how it works

| Tool | Project-local config file | Notes |
|---|---|---|
| **gopls** (LSP) | `gopls.json` at module root | Supported since gopls v0.11; editors that use gopls pick this up automatically |
| **golangci-lint** | `.golangci.yml` at repo root | Standard; `golangci-lint run` picks it up automatically |
| **VS Code** | `.vscode/settings.json` | Can also embed gopls settings under `"gopls"` key; but `gopls.json` is editor-agnostic |

The provided settings block is in **gopls** format.
The Make `lint` target will use **golangci-lint**, which has its own config format but covers the same analyses.

---

## Changes

### 1. `Makefile` — add two targets

```makefile
fmt:
	$(info ===>  FORMAT)
	@gofumpt -l -w .

lint:
	$(info ===>  LINT)
	@golangci-lint run ./...
```

Also add `fmt` and `lint` to the `.PHONY` line.

**File:** `Makefile`

---

### 2. `.golangci.yml` — new file at repo root

Maps the provided gopls settings to golangci-lint equivalents:

```yaml
run:
  timeout: 5m

linters:
  enable:
    - gofumpt        # gofumpt = true
    - staticcheck    # staticcheck = true
    - unparam        # unusedparams = true
    - unusedresult   # unusedwrite = true (closest equivalent)
    - nilnil         # nilness = true (nil return of nil interface)
    - govet          # base vet; useany analysis via govet

linters-settings:
  gofumpt:
    module-path: github.com/ClusterCockpit/cc-backend

  govet:
    enable:
      - nilness
      - unusedwrite

issues:
  exclude-dirs:
    - .git
    - .vscode
    - .idea
    - node_modules
  exclude-rules:
    - path: _test\.go
      linters:
        - unparam
```

---

### 3. `gopls.json` — new file at repo root

Provides project-specific gopls settings picked up by any editor using gopls:

```json
{
    "gofumpt": true,
    "codelenses": {
        "gc_details": false,
        "generate": true,
        "regenerate_cgo": true,
        "run_govulncheck": true,
        "test": true,
        "tidy": true,
        "upgrade_dependency": true,
        "vendor": true
    },
    "hints": {
        "assignVariableTypes": true,
        "compositeLiteralFields": true,
        "compositeLiteralTypes": true,
        "constantValues": true,
        "functionTypeParameters": true,
        "parameterNames": true,
        "rangeVariableTypes": true
    },
    "analyses": {
        "nilness": true,
        "unusedparams": true,
        "unusedwrite": true,
        "useany": true
    },
    "usePlaceholders": true,
    "completeUnimported": true,
    "staticcheck": true,
    "directoryFilters": [
        "-.git",
        "-.vscode",
        "-.idea",
        "-.vscode-test",
        "-node_modules"
    ],
    "semanticTokens": true
}
```

---

## Verification

```bash
# Install tools if needed
go install mvdan.cc/gofumpt@latest
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest

# Test targets
make fmt
make lint
```

## Notes

- `gofumpt` must be installed separately (`go install mvdan.cc/gofumpt@latest`); consider adding it to `go.mod` as a tool dependency or documenting it in the README.
- `golangci-lint` is also a separate install; same note applies.
- `gopls.json` is editor-agnostic — VS Code, Neovim, GoLand all read it via gopls.
- The existing `.vscode/settings.json` does **not** need to change; gopls will prefer `gopls.json`.


User has approved your plan. You can now start coding. Start with updating your todo list if applicable

Your plan has been saved to: /Users/jan/.claude/plans/jolly-cooking-abelson.md
You can refer back to it if needed during implementation.

## Approved Plan:
# Plan: Add `fmt` and `lint` Make Targets + Project-Specific Linter Config

## Context

The project has no Go formatter or linter automation. The user wants:
1. A `fmt` Make target running `gofumpt` over all Go files
2. A `lint` Make target running `golangci-lint` with settings matching the provided gopls config
3. Project-specific linter/gopls config files so settings are checked into the repo

---

## Project-specific linter configuration: how it works

| Tool | Project-local config file | Notes |
|---|---|---|
| **gopls** (LSP) | `gopls.json` at module root | Supported since gopls v0.11; editors that use gopls pick this up automatically |
| **golangci-lint** | `.golangci.yml` at repo root | Standard; `golangci-lint run` picks it up automatically |
| **VS Code** | `.vscode/settings.json` | Can also embed gopls settings under `"gopls"` key; but `gopls.json` is editor-agnostic |

The provided settings block is in **gopls** format.
The Make `lint` target will use **golangci-lint**, which has its own config format but covers the same analyses.

---

## Changes

### 1. `Makefile` — add two targets

```makefile
fmt:
	$(info ===>  FORMAT)
	@gofumpt -l -w .

lint:
	$(info ===>  LINT)
	@golangci-lint run ./...
```

Also add `fmt` and `lint` to the `.PHONY` line.

**File:** `Makefile`

---

### 2. `.golangci.yml` — new file at repo root

Maps the provided gopls settings to golangci-lint equivalents:

```yaml
run:
  timeout: 5m

linters:
  enable:
    - gofumpt        # gofumpt = true
    - staticcheck    # staticcheck = true
    - unparam        # unusedparams = true
    - unusedresult   # unusedwrite = true (closest equivalent)
    - nilnil         # nilness = true (nil return of nil interface)
    - govet          # base vet; useany analysis via govet

linters-settings:
  gofumpt:
    module-path: github.com/ClusterCockpit/cc-backend

  govet:
    enable:
      - nilness
      - unusedwrite

issues:
  exclude-dirs:
    - .git
    - .vscode
    - .idea
    - node_modules
  exclude-rules:
    - path: _test\.go
      linters:
        - unparam
```

---

### 3. `gopls.json` — new file at repo root

Provides project-specific gopls settings picked up by any editor using gopls:

```json
{
    "gofumpt": true,
    "codelenses": {
        "gc_details": false,
        "generate": true,
        "regenerate_cgo": true,
        "run_govulncheck": true,
        "test": true,
        "tidy": true,
        "upgrade_dependency": true,
        "vendor": true
    },
    "hints": {
        "assignVariableTypes": true,
        "compositeLiteralFields": true,
        "compositeLiteralTypes": true,
        "constantValues": true,
        "functionTypeParameters": true,
        "parameterNames": true,
        "rangeVariableTypes": true
    },
    "analyses": {
        "nilness": true,
        "unusedparams": true,
        "unusedwrite": true,
        "useany": true
    },
    "usePlaceholders": true,
    "completeUnimported": true,
    "staticcheck": true,
    "directoryFilters": [
        "-.git",
        "-.vscode",
        "-.idea",
        "-.vscode-test",
        "-node_modules"
    ],
    "semanticTokens": true
}
```

---

## Verification

```bash
# Install tools if needed
go install mvdan.cc/gofumpt@latest
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest

# Test targets
make fmt
make lint
```

## Notes

- `gofumpt` must be installed separately (`go install mvdan.cc/gofumpt@latest`); consider adding it to `go.mod` as a tool dependency or documenting it in the README.
- `golangci-lint` is also a separate install; same note applies.
- `gopls.json` is editor-agnostic — VS Code, Neovim, GoLand all read it via gopls.
- The existing `.vscode/settings.json` does **not** need to change; gopls will prefer `gopls.json`.


Now I'll implement all three changes in parallel.


Edit

.PHONY: clean distclean test tags frontend swagger graphql $(TARGET)

.PHONY: clean distclean fmt lint test tags frontend swagger graphql $(TARGET)


The file /Users/jan/prg/ClusterCockpit/cc-backend/Makefile has been updated successfully.

TARGET = ./cc-backend
FRONTEND = ./web/frontend
VERSION = 1.5.3
GIT_HASH := $(shell git rev-parse --short HEAD || echo 'development')
CURRENT_TIME = $(shell date +"%Y-%m-%d:T%H:%M:%S")
LD_FLAGS = '-s -X main.date=${CURRENT_TIME} -X main.version=${VERSION} -X main.commit=${GIT_HASH}'

EXECUTABLES = go npm
K := $(foreach exec,$(EXECUTABLES),\
        $(if $(shell which $(exec)),some string,$(error "No $(exec) in PATH")))

SVELTE_COMPONENTS = status   \
					analysis \
					node     \
					systems  \
					job      \
					list     \
					user     \
					jobs     \
					header

SVELTE_TARGETS = $(addprefix $(FRONTEND)/public/build/,$(addsuffix .js, $(SVELTE_COMPONENTS)))
SVELTE_SRC = $(wildcard $(FRONTEND)/src/*.svelte)                 \
			 $(wildcard $(FRONTEND)/src/*.js)                     \
			 $(wildcard $(FRONTEND)/src/analysis/*.svelte)        \
			 $(wildcard $(FRONTEND)/src/config/*.svelte)          \
			 $(wildcard $(FRONTEND)/src/config/admin/*.svelte)    \
			 $(wildcard $(FRONTEND)/src/config/user/*.svelte)     \
			 $(wildcard $(FRONTEND)/src/generic/*.js)             \
			 $(wildcard $(FRONTEND)/src/generic/*.svelte)         \
			 $(wildcard $(FRONTEND)/src/generic/filters/*.svelte) \
			 $(wildcard $(FRONTEND)/src/generic/plots/*.svelte)   \
			 $(wildcard $(FRONTEND)/src/generic/joblist/*.svelte) \
			 $(wildcard $(FRONTEND)/src/generic/helper/*.svelte)  \
			 $(wildcard $(FRONTEND)/src/generic/select/*.svelte)  \
			 $(wildcard $(FRONTEND)/src/header/*.svelte)          \
			 $(wildcard $(FRONTEND)/src/job/*.svelte)

.PHONY: clean distclean test tags frontend swagger graphql $(TARGET)

.NOTPARALLEL:

$(TARGET): $(SVELTE_TARGETS)
	$(info ===>  BUILD cc-backend)
	@go build -ldflags=${LD_FLAGS} ./cmd/cc-backend

frontend:
	$(info ===>  BUILD frontend)
	cd web/frontend && npm ci && npm run build

swagger:
	$(info ===>  GENERATE swagger)
	@go tool github.com/swaggo/swag/cmd/swag init  --parseDependency -d ./internal/api -g rest.go -o ./api
	@mv ./api/docs.go ./internal/api/docs.go

graphql:
	$(info ===>  GENERATE graphql)
	@go tool github.com/99designs/gqlgen

clean:
	$(info ===>  CLEAN)
	@go clean
	@rm -f $(TARGET)

distclean:
	@$(MAKE) clean
	$(info ===>  DISTCLEAN)
	@rm -rf $(FRONTEND)/node_modules
	@rm -rf ./var

test:
	$(info ===>  TESTING)
	@go clean -testcache
	@go build ./...
	@go vet ./...
	@go test ./...

tags:
	$(info ===>  TAGS)
	@ctags -R

$(VAR):
	@mkdir -p $(VAR)

$(SVELTE_TARGETS): $(SVELTE_SRC)
	$(info ===>  BUILD frontend)
	cd web/frontend && npm ci && npm run build

-.PHONY: clean distclean test tags frontend swagger graphql $(TARGET)

+.PHONY: clean distclean fmt lint test tags frontend swagger graphql $(TARGET)


Write

run:
  timeout: 5m

linters:
  enable:
    - gofumpt      # gofumpt = true
    - staticcheck  # staticcheck = true
    - unparam      # unusedparams = true
    - nilnil       # nilness = true (catches nil returns of nil interface values)
    - govet        # base vet; nilness + unusedwrite via govet analyzers

linters-settings:
  gofumpt:
    module-path: github.com/ClusterCockpit/cc-backend

  govet:
    enable:
      - nilness
      - unusedwrite

issues:
  exclude-dirs:
    - .git
    - .vscode
    - .idea
    - node_modules
  exclude-rules:
    - path: _test\.go
      linters:
        - unparam


File created successfully at: /Users/jan/prg/ClusterCockpit/cc-backend/.golangci.yml

run:
  timeout: 5m

linters:
  enable:
    - gofumpt      # gofumpt = true
    - staticcheck  # staticcheck = true
    - unparam      # unusedparams = true
    - nilnil       # nilness = true (catches nil returns of nil interface values)
    - govet        # base vet; nilness + unusedwrite via govet analyzers

linters-settings:
  gofumpt:
    module-path: github.com/ClusterCockpit/cc-backend

  govet:
    enable:
      - nilness
      - unusedwrite

issues:
  exclude-dirs:
    - .git
    - .vscode
    - .idea
    - node_modules
  exclude-rules:
    - path: _test\.go
      linters:
        - unparam


{
    "gofumpt": true,
    "codelenses": {
        "gc_details": false,
        "generate": true,
        "regenerate_cgo": true,
        "run_govulncheck": true,
        "test": true,
        "tidy": true,
        "upgrade_dependency": true,
        "vendor": true
    },
    "hints": {
        "assignVariableTypes": true,
        "compositeLiteralFields": true,
        "compositeLiteralTypes": true,
        "constantValues": true,
        "functionTypeParameters": true,
        "parameterNames": true,
        "rangeVariableTypes": true
    },
    "analyses": {
        "nilness": true,
        "unusedparams": true,
        "unusedwrite": true,
        "useany": true
    },
    "usePlaceholders": true,
    "completeUnimported": true,
    "staticcheck": true,
    "directoryFilters": [
        "-.git",
        "-.vscode",
        "-.idea",
        "-.vscode-test",
        "-node_modules"
    ],
    "semanticTokens": true
}


File created successfully at: /Users/jan/prg/ClusterCockpit/cc-backend/gopls.json


Now add the two targets to the Makefile:


Edit

fmt:
	$(info ===>  FORMAT)
	@gofumpt -l -w .

lint:
	$(info ===>  LINT)
	@golangci-lint run ./...

tags:
	$(info ===>  TAGS)
	@ctags -R


fmt:
	$(info ===>  FORMAT)
	@gofumpt -l -w .

lint:
	$(info ===>  LINT)
	@golangci-lint run ./...

tags:
	$(info ===>  TAGS)
	@ctags -R

TARGET = ./cc-backend
FRONTEND = ./web/frontend
VERSION = 1.5.3
GIT_HASH := $(shell git rev-parse --short HEAD || echo 'development')
CURRENT_TIME = $(shell date +"%Y-%m-%d:T%H:%M:%S")
LD_FLAGS = '-s -X main.date=${CURRENT_TIME} -X main.version=${VERSION} -X main.commit=${GIT_HASH}'

EXECUTABLES = go npm
K := $(foreach exec,$(EXECUTABLES),\
        $(if $(shell which $(exec)),some string,$(error "No $(exec) in PATH")))

SVELTE_COMPONENTS = status   \
					analysis \
					node     \
					systems  \
					job      \
					list     \
					user     \
					jobs     \
					header

SVELTE_TARGETS = $(addprefix $(FRONTEND)/public/build/,$(addsuffix .js, $(SVELTE_COMPONENTS)))
SVELTE_SRC = $(wildcard $(FRONTEND)/src/*.svelte)                 \
			 $(wildcard $(FRONTEND)/src/*.js)                     \
			 $(wildcard $(FRONTEND)/src/analysis/*.svelte)        \
			 $(wildcard $(FRONTEND)/src/config/*.svelte)          \
			 $(wildcard $(FRONTEND)/src/config/admin/*.svelte)    \
			 $(wildcard $(FRONTEND)/src/config/user/*.svelte)     \
			 $(wildcard $(FRONTEND)/src/generic/*.js)             \
			 $(wildcard $(FRONTEND)/src/generic/*.svelte)         \
			 $(wildcard $(FRONTEND)/src/generic/filters/*.svelte) \
			 $(wildcard $(FRONTEND)/src/generic/plots/*.svelte)   \
			 $(wildcard $(FRONTEND)/src/generic/joblist/*.svelte) \
			 $(wildcard $(FRONTEND)/src/generic/helper/*.svelte)  \
			 $(wildcard $(FRONTEND)/src/generic/select/*.svelte)  \
			 $(wildcard $(FRONTEND)/src/header/*.svelte)          \
			 $(wildcard $(FRONTEND)/src/job/*.svelte)

.PHONY: clean distclean fmt lint test tags frontend swagger graphql $(TARGET)

.NOTPARALLEL:

$(TARGET): $(SVELTE_TARGETS)
	$(info ===>  BUILD cc-backend)
	@go build -ldflags=${LD_FLAGS} ./cmd/cc-backend

frontend:
	$(info ===>  BUILD frontend)
	cd web/frontend && npm ci && npm run build

swagger:
	$(info ===>  GENERATE swagger)
	@go tool github.com/swaggo/swag/cmd/swag init  --parseDependency -d ./internal/api -g rest.go -o ./api
	@mv ./api/docs.go ./internal/api/docs.go

graphql:
	$(info ===>  GENERATE graphql)
	@go tool github.com/99designs/gqlgen

clean:
	$(info ===>  CLEAN)
	@go clean
	@rm -f $(TARGET)

distclean:
	@$(MAKE) clean
	$(info ===>  DISTCLEAN)
	@rm -rf $(FRONTEND)/node_modules
	@rm -rf ./var

test:
	$(info ===>  TESTING)
	@go clean -testcache
	@go build ./...
	@go vet ./...
	@go test ./...

tags:
	$(info ===>  TAGS)
	@ctags -R

$(VAR):
	@mkdir -p $(VAR)

$(SVELTE_TARGETS): $(SVELTE_SRC)
	$(info ===>  BUILD frontend)
	cd web/frontend && npm ci && npm run build

@go vet ./...
@go test ./...
+fmt:
+  $(info ===>  FORMAT)
+  @gofumpt -l -w .
+
+lint:
+  $(info ===>  LINT)
+  @golangci-lint run ./...
+
tags:
$(info ===>  TAGS)
@ctags -R


Done. Here's a summary of what was created:

**`Makefile`** — two new targets added:
- `make fmt` — runs `gofumpt -l -w .` over all Go files
- `make lint` — runs `golangci-lint run ./...` using `.golangci.yml`

**`.golangci.yml`** — golangci-lint config mapping the gopls settings to CLI linter equivalents:
- `gofumpt` (formatter enforcement)
- `staticcheck` (staticcheck suite)
- `unparam` (unused parameters)
- `nilnil` (nilness — catches nil returns of nil interface)
- `govet` with `nilness` + `unusedwrite` analyzers

**`gopls.json`** — project-specific gopls settings at the module root. This is how you set gopls config **project-specifically**: place a `gopls.json` file at the workspace root. Any editor running gopls (VS Code, Neovim/nvim-lspconfig, GoLand, Zed, etc.) will pick it up automatically since gopls v0.11 — no per-editor configuration needed. The existing `.vscode/settings.json` does not need to change.

**Prerequisites** (install once):
```bash
go install mvdan.cc/gofumpt@latest
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
```


Analyse which directories should be added to the directoryFilters


Bash

find /Users/jan/prg/ClusterCockpit/cc-backend -maxdepth 3 -type d | grep -v '/\.' | sort

List all directories up to depth 3


/Users/jan/prg/ClusterCockpit/cc-backend
/Users/jan/prg/ClusterCockpit/cc-backend/api
/Users/jan/prg/ClusterCockpit/cc-backend/cmd
/Users/jan/prg/ClusterCockpit/cc-backend/cmd/cc-backend
/Users/jan/prg/ClusterCockpit/cc-backend/configs
/Users/jan/prg/ClusterCockpit/cc-backend/configs/tagger
/Users/jan/prg/ClusterCockpit/cc-backend/configs/tagger/apps
/Users/jan/prg/ClusterCockpit/cc-backend/configs/tagger/jobclasses
/Users/jan/prg/ClusterCockpit/cc-backend/init
/Users/jan/prg/ClusterCockpit/cc-backend/internal
/Users/jan/prg/ClusterCockpit/cc-backend/internal/api
/Users/jan/prg/ClusterCockpit/cc-backend/internal/archiver
/Users/jan/prg/ClusterCockpit/cc-backend/internal/auth
/Users/jan/prg/ClusterCockpit/cc-backend/internal/config
/Users/jan/prg/ClusterCockpit/cc-backend/internal/graph
/Users/jan/prg/ClusterCockpit/cc-backend/internal/graph/generated
/Users/jan/prg/ClusterCockpit/cc-backend/internal/graph/model
/Users/jan/prg/ClusterCockpit/cc-backend/internal/importer
/Users/jan/prg/ClusterCockpit/cc-backend/internal/importer/testdata
/Users/jan/prg/ClusterCockpit/cc-backend/internal/metricdispatch
/Users/jan/prg/ClusterCockpit/cc-backend/internal/metricstoreclient
/Users/jan/prg/ClusterCockpit/cc-backend/internal/repository
/Users/jan/prg/ClusterCockpit/cc-backend/internal/repository/migrations
/Users/jan/prg/ClusterCockpit/cc-backend/internal/repository/testdata
/Users/jan/prg/ClusterCockpit/cc-backend/internal/routerConfig
/Users/jan/prg/ClusterCockpit/cc-backend/internal/tagger
/Users/jan/prg/ClusterCockpit/cc-backend/internal/taskmanager
/Users/jan/prg/ClusterCockpit/cc-backend/pkg
/Users/jan/prg/ClusterCockpit/cc-backend/pkg/archive
/Users/jan/prg/ClusterCockpit/cc-backend/pkg/archive/parquet
/Users/jan/prg/ClusterCockpit/cc-backend/pkg/archive/testdata
/Users/jan/prg/ClusterCockpit/cc-backend/pkg/metricstore
/Users/jan/prg/ClusterCockpit/cc-backend/tools
/Users/jan/prg/ClusterCockpit/cc-backend/tools/archive-manager
/Users/jan/prg/ClusterCockpit/cc-backend/tools/archive-migration
/Users/jan/prg/ClusterCockpit/cc-backend/tools/binaryCheckpointReader
/Users/jan/prg/ClusterCockpit/cc-backend/tools/convert-pem-pubkey
/Users/jan/prg/ClusterCockpit/cc-backend/tools/gen-keypair
/Users/jan/prg/ClusterCockpit/cc-backend/var
/Users/jan/prg/ClusterCockpit/cc-backend/var/checkpoints
/Users/jan/prg/ClusterCockpit/cc-backend/var/job-archive
/Users/jan/prg/ClusterCockpit/cc-backend/var/job-archive/alex
/Users/jan/prg/ClusterCockpit/cc-backend/var/job-archive/fritz
/Users/jan/prg/ClusterCockpit/cc-backend/web
/Users/jan/prg/ClusterCockpit/cc-backend/web/frontend
/Users/jan/prg/ClusterCockpit/cc-backend/web/frontend/node_modules
/Users/jan/prg/ClusterCockpit/cc-backend/web/frontend/public
/Users/jan/prg/ClusterCockpit/cc-backend/web/frontend/src
/Users/jan/prg/ClusterCockpit/cc-backend/web/templates
/Users/jan/prg/ClusterCockpit/cc-backend/web/templates/monitoring


ls /Users/jan/prg/ClusterCockpit/cc-backend/init/ && echo "---" && ls /Users/jan/prg/ClusterCockpit/cc-backend/api/ && echo "---" && ls /Users/jan/prg/ClusterCockpit/cc-backend/configs/ && echo "---" && ls /Users/jan/prg/ClusterCockpit/cc-backend/web/frontend/public/ 2>/dev/null | head -5 && echo "---" && du -sh /Users/jan/prg/ClusterCockpit/cc-backend/var/ 2>/dev/null

Check contents of non-Go directories and var size


clustercockpit.service
README.md
---
schema.graphqls
swagger.json
swagger.yaml
---
cluster.json
config-demo.json
config.json
env-template.txt
generate-subcluster.pl
startJobPayload.json
stopJobPayload.json
tagger
uiConfig.json
---
bootstrap-icons.css
bootstrap.min.css
bootstrap.min.css.map
build
favicon.png
---
 45M	/Users/jan/prg/ClusterCockpit/cc-backend/var/


find /Users/jan/prg/ClusterCockpit/cc-backend -name "*.go" | sed 's|/[^/]*$||' | sort -u

List all directories that contain Go files


Check Go files in web/ root


/Users/jan/prg/ClusterCockpit/cc-backend/web/configSchema.go
/Users/jan/prg/ClusterCockpit/cc-backend/web/web.go
/Users/jan/prg/ClusterCockpit/cc-backend/web/webConfig_test.go


ls /Users/jan/prg/ClusterCockpit/cc-backend/internal/repository/migrations/sqlite3/ | head -5 && echo "---" && ls /Users/jan/prg/ClusterCockpit/cc-backend/internal/repository/testdata/ | head -5 && echo "---" && ls /Users/jan/prg/ClusterCockpit/cc-backend/pkg/archive/testdata/ | head -5

Check content types of migrations and testdata dirs


01_init-schema.down.sql
01_init-schema.up.sql
02_add-index.down.sql
02_add-index.up.sql
03_add-userprojects.down.sql
---
job.db
job.db-shm
job.db-wal
---
archive


Check importer testdata contents


cluster-fritz.json
data-fritzError.json
data-fritzMinimal.json
fritzError-golden.json
fritzMinimal-golden.json
meta-fritzError.input
meta-fritzMinimal.input


Now I have a complete picture. Here's the analysis:

**Directories with no Go files — candidates for exclusion:**

| Directory | Contents | Add? |
|---|---|---|
| `-.git` | git internals | ✓ already |
| `-.vscode` | editor config | ✓ already |
| `-.idea` | editor config | ✓ already |
| `-node_modules` | JS deps | ✓ already (matches anywhere) |
| `-web/frontend` | Svelte/JS/CSS/built assets | **add** — supersedes `-node_modules` for this project |
| `-web/templates` | HTML templates | **add** |
| `-var` | 45 MB runtime data (job archive, checkpoints, SQLite DBs) | **add** — biggest win |
| `-api` | `swagger.yaml`, `schema.graphqls` — no `.go` files | **add** |
| `-configs` | JSON config files | **add** |
| `-init` | systemd service file | **add** |
| `-internal/repository/migrations` | `.sql` files only | **add** |
| `-internal/repository/testdata` | SQLite DB files | **add** |
| `-internal/importer/testdata` | JSON test fixtures | **add** |
| `-pkg/archive/testdata` | archive test fixtures | **add** |
| `-.vscode-test` | not present | harmless, keep |

**Keep indexed (contain Go files):**
- `internal/graph/generated` — generated GraphQL resolvers; gopls needs this for navigation and completion
- `tools/` — valid Go programs
- `web/` root — contains `web.go`, `configSchema.go`


Edit

    "directoryFilters": [
        "-.git",
        "-.vscode",
        "-.idea",
        "-.vscode-test",
        "-node_modules"
    ],

    "directoryFilters": [
        "-.git",
        "-.vscode",
        "-.idea",
        "-.vscode-test",
        "-node_modules",
        "-web/frontend",
        "-web/templates",
        "-var",
        "-api",
        "-configs",
        "-init",
        "-internal/repository/migrations",
        "-internal/repository/testdata",
        "-internal/importer/testdata",
        "-pkg/archive/testdata"
    ],


The file /Users/jan/prg/ClusterCockpit/cc-backend/gopls.json has been updated successfully.

    "directoryFilters": [
        "-.git",
        "-.vscode",
        "-.idea",
        "-.vscode-test",
        "-node_modules"
    ],

    "directoryFilters": [
        "-.git",
        "-.vscode",
        "-.idea",
        "-.vscode-test",
        "-node_modules",
        "-web/frontend",
        "-web/templates",
        "-var",
        "-api",
        "-configs",
        "-init",
        "-internal/repository/migrations",
        "-internal/repository/testdata",
        "-internal/importer/testdata",
        "-pkg/archive/testdata"
    ],

+        "-internal/repository/migrations",

+        "-internal/repository/testdata",


Done. Key additions and rationale:

- **`-web/frontend`** — entire Svelte app subtree (src, node_modules, public/build); no Go files at all
- **`-web/templates`** — HTML templates only
- **`-var`** — largest win: 45 MB of runtime data (job archives, checkpoints, SQLite DBs); completely irrelevant to gopls
- **`-api`** — only `swagger.yaml` / `schema.graphqls`; the generated `docs.go` lives at `internal/api/docs.go`, not here
- **`-configs`** — JSON config files
- **`-init`** — systemd service file
- **`-internal/repository/migrations`** — SQL migration files
- **`-internal/repository/testdata`**, **`-internal/importer/testdata`**, **`-pkg/archive/testdata`** — JSON/SQLite test fixtures

`internal/graph/generated` is intentionally **not** excluded — gopls needs to index the generated GraphQL code to provide navigation and completion across the codebase.


Add infos about the new make targets including the required tools and how to install them to the README


init/README.md
internal/importer/README.md
tools/archive-migration/README.md
configs/tagger/README.md
internal/archiver/README.md
tools/archive-manager/README.md
web/frontend/README.md
README.md
web/frontend/node_modules/@types/estree/README.md
web/frontend/node_modules/@types/resolve/README.md
web/frontend/node_modules/@types/trusted-types/README.md
web/frontend/node_modules/@rollup/rollup-darwin-arm64/README.md
web/frontend/node_modules/@babel/runtime/README.md
web/frontend/node_modules/source-map/README.md
web/frontend/node_modules/is-module/README.md
web/frontend/node_modules/javascript-natural-sort/README.md
web/frontend/node_modules/escape-latex/README.md
web/frontend/node_modules/@popperjs/core/README.md
web/frontend/node_modules/path-parse/README.md
web/frontend/node_modules/fsevents/README.md
web/frontend/node_modules/serialize-javascript/README.md
web/frontend/node_modules/rollup-plugin-svelte/README.md
web/frontend/node_modules/rollup-plugin-css-only/README.md
web/frontend/node_modules/tiny-emitter/README.md
web/frontend/node_modules/locate-character/README.md
web/frontend/node_modules/zimmerframe/README.md
web/frontend/node_modules/svelte/node_modules/is-reference/README.md
web/frontend/node_modules/hasown/README.md
web/frontend/node_modules/is-reference/README.md
web/frontend/node_modules/esm-env/README.md
web/frontend/node_modules/supports-preserve-symlinks-flag/README.md
web/frontend/node_modules/seedrandom/README.md
web/frontend/node_modules/fdir/README.md
web/frontend/node_modules/decimal.js/README.md
web/frontend/node_modules/@jridgewell/resolve-uri/README.md
web/frontend/node_modules/source-map-support/README.md
web/frontend/node_modules/is-core-module/README.md
web/frontend/node_modules/@rollup/plugin-replace/README.md
web/frontend/node_modules/@rollup/plugin-terser/README.md
web/frontend/node_modules/smob/README.MD
web/frontend/node_modules/@urql/svelte/README.md
web/frontend/node_modules/function-bind/README.md
web/frontend/node_modules/@0no-co/graphql.web/README.md
web/frontend/node_modules/@typescript-eslint/types/README.md
web/frontend/node_modules/@sveltejs/acorn-typescript/README.md
web/frontend/node_modules/complex.js/README.md
web/frontend/node_modules/@kurkle/color/README.md
web/frontend/node_modules/rollup-plugin-svelte/node_modules/@rollup/pluginutils/README.md
web/frontend/node_modules/@jridgewell/source-map/README.md
web/frontend/node_modules/@rollup/pluginutils/README.md
web/frontend/node_modules/picomatch/README.md
web/frontend/node_modules/esrap/README.md
web/frontend/node_modules/rollup-plugin-svelte/node_modules/picomatch/README.md
web/frontend/node_modules/wonka/README.md
web/frontend/node_modules/acorn/README.md
web/frontend/node_modules/devalue/README.md
web/frontend/node_modules/estree-walker/README.md
web/frontend/node_modules/@rollup/plugin-node-resolve/README.md
web/frontend/node_modules/@rollup/plugin-commonjs/README.md
web/frontend/node_modules/typed-function/README.md
web/frontend/node_modules/magic-string/README.md
web/frontend/node_modules/@timohausmann/quadtree-js/README.md
web/frontend/node_modules/@urql/core/README.md
web/frontend/node_modules/fraction.js/README.md
web/frontend/node_modules/uplot/README.md
web/frontend/node_modules/@jridgewell/sourcemap-codec/README.md
web/frontend/node_modules/@jridgewell/gen-mapping/README.md
web/frontend/node_modules/@jridgewell/remapping/README.md
web/frontend/node_modules/@jridgewell/trace-mapping/README.md
web/frontend/node_modules/rollup/README.md
web/frontend/node_modules/terser/README.md
web/frontend/node_modules/chart.js/README.md
web/frontend/node_modules/graphql/README.md
web/frontend/node_modules/axobject-query/README.md
web/frontend/node_modules/aria-query/README.md
web/frontend/node_modules/@sveltestrap/sveltestrap/README.md
web/frontend/node_modules/svelte/README.md
web/frontend/node_modules/date-fns/README.md
web/frontend/node_modules/mathjs/README.md


1	# NOTE
2
3	While we do our best to keep the master branch in a usable state, there is no guarantee the master branch works.
4	Please do not use it for production!
5
6	Please have a look at the [Release
7	Notes](https://github.com/ClusterCockpit/cc-backend/blob/master/ReleaseNotes.md)
8	for breaking changes!
9
10	# ClusterCockpit REST and GraphQL API backend
11
12	[![Build](https://github.com/ClusterCockpit/cc-backend/actions/workflows/test.yml/badge.svg)](https://github.com/ClusterCockpit/cc-backend/actions/workflows/test.yml)
13
14	This is a Golang backend implementation for a REST and GraphQL API according to
15	the [ClusterCockpit
16	specifications](https://github.com/ClusterCockpit/cc-specifications). It also
17	includes a web interface for ClusterCockpit. This implementation replaces the
18	previous PHP Symfony based ClusterCockpit web interface. The reasons for
19	switching from PHP Symfony to a Golang based solution are explained
20	[here](https://github.com/ClusterCockpit/ClusterCockpit/wiki/Why-we-switched-from-PHP-Symfony-to-a-Golang-based-solution).
21
22	## Overview
23
24	This is a Golang web backend for the ClusterCockpit job-specific performance
25	monitoring framework. It provides a REST API and an optional NATS-based messaging
26	API for integrating ClusterCockpit with an HPC cluster batch system and external
27	analysis scripts. Data exchange between the web front-end and the back-end is
28	based on a GraphQL API. The web frontend is also served by the backend using
29	[Svelte](https://svelte.dev/) components. Layout and styling are based on
30	[Bootstrap 5](https://getbootstrap.com/) using
31	[Bootstrap Icons](https://icons.getbootstrap.com/).
32
33	The backend uses [SQLite 3](https://sqlite.org/) as the relational SQL database.
34	While there are metric data backends for the InfluxDB and Prometheus time series
35	databases, the only tested and supported setup is to use cc-metric-store as the
36	metric data backend. Documentation on how to integrate ClusterCockpit with other
37	time series databases will be added in the future.
38
39	For real-time integration with HPC systems, the backend can subscribe to
40	[NATS](https://nats.io/) subjects to receive job start/stop events and node
41	state updates, providing an alternative to REST API polling.
42
43	Completed batch jobs are stored in a file-based job archive according to
44	[this specification](https://github.com/ClusterCockpit/cc-specifications/tree/master/job-archive).
45	The backend supports authentication via local accounts, an external LDAP
46	directory, and JWT tokens. Authorization for APIs is implemented with
47	[JWT](https://jwt.io/) tokens created with public/private key encryption.
48
49	You find a detailed documentation on the [ClusterCockpit
50	Webpage](https://clustercockpit.org).
51
52	## Build requirements
53
54	ClusterCockpit requires a current version of the golang toolchain and node.js.
55	You can check `go.mod` to see what is the current minimal golang version needed.
56	Homebrew and Archlinux usually have current golang versions. For other Linux
57	distros this often means that you have to install the golang compiler yourself.
58	Fortunately, this is easy with golang. Since much of the functionality is based
59	on the Go standard library, it is crucial for security and performance to use a
60	current version of golang. In addition, an old golang toolchain may limit the supported
61	versions of third-party packages.
62
63	## How to try ClusterCockpit with a demo setup
64
65	We provide a shell script that downloads demo data and automatically starts the
66	cc-backend. You will need `wget`, `go`, `node`, `npm` in your path to
67	start the demo. The demo downloads 32MB of data (223MB on disk).
68
69	```sh
70	git clone https://github.com/ClusterCockpit/cc-backend.git
71	cd ./cc-backend
72	./startDemo.sh
73	```
74
75	You can also try the demo using the latest release binary.
76	Create a folder and put the release binary `cc-backend` into this folder.
77	Execute the following steps:
78
79	```shell
80	./cc-backend -init
81	vim config.json (Add a second cluster entry and name the clusters alex and fritz)
82	wget https://hpc-mover.rrze.uni-erlangen.de/HPC-Data/0x7b58aefb/eig7ahyo6fo2bais0ephuf2aitohv1ai/job-archive-demo.tar
83	tar xf job-archive-demo.tar
84	./cc-backend -init-db -add-user demo:admin:demo -loglevel info
85	./cc-backend -server -dev -loglevel info
86	```
87
88	You can access the web interface at [http://localhost:8080](http://localhost:8080).
89	Credentials for login are `demo:demo`.
90	Please note that some views do not work without a metric backend (e.g., the
91	Analysis, Systems and Status views).
92
93	## How to build and run
94
95	There is a Makefile to automate the build of cc-backend. The Makefile supports
96	the following targets:
97
98	- `make`: Initialize `var` directory and build svelte frontend and backend
99	  binary. Note that there is no proper prerequisite handling. Any change of
100	  frontend source files will result in a complete rebuild.
101	- `make clean`: Clean go build cache and remove binary.
102	- `make test`: Run the tests that are also run in the GitHub workflow setup.
103
104	A common workflow for setting up cc-backend from scratch is:
105
106	```sh
107	git clone https://github.com/ClusterCockpit/cc-backend.git
108
109	# Build binary
110	cd ./cc-backend/
111	make
112
113	# EDIT THE .env FILE BEFORE YOU DEPLOY (Change the secrets)!
114	# If authentication is disabled, it can be empty.
115	cp configs/env-template.txt  .env
116	vim .env
117
118	cp configs/config.json .
119	vim config.json
120
121	#Optional: Link an existing job archive:
122	ln -s <your-existing-job-archive> ./var/job-archive
123
124	# This will first initialize the job.db database by traversing all
125	# `meta.json` files in the job-archive and add a new user.
126	./cc-backend -init-db -add-user <your-username>:admin:<your-password>
127
128	# Start a HTTP server (HTTPS can be enabled in the configuration, the default port is 8080).
129	# The --dev flag enables GraphQL Playground (http://localhost:8080/playground) and Swagger UI (http://localhost:8080/swagger).
130	./cc-backend -server  -dev
131
132	# Show other options:
133	./cc-backend -help
134	```
135
136	## Database Configuration
137
138	cc-backend uses SQLite as its database. For large installations, SQLite memory
139	usage can be tuned via the optional `db-config` section in config.json under
140	`main`:
141
142	```json
143	{
144	  "main": {
145	    "db": "./var/job.db",
146	    "db-config": {
147	      "cache-size-mb": 2048,
148	      "soft-heap-limit-mb": 16384,
149	      "max-open-connections": 4,
150	      "max-idle-connections": 4,
151	      "max-idle-time-minutes": 10,
152	      "busy-timeout-ms": 60000
153	    }
154	  }
155	}
156	```
157
158	All fields are optional. If `db-config` is omitted entirely, built-in defaults
159	are used.
160
161	### Options
162
163	| Option                  | Default | Description                                                                                |
164	| ----------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
165	| `cache-size-mb`         | 2048    | SQLite page cache size per connection in MB. Maps to `PRAGMA cache_size`. Total cache memory is up to `cache-size-mb × max-open-connections`.                                           |
166	| `soft-heap-limit-mb`    | 16384   | Process-wide SQLite soft heap limit in MB. SQLite will try to release cache pages to stay under this limit. Queries won't fail if exceeded, but cache eviction becomes more aggressive. |
167	| `max-open-connections`  | 4       | Maximum number of open database connections.                                                                                |
168	| `max-idle-connections`  | 4       | Maximum number of idle database connections kept in the pool.                                                                                |
169	| `max-idle-time-minutes` | 10      | Maximum time in minutes a connection can sit idle before being closed.                                                                                |
170	| `busy-timeout-ms`       | 60000   | SQLite busy timeout in milliseconds. When a write is blocked by another writer, SQLite retries internally with backoff for up to this duration before returning `SQLITE_BUSY`.          |
171
172	### Sizing Guidelines
173
174	SQLite's `cache_size` is a **per-connection** setting — each connection
175	maintains its own independent page cache. With multiple connections, the total
176	memory available for caching is the sum across all connections.
177
178	In practice, different connections tend to cache **different pages** (e.g., one
179	handles a job listing query while another runs a statistics aggregation), so
180	their caches naturally spread across the database. The formula
181	`DB_size / max-open-connections` gives enough per-connection cache that the
182	combined caches can cover the entire database.
183
184	However, this is a best-case estimate. Connections running similar queries will
185	cache the same pages redundantly. In the worst case (all connections caching
186	identical pages), only `cache-size-mb` worth of unique data is cached rather
187	than `cache-size-mb × max-open-connections`. For workloads with diverse
188	concurrent queries, cache overlap is typically low.
189
190	**Rules of thumb:**
191
192	- **cache-size-mb**: Set to `DB_size_in_MB / max-open-connections` to allow the
193	  entire database to be cached in memory. For example, an 80GB database with 8
194	  connections needs at least 10240 MB (10GB) per connection. If your workload
195	  has many similar concurrent queries, consider setting it higher to account for
196	  cache overlap between connections.
197
198	- **soft-heap-limit-mb**: Should be >= `cache-size-mb × max-open-connections` to
199	  avoid cache thrashing. This is the total SQLite memory budget for the process.
200	- On small installations the defaults work well. On servers with large databases
201	  (tens of GB) and plenty of RAM, increasing these values significantly improves
202	  query performance by reducing disk I/O.
203
204	### Example: Large Server (512GB RAM, 80GB database)
205
206	```json
207	{
208	  "main": {
209	    "db-config": {
210	      "cache-size-mb": 16384,
211	      "soft-heap-limit-mb": 131072,
212	      "max-open-connections": 8,
213	      "max-idle-time-minutes": 30
214	    }
215	  }
216	}
217	```
218
219	This allows the entire 80GB database to be cached (8 × 16GB = 128GB page cache)
220	with a 128GB soft heap limit, using about 25% of available RAM.
221
222	The effective configuration is logged at startup for verification.
223
224	## Project file structure
225
226	- [`.github/`](https://github.com/ClusterCockpit/cc-backend/tree/master/.github)
227	  GitHub Actions workflows and dependabot configuration for CI/CD.
228	- [`api/`](https://github.com/ClusterCockpit/cc-backend/tree/master/api)
229	  contains the API schema files for the REST and GraphQL APIs. The REST API is
230	  documented in the OpenAPI 3.0 format in
231	  [./api/swagger.yaml](./api/swagger.yaml). The GraphQL schema is in
232	  [./api/schema.graphqls](./api/schema.graphqls).
233	- [`cmd/cc-backend`](https://github.com/ClusterCockpit/cc-backend/tree/master/cmd/cc-backend)
234	  contains the main application entry point and CLI implementation.
235	- [`configs/`](https://github.com/ClusterCockpit/cc-backend/tree/master/configs)
236	  contains documentation about configuration and command line options and required
237	  environment variables. Sample configuration files are provided.
238	- [`init/`](https://github.com/ClusterCockpit/cc-backend/tree/master/init)
239	  contains an example of setting up systemd for production use.
240	- [`internal/`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal)
241	  contains library source code that is not intended for use by others.
242	  - [`api`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/api)
243	    REST API handlers and NATS integration
244	  - [`archiver`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/archiver)
245	    Job archiving functionality
246	  - [`auth`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/auth)
247	    Authentication (local, LDAP, OIDC) and JWT token handling
248	  - [`config`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/config)
249	    Configuration management and validation
250	  - [`graph`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/graph)
251	    GraphQL schema and resolvers
252	  - [`importer`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/importer)
253	    Job data import and database initialization
254	  - [`metricdispatch`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/metricdispatch)
255	    Dispatches metric data loading to appropriate backends
256	  - [`repository`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/repository)
257	    Database repository layer for jobs and metadata
258	  - [`routerConfig`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/routerConfig)
259	    HTTP router configuration and middleware
260	  - [`tagger`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/tagger)
261	    Job classification and application detection
262	  - [`taskmanager`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/taskmanager)
263	    Background task management and scheduled jobs
264	  - [`metricstoreclient`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/metricstoreclient)
265	    Client for cc-metric-store queries
266	- [`pkg/`](https://github.com/ClusterCockpit/cc-backend/tree/master/pkg)
267	  contains Go packages that can be used by other projects.
268	  - [`archive`](https://github.com/ClusterCockpit/cc-backend/tree/master/pkg/archive)
269	    Job archive backend implementations (filesystem, S3, SQLite)
270	  - [`metricstore`](https://github.com/ClusterCockpit/cc-backend/tree/master/pkg/metricstore)
271	    In-memory metric data store with checkpointing and metric loading
272	- [`tools/`](https://github.com/ClusterCockpit/cc-backend/tree/master/tools)
273	  Additional command line helper tools.
274	  - [`archive-manager`](https://github.com/ClusterCockpit/cc-backend/tree/master/tools/archive-manager)
275	    Commands for getting infos about an existing job archive, importing jobs
276	    between archive backends, and converting archives between JSON and Parquet formats.
277	  - [`archive-migration`](https://github.com/ClusterCockpit/cc-backend/tree/master/tools/archive-migration)
278	    Tool for migrating job archives between formats.
279	  - [`convert-pem-pubkey`](https://github.com/ClusterCockpit/cc-backend/tree/master/tools/convert-pem-pubkey)
280	    Tool to convert external pubkey for use in `cc-backend`.
281	  - [`gen-keypair`](https://github.com/ClusterCockpit/cc-backend/tree/master/tools/gen-keypair)
282	    contains a small application to generate a compatible JWT keypair. You find
283	    documentation on how to use it
284	    [here](https://github.com/ClusterCockpit/cc-backend/blob/master/docs/JWT-Handling.md).
285	- [`web/`](https://github.com/ClusterCockpit/cc-backend/tree/master/web)
286	  Server-side templates and frontend-related files:
287	  - [`frontend`](https://github.com/ClusterCockpit/cc-backend/tree/master/web/frontend)
288	    Svelte components and static assets for the frontend UI
289	  - [`templates`](https://github.com/ClusterCockpit/cc-backend/tree/master/web/templates)
290	    Server-side Go templates, including monitoring views
291	- [`gqlgen.yml`](https://github.com/ClusterCockpit/cc-backend/blob/master/gqlgen.yml)
292	  Configures the behaviour and generation of
293	  [gqlgen](https://github.com/99designs/gqlgen).
294	- [`startDemo.sh`](https://github.com/ClusterCockpit/cc-backend/blob/master/startDemo.sh)
295	  is a shell script that sets up demo data, and builds and starts `cc-backend`.
296

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

# NOTE

While we do our best to keep the master branch in a usable state, there is no guarantee the master branch works.
Please do not use it for production!

Please have a look at the [Release
Notes](https://github.com/ClusterCockpit/cc-backend/blob/master/ReleaseNotes.md)
for breaking changes!

# ClusterCockpit REST and GraphQL API backend

[![Build](https://github.com/ClusterCockpit/cc-backend/actions/workflows/test.yml/badge.svg)](https://github.com/ClusterCockpit/cc-backend/actions/workflows/test.yml)

This is a Golang backend implementation for a REST and GraphQL API according to
the [ClusterCockpit
specifications](https://github.com/ClusterCockpit/cc-specifications). It also
includes a web interface for ClusterCockpit. This implementation replaces the
previous PHP Symfony based ClusterCockpit web interface. The reasons for
switching from PHP Symfony to a Golang based solution are explained
[here](https://github.com/ClusterCockpit/ClusterCockpit/wiki/Why-we-switched-from-PHP-Symfony-to-a-Golang-based-solution).

## Overview

This is a Golang web backend for the ClusterCockpit job-specific performance
monitoring framework. It provides a REST API and an optional NATS-based messaging
API for integrating ClusterCockpit with an HPC cluster batch system and external
analysis scripts. Data exchange between the web front-end and the back-end is
based on a GraphQL API. The web frontend is also served by the backend using
[Svelte](https://svelte.dev/) components. Layout and styling are based on
[Bootstrap 5](https://getbootstrap.com/) using
[Bootstrap Icons](https://icons.getbootstrap.com/).

The backend uses [SQLite 3](https://sqlite.org/) as the relational SQL database.
While there are metric data backends for the InfluxDB and Prometheus time series
databases, the only tested and supported setup is to use cc-metric-store as the
metric data backend. Documentation on how to integrate ClusterCockpit with other
time series databases will be added in the future.

For real-time integration with HPC systems, the backend can subscribe to
[NATS](https://nats.io/) subjects to receive job start/stop events and node
state updates, providing an alternative to REST API polling.

Completed batch jobs are stored in a file-based job archive according to
[this specification](https://github.com/ClusterCockpit/cc-specifications/tree/master/job-archive).
The backend supports authentication via local accounts, an external LDAP
directory, and JWT tokens. Authorization for APIs is implemented with
[JWT](https://jwt.io/) tokens created with public/private key encryption.

You find a detailed documentation on the [ClusterCockpit
Webpage](https://clustercockpit.org).

## Build requirements

ClusterCockpit requires a current version of the golang toolchain and node.js.
You can check `go.mod` to see what is the current minimal golang version needed.
Homebrew and Archlinux usually have current golang versions. For other Linux
distros this often means that you have to install the golang compiler yourself.
Fortunately, this is easy with golang. Since much of the functionality is based
on the Go standard library, it is crucial for security and performance to use a
current version of golang. In addition, an old golang toolchain may limit the supported
versions of third-party packages.

## How to try ClusterCockpit with a demo setup

We provide a shell script that downloads demo data and automatically starts the
cc-backend. You will need `wget`, `go`, `node`, `npm` in your path to
start the demo. The demo downloads 32MB of data (223MB on disk).

```sh
git clone https://github.com/ClusterCockpit/cc-backend.git
cd ./cc-backend
./startDemo.sh
```

You can also try the demo using the latest release binary.
Create a folder and put the release binary `cc-backend` into this folder.
Execute the following steps:

```shell
./cc-backend -init
vim config.json (Add a second cluster entry and name the clusters alex and fritz)
wget https://hpc-mover.rrze.uni-erlangen.de/HPC-Data/0x7b58aefb/eig7ahyo6fo2bais0ephuf2aitohv1ai/job-archive-demo.tar
tar xf job-archive-demo.tar
./cc-backend -init-db -add-user demo:admin:demo -loglevel info
./cc-backend -server -dev -loglevel info
```

You can access the web interface at [http://localhost:8080](http://localhost:8080).
Credentials for login are `demo:demo`.
Please note that some views do not work without a metric backend (e.g., the
Analysis, Systems and Status views).

## How to build and run

There is a Makefile to automate the build of cc-backend. The Makefile supports
the following targets:

- `make`: Initialize `var` directory and build svelte frontend and backend
  binary. Note that there is no proper prerequisite handling. Any change of
  frontend source files will result in a complete rebuild.
- `make clean`: Clean go build cache and remove binary.
- `make test`: Run the tests that are also run in the GitHub workflow setup.

A common workflow for setting up cc-backend from scratch is:

```sh
git clone https://github.com/ClusterCockpit/cc-backend.git

# Build binary
cd ./cc-backend/
make

# EDIT THE .env FILE BEFORE YOU DEPLOY (Change the secrets)!
# If authentication is disabled, it can be empty.
cp configs/env-template.txt  .env
vim .env

cp configs/config.json .
vim config.json

#Optional: Link an existing job archive:
ln -s <your-existing-job-archive> ./var/job-archive

# This will first initialize the job.db database by traversing all
# `meta.json` files in the job-archive and add a new user.
./cc-backend -init-db -add-user <your-username>:admin:<your-password>

# Start a HTTP server (HTTPS can be enabled in the configuration, the default port is 8080).
# The --dev flag enables GraphQL Playground (http://localhost:8080/playground) and Swagger UI (http://localhost:8080/swagger).
./cc-backend -server  -dev

# Show other options:
./cc-backend -help
```

## Database Configuration

cc-backend uses SQLite as its database. For large installations, SQLite memory
usage can be tuned via the optional `db-config` section in config.json under
`main`:

```json
{
  "main": {
    "db": "./var/job.db",
    "db-config": {
      "cache-size-mb": 2048,
      "soft-heap-limit-mb": 16384,
      "max-open-connections": 4,
      "max-idle-connections": 4,
      "max-idle-time-minutes": 10,
      "busy-timeout-ms": 60000
    }
  }
}
```

All fields are optional. If `db-config` is omitted entirely, built-in defaults
are used.

### Options

| Option                  | Default | Description                                                                                |
| ----------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `cache-size-mb`         | 2048    | SQLite page cache size per connection in MB. Maps to `PRAGMA cache_size`. Total cache memory is up to `cache-size-mb × max-open-connections`.                                           |
| `soft-heap-limit-mb`    | 16384   | Process-wide SQLite soft heap limit in MB. SQLite will try to release cache pages to stay under this limit. Queries won't fail if exceeded, but cache eviction becomes more aggressive. |
| `max-open-connections`  | 4       | Maximum number of open database connections.                                                                                |
| `max-idle-connections`  | 4       | Maximum number of idle database connections kept in the pool.                                                                                |
| `max-idle-time-minutes` | 10      | Maximum time in minutes a connection can sit idle before being closed.                                                                                |
| `busy-timeout-ms`       | 60000   | SQLite busy timeout in milliseconds. When a write is blocked by another writer, SQLite retries internally with backoff for up to this duration before returning `SQLITE_BUSY`.          |

### Sizing Guidelines

SQLite's `cache_size` is a **per-connection** setting — each connection
maintains its own independent page cache. With multiple connections, the total
memory available for caching is the sum across all connections.

In practice, different connections tend to cache **different pages** (e.g., one
handles a job listing query while another runs a statistics aggregation), so
their caches naturally spread across the database. The formula
`DB_size / max-open-connections` gives enough per-connection cache that the
combined caches can cover the entire database.

However, this is a best-case estimate. Connections running similar queries will
cache the same pages redundantly. In the worst case (all connections caching
identical pages), only `cache-size-mb` worth of unique data is cached rather
than `cache-size-mb × max-open-connections`. For workloads with diverse
concurrent queries, cache overlap is typically low.

**Rules of thumb:**

- **cache-size-mb**: Set to `DB_size_in_MB / max-open-connections` to allow the
  entire database to be cached in memory. For example, an 80GB database with 8
  connections needs at least 10240 MB (10GB) per connection. If your workload
  has many similar concurrent queries, consider setting it higher to account for
  cache overlap between connections.

- **soft-heap-limit-mb**: Should be >= `cache-size-mb × max-open-connections` to
  avoid cache thrashing. This is the total SQLite memory budget for the process.
- On small installations the defaults work well. On servers with large databases
  (tens of GB) and plenty of RAM, increasing these values significantly improves
  query performance by reducing disk I/O.

### Example: Large Server (512GB RAM, 80GB database)

```json
{
  "main": {
    "db-config": {
      "cache-size-mb": 16384,
      "soft-heap-limit-mb": 131072,
      "max-open-connections": 8,
      "max-idle-time-minutes": 30
    }
  }
}
```

This allows the entire 80GB database to be cached (8 × 16GB = 128GB page cache)
with a 128GB soft heap limit, using about 25% of available RAM.

The effective configuration is logged at startup for verification.

## Project file structure

- [`.github/`](https://github.com/ClusterCockpit/cc-backend/tree/master/.github)
  GitHub Actions workflows and dependabot configuration for CI/CD.
- [`api/`](https://github.com/ClusterCockpit/cc-backend/tree/master/api)
  contains the API schema files for the REST and GraphQL APIs. The REST API is
  documented in the OpenAPI 3.0 format in
  [./api/swagger.yaml](./api/swagger.yaml). The GraphQL schema is in
  [./api/schema.graphqls](./api/schema.graphqls).
- [`cmd/cc-backend`](https://github.com/ClusterCockpit/cc-backend/tree/master/cmd/cc-backend)
  contains the main application entry point and CLI implementation.
- [`configs/`](https://github.com/ClusterCockpit/cc-backend/tree/master/configs)
  contains documentation about configuration and command line options and required
  environment variables. Sample configuration files are provided.
- [`init/`](https://github.com/ClusterCockpit/cc-backend/tree/master/init)
  contains an example of setting up systemd for production use.
- [`internal/`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal)
  contains library source code that is not intended for use by others.
  - [`api`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/api)
    REST API handlers and NATS integration
  - [`archiver`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/archiver)
    Job archiving functionality
  - [`auth`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/auth)
    Authentication (local, LDAP, OIDC) and JWT token handling
  - [`config`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/config)
    Configuration management and validation
  - [`graph`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/graph)
    GraphQL schema and resolvers
  - [`importer`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/importer)
    Job data import and database initialization
  - [`metricdispatch`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/metricdispatch)
    Dispatches metric data loading to appropriate backends
  - [`repository`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/repository)
    Database repository layer for jobs and metadata
  - [`routerConfig`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/routerConfig)
    HTTP router configuration and middleware
  - [`tagger`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/tagger)
    Job classification and application detection
  - [`taskmanager`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/taskmanager)
    Background task management and scheduled jobs
  - [`metricstoreclient`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/metricstoreclient)
    Client for cc-metric-store queries
- [`pkg/`](https://github.com/ClusterCockpit/cc-backend/tree/master/pkg)
  contains Go packages that can be used by other projects.
  - [`archive`](https://github.com/ClusterCockpit/cc-backend/tree/master/pkg/archive)
    Job archive backend implementations (filesystem, S3, SQLite)
  - [`metricstore`](https://github.com/ClusterCockpit/cc-backend/tree/master/pkg/metricstore)
    In-memory metric data store with checkpointing and metric loading
- [`tools/`](https://github.com/ClusterCockpit/cc-backend/tree/master/tools)
  Additional command line helper tools.
  - [`archive-manager`](https://github.com/ClusterCockpit/cc-backend/tree/master/tools/archive-manager)
    Commands for getting infos about an existing job archive, importing jobs
    between archive backends, and converting archives between JSON and Parquet formats.
  - [`archive-migration`](https://github.com/ClusterCockpit/cc-backend/tree/master/tools/archive-migration)
    Tool for migrating job archives between formats.
  - [`convert-pem-pubkey`](https://github.com/ClusterCockpit/cc-backend/tree/master/tools/convert-pem-pubkey)
    Tool to convert external pubkey for use in `cc-backend`.
  - [`gen-keypair`](https://github.com/ClusterCockpit/cc-backend/tree/master/tools/gen-keypair)
    contains a small application to generate a compatible JWT keypair. You find
    documentation on how to use it
    [here](https://github.com/ClusterCockpit/cc-backend/blob/master/docs/JWT-Handling.md).
- [`web/`](https://github.com/ClusterCockpit/cc-backend/tree/master/web)
  Server-side templates and frontend-related files:
  - [`frontend`](https://github.com/ClusterCockpit/cc-backend/tree/master/web/frontend)
    Svelte components and static assets for the frontend UI
  - [`templates`](https://github.com/ClusterCockpit/cc-backend/tree/master/web/templates)
    Server-side Go templates, including monitoring views
- [`gqlgen.yml`](https://github.com/ClusterCockpit/cc-backend/blob/master/gqlgen.yml)
  Configures the behaviour and generation of
  [gqlgen](https://github.com/99designs/gqlgen).
- [`startDemo.sh`](https://github.com/ClusterCockpit/cc-backend/blob/master/startDemo.sh)
  is a shell script that sets up demo data, and builds and starts `cc-backend`.


- `make`: Initialize `var` directory and build svelte frontend and backend
  binary. Note that there is no proper prerequisite handling. Any change of
  frontend source files will result in a complete rebuild.
- `make clean`: Clean go build cache and remove binary.
- `make test`: Run the tests that are also run in the GitHub workflow setup.

- `make`: Initialize `var` directory and build svelte frontend and backend
  binary. Note that there is no proper prerequisite handling. Any change of
  frontend source files will result in a complete rebuild.
- `make clean`: Clean go build cache and remove binary.
- `make test`: Run the tests that are also run in the GitHub workflow setup.
- `make fmt`: Format all Go source files using
  [gofumpt](https://github.com/mvdan/gofumpt), a stricter superset of `gofmt`.
  Requires `gofumpt` to be installed (see below).
- `make lint`: Run [golangci-lint](https://golangci-lint.run/) with the project
  configuration in `.golangci.yml`. Requires `golangci-lint` to be installed
  (see below).

### Installing development tools

`gofumpt` and `golangci-lint` are not part of the Go module and must be
installed separately:

```sh
# Formatter (gofumpt)
go install mvdan.cc/gofumpt@latest

# Linter (golangci-lint) — use the official install script to get a
# pre-built binary; installing via `go install` is not supported upstream.
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin
```


The file /Users/jan/prg/ClusterCockpit/cc-backend/README.md has been updated successfully.

- `make`: Initialize `var` directory and build svelte frontend and backend
  binary. Note that there is no proper prerequisite handling. Any change of
  frontend source files will result in a complete rebuild.
- `make clean`: Clean go build cache and remove binary.
- `make test`: Run the tests that are also run in the GitHub workflow setup.
- `make fmt`: Format all Go source files using
  [gofumpt](https://github.com/mvdan/gofumpt), a stricter superset of `gofmt`.
  Requires `gofumpt` to be installed (see below).
- `make lint`: Run [golangci-lint](https://golangci-lint.run/) with the project
  configuration in `.golangci.yml`. Requires `golangci-lint` to be installed
  (see below).

### Installing development tools

`gofumpt` and `golangci-lint` are not part of the Go module and must be
installed separately:

```sh
# Formatter (gofumpt)
go install mvdan.cc/gofumpt@latest

# Linter (golangci-lint) — use the official install script to get a
# pre-built binary; installing via `go install` is not supported upstream.
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin
```

# NOTE

While we do our best to keep the master branch in a usable state, there is no guarantee the master branch works.
Please do not use it for production!

Please have a look at the [Release
Notes](https://github.com/ClusterCockpit/cc-backend/blob/master/ReleaseNotes.md)
for breaking changes!

# ClusterCockpit REST and GraphQL API backend

[![Build](https://github.com/ClusterCockpit/cc-backend/actions/workflows/test.yml/badge.svg)](https://github.com/ClusterCockpit/cc-backend/actions/workflows/test.yml)

This is a Golang backend implementation for a REST and GraphQL API according to
the [ClusterCockpit
specifications](https://github.com/ClusterCockpit/cc-specifications). It also
includes a web interface for ClusterCockpit. This implementation replaces the
previous PHP Symfony based ClusterCockpit web interface. The reasons for
switching from PHP Symfony to a Golang based solution are explained
[here](https://github.com/ClusterCockpit/ClusterCockpit/wiki/Why-we-switched-from-PHP-Symfony-to-a-Golang-based-solution).

## Overview

This is a Golang web backend for the ClusterCockpit job-specific performance
monitoring framework. It provides a REST API and an optional NATS-based messaging
API for integrating ClusterCockpit with an HPC cluster batch system and external
analysis scripts. Data exchange between the web front-end and the back-end is
based on a GraphQL API. The web frontend is also served by the backend using
[Svelte](https://svelte.dev/) components. Layout and styling are based on
[Bootstrap 5](https://getbootstrap.com/) using
[Bootstrap Icons](https://icons.getbootstrap.com/).

The backend uses [SQLite 3](https://sqlite.org/) as the relational SQL database.
While there are metric data backends for the InfluxDB and Prometheus time series
databases, the only tested and supported setup is to use cc-metric-store as the
metric data backend. Documentation on how to integrate ClusterCockpit with other
time series databases will be added in the future.

For real-time integration with HPC systems, the backend can subscribe to
[NATS](https://nats.io/) subjects to receive job start/stop events and node
state updates, providing an alternative to REST API polling.

Completed batch jobs are stored in a file-based job archive according to
[this specification](https://github.com/ClusterCockpit/cc-specifications/tree/master/job-archive).
The backend supports authentication via local accounts, an external LDAP
directory, and JWT tokens. Authorization for APIs is implemented with
[JWT](https://jwt.io/) tokens created with public/private key encryption.

You find a detailed documentation on the [ClusterCockpit
Webpage](https://clustercockpit.org).

## Build requirements

ClusterCockpit requires a current version of the golang toolchain and node.js.
You can check `go.mod` to see what is the current minimal golang version needed.
Homebrew and Archlinux usually have current golang versions. For other Linux
distros this often means that you have to install the golang compiler yourself.
Fortunately, this is easy with golang. Since much of the functionality is based
on the Go standard library, it is crucial for security and performance to use a
current version of golang. In addition, an old golang toolchain may limit the supported
versions of third-party packages.

## How to try ClusterCockpit with a demo setup

We provide a shell script that downloads demo data and automatically starts the
cc-backend. You will need `wget`, `go`, `node`, `npm` in your path to
start the demo. The demo downloads 32MB of data (223MB on disk).

```sh
git clone https://github.com/ClusterCockpit/cc-backend.git
cd ./cc-backend
./startDemo.sh
```

You can also try the demo using the latest release binary.
Create a folder and put the release binary `cc-backend` into this folder.
Execute the following steps:

```shell
./cc-backend -init
vim config.json (Add a second cluster entry and name the clusters alex and fritz)
wget https://hpc-mover.rrze.uni-erlangen.de/HPC-Data/0x7b58aefb/eig7ahyo6fo2bais0ephuf2aitohv1ai/job-archive-demo.tar
tar xf job-archive-demo.tar
./cc-backend -init-db -add-user demo:admin:demo -loglevel info
./cc-backend -server -dev -loglevel info
```

You can access the web interface at [http://localhost:8080](http://localhost:8080).
Credentials for login are `demo:demo`.
Please note that some views do not work without a metric backend (e.g., the
Analysis, Systems and Status views).

## How to build and run

There is a Makefile to automate the build of cc-backend. The Makefile supports
the following targets:

- `make`: Initialize `var` directory and build svelte frontend and backend
  binary. Note that there is no proper prerequisite handling. Any change of
  frontend source files will result in a complete rebuild.
- `make clean`: Clean go build cache and remove binary.
- `make test`: Run the tests that are also run in the GitHub workflow setup.

A common workflow for setting up cc-backend from scratch is:

```sh
git clone https://github.com/ClusterCockpit/cc-backend.git

# Build binary
cd ./cc-backend/
make

# EDIT THE .env FILE BEFORE YOU DEPLOY (Change the secrets)!
# If authentication is disabled, it can be empty.
cp configs/env-template.txt  .env
vim .env

cp configs/config.json .
vim config.json

#Optional: Link an existing job archive:
ln -s <your-existing-job-archive> ./var/job-archive

# This will first initialize the job.db database by traversing all
# `meta.json` files in the job-archive and add a new user.
./cc-backend -init-db -add-user <your-username>:admin:<your-password>

# Start a HTTP server (HTTPS can be enabled in the configuration, the default port is 8080).
# The --dev flag enables GraphQL Playground (http://localhost:8080/playground) and Swagger UI (http://localhost:8080/swagger).
./cc-backend -server  -dev

# Show other options:
./cc-backend -help
```

## Database Configuration

cc-backend uses SQLite as its database. For large installations, SQLite memory
usage can be tuned via the optional `db-config` section in config.json under
`main`:

```json
{
  "main": {
    "db": "./var/job.db",
    "db-config": {
      "cache-size-mb": 2048,
      "soft-heap-limit-mb": 16384,
      "max-open-connections": 4,
      "max-idle-connections": 4,
      "max-idle-time-minutes": 10,
      "busy-timeout-ms": 60000
    }
  }
}
```

All fields are optional. If `db-config` is omitted entirely, built-in defaults
are used.

### Options

| Option                  | Default | Description                                                                                |
| ----------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `cache-size-mb`         | 2048    | SQLite page cache size per connection in MB. Maps to `PRAGMA cache_size`. Total cache memory is up to `cache-size-mb × max-open-connections`.                                           |
| `soft-heap-limit-mb`    | 16384   | Process-wide SQLite soft heap limit in MB. SQLite will try to release cache pages to stay under this limit. Queries won't fail if exceeded, but cache eviction becomes more aggressive. |
| `max-open-connections`  | 4       | Maximum number of open database connections.                                                                                |
| `max-idle-connections`  | 4       | Maximum number of idle database connections kept in the pool.                                                                                |
| `max-idle-time-minutes` | 10      | Maximum time in minutes a connection can sit idle before being closed.                                                                                |
| `busy-timeout-ms`       | 60000   | SQLite busy timeout in milliseconds. When a write is blocked by another writer, SQLite retries internally with backoff for up to this duration before returning `SQLITE_BUSY`.          |

### Sizing Guidelines

SQLite's `cache_size` is a **per-connection** setting — each connection
maintains its own independent page cache. With multiple connections, the total
memory available for caching is the sum across all connections.

In practice, different connections tend to cache **different pages** (e.g., one
handles a job listing query while another runs a statistics aggregation), so
their caches naturally spread across the database. The formula
`DB_size / max-open-connections` gives enough per-connection cache that the
combined caches can cover the entire database.

However, this is a best-case estimate. Connections running similar queries will
cache the same pages redundantly. In the worst case (all connections caching
identical pages), only `cache-size-mb` worth of unique data is cached rather
than `cache-size-mb × max-open-connections`. For workloads with diverse
concurrent queries, cache overlap is typically low.

**Rules of thumb:**

- **cache-size-mb**: Set to `DB_size_in_MB / max-open-connections` to allow the
  entire database to be cached in memory. For example, an 80GB database with 8
  connections needs at least 10240 MB (10GB) per connection. If your workload
  has many similar concurrent queries, consider setting it higher to account for
  cache overlap between connections.

- **soft-heap-limit-mb**: Should be >= `cache-size-mb × max-open-connections` to
  avoid cache thrashing. This is the total SQLite memory budget for the process.
- On small installations the defaults work well. On servers with large databases
  (tens of GB) and plenty of RAM, increasing these values significantly improves
  query performance by reducing disk I/O.

### Example: Large Server (512GB RAM, 80GB database)

```json
{
  "main": {
    "db-config": {
      "cache-size-mb": 16384,
      "soft-heap-limit-mb": 131072,
      "max-open-connections": 8,
      "max-idle-time-minutes": 30
    }
  }
}
```

This allows the entire 80GB database to be cached (8 × 16GB = 128GB page cache)
with a 128GB soft heap limit, using about 25% of available RAM.

The effective configuration is logged at startup for verification.

## Project file structure

- [`.github/`](https://github.com/ClusterCockpit/cc-backend/tree/master/.github)
  GitHub Actions workflows and dependabot configuration for CI/CD.
- [`api/`](https://github.com/ClusterCockpit/cc-backend/tree/master/api)
  contains the API schema files for the REST and GraphQL APIs. The REST API is
  documented in the OpenAPI 3.0 format in
  [./api/swagger.yaml](./api/swagger.yaml). The GraphQL schema is in
  [./api/schema.graphqls](./api/schema.graphqls).
- [`cmd/cc-backend`](https://github.com/ClusterCockpit/cc-backend/tree/master/cmd/cc-backend)
  contains the main application entry point and CLI implementation.
- [`configs/`](https://github.com/ClusterCockpit/cc-backend/tree/master/configs)
  contains documentation about configuration and command line options and required
  environment variables. Sample configuration files are provided.
- [`init/`](https://github.com/ClusterCockpit/cc-backend/tree/master/init)
  contains an example of setting up systemd for production use.
- [`internal/`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal)
  contains library source code that is not intended for use by others.
  - [`api`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/api)
    REST API handlers and NATS integration
  - [`archiver`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/archiver)
    Job archiving functionality
  - [`auth`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/auth)
    Authentication (local, LDAP, OIDC) and JWT token handling
  - [`config`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/config)
    Configuration management and validation
  - [`graph`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/graph)
    GraphQL schema and resolvers
  - [`importer`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/importer)
    Job data import and database initialization
  - [`metricdispatch`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/metricdispatch)
    Dispatches metric data loading to appropriate backends
  - [`repository`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/repository)
    Database repository layer for jobs and metadata
  - [`routerConfig`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/routerConfig)
    HTTP router configuration and middleware
  - [`tagger`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/tagger)
    Job classification and application detection
  - [`taskmanager`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/taskmanager)
    Background task management and scheduled jobs
  - [`metricstoreclient`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/metricstoreclient)
    Client for cc-metric-store queries
- [`pkg/`](https://github.com/ClusterCockpit/cc-backend/tree/master/pkg)
  contains Go packages that can be used by other projects.
  - [`archive`](https://github.com/ClusterCockpit/cc-backend/tree/master/pkg/archive)
    Job archive backend implementations (filesystem, S3, SQLite)
  - [`metricstore`](https://github.com/ClusterCockpit/cc-backend/tree/master/pkg/metricstore)
    In-memory metric data store with checkpointing and metric loading
- [`tools/`](https://github.com/ClusterCockpit/cc-backend/tree/master/tools)
  Additional command line helper tools.
  - [`archive-manager`](https://github.com/ClusterCockpit/cc-backend/tree/master/tools/archive-manager)
    Commands for getting infos about an existing job archive, importing jobs
    between archive backends, and converting archives between JSON and Parquet formats.
  - [`archive-migration`](https://github.com/ClusterCockpit/cc-backend/tree/master/tools/archive-migration)
    Tool for migrating job archives between formats.
  - [`convert-pem-pubkey`](https://github.com/ClusterCockpit/cc-backend/tree/master/tools/convert-pem-pubkey)
    Tool to convert external pubkey for use in `cc-backend`.
  - [`gen-keypair`](https://github.com/ClusterCockpit/cc-backend/tree/master/tools/gen-keypair)
    contains a small application to generate a compatible JWT keypair. You find
    documentation on how to use it
    [here](https://github.com/ClusterCockpit/cc-backend/blob/master/docs/JWT-Handling.md).
- [`web/`](https://github.com/ClusterCockpit/cc-backend/tree/master/web)
  Server-side templates and frontend-related files:
  - [`frontend`](https://github.com/ClusterCockpit/cc-backend/tree/master/web/frontend)
    Svelte components and static assets for the frontend UI
  - [`templates`](https://github.com/ClusterCockpit/cc-backend/tree/master/web/templates)
    Server-side Go templates, including monitoring views
- [`gqlgen.yml`](https://github.com/ClusterCockpit/cc-backend/blob/master/gqlgen.yml)
  Configures the behaviour and generation of
  [gqlgen](https://github.com/99designs/gqlgen).
- [`startDemo.sh`](https://github.com/ClusterCockpit/cc-backend/blob/master/startDemo.sh)
  is a shell script that sets up demo data, and builds and starts `cc-backend`.

+- `make fmt`: Format all Go source files using

+  [gofumpt](https://github.com/mvdan/gofumpt), a stricter superset of `gofmt`.

+  Requires `gofumpt` to be installed (see below).

+- `make lint`: Run [golangci-lint](https://golangci-lint.run/) with the project

+  configuration in `.golangci.yml`. Requires `golangci-lint` to be installed

+`gofumpt` and `golangci-lint` are not part of the Go module and must be

+# Linter (golangci-lint) — use the official install script to get a

+# pre-built binary; installing via `go install` is not supported upstream.

+curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin


Done. Added to the "How to build and run" section:

- Descriptions for `make fmt` and `make lint` alongside the existing targets
- A "Installing development tools" subsection with install commands for both `gofumpt` and `golangci-lint`, including the note that `golangci-lint` should be installed via the official install script rather than `go install` (which is explicitly not supported upstream).