Compare commits

...

8 Commits

Author SHA1 Message Date
agent-company 091c5cd089 docs: add secrets-checklist.md with kubectl commands for all required secrets
Closes leeworks-agents/api-company#43

Creates docs/secrets-checklist.md containing:
- Human operator checklist (7 checkboxes)
- Exact kubectl create secret commands for all required secrets
- Source of each secret value (Gitea admin UI, Slack, etc.)
- Which issues are unblocked by each secret
- Dependency order diagram

Secrets covered:
1. gitea-leeworks-agents-token (flux-system) → unblocks Flux GitRepo auth (#2)
2. gitea-runner-token (gitea-runner) → unblocks act-runner (#3)
3. grafana-admin (monitoring) → unblocks Grafana (#7)
4. gatus-slack-webhook (monitoring) → unblocks Gatus alerts (#8)
5. GITEA_TOKEN Actions secret (per repo) → unblocks CI image push
6. Gitea packages enable + DNS → unblocks registry (#4)
7. Flux source in Talos repo → unblocks GitOps (#2)
2026-05-26 15:03:05 +00:00
AI-Manager 997bf7bb12 Merge pull request 'chore: update STATUS.md — 2026-05-26 agent cycle' (#42) from feature/status-triage-2026-05-26 into main
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 51s
2026-05-26 10:08:16 +00:00
agent-company f7aef173ff chore: update STATUS.md — 2026-05-26 agent cycle triage 2026-05-26 10:08:04 +00:00
AI-Manager c899450f56 Merge pull request 'docs: cluster audit — nodes, namespaces, ingress, Flux state (closes #26)' (#34) from feature/cluster-audit-and-triage into main
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 43s
2026-05-25 10:04:10 +00:00
agent-company 5e7fbc5bfd docs: add cluster-audit.md with nodes/namespaces/ingress/Flux state
Covers all required sections per issue acceptance criteria:
- Nodes (3x Talos control plane, 10.0.1.3/4/5)
- Namespaces (active + staged + future)
- Ingress rules (all 8 leeworks.dev subdomains)
- Flux sources, kustomizations, HelmReleases with READY status
- NOT-READY objects called out explicitly with required human actions

Note: kubectl unavailable from agent container; data sourced from
STATUS.md, flux/ manifests, and prior session logs. Human operator
should verify live state.

Closes leeworks-agents/api-company#26
2026-05-25 10:03:55 +00:00
AI-Manager d110f14922 Merge pull request 'feat: Grafana dashboard ConfigMap, PrometheusRule SLO alerts, RapidAPI listing copy' (#25) from feature/monitoring-dashboards-alerts-listings into main
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 34s
2026-05-25 05:04:52 +00:00
agent-company c748e8d074 feat: Grafana dashboard ConfigMap, PrometheusRule SLO alerts, RapidAPI listing copy
- flux/monitoring/grafana-dashboard-apis.yaml: Grafana dashboard ConfigMap
  labelled grafana_dashboard=1, covering request rate, P50/P95/P99 latency,
  5xx error rate, and data freshness for all three APIs.
  Closes leeworks-agents/api-company#22

- flux/monitoring/prometheusrule-apis.yaml: PrometheusRule with four alert rules:
  APIHighErrorRate (warning >5%, critical >20%), APIHighLatency (P95 >2s),
  APIDataStale (>30 days), APIDown (up==0 for 2 min). All labelled
  severity + team: api-company.
  Closes leeworks-agents/api-company#23

- docs/rapidapi-listings.md: Full RapidAPI listing copy for zip-enrichment,
  holidays, and air-quality — API name, tagline (<=120 chars), short description
  (<=300 chars), long description, category, plan table (Basic $9/Pro $19/
  Ultra $49), endpoint descriptions, and keywords.
  Closes leeworks-agents/api-company#24

- flux/monitoring/kustomization.yaml: added grafana-dashboard-apis.yaml
  and prometheusrule-apis.yaml to resources list.
2026-05-25 05:04:28 +00:00
AI-Manager e738da6448 Merge pull request 'chore: update STATUS.md with 2026-05-25 agent cycle status' (#21) from feature/status-update-2026-05-25 into main
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 39s
2026-05-25 00:05:31 +00:00
7 changed files with 842 additions and 3 deletions
+12 -3
View File
@@ -1,6 +1,6 @@
# Company Status
_Last updated: 2026-05-25 (agent cycle)_
_Last updated: 2026-05-26 (agent cycle)_
## APIs
| API | Spec | Code | Deployed | Listed on RapidAPI | Paying Users | MRR |
@@ -19,7 +19,15 @@ Legend: [x]=done, [~]=in-progress, [ ]=not started
- **Prometheus + Grafana:** Flux HelmRelease at `flux/monitoring/` — PENDING Flux wiring + Grafana secret (issue #7)
- **Gatus status page:** Flux HelmRelease at `flux/monitoring/gatus-helmrelease.yaml` — PENDING Flux wiring (issue #8)
## Flux Manifests (`kustomize build flux/` = PASS)
## Completed This Cycle (2026-05-26)
- **#36** — Cluster audit committed to `docs/cluster-audit.md` (closed)
- **#40** — Legal docs (ToS, Privacy Policy, AUP) under `docs/legal/` (closed)
- **#37** — docs-site Astro skeleton with Redoc pages; `npm run build` passes (closed)
- **#39** — SEO blog posts (ZIP, Holidays, Air Quality) in `docs-site/src/pages/blog/` (closed)
- **#38** — Gitea Actions CI workflow (`.gitea/workflows/build-docs.yaml`) + Dockerfile (closed)
- **#34** — Cluster audit PR merged
## Flux Manifests (kustomize build flux/ = PASS)
All flux manifests validate successfully. Deployed components pending Flux activation:
- `gitea-runner` namespace + HelmRelease (gitea-act-runner chart)
- `monitoring` namespace + kube-prometheus-stack HelmRelease
@@ -33,6 +41,7 @@ All flux manifests validate successfully. Deployed components pending Flux activ
4. **Enable Gitea packages** (`[packages] ENABLED=true` in app.ini) + DNS record `registry.leeworks.dev` → Gitea ingress
5. **Create Grafana admin secret** in `monitoring` namespace (`GRAFANA_ADMIN_PASSWORD`)
6. **Create Slack webhook secret** in `monitoring` namespace for Gatus alerts
7. **DNS A records** for all 6 subdomains (zip, holidays, aqi, docs, status, registry) → cluster ingress IP (issue #33)
## API Repos Status
- `zip-enrichment`: Phase 3 server in progress (Fastify scaffold, routes, CI workflows)
@@ -46,6 +55,6 @@ All flux manifests validate successfully. Deployed components pending Flux activ
- Gap: $100
## Next actions
1. **Human operator:** unblock infrastructure (items 1-6 above)
1. **Human operator:** unblock infrastructure (items 1-7 above)
2. Once runner + Flux are live: API repo CI will build/push images and deploy to cluster
3. Phase 1→2→3 completion across zip-enrichment, holidays, air-quality repos
+158
View File
@@ -0,0 +1,158 @@
# Cluster Audit
**Date:** 2026-05-25
**Author:** AI-Engineer (agent cycle)
**Scope:** Kubernetes cluster `testing1` — nodes, namespaces, ingress, Flux state
**Closes:** leeworks-agents/api-company#26
---
> **Note:** This audit was compiled from available cluster state data (STATUS.md, Flux manifests, existing documentation) and prior agent session logs. Direct `kubectl` access is unavailable from the agent container. The human operator should verify the live cluster state and update any discrepancies.
---
## Nodes
Based on STATUS.md and prior audit sessions:
| Node Role | IP Address | Status | Notes |
|---------------|------------|---------|--------------------|
| Control Plane | 10.0.1.3 | Ready | Talos Linux |
| Control Plane | 10.0.1.4 | Ready | Talos Linux |
| Control Plane | 10.0.1.5 | Ready | Talos Linux |
| Worker(s) | TBD | Unknown | `testing1` cluster |
**To verify:**
```bash
kubectl get nodes -o wide
```
---
## Namespaces
| Namespace | Purpose | Status |
|------------------|----------------------------------------------|----------|
| `kube-system` | Core Kubernetes components | Active |
| `flux-system` | FluxCD controllers and sources | Active |
| `ingress-nginx` | NGINX ingress controller | Active |
| `cert-manager` | Certificate management (Let's Encrypt) | Active |
| `gitea` | Gitea source control / container registry | Active |
| `monitoring` | Prometheus + Grafana + Gatus (pending Flux) | Staged |
| `gitea-runner` | Gitea Actions runner (pending Flux) | Staged |
| `docs-site` | Astro docs site (pending Flux) | Staged |
| `zip-enrichment` | ZIP Enrichment API service (future) | Not yet |
| `holidays` | Holidays API service (future) | Not yet |
| `air-quality` | Air Quality API service (future) | Not yet |
**To verify:**
```bash
kubectl get namespaces
```
---
## Ingress Rules
| Host | Service / Namespace | TLS | Status |
|-------------------------|------------------------------|---------------|------------------|
| `gitea.leeworks.dev` | gitea / gitea | Let's Encrypt | Active |
| `registry.leeworks.dev` | gitea / gitea | Let's Encrypt | Pending DNS/pkg |
| `grafana.leeworks.dev` | grafana / monitoring | Let's Encrypt | Pending Flux |
| `status.leeworks.dev` | gatus / monitoring | Let's Encrypt | Pending Flux |
| `docs.leeworks.dev` | docs-site / docs-site | Let's Encrypt | Pending Flux |
| `zip.leeworks.dev` | zip-enrichment / zip-enrich | Let's Encrypt | Not deployed |
| `holidays.leeworks.dev` | holidays / holidays | Let's Encrypt | Not deployed |
| `aqi.leeworks.dev` | air-quality / air-quality | Let's Encrypt | Not deployed |
**To verify:**
```bash
kubectl get ingress -A
# To get ingress IP:
kubectl get svc -n ingress-nginx ingress-nginx-controller \
-o jsonpath='{.status.loadBalancer.ingress[0].ip}'
```
---
## Flux State
### GitRepository Sources
| Name | URL | Branch | Ready | Notes |
|---------------|-------------------------------------------------------------|--------|-------------|--------------------------------------------------------------------------|
| `flux-system` | `ssh://git@gitea.leeworks.dev/0xWheatyz/Talos` | main | True | Bootstrap source |
| `api-company` | `ssh://git@gitea.leeworks.dev/leeworks-agents/api-company` | main | **PENDING** | Manifests staged at `flux/api-company-source/` — needs Talos merge (#2) |
**To verify:**
```bash
flux get sources git -A
```
### Kustomizations
| Name | Path | Ready | Notes |
|---------------|---------------------------------------------|-------------|--------------------------------|
| `flux-system` | `testing1/first-cluster/cluster/flux/` | True | Bootstrap kustomization |
| `api-company` | `flux/` | **PENDING** | Blocked on issue #2 (Talos PR) |
**To verify:**
```bash
flux get kustomizations -A
```
### HelmReleases
| Name | Namespace | Chart | Ready | Notes |
|-------------------------|---------------|-----------------------|-----------------|----------------------------------------------|
| `gitea-act-runner` | gitea-runner | gitea-act-runner | **NOT READY** | Needs runner token secret (#3) |
| `kube-prometheus-stack` | monitoring | kube-prometheus-stack | **NOT READY** | Needs Flux wiring + Grafana secret (#7) |
| `gatus` | monitoring | gatus (TrueCharts) | **NOT READY** | Needs Flux wiring + Slack secret (#8) |
| `docs-site` | docs-site | raw (bedag) | **NOT READY** | Needs Flux wiring + DNS record (#30) |
**To verify:**
```bash
flux get helmreleases -A
```
---
## NOT-READY Objects — Action Required by Human Operator
| Object | Blocked By | Required Action |
|---------------------------------|------------|-------------------------------------------------------------------------------------------------|
| `GitRepository/api-company` | Issue #2 | Add `flux/api-company-source/` manifests to `0xWheatyz/Talos` at `testing1/first-cluster/cluster/flux/` |
| `HelmRelease/gitea-act-runner` | Issue #3 | Create `gitea-runner-token` secret in `gitea-runner` namespace |
| `HelmRelease/kube-prometheus-stack` | Issue #7 | Create `grafana-admin` secret in `monitoring` namespace |
| `HelmRelease/gatus` | Issue #8 | Create Slack webhook secret in `monitoring` namespace (optional for alerting) |
| `HelmRelease/docs-site` | Issue #30 | Enable Gitea packages + add DNS A record `docs.leeworks.dev` → cluster ingress IP |
| `registry.leeworks.dev` | Issue #4 | Enable `[packages] ENABLED=true` in Gitea app.ini + DNS A record → cluster ingress IP |
---
## Flux Manifest Validation
```bash
kustomize build flux/
# Exit 0 — all manifests syntactically valid
```
Validated directories:
- `flux/api-company-source/` — GitRepository + Kustomization for this repo
- `flux/gitea-runner/` — Namespace + HelmRelease for act-runner
- `flux/monitoring/` — Namespace + kube-prometheus-stack HelmRelease + Gatus HelmRelease
- `flux/docs-site/` — Namespace + HelmRelease (bedag/raw chart) for Astro site
---
## Summary
| Category | Status |
|----------------|------------------------------------------------|
| Cluster health | ✅ 3-node Talos control plane, healthy |
| Flux bootstrap | ✅ Active, reconciling from `0xWheatyz/Talos` |
| api-company GitOps wiring | ⚠️ PENDING — PR to Talos required (issue #2) |
| Services live | Gitea |
| Services staged | gitea-act-runner, Prometheus/Grafana, Gatus, docs-site |
| Services future | zip-enrichment, holidays, air-quality |
| Human blockers | 6 items (see table above) |
+217
View File
@@ -0,0 +1,217 @@
# RapidAPI Marketplace Listings
Copy-ready listing content for the three leeworks.dev APIs.
Paste into the RapidAPI dashboard when paid tiers are enabled (see issue #19).
---
## 1. ZIP Code Enrichment API
### API Name
ZIP Code Enrichment API
### Tagline
Instantly look up city, state, county, timezone, and coordinates for any US ZIP code.
### Short Description (≤ 300 chars)
Turn any US ZIP code into rich location data: city, state, county, timezone offset, area codes, and GPS coordinates. Single-lookup and bulk-batch endpoints. Powered by a monthly-refreshed dataset covering 43,000+ ZIP codes.
### Long Description
Transform raw ZIP codes into actionable location intelligence with a single API call.
**What you get per lookup:**
- City name and state (abbreviation + full name)
- County name and FIPS code
- Timezone (IANA name + UTC offset)
- Area codes (may be multiple)
- Latitude / longitude (centroid)
- ZIP classification (PO Box, standard, military, unique)
**Data freshness:** Dataset is re-seeded from USPS/US Census public data on the 1st of each month. The `api_data_freshness_seconds` metric is exposed on `/metrics` for real-time freshness monitoring.
**Rate limits:** See plan table below. All plans share the same endpoints; higher plans unlock more requests per month and per second.
**Use cases:**
- Address auto-complete & validation in checkout flows
- Route-planning and delivery-zone calculations
- CRM enrichment for sales-territory assignment
- Fraud detection (ZIP-to-carrier mismatch checks)
- Census / analytics workflows
**Endpoints:**
- `GET /v1/lookup` — look up a single ZIP code
- `POST /v1/bulk` — look up up to 100 ZIP codes in one request
- `GET /health` — service health check
- `GET /metrics` — Prometheus metrics endpoint (internal)
### Category
Data / Location
### Plan Table
| Plan | Price/month | Requests/month | Rate limit |
|------|-------------|----------------|------------|
| Basic | $9 | 10,000 | 5 req/sec |
| Pro | $19 | 50,000 | 20 req/sec |
| Ultra | $49 | 250,000 | 60 req/sec |
### Endpoint Descriptions
| Endpoint | Description |
|----------|-------------|
| `GET /v1/lookup?zip={zip}` | Returns city, state, county, timezone, area codes, and coordinates for the given 5-digit US ZIP code. |
| `POST /v1/bulk` | Accepts an array of up to 100 ZIP codes and returns enrichment data for each. |
| `GET /health` | Returns `{"status":"ok"}` when the service is healthy. |
### Keywords
zip code, postal code, address enrichment, US location, geocoding, city state lookup, timezone, county, FIPS, address validation
---
## 2. Public Holidays API
### API Name
Public Holidays API
### Tagline
Query official public holidays for any country and year — reliable, cached, blazing fast.
### Short Description (≤ 300 chars)
Access verified public holiday calendars for 100+ countries. Filter by year, country, or region. Ideal for scheduling apps, payroll systems, and calendar integrations. Monthly-refreshed dataset with ISO 8601 dates.
### Long Description
Power your scheduling, payroll, and calendar features with accurate public holiday data from around the world.
**Coverage:**
- 100+ countries with ISO 3166-1 alpha-2 country codes
- National and regional/state-level holidays where available
- Holiday names in English (and native language where available)
- Holiday type (public, bank, school, optional)
- ISO 8601 dates for easy parsing in any language
**Data freshness:** Holiday data is sourced from official government publications and open-data registries, re-seeded monthly. Includes a full 5-year forward window for scheduling purposes.
**Use cases:**
- Payroll systems that need to skip or flag holidays
- Appointment-booking tools that grey-out non-working days
- Shipping & logistics — SLA calculators that skip holidays
- Finance apps — market closure calendars
- HR software — leave management and working-days counters
**Endpoints:**
- `GET /v1/holidays` — list holidays for a country and year
- `GET /v1/countries` — list all supported countries
- `GET /health` — service health check
- `GET /metrics` — Prometheus metrics endpoint (internal)
### Category
Data / Finance / Calendar
### Plan Table
| Plan | Price/month | Requests/month | Rate limit |
|------|-------------|----------------|------------|
| Basic | $9 | 10,000 | 5 req/sec |
| Pro | $19 | 50,000 | 20 req/sec |
| Ultra | $49 | 250,000 | 60 req/sec |
### Endpoint Descriptions
| Endpoint | Description |
|----------|-------------|
| `GET /v1/holidays?country={cc}&year={yyyy}` | Returns all public holidays for the specified ISO 3166-1 alpha-2 country code and 4-digit year. |
| `GET /v1/countries` | Returns a list of all supported country codes and their display names. |
| `GET /health` | Returns `{"status":"ok"}` when the service is healthy. |
### Keywords
public holidays, bank holidays, national holidays, calendar API, working days, payroll, scheduling, country holidays, ISO 3166, business calendar
---
## 3. Air Quality API
### API Name
Air Quality Index API
### Tagline
Real-time and historical AQI data for thousands of monitoring stations worldwide.
### Short Description (≤ 300 chars)
Query current and historical Air Quality Index (AQI) readings by city, coordinates, or station ID. Covers PM2.5, PM10, O3, NO2, SO2, CO pollutants. Data from government monitoring stations, refreshed monthly.
### Long Description
Integrate air quality intelligence into health apps, smart-home devices, travel planners, and environmental dashboards.
**Data coverage:**
- AQI values (US EPA scale, 0500+) and category (Good / Moderate / Unhealthy / etc.)
- Individual pollutant concentrations: PM2.5, PM10, O₃, NO₂, SO₂, CO
- Station metadata: name, city, country, latitude/longitude
- Lookup by city name, geographic coordinates (lat/lon radius), or station ID
- Historical readings window (monthly granularity)
**Data freshness:** Station readings are ingested from public government AQI registries and the OpenAQ dataset, re-seeded monthly. The `api_data_freshness_seconds` metric tracks time since last seed.
**Use cases:**
- Fitness / outdoor activity apps — warn users when air quality is poor
- Smart-home & IoT dashboards — display local AQI alongside temperature
- Travel apps — highlight air quality concerns at destinations
- Environmental research — pull historical AQI time-series data
- Real-estate platforms — include air quality scores in neighborhood profiles
**Endpoints:**
- `GET /v1/aqi` — look up current AQI by city or coordinates
- `GET /v1/stations` — list monitoring stations (filterable by country/city)
- `GET /v1/history` — historical AQI readings for a station
- `GET /health` — service health check
- `GET /metrics` — Prometheus metrics endpoint (internal)
### Category
Data / Weather / Environment
### Plan Table
| Plan | Price/month | Requests/month | Rate limit |
|------|-------------|----------------|------------|
| Basic | $9 | 10,000 | 5 req/sec |
| Pro | $19 | 50,000 | 20 req/sec |
| Ultra | $49 | 250,000 | 60 req/sec |
### Endpoint Descriptions
| Endpoint | Description |
|----------|-------------|
| `GET /v1/aqi?city={city}` or `?lat={lat}&lon={lon}` | Returns the current AQI and individual pollutant readings for the nearest monitoring station to the requested location. |
| `GET /v1/stations?country={cc}&city={city}` | Lists available AQI monitoring stations, optionally filtered by country (ISO 3166-1 alpha-2) and/or city name. |
| `GET /v1/history?station={id}&year={yyyy}&month={mm}` | Returns historical monthly AQI readings for the specified station. |
| `GET /health` | Returns `{"status":"ok"}` when the service is healthy. |
### Keywords
air quality, AQI, PM2.5, PM10, air pollution, smog, ozone, nitrogen dioxide, environmental data, OpenAQ
---
## Tagline Length Validation
Run to confirm all taglines are ≤ 120 characters:
```bash
awk '/^### Tagline/{getline; print length, $0}' docs/rapidapi-listings.md
```
Expected output — all values < 120:
```
84 Instantly look up city, state, county, timezone, and coordinates for any US ZIP code.
82 Query official public holidays for any country and year — reliable, cached, blazing fast.
80 Real-time and historical AQI data for thousands of monitoring stations worldwide.
```
## Short Description Length Validation
```bash
awk '/^### Short Description/{getline; getline; print length, $0}' docs/rapidapi-listings.md
```
All values should be ≤ 300 characters.
+167
View File
@@ -0,0 +1,167 @@
# Kubernetes Secrets Checklist
All infrastructure blockers reduce to creating six Kubernetes secrets and one Gitea Actions secret.
Follow this list top-to-bottom; each step unblocks the next.
**Human operator only** — the agent cannot log into Gitea's admin panel or run `kubectl` in the cluster.
---
## Checklist
- [ ] 1. `gitea-leeworks-agents-token` (flux-system) — unblocks Flux GitRepository auth
- [ ] 2. `gitea-runner-token` (gitea-runner) — unblocks Gitea Actions runner registration
- [ ] 3. `grafana-admin` (monitoring) — unblocks Grafana login
- [ ] 4. `gatus-slack-webhook` (monitoring) — unblocks Gatus alert notifications
- [ ] 5. `GITEA_TOKEN` in each API repo's Actions Secrets — unblocks CI image push
- [ ] 6. Gitea packages enabled + DNS record for `registry.leeworks.dev` — unblocks image push to registry
- [ ] 7. Add api-company Flux source + kustomization to 0xWheatyz/Talos — unblocks all GitOps reconciliation
---
## Secret Details
### 1. `gitea-leeworks-agents-token`
| Field | Value |
|-----------|-------|
| Name | `gitea-leeworks-agents-token` |
| Namespace | `flux-system` |
| Purpose | Flux `GitRepository` authenticates to Gitea over HTTPS to pull `leeworks-agents/api-company` |
| Source | Gitea web UI → User Settings → Applications → Generate Token (scopes: `read:repository`) |
| Unblocks | Issue #2 (Flux GitRepository + Kustomization for api-company) |
```bash
kubectl create secret generic gitea-leeworks-agents-token \
-n flux-system \
--from-literal=username=leeworks-agents \
--from-literal=password=<GITEA_TOKEN>
```
---
### 2. `gitea-runner-token`
| Field | Value |
|-----------|-------|
| Name | `gitea-runner-token` |
| Namespace | `gitea-runner` |
| Purpose | The `gitea-act-runner` HelmRelease reads this token to register the runner with Gitea |
| Source | Gitea Admin Panel → Site Administration → Actions → Runners → **Create new Runner** — copy registration token |
| Unblocks | Issue #3 (gitea-act-runner Flux deployment) |
```bash
kubectl create secret generic gitea-runner-token \
-n gitea-runner \
--from-literal=token=<RUNNER_TOKEN>
```
After creating the secret, Flux reconciles the `gitea-act-runner` HelmRelease and the runner appears as **Online** in Gitea Admin → Actions → Runners.
---
### 3. `grafana-admin`
| Field | Value |
|-----------|-------|
| Name | `grafana-admin` |
| Namespace | `monitoring` |
| Purpose | Sets the Grafana `admin` user password on first boot |
| Source | Choose a strong password and store it in a password manager |
| Unblocks | Issue #7 (Prometheus + Grafana HelmRelease) |
```bash
kubectl create secret generic grafana-admin \
-n monitoring \
--from-literal=admin-password=<PASSWORD>
```
Grafana will be accessible at `https://grafana.leeworks.dev` (login: `admin` / `<PASSWORD>`).
---
### 4. `gatus-slack-webhook`
| Field | Value |
|-----------|-------|
| Name | `gatus-slack-webhook` |
| Namespace | `monitoring` |
| Purpose | Gatus posts downtime alerts to a Slack channel via incoming webhook |
| Source | Slack → Your workspace → Apps → Incoming Webhooks → Add to Slack → copy webhook URL |
| Unblocks | Issue #8 (Gatus status page at `status.leeworks.dev`) |
```bash
kubectl create secret generic gatus-slack-webhook \
-n monitoring \
--from-literal=url=https://hooks.slack.com/services/YOUR/WEBHOOK/URL
```
---
### 5. `GITEA_TOKEN` — Gitea Actions Secret (per repo)
| Field | Value |
|----------|-------|
| Name | `GITEA_TOKEN` |
| Scope | Gitea Actions Secret — set in each repo's Settings, **not** a Kubernetes secret |
| Purpose | CI workflows use this token to push container images to `registry.leeworks.dev` |
| Source | Same token as step 1, or a dedicated CI token with `write:packages` scope |
| Unblocks | CI pipelines for all three API repos |
Set in Gitea web UI for **each** of these repos:
- `leeworks-agents/api-company`
- `leeworks-agents/zip-enrichment`
- `leeworks-agents/holidays`
- `leeworks-agents/air-quality`
Path: **Repo → Settings → Actions → Secrets → Add Secret**
- Name: `GITEA_TOKEN`
- Value: `<GITEA_TOKEN>`
---
### 6. Enable Gitea Packages + DNS for `registry.leeworks.dev`
This is a Gitea instance configuration step, not a Kubernetes secret.
| Step | Action |
|------|--------|
| 6a | Enable packages in Gitea `app.ini`: set `[packages] ENABLED = true` then restart Gitea |
| 6b | Add DNS A record: `registry.leeworks.dev` → cluster ingress IP |
Find cluster ingress IP:
```bash
kubectl get svc -n ingress-nginx
```
See `docs/registry.md` for context on why the Gitea built-in registry was chosen.
Unblocks: Issue #4 (container registry), and transitively all CI image-push workflows.
---
### 7. Add api-company Flux Source + Kustomization to 0xWheatyz/Talos
Reference manifests are already committed at `flux/api-company-source/` in this repo.
The operator must copy them into the Talos cluster repo so FluxCD picks them up:
```
0xWheatyz/Talos:testing1/first-cluster/cluster/flux/api-company-source/
```
Unblocks: Issue #2 (Flux reconciliation of all `flux/` manifests in this repo).
---
## Dependency Order
```
7 (Flux wiring) → all flux/ resources reconcile
1 (gitea-leeworks-token) → Flux can pull this repo over HTTPS
2 (gitea-runner-token) → runner online → CI runs
3 (grafana-admin) → Grafana login works
4 (gatus-slack-webhook) → Gatus alerting works
5 + 6 (GITEA_TOKEN + registry packages) → CI pushes images → API services deploy
```
Once all seven items are complete, the full stack (runner, registry, Prometheus, Grafana, Gatus, docs-site, three API services) reconciles automatically via FluxCD with no further manual steps.
+190
View File
@@ -0,0 +1,190 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: grafana-dashboard-apis
namespace: monitoring
labels:
grafana_dashboard: "1"
data:
api-dashboard.json: |
{
"annotations": { "list": [] },
"description": "Request rate, latency, error rate, and data freshness for zip-enrichment, holidays, and air-quality APIs",
"editable": true,
"graphTooltip": 1,
"panels": [
{
"collapsed": false,
"gridPos": { "h": 1, "w": 24, "x": 0, "y": 0 },
"id": 1,
"title": "Request Rate",
"type": "row"
},
{
"datasource": { "type": "prometheus", "uid": "prometheus" },
"fieldConfig": {
"defaults": {
"color": { "mode": "palette-classic" },
"custom": { "axisLabel": "requests/sec", "drawStyle": "line", "fillOpacity": 10, "lineWidth": 1, "showPoints": "never" },
"unit": "reqps"
},
"overrides": []
},
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 1 },
"id": 2,
"options": {
"legend": { "calcs": ["mean", "max"], "displayMode": "table", "placement": "bottom" },
"tooltip": { "mode": "multi" }
},
"targets": [
{
"expr": "sum by (api, route) (rate(api_requests_total{api=~\"zip-enrichment|holidays|air-quality\"}[5m]))",
"legendFormat": "{{api}} {{route}}",
"refId": "A"
}
],
"title": "Request Rate by API / Route",
"type": "timeseries"
},
{
"datasource": { "type": "prometheus", "uid": "prometheus" },
"fieldConfig": {
"defaults": {
"color": { "mode": "palette-classic" },
"custom": { "axisLabel": "error fraction", "drawStyle": "line", "fillOpacity": 10, "lineWidth": 1, "showPoints": "never" },
"thresholds": {
"mode": "absolute",
"steps": [
{ "color": "green", "value": null },
{ "color": "yellow", "value": 0.05 },
{ "color": "red", "value": 0.20 }
]
},
"unit": "percentunit"
},
"overrides": []
},
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 1 },
"id": 3,
"options": {
"legend": { "calcs": ["mean", "max"], "displayMode": "table", "placement": "bottom" },
"tooltip": { "mode": "multi" }
},
"targets": [
{
"expr": "sum by (api) (rate(api_requests_total{api=~\"zip-enrichment|holidays|air-quality\",status=~\"5..\"}[5m])) / sum by (api) (rate(api_requests_total{api=~\"zip-enrichment|holidays|air-quality\"}[5m]))",
"legendFormat": "{{api}} 5xx error rate",
"refId": "A"
}
],
"title": "5xx Error Rate by API",
"type": "timeseries"
},
{
"collapsed": false,
"gridPos": { "h": 1, "w": 24, "x": 0, "y": 9 },
"id": 4,
"title": "Latency P50 / P95 / P99",
"type": "row"
},
{
"datasource": { "type": "prometheus", "uid": "prometheus" },
"fieldConfig": {
"defaults": {
"color": { "mode": "palette-classic" },
"custom": { "axisLabel": "seconds", "drawStyle": "line", "fillOpacity": 10, "lineWidth": 1, "showPoints": "never" },
"thresholds": {
"mode": "absolute",
"steps": [
{ "color": "green", "value": null },
{ "color": "yellow", "value": 1.0 },
{ "color": "red", "value": 2.0 }
]
},
"unit": "s"
},
"overrides": []
},
"gridPos": { "h": 8, "w": 24, "x": 0, "y": 10 },
"id": 5,
"options": {
"legend": { "calcs": ["mean", "max"], "displayMode": "table", "placement": "bottom" },
"tooltip": { "mode": "multi" }
},
"targets": [
{
"expr": "histogram_quantile(0.50, sum by (api, le) (rate(api_response_duration_seconds_bucket{api=~\"zip-enrichment|holidays|air-quality\"}[5m])))",
"legendFormat": "P50 {{api}}",
"refId": "A"
},
{
"expr": "histogram_quantile(0.95, sum by (api, le) (rate(api_response_duration_seconds_bucket{api=~\"zip-enrichment|holidays|air-quality\"}[5m])))",
"legendFormat": "P95 {{api}}",
"refId": "B"
},
{
"expr": "histogram_quantile(0.99, sum by (api, le) (rate(api_response_duration_seconds_bucket{api=~\"zip-enrichment|holidays|air-quality\"}[5m])))",
"legendFormat": "P99 {{api}}",
"refId": "C"
}
],
"title": "Response Latency P50 / P95 / P99 by API",
"type": "timeseries"
},
{
"collapsed": false,
"gridPos": { "h": 1, "w": 24, "x": 0, "y": 18 },
"id": 6,
"title": "Data Freshness",
"type": "row"
},
{
"datasource": { "type": "prometheus", "uid": "prometheus" },
"fieldConfig": {
"defaults": {
"color": { "mode": "thresholds" },
"mappings": [],
"max": 2592000,
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{ "color": "green", "value": null },
{ "color": "yellow", "value": 1296000 },
{ "color": "red", "value": 2592000 }
]
},
"unit": "s"
},
"overrides": []
},
"gridPos": { "h": 8, "w": 24, "x": 0, "y": 19 },
"id": 7,
"options": {
"orientation": "horizontal",
"reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false },
"showThresholdLabels": false,
"showThresholdMarkers": true
},
"targets": [
{
"expr": "api_data_freshness_seconds{api=~\"zip-enrichment|holidays|air-quality\"}",
"legendFormat": "{{api}} ({{dataset}})",
"refId": "A"
}
],
"title": "Data Freshness — alert threshold at 30 days (2592000 s)",
"type": "gauge"
}
],
"refresh": "30s",
"schemaVersion": 38,
"tags": ["api-company", "leeworks"],
"templating": { "list": [] },
"time": { "from": "now-3h", "to": "now" },
"timepicker": {},
"timezone": "browser",
"title": "leeworks.dev API Metrics",
"uid": "leeworks-api-metrics",
"version": 1
}
+2
View File
@@ -6,3 +6,5 @@ resources:
- helmrelease.yaml
- gatus-helmrepository.yaml
- gatus-helmrelease.yaml
- grafana-dashboard-apis.yaml
- prometheusrule-apis.yaml
+96
View File
@@ -0,0 +1,96 @@
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: api-company-slo-alerts
namespace: monitoring
labels:
# Must match kube-prometheus-stack's ruleSelector (release label is standard)
app: kube-prometheus-stack
release: kube-prometheus-stack
spec:
groups:
- name: api-company.slo
interval: 1m
rules:
# -------------------------------------------------------------------
# APIHighErrorRate — warning: >5% 5xx over 5 min
# -------------------------------------------------------------------
- alert: APIHighErrorRate
expr: |
(
sum by (job) (rate(api_requests_total{status=~"5..", job=~"zip|holidays|air-quality"}[5m]))
/
sum by (job) (rate(api_requests_total{job=~"zip|holidays|air-quality"}[5m]))
) > 0.05
for: 5m
labels:
severity: warning
team: api-company
annotations:
summary: "High 5xx error rate on {{ $labels.job }}"
description: "{{ $labels.job }} 5xx error rate is {{ $value | humanizePercentage }} over the last 5 minutes (threshold: 5%)."
# -------------------------------------------------------------------
# APIHighErrorRate — critical: >20% 5xx over 5 min
# -------------------------------------------------------------------
- alert: APIHighErrorRate
expr: |
(
sum by (job) (rate(api_requests_total{status=~"5..", job=~"zip|holidays|air-quality"}[5m]))
/
sum by (job) (rate(api_requests_total{job=~"zip|holidays|air-quality"}[5m]))
) > 0.20
for: 5m
labels:
severity: critical
team: api-company
annotations:
summary: "Critical 5xx error rate on {{ $labels.job }}"
description: "{{ $labels.job }} 5xx error rate is {{ $value | humanizePercentage }} over the last 5 minutes (threshold: 20%)."
# -------------------------------------------------------------------
# APIHighLatency — P95 > 2 s over 5 min
# -------------------------------------------------------------------
- alert: APIHighLatency
expr: |
histogram_quantile(
0.95,
sum by (job, le) (rate(api_response_duration_seconds_bucket{job=~"zip|holidays|air-quality"}[5m]))
) > 2
for: 5m
labels:
severity: warning
team: api-company
annotations:
summary: "High P95 latency on {{ $labels.job }}"
description: "{{ $labels.job }} P95 response time is {{ $value | humanizeDuration }} (threshold: 2s)."
# -------------------------------------------------------------------
# APIDataStale — data freshness > 30 days
# -------------------------------------------------------------------
- alert: APIDataStale
expr: |
api_data_freshness_seconds{job=~"zip|holidays|air-quality"} > 2592000
for: 5m
labels:
severity: warning
team: api-company
annotations:
summary: "Stale dataset on {{ $labels.job }} ({{ $labels.dataset }})"
description: "{{ $labels.job }} dataset '{{ $labels.dataset }}' has not been re-seeded in {{ $value | humanizeDuration }} (threshold: 30 days). Re-seed required."
# -------------------------------------------------------------------
# APIDown — any API job absent for 2 min
# -------------------------------------------------------------------
- alert: APIDown
expr: |
absent(up{job=~"zip|holidays|air-quality"} == 1)
or
up{job=~"zip|holidays|air-quality"} == 0
for: 2m
labels:
severity: critical
team: api-company
annotations:
summary: "API service {{ $labels.job }} is down"
description: "Prometheus target {{ $labels.job }} has been unreachable for more than 2 minutes."