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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# seictl

Dual-purpose tool: CLI for Sei node operators and HTTP sidecar server for the sei-k8s-controller. Packaged as a single Go binary (`ghcr.io/sei-protocol/seictl`) distributed via GoReleaser (native binaries) and Docker (distroless).

## Architecture

- **CLI commands**: `config patch`, `genesis patch`, `patch`, `serve`, `await` (top-level files: `config.go`, `genesis.go`, `patch.go`, `serve.go`, `await.go`)
- **Sidecar server**: `sidecar/server/` — HTTP API on `127.0.0.1:7777`
- **Task engine**: `sidecar/engine/` — serial one-shot executor + cron scheduler
- **Task handlers**: `sidecar/tasks/` — snapshot, peers, genesis, config, state-sync, upload
- **Generated client**: `sidecar/client/` — OpenAPI-generated HTTP client for the sidecar API, consumed by sei-k8s-controller
- **OpenAPI spec**: `sidecar/api/openapi.yaml` — source of truth for the sidecar HTTP contract
- **Internal**: `internal/patch/` — TOML/JSON merge-patch logic

## Code Standards

### Go

- Write clear, self-documenting code. Prefer descriptive names over comments that restate what the code does.
- Comments should explain *why*, not *what*. Reserve them for non-obvious intent, trade-offs, constraints, or public API contracts. Do not use comments as section dividers, narration, or decoration.
- No unnecessary abstractions. Three similar lines are better than a premature helper.
- Functions should do one thing. If a function needs a comment explaining what each section does, it should be multiple functions.
- Keep functions short. A function that doesn't fit on one screen is usually doing too much.
- Names are the best documentation: `sidecarImage(node)` needs no comment, `si(n)` needs a rewrite.
- Error messages should provide enough context to diagnose without a debugger.
- Imports grouped: stdlib, external, then `github.com/sei-protocol/seictl`.
- All code must pass `gofmt -s`. Run `make fmt` before committing.

### Testing

- Tests use `testing` from the standard library. No assertion frameworks unless already established.
- Table-driven tests for any function with more than two interesting input variations.
- Test names should describe the scenario, not the function: `TestValidateCron/empty_string` over `TestValidateCronEmpty`.
- Run `make test` before submitting changes.

### API Client (`sidecar/client/`)

- Generated from `sidecar/api/openapi.yaml` using oapi-codegen.
- Run `make generate` to regenerate after spec changes. Never hand-edit `sidecar.gen.go`.
- The high-level `SidecarClient` in `client.go` and typed task builders in `tasks.go` are hand-written wrappers over the generated code.
- Package name is `client`; downstream consumers alias it as `sidecar` by convention.

## Build & Run

```bash
make build # Build to ./build/seictl
make test # Run all tests
make lint # Check formatting
make fmt # Auto-format
make generate # Regenerate OpenAPI client
make clean # Remove build artifacts
```
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
BIN := seictl
BUILD_DIR := ./build

.PHONY: build install run test lint fmt clean
.PHONY: build install run test lint fmt generate clean

build:
go build -o $(BUILD_DIR)/$(BIN) .
Expand All @@ -21,5 +21,8 @@ lint:
fmt:
gofmt -s -w .

generate:
go generate ./...

clean:
rm -rf $(BUILD_DIR)
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ require (
github.com/aws/aws-sdk-go-v2/service/ec2 v1.293.0
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.3
github.com/google/uuid v1.6.0
github.com/leanovate/gopter v0.2.11
github.com/oapi-codegen/runtime v1.2.0
github.com/pelletier/go-toml/v2 v2.2.4
github.com/robfig/cron/v3 v3.0.1
github.com/urfave/cli/v3 v3.6.1
)

require (
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.6 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.19.11 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.19 // indirect
Expand Down
611 changes: 611 additions & 0 deletions go.sum

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions sidecar/api/codegen.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package: client
output: ../client/sidecar.gen.go
generate:
models: true
client: true
output-options:
skip-prune: true
3 changes: 3 additions & 0 deletions sidecar/api/generate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package api

//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@v2.4.1 -config codegen.yaml openapi.yaml
211 changes: 211 additions & 0 deletions sidecar/api/openapi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
openapi: 3.1.0
info:
title: sei-sidecar API
description: HTTP API for the sei-sidecar task executor.
version: 0.3.0
license:
name: Apache-2.0

servers:
- url: http://localhost:7777

paths:
/v0/healthz:
get:
operationId: healthz
summary: Liveness probe
description: Returns 200 after mark-ready has completed; 503 otherwise.
responses:
"200":
description: Sidecar is ready.
"503":
description: Sidecar is not yet ready.

/v0/status:
get:
operationId: getStatus
summary: Status snapshot
responses:
"200":
description: Current status.
content:
application/json:
schema:
$ref: "#/components/schemas/StatusResponse"

/v0/tasks:
post:
operationId: submitTask
summary: Submit a task
description: |
If `schedule` is provided, a recurring scheduled task is created and
201 is returned. Otherwise the task is submitted for immediate execution
and 202 is returned. Returns 409 if a one-shot task is already running.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/TaskRequest"
responses:
"201":
description: Scheduled task created.
content:
application/json:
schema:
$ref: "#/components/schemas/TaskSubmitResponse"
"202":
description: Task submitted for immediate execution.
content:
application/json:
schema:
$ref: "#/components/schemas/TaskSubmitResponse"
"400":
description: Invalid request.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"409":
description: A task is already running.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
get:
operationId: listTasks
summary: List recent task results
description: Returns the most recent completed task results and active scheduled tasks.
responses:
"200":
description: Task results.
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/TaskResult"

/v0/tasks/{id}:
get:
operationId: getTask
summary: Get a task result
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: Task result.
content:
application/json:
schema:
$ref: "#/components/schemas/TaskResult"
"404":
description: Task not found.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
delete:
operationId: deleteTask
summary: Remove a task
description: Removes a completed task result or stops and removes a scheduled task.
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
"204":
description: Task deleted.
"404":
description: Task not found.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"

components:
schemas:
TaskRequest:
type: object
required: [type]
properties:
type:
type: string
description: Task type identifier.
params:
type: object
additionalProperties: true
schedule:
$ref: "#/components/schemas/Schedule"

Schedule:
type: object
description: Defines when a task should recur. Currently only cron is supported; blockHeight is reserved for future use.
properties:
cron:
type: string
description: Cron expression for recurring execution.
blockHeight:
type: integer
format: int64
description: Reserved for future block-height-based scheduling.

StatusResponse:
type: object
required: [status]
properties:
status:
type: string
enum: [Initializing, Running, Ready]

TaskSubmitResponse:
type: object
required: [id]
properties:
id:
type: string
format: uuid
description: The assigned task UUID.

TaskResult:
type: object
required: [id, type, submittedAt]
properties:
id:
type: string
format: uuid
type:
type: string
description: Task type that was executed.
params:
type: object
additionalProperties: true
schedule:
$ref: "#/components/schemas/Schedule"
error:
type: string
description: Error message if the task failed.
submittedAt:
type: string
format: date-time
completedAt:
type: string
format: date-time
nextRunAt:
type: string
format: date-time

ErrorResponse:
type: object
required: [error]
properties:
error:
type: string
Loading
Loading