{"id":49236620,"url":"https://github.com/sergeyslonimsky/elara","last_synced_at":"2026-04-26T21:03:05.280Z","repository":{"id":352128645,"uuid":"1213951157","full_name":"sergeyslonimsky/elara","owner":"sergeyslonimsky","description":"Kube-native configuration management with a Web UI and an etcd-compatible gRPC API","archived":false,"fork":false,"pushed_at":"2026-04-18T03:08:04.000Z","size":1326,"stargazers_count":0,"open_issues_count":10,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-04-18T03:13:41.620Z","etag":null,"topics":["config-server","configuration-management","etcd","go","golang","grpc","helm-chart","kubernetes","self-hosted","web-ui"],"latest_commit_sha":null,"homepage":"https://sergeyslonimsky.github.io/elara","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sergeyslonimsky.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-18T00:07:30.000Z","updated_at":"2026-04-18T02:10:13.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/sergeyslonimsky/elara","commit_stats":null,"previous_names":["sergeyslonimsky/elara"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/sergeyslonimsky/elara","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sergeyslonimsky%2Felara","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sergeyslonimsky%2Felara/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sergeyslonimsky%2Felara/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sergeyslonimsky%2Felara/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sergeyslonimsky","download_url":"https://codeload.github.com/sergeyslonimsky/elara/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sergeyslonimsky%2Felara/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32230459,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-24T13:21:15.438Z","status":"ssl_error","status_checked_at":"2026-04-24T13:21:15.005Z","response_time":64,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["config-server","configuration-management","etcd","go","golang","grpc","helm-chart","kubernetes","self-hosted","web-ui"],"created_at":"2026-04-24T16:10:32.630Z","updated_at":"2026-04-24T16:10:33.236Z","avatar_url":"https://github.com/sergeyslonimsky.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./logo.svg\" alt=\"Elara\" width=\"128\" height=\"128\"/\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003eElara\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  Configuration management service with a Web UI, a ConnectRPC API, and an etcd-compatible gRPC API.\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/sergeyslonimsky/elara/actions/workflows/ci.yml\"\u003e\n    \u003cimg src=\"https://github.com/sergeyslonimsky/elara/actions/workflows/ci.yml/badge.svg\" alt=\"CI\"/\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/sergeyslonimsky/elara/actions/workflows/github-code-scanning/codeql\"\u003e\n    \u003cimg src=\"https://github.com/sergeyslonimsky/elara/actions/workflows/github-code-scanning/codeql/badge.svg\" alt=\"CodeQL\"/\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://sonarcloud.io/project/overview?id=sergeyslonimsky_elara\"\u003e\n    \u003cimg src=\"https://sonarcloud.io/api/project_badges/measure?project=sergeyslonimsky_elara\u0026metric=alert_status\" alt=\"Quality Gate\"/\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/sergeyslonimsky/elara/releases/latest\"\u003e\n    \u003cimg src=\"https://img.shields.io/github/v/release/sergeyslonimsky/elara\" alt=\"Latest Release\"/\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://pkg.go.dev/github.com/sergeyslonimsky/elara\"\u003e\n    \u003cimg src=\"https://img.shields.io/github/go-mod/go-version/sergeyslonimsky/elara\" alt=\"Go Version\"/\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://goreportcard.com/report/github.com/sergeyslonimsky/elara\"\u003e\n    \u003cimg src=\"https://goreportcard.com/badge/github.com/sergeyslonimsky/elara\" alt=\"Go Report Card\"/\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://pkg.go.dev/github.com/sergeyslonimsky/elara\"\u003e\n    \u003cimg src=\"https://pkg.go.dev/badge/github.com/sergeyslonimsky/elara.svg\" alt=\"Go Reference\"/\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/sergeyslonimsky/elara/pkgs/container/elara\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/ghcr.io-elara-blue?logo=docker\u0026logoColor=white\" alt=\"Container Image\"/\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://artifacthub.io/packages/helm/elara/elara\"\u003e\n    \u003cimg src=\"https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/elara\" alt=\"Artifact Hub\"/\u003e\n  \u003c/a\u003e\n  \u003ca href=\"LICENSE\"\u003e\n    \u003cimg src=\"https://img.shields.io/github/license/sergeyslonimsky/elara\" alt=\"License: MIT\"/\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\nElara stores, edits, and serves application configuration. Operators use the\nbuilt-in Web UI for CRUD; services consume values through the same API\nsurface as etcd (drop-in for any etcd v3 client) or through a typed\nConnectRPC client. A single bbolt file holds all state with ACID\ntransactions and global revision tracking.\n\n**Status:** early, pre-1.0. Single-instance bbolt today; raft-based HA and\npluggable storage backends (PostgreSQL, S3) are on the roadmap.\n\n![Elara dashboard](./assets/dashboard_main.png)\n\n## Features\n\n- **Web UI** for browsing, creating, editing, and deleting configs across namespaces.\n- **ConnectRPC API** (`elara.config.v1.ConfigService`, `elara.namespace.v1.NamespaceService`, …) — works from Go, TypeScript, Python, etc. with native clients.\n- **etcd-compatible gRPC API** on port 2379 (`KV`, `Watch`, `Maintenance`, `Cluster`) — connect with `etcdctl` or any etcd v3 SDK.\n- **Config history** — every version stored, retrievable by revision.\n- **Global revision counter** — monotonic, etcd-style semantics.\n- **Format-aware validation** for JSON and YAML; pass-through for everything else (ini, toml, plain text).\n- **Single bbolt file storage** — ACID transactions, no external DB required.\n- **Observability** — optional Prometheus `/metrics` and OTLP tracing.\n- **Kube-native Helm chart** with StatefulSet, ServiceMonitor, NetworkPolicy, JSON Schema validation, and a smoke test.\n\n## Quick start\n\nRun locally with Docker:\n\n```bash\ndocker run --rm -p 8080:8080 -p 2379:2379 ghcr.io/sergeyslonimsky/elara:latest\n```\n\nOpen \u003chttp://localhost:8080\u003e for the UI. Talk to the etcd-compatible API at\n`localhost:2379`. Jump to [Deploying to Kubernetes](#deploying-to-kubernetes)\nfor the Helm path.\n\n## Usage\n\n### Web UI\n\nThe UI (served on the HTTP port, port 8080 by default) covers the full\noperator workflow:\n\n- **Dashboard** — cluster-wide KPIs (total namespaces, configs, active\n  clients, current global revision) plus the last 20 config changes and a\n  per-namespace config count.\n- **Configs** — directory-style browser across folders/files, per-namespace.\n  Create, edit (format-aware: JSON / YAML / raw), copy, delete, and view\n  version history. Every edit bumps the global revision.\n- **Namespaces** — CRUD for namespaces (logical grouping of configs).\n  Deletion is blocked while the namespace still has configs.\n- **Clients** — live list of connected etcd-compatible clients, with\n  recent events and basic history.\n\n### etcd-compatible CLI\n\nAny etcd v3 client works. Example with `etcdctl`:\n\n```bash\nexport ETCDCTL_API=3\nexport ETCDCTL_ENDPOINTS=localhost:2379\n\n# Write a config (namespace = prefix segment, path = key)\netcdctl put /prod/services/billing/config.yaml \"$(cat config.yaml)\"\n\n# Read it back\netcdctl get /prod/services/billing/config.yaml\n\n# Watch a prefix for live updates\netcdctl watch --prefix /prod/services/billing/\n\n# Check endpoint health\netcdctl endpoint health\n```\n\n### Locked configs and namespaces\n\nLock is an admin/ops concern, not a data concern. Configs and namespaces can each be locked\nindependently from the Web UI or the v2 connectrpc admin API; effective lock is true if either\nis locked. Locking protects against accidental writes — it does not affect the data itself.\n\nFor etcd clients the contract is intentionally narrow:\n\n- **Reads and watches always work.** Locked configs are returned by `Get`/`Range` and appear in\n  `Watch` like any other key. The lock state is not surfaced to etcd clients.\n- **Writes on locked targets fail with `FailedPrecondition`.** Both `Put` and `DeleteRange` return\n  the same uniform message regardless of cause:\n\n  ```text\n  Error: etcdserver: put: config \"/services/billing/config.yaml\" is locked\n  ```\n\n  etcd has no concept of namespace, so the message always reads as if the *config* is locked\n  even when the parent namespace is the actual cause.\n- **Watch streams do not carry lock/unlock events.** This is by design — the etcd channel is a\n  clean data plane. Subscribe to `WatchConfigs` on the v2 connectrpc API if you need to react\n  to lock state changes.\n- **Lock management** (lock/unlock, audit history) is available only via the Web UI and the v2\n  connectrpc admin API. There is no etcd-side knob to flip the lock.\n\nOperators can detect clients that keep retrying against locked targets via the\n`elara_writes_rejected_total{op,reason,namespace}` Prometheus counter, where `reason` is\n`config_locked` or `namespace_locked`.\n\n### ConnectRPC client (Go)\n\n```go\nimport (\n    \"connectrpc.com/connect\"\n    \"net/http\"\n\n    configv1 \"github.com/sergeyslonimsky/elara/gen/elara/config/v1\"\n    \"github.com/sergeyslonimsky/elara/gen/elara/config/v1/configv1connect\"\n)\n\nclient := configv1connect.NewConfigServiceClient(\n    http.DefaultClient,\n    \"http://localhost:8080\",\n)\n\nresp, _ := client.CreateConfig(ctx, connect.NewRequest(\u0026configv1.CreateConfigRequest{\n    Namespace: \"default\",\n    Path:      \"/services/billing/config.yaml\",\n    Content:   []byte(\"retries: 3\\n\"),\n}))\n```\n\n### ConnectRPC client (TypeScript)\n\n```ts\nimport { createClient } from \"@connectrpc/connect\";\nimport { createConnectTransport } from \"@connectrpc/connect-web\";\nimport { ConfigService } from \"./gen/elara/config/v1/config_service_pb\";\n\nconst client = createClient(\n  ConfigService,\n  createConnectTransport({ baseUrl: \"http://localhost:8080\" }),\n);\n\nawait client.createConfig({\n  namespace: \"default\",\n  path: \"/services/billing/config.yaml\",\n  content: new TextEncoder().encode(\"retries: 3\\n\"),\n});\n```\n\n## Deploying to Kubernetes\n\nThe chart lives at [`helm/elara/`](helm/elara/) and is designed to be\nproduction-ready by default: StatefulSet with `volumeClaimTemplates`,\nnon-root security context, JSON-Schema-validated values, optional\nServiceMonitor and NetworkPolicy, and a `helm test` smoke check.\n\n### Install from the Helm repository\n\nOnce the GitHub Pages repo is published:\n\n```bash\nhelm repo add elara https://sergeyslonimsky.github.io/elara\nhelm repo update\n\n# default: single replica, 2Gi RWO PVC, ClusterIP service\nhelm install elara elara/elara --namespace elara --create-namespace\n```\n\n### Install from a checkout\n\n```bash\nhelm install elara ./helm/elara --namespace elara --create-namespace\n```\n\n### Production values\n\n```yaml\n# values-prod.yaml\nimage:\n  digest: sha256:…              # pin by digest, not tag, in prod\n\nresources:\n  requests: { cpu: 250m, memory: 256Mi }\n  limits:   { cpu: \"2\",  memory: 1Gi   }\n\npersistence:\n  size: 50Gi\n  storageClassName: ssd\n\ningress:\n  enabled: true\n  className: nginx\n  hosts:\n    - host: elara.example.com\n      paths: [ { path: /, pathType: Prefix, port: http } ]\n  tls:\n    - secretName: elara-tls\n      hosts: [ elara.example.com ]\n\nmetrics:\n  enabled: true\n  serviceMonitor:\n    enabled: true\n    labels: { release: kube-prometheus-stack }\n\ntracing:\n  enabled: true\n  otlpEndpoint: http://otel-collector.observability:4318\n```\n\n```bash\nhelm install elara elara/elara -f values-prod.yaml \\\n  --namespace elara --create-namespace\n```\n\n### Upgrade\n\n```bash\nhelm upgrade elara elara/elara --namespace elara -f values-prod.yaml\n```\n\nPods restart automatically on ConfigMap changes (via checksum annotation).\n`helm.sh/resource-policy: keep` is NOT applied to the PVC, but because the\nchart uses `volumeClaimTemplates`, `helm uninstall` leaves the PVC in\nplace regardless — data survives uninstall.\n\n### Uninstall\n\n```bash\nhelm uninstall elara --namespace elara\n\n# Optional: drop the PVC too (destroys all stored configs)\nkubectl delete pvc data-elara-0 --namespace elara\n```\n\n### Exposing the etcd-compatible gRPC port\n\nThe chart Ingress exposes only the HTTP / ConnectRPC / UI port (8080).\nPort 2379 (etcd gRPC) is reachable cluster-internally over the ClusterIP\nservice by default. For external exposure, use `service.type: LoadBalancer`\nor add a gRPC-aware Ingress — see the [chart\nREADME](helm/elara/README.md#grpc-exposure).\n\n### Invariants\n\n- `replicaCount` is schema-pinned to `1` until raft-based HA is implemented.\n  bbolt holds an exclusive file lock — more than one replica corrupts data.\n  The schema will relax to `minimum: 1` when raft ships.\n- `persistence.accessMode` is pinned to `ReadWriteOnce` for the same reason.\n- `storage.type` currently accepts only `bbolt`; the enum will expand with\n  future storage backends.\n\nThe full values reference, extensibility hooks, and examples live in\n[`helm/elara/README.md`](helm/elara/README.md).\n\n## Local development\n\n```bash\nmake proto       # regenerate protobuf stubs\nmake test        # go test -race ./...\nmake lint        # golangci-lint\nmake format      # golines + gofumpt + gci\ngo run ./cmd/service\n```\n\nThe UI is served embedded from `web/dist`; for live reload during frontend\nwork run `cd web \u0026\u0026 npm run dev` and hit \u003chttp://localhost:3000\u003e.\n\n## Architecture\n\n```\nWeb UI (React)  ──┐\nConnectRPC client ┤──→  HTTP/2 server (:8080)  ──→  UseCases  ──→  Domain\netcdctl / grpc  ──────→  gRPC server  (:2379)  ──→  UseCases  ──→  Domain\n                                                                       │\n                                                           Adapter ────┘\n                                                           (bbolt)\n```\n\n- **Handler** — ConnectRPC / etcd gRPC; proto ↔ domain conversion.\n- **UseCase** — application logic; each usecase owns its minimal interface.\n- **Domain** — pure entities, validation, errors; no infrastructure imports.\n- **Adapter** — bbolt storage and in-memory watch pub/sub.\n\n## Configuration\n\nAll config keys flow through Viper; environment variables override every\nsource. See the\n[mapping table in the chart README](helm/elara/README.md#how-configuration-reaches-the-service)\nfor the full list.\n\nKey defaults:\n\n| Key                   | Env var                | Default          |\n| --------------------- | ---------------------- | ---------------- |\n| `http.frontend.port`  | `HTTP_FRONTEND_PORT`   | `8080`           |\n| `grpc.etcd.port`      | `GRPC_ETCD_PORT`       | `2379`           |\n| `config.data.path`    | `CONFIG_DATA_PATH`     | `./data`         |\n| `metrics.enabled`     | `METRICS_ENABLED`      | `false`          |\n| `tracing.enabled`     | `TRACING_ENABLED`      | `false`          |\n| `log.level`           | `LOG_LEVEL`            | `info`           |\n| `log.format`          | `LOG_FORMAT`           | `json`           |\n| `log.noSource`        | `LOG_NOSOURCE`         | `false`          |\n\n## Contributing\n\nPRs welcome. A few house rules:\n\n- Go: `golines` (120 cols), `gofumpt`, `gci` (stdlib → default → `github.com/sergeyslonimsky/elara` prefix).\n- Proto: `make proto` — `buf lint` and `buf breaking` run in CI.\n- Tests: `go test -race` must pass.\n- Keep changes focused; split unrelated refactors into separate PRs.\n\n## License\n\n[MIT](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsergeyslonimsky%2Felara","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsergeyslonimsky%2Felara","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsergeyslonimsky%2Felara/lists"}