{"id":43049113,"url":"https://github.com/neodatsu/itercraft","last_synced_at":"2026-02-08T19:16:38.028Z","repository":{"id":335275654,"uuid":"1144363699","full_name":"neodatsu/itercraft","owner":"neodatsu","description":"IterCraft - Learning by building  A full-stack automation platform built to explore modern cloud development practices. From local dev to production deployment, on a startup budget.","archived":false,"fork":false,"pushed_at":"2026-01-31T13:35:04.000Z","size":125,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-31T21:32:52.461Z","etag":null,"topics":["automation","cloud-native","devsecops","learning-project"],"latest_commit_sha":null,"homepage":"https://www.itercraft.com","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/neodatsu.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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-01-28T16:04:23.000Z","updated_at":"2026-01-31T13:32:30.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/neodatsu/itercraft","commit_stats":null,"previous_names":["neodatsu/itercraft"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/neodatsu/itercraft","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neodatsu%2Fitercraft","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neodatsu%2Fitercraft/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neodatsu%2Fitercraft/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neodatsu%2Fitercraft/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/neodatsu","download_url":"https://codeload.github.com/neodatsu/itercraft/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neodatsu%2Fitercraft/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29047822,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-03T14:55:20.264Z","status":"ssl_error","status_checked_at":"2026-02-03T14:55:19.725Z","response_time":96,"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":["automation","cloud-native","devsecops","learning-project"],"created_at":"2026-01-31T10:12:15.010Z","updated_at":"2026-02-08T19:16:38.022Z","avatar_url":"https://github.com/neodatsu.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Itercraft\n\n## Overview\n\nItercraft is a cloud-native web application deployed on AWS (eu-west-1), built with a Java/Spring Boot backend, a React/TypeScript frontend, and supported by a full DevSecOps pipeline.\n\n## Features\n\n- **Subscriptions** — Manage service subscriptions with usage tracking\n- **Entretien** — Track home maintenance activities with time logging and auto-stop\n- **Weather Activities** — AI-powered activity suggestions based on weather analysis (Claude + Météo France)\n- **Ma Ludothèque** — Personal board game collection with AI-powered game info and suggestions\n- **IoT Sensors** — MQTT-based sensor data collection (ESP32, TLS 1.3)\n- **Observability** — Full monitoring stack (Prometheus, Loki, Tempo, Grafana)\n- **Runtime Security** — Threat detection with Falco (eBPF syscall monitoring)\n\n## Architecture\n\n```mermaid\ngraph TB\n    subgraph CI/CD\n        GH[GitHub Actions]\n        GH --\u003e|build \u0026 test| MVN[Maven + JaCoCo]\n        GH --\u003e|build \u0026 type check| VITE[Vite + TypeScript]\n        GH --\u003e|coverage report| PR[Pull Request]\n        GH --\u003e|dependency check| OWASP[OWASP]\n        GH --\u003e|SBOM + vuln scan| TRIVY[Trivy + CycloneDX]\n        GH --\u003e|code quality| SONAR[SonarCloud]\n        GH --\u003e|accessibility| LH[Lighthouse CI]\n        GH --\u003e|tag v*| ECR_PUSH[Push images to ECR]\n        GH --\u003e|notify| SLACK[Slack]\n        SLACK --\u003e|/infra apply| GH\n        SLACK --\u003e|/infra destroy| GH\n    end\n\n    subgraph AWS\n        ECR[ECR\u003cbr/\u003e12 repos]\n        BUD[Budgets\u003cbr/\u003e10$ alert]\n        EC2[EC2 t3.large\u003cbr/\u003eUbuntu 22.04]\n        EIP[Elastic IP]\n        SG[Security Group\u003cbr/\u003eCloudflare IPs only]\n        IAM[IAM Role\u003cbr/\u003eECR ReadOnly + SSM]\n        S3[S3\u003cbr/\u003eTerraform state]\n        DDB[DynamoDB\u003cbr/\u003eState locking]\n        LAMBDA[Lambda\u003cbr/\u003eSlack bridge]\n        APIGW[API Gateway\u003cbr/\u003eSlack webhook]\n        EC2 --\u003e EIP\n        EC2 --\u003e SG\n        EC2 --\u003e IAM\n        IAM --\u003e|pull images| ECR\n        APIGW --\u003e LAMBDA\n        LAMBDA --\u003e|workflow_dispatch| GH\n    end\n\n    subgraph Cloudflare\n        CF[Cloudflare DNS\u003cbr/\u003eitercraft.com]\n        CF --\u003e|proxy HTTPS| EIP\n    end\n\n    subgraph \"Application (EC2 / docker-compose)\"\n        TRAEFIK[Traefik v3\u003cbr/\u003ereverse proxy\u003cbr/\u003e:80]\n        TRAEFIK --\u003e|www| FRONT[itercraft_front\u003cbr/\u003eReact / Vite / TypeScript]\n        TRAEFIK --\u003e|api| API[itercraft_api\u003cbr/\u003eSpring Boot 4 / Java 25]\n        TRAEFIK --\u003e|authent| KC[Keycloak 26\u003cbr/\u003eOAuth2/OIDC + PKCE\u003cbr/\u003e:8180]\n        TRAEFIK --\u003e|grafana| GRAF[Grafana\u003cbr/\u003e:3001]\n        API -.-|/healthcheck| API\n        FRONT -.-|/healthcheck| FRONT\n        FRONT --\u003e|auth| KC\n        FRONT --\u003e|API + CSRF| API\n        FRONT --\u003e|SSE /api/events| API\n        API --\u003e|JWT validation| KC\n        API --\u003e|JPA| PG[PostgreSQL 17\u003cbr/\u003e+ Liquibase\u003cbr/\u003e:5432]\n        API --\u003e|suggest activities| CLAUDE[Claude API\u003cbr/\u003eAnthropic]\n        API --\u003e|WMS GetMap| MF[Météo France\u003cbr/\u003eAROME PI]\n        PROM[Prometheus\u003cbr/\u003e:9090] --\u003e|scrape /actuator/prometheus| API\n        GRAF --\u003e|query| PROM\n        LOKI[Loki\u003cbr/\u003e:3100] --\u003e|store| LOGS[(Logs)]\n        PROMTAIL[Promtail] --\u003e|push logs| LOKI\n        PROMTAIL --\u003e|scrape| API\n        PROMTAIL --\u003e|scrape| KC\n        TEMPO[Tempo\u003cbr/\u003e:3200] --\u003e|store| TRACES[(Traces)]\n        API --\u003e|send traces| TEMPO\n        GRAF --\u003e|query logs| LOKI\n        GRAF --\u003e|query traces| TEMPO\n        MQTT[Mosquitto\u003cbr/\u003eMQTT Broker\u003cbr/\u003e:8883 TLS]\n        API --\u003e|subscribe sensors/#| MQTT\n    end\n\n    subgraph IoT\n        ESP32[ESP32\u003cbr/\u003eCapteurs]\n        ESP32 --\u003e|MQTTS 8883| MQTT\n    end\n\n    subgraph Infrastructure\n        TF[Terraform]\n        DOCKER[Docker]\n        TF --\u003e AWS\n        DOCKER --\u003e TRAEFIK\n    end\n```\n\n## Domain Model — Ludothèque\n\n```mermaid\nclassDiagram\n    direction LR\n\n    class Jeu {\n        UUID id\n        String nom\n        String description\n        Short joueursMin\n        Short joueursMax\n        Short dureeMoyenneMinutes\n        String imageUrl\n    }\n\n    class JeuUser {\n        UUID id\n        String userSub\n        Short note\n        LocalDateTime createdAt\n    }\n\n    class TypeJeu {\n        UUID id\n        String code\n        String libelle\n    }\n\n    class AgeJeu {\n        UUID id\n        String code\n        String libelle\n        Short ageMinimum\n    }\n\n    class ComplexiteJeu {\n        UUID id\n        Short niveau\n        String libelle\n    }\n\n    Jeu \"*\" -- \"1\" TypeJeu : type\n    Jeu \"*\" -- \"1\" AgeJeu : âge\n    Jeu \"*\" -- \"1\" ComplexiteJeu : complexité\n    JeuUser \"*\" -- \"1\" Jeu : jeu\n```\n\n**Reference data:**\n\n- **TypeJeu**: lettres_mots, strategie, enquete_escape, culture_quiz, ambiance, classique, reflexion, adresse\n- **AgeJeu**: enfant, tout_public, adulte\n- **ComplexiteJeu**: 1 (Très simple), 2 (Simple), 3 (Moyen), 4 (Complexe), 5 (Expert)\n\n## Domain Model — Maintenance Sessions\n\n```mermaid\nclassDiagram\n    direction LR\n\n    class MaintenanceSession {\n        UUID id\n        OffsetDateTime startedAt\n        OffsetDateTime endedAt\n        Integer durationMinutes\n        Boolean autoStopped\n    }\n\n    class AppUser {\n        UUID id\n        String keycloakSub\n    }\n\n    class ServiceEntity {\n        UUID id\n        String code\n        String label\n    }\n\n    MaintenanceSession \"*\" -- \"1\" AppUser : user\n    MaintenanceSession \"*\" -- \"1\" ServiceEntity : service\n```\n\n**Features:**\n\n- Time tracking with start/stop buttons\n- Automatic stop after 4 hours\n- Duration aggregation by day/week/month/year\n- SSE real-time updates\n\n## Domain Model — IoT Sensors\n\n```mermaid\nclassDiagram\n    direction LR\n\n    class SensorDevice {\n        UUID id\n        String name\n        OffsetDateTime createdAt\n    }\n\n    class SensorData {\n        UUID id\n        OffsetDateTime measuredAt\n        OffsetDateTime receivedAt\n        Double dhtTemperature\n        Double dhtHumidity\n        Double ntcTemperature\n        Double luminosity\n    }\n\n    class AppUser {\n        UUID id\n        String keycloakSub\n        String email\n    }\n\n    SensorDevice \"*\" -- \"1\" AppUser : user\n    SensorData \"*\" -- \"1\" SensorDevice : device\n```\n\n**Data flow:** ESP32 → MQTTS 8883 → Mosquitto → Spring Integration → `SensorDataService` → PostgreSQL → REST API → Dashboard (recharts)\n\n## Project Structure\n\n```\nitercraft/\n├── .github/workflows/     # CI/CD pipeline (backend + frontend)\n├── devsecops/\n│   ├── docker/\n│   │   ├── Dockerfile          # Backend (multi-stage, Java 25)\n│   │   ├── Dockerfile.front    # Frontend (multi-stage, Nginx)\n│   │   ├── Dockerfile.keycloak # Keycloak (multi-stage, realm import)\n│   │   ├── Dockerfile.postgres # PostgreSQL 17 + Liquibase\n│   │   ├── keycloak/\n│   │   │   └── itercraft-realm.json # Realm config (clients, user, roles)\n│   │   ├── prometheus/\n│   │   │   └── prometheus.yml       # Scrape config (itercraft-api)\n│   │   ├── grafana/\n│   │   │   ├── datasource.yml       # Prometheus, Loki, Tempo datasources\n│   │   │   └── dashboards/          # Pre-configured dashboards\n│   │   ├── loki/\n│   │   │   └── loki-config.yml      # Loki configuration (log storage)\n│   │   ├── promtail/\n│   │   │   └── promtail-config.yml  # Promtail configuration (log collection)\n│   │   ├── tempo/\n│   │   │   └── tempo-config.yml     # Tempo configuration (trace storage)\n│   │   ├── Dockerfile.prometheus    # Prometheus\n│   │   ├── Dockerfile.grafana       # Grafana (port 3001)\n│   │   ├── Dockerfile.loki          # Loki (log aggregation)\n│   │   ├── Dockerfile.promtail      # Promtail (log collector)\n│   │   ├── Dockerfile.tempo         # Tempo (distributed tracing)\n│   │   └── postgres/\n│   │       └── entrypoint-wrapper.sh # Postgres + Liquibase bootstrap\n│   ├── liquibase/             # Database migrations\n│   │   ├── db.changelog-master.yaml\n│   │   └── changelogs/        # 001-init-schema, 002-seed-services\n│   └── terraform/           # Infrastructure as Code\n│       ├── aws_budget/      # Cost alert (10$/month)\n│       ├── aws_ec2/         # EC2 + Elastic IP + SSM + Cloudflare DNS (Traefik + docker-compose)\n│       ├── aws_ecr/         # Container registries (12 repos)\n│       ├── aws_oidc_github/ # OIDC provider + IAM roles (GitHub Actions → ECR + Terraform)\n│       ├── aws_backend/     # S3 bucket + DynamoDB for Terraform state (remote backend)\n│       ├── aws_lambda_slack/# Lambda + API Gateway for Slack /infra command\n│       ├── env.sh           # Environment variables (not committed)\n│       └── tf.sh            # Terraform wrapper script\n├── iot/                     # IoT / MQTT\n│   └── mosquitto/           # MQTT broker configuration\n│       ├── Dockerfile           # Mosquitto 2.0 with TLS\n│       ├── mosquitto.conf       # Broker config (TLS 1.3, auth, ACL)\n│       ├── acl.conf             # Topic-based access control\n│       ├── docker-compose.yml   # Local development\n│       └── scripts/             # Certificate \u0026 user management\n│           ├── generate-certs.sh\n│           ├── add-user.sh\n│           └── generate-device-cert.sh\n├── itercraft_api/           # Backend API (:8080)\n│   └── src/\n│       ├── main/            # Domain-Driven Design architecture\n│       │   ├── domain/          # Entities, repositories, value objects\n│       │   ├── application/     # Services (interface + impl)\n│       │   └── infrastructure/  # REST controllers, security, DTOs\n│       └── test/            # Unit \u0026 integration tests (H2)\n└── itercraft_front/         # Frontend (:3000)\n    └── src/\n        ├── api/             # API client (subscriptions, CSRF)\n        ├── auth/            # Keycloak auth provider + protected route\n        ├── pages/           # Pages (home, dashboard, healthcheck)\n        ├── components/      # Reusable components (Header, Footer)\n        └── utils/           # Utilities\n```\n\n## Tech Stack\n\n| Layer          | Technology                                                                              |\n|----------------|-----------------------------------------------------------------------------------------|\n| Backend        | Java 25, Spring Boot 4.0.2, Spring Security                                             |\n| Frontend       | React, TypeScript, Vite                                                                 |\n| Database       | PostgreSQL 17, Liquibase (schema migrations)                                            |\n| Build          | Maven, JaCoCo, npm, Vitest                                                              |\n| Analytics      | Google Analytics (GA4, after consent only)                                              |\n| Accessibility  | Lighthouse CI (score ≥ 90 in CI)                                                        |\n| Security       | OWASP Dependency-Check, Trivy, SBOM (CycloneDX), SonarCloud, CSRF (cookie), Falco (runtime) |\n| Auth           | Keycloak 26 (OAuth2/OIDC, PKCE, JWT)                                                    |\n| AI / Vision    | Claude API, Anthropic (activity suggestions based on weather)                           |\n| Real-time      | Server-Sent Events (SSE, SseEmitter)                                                    |\n| IoT            | Mosquitto MQTT (TLS 1.3, password auth, ACL), ESP32                                     |\n| Monitoring     | Prometheus, Grafana, Loki (logs), Tempo (traces), Micrometer, Spring Boot Actuator      |\n| Resilience     | Resilience4j (Circuit Breaker, Retry, Time Limiter)                                     |\n| Infrastructure | Terraform, Docker, Nginx, Traefik                                                       |\n| Cloud          | AWS (ECR, EC2, Elastic IP, Budgets, SSM, S3, DynamoDB, Lambda, API Gateway), Cloudflare |\n| CI/CD          | GitHub Actions, Slack ChatOps (`/infra`)                                                |\n| Region         | eu-west-1 (Ireland)                                                                     |\n\n## Getting Started\n\n### Prerequisites\n\n- Java 25\n- Node.js 25\n- Maven 3.8+\n- Terraform 1.x\n- Docker\n- AWS CLI\n\n### Environment Variables\n\nThe backend is configured via environment variables (with defaults for local development):\n\n| Variable                 | Default                                    | Description                            |\n|--------------------------|--------------------------------------------|----------------------------------------|\n| `DB_HOST`                | `localhost`                                | PostgreSQL host                        |\n| `DB_PORT`                | `5432`                                     | PostgreSQL port                        |\n| `DB_NAME`                | `itercraft`                                | Database name                          |\n| `DB_USER`                | `itercraft`                                | Database user                          |\n| `DB_PASSWORD`            | `itercraft`                                | Database password                      |\n| `KEYCLOAK_INTERNAL_URL`  | `http://keycloak:8180`                     | Keycloak internal URL (for JWK fetch)  |\n| `KEYCLOAK_ISSUER_URI`    | `http://localhost:8180/realms/itercraft`   | Expected JWT issuer (external URL)     |\n| `KEYCLOAK_REALM`         | `itercraft`                                | Keycloak realm                         |\n| `CORS_ORIGINS`           | `http://localhost:3000`                    | Allowed CORS origins (comma-separated) |\n| `METEOFRANCE_API_TOKEN`  | `changeme`                                 | Météo France API key                   |\n| `ANTHROPIC_API_KEY`      | `changeme`                                 | Anthropic API key (Claude)             |\n| `ANTHROPIC_MODEL`        | `claude-sonnet-4-20250514`                 | Claude model for image analysis        |\n| `MQTT_HOST`              | `localhost`                                | MQTT broker host                       |\n| `MQTT_PORT`              | `8883`                                     | MQTT broker port (TLS)                 |\n| `MQTT_BACKEND_USER`      | `itercraft-backend`                        | MQTT service account username          |\n| `MQTT_BACKEND_PASSWORD`  | `changeme`                                 | MQTT service account password          |\n| `MQTT_TRUST_ALL_CERTS`   | `false`                                    | Trust self-signed certs (dev only)     |\n\nThe frontend uses a `.env` file:\n\n| Variable              | Default                  | Description           |\n|-----------------------|--------------------------|-----------------------|\n| `VITE_KEYCLOAK_URL`   | `http://localhost:8180`  | Keycloak base URL     |\n| `VITE_KEYCLOAK_REALM` | `itercraft`              | Keycloak realm        |\n| `VITE_API_URL`        | `http://localhost:8080`  | Backend API base URL  |\n\n### Run the API locally\n\nRequires a running PostgreSQL (with Liquibase migrations applied) and Keycloak instance.\n\n```bash\ncd itercraft_api\nmvn spring-boot:run\n```\n\nThe API is available at `http://localhost:8080/healthcheck`.\n\n### Run the frontend locally\n\n```bash\ncd itercraft_front\nnpm install\nnpm run dev\n```\n\nThe frontend is available at `http://localhost:3000/healthcheck`.\n\n### Run tests\n\n```bash\n# Backend (uses H2 in-memory database)\ncd itercraft_api\nmvn clean verify\n\n# Frontend\ncd itercraft_front\nnpx vitest run\n```\n\nBackend coverage report is generated in `itercraft_api/target/site/jacoco/index.html`.\n\n### Deploy infrastructure\n\n#### Required credentials\n\n1. **AWS**: Configure `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `AWS_MFA_SERIAL` in `devsecops/terraform/env.sh`\n2. **Cloudflare API Token**:\n   - Cloudflare Dashboard → My Profile → API Tokens → Create Token\n   - Template: **Edit zone DNS**\n   - Add permission: Zone → Zone Settings → Edit\n   - Zone Resources: Include → itercraft.com\n   - Copy the token to `TF_VAR_cloudflare_api_token` in `env.sh`\n3. **GitHub Secrets** (for CI/CD): `AWS_ACCOUNT_ID` in the `itercraft` environment secrets\n\n#### Terraform apply\n\n```bash\ncd devsecops/terraform\n# Configure env.sh with your credentials\n\n# 1. Budget (cost alert)\n./tf.sh aws_budget init \u0026\u0026 ./tf.sh aws_budget apply\n\n# 2. ECR (container registries)\n./tf.sh aws_ecr init \u0026\u0026 ./tf.sh aws_ecr apply\n\n# 3. OIDC GitHub (IAM role for CI/CD → ECR push, no AWS keys needed)\n./tf.sh aws_oidc_github init \u0026\u0026 ./tf.sh aws_oidc_github apply\n\n# 4. EC2 + Cloudflare DNS (Elastic IP + DNS records + SSL Flexible)\n./tf.sh aws_ec2 init \u0026\u0026 ./tf.sh aws_ec2 apply\n```\n\n### Deploy images (CI/CD)\n\nThe `deploy.yml` workflow is automatically triggered on `v*` tags:\n\n```bash\ngit tag v1.0.0\ngit push origin v1.0.0\n```\n\nGitHub Actions builds the 12 Docker images, tags them with the version + `latest`, and pushes them to ECR.\n\nAuthentication uses **OIDC** (OpenID Connect): GitHub assumes an IAM role directly with AWS, without access keys stored in secrets. The `aws_oidc_github` Terraform module creates the identity provider and the `github-actions-ecr-push` role restricted to `v*` tags of the repo.\n\nOnly required GitHub secret: `AWS_ACCOUNT_ID`.\n\n### Deploy infrastructure via Slack (ChatOps)\n\nThe EC2 stack can be deployed or destroyed directly from Slack using the `/infra` command:\n\n```text\n/infra apply ec2    # Terraform apply on aws_ec2\n/infra destroy ec2  # Terraform destroy on aws_ec2\n/infra plan ec2     # Terraform plan (dry-run)\n```\n\nThe workflow is triggered via GitHub Actions (`terraform.yml`) and notifies the result in Slack.\n\n#### Architecture ChatOps\n\n```text\nSlack /infra command\n       ↓\nAPI Gateway (HTTPS)\n       ↓\nLambda (Node.js) — verifies Slack signature, calls GitHub API\n       ↓\nGitHub Actions workflow_dispatch\n       ↓\nTerraform apply/destroy (OIDC → AWS)\n       ↓\nSlack notification (webhook)\n```\n\n#### Setup ChatOps\n\n1. **Backend S3 + DynamoDB** (remote state + locking):\n\n   ```bash\n   ./tf.sh aws_backend init\n   ./tf.sh aws_backend apply\n   ```\n\n   Creates the `itercraft-terraform-state` bucket and the `itercraft-terraform-locks` table.\n\n2. **Terraform OIDC Role**:\n\n   ```bash\n   ./tf.sh aws_oidc_github init\n   ./tf.sh aws_oidc_github apply\n   ```\n\n   Adds the `github-actions-terraform` role with EC2/S3/DynamoDB permissions.\n\n3. **GitHub Secrets** (Settings → Environments → `itercraft`):\n   - `AWS_TERRAFORM_ROLE_ARN`: ARN of the terraform role (output from aws_oidc_github)\n   - `SLACK_WEBHOOK_URL`: Slack Incoming Webhook for notifications\n\n4. **Slack App**:\n   - Create an app at \u003chttps://api.slack.com/apps\u003e\n   - Add a Slash Command `/infra`\n   - Note the **Signing Secret** (Basic Information → App Credentials)\n\n5. **Lambda Slack → GitHub**:\n\n   ```bash\n   ./tf.sh aws_lambda_slack init\n   ./tf.sh aws_lambda_slack apply\n   ```\n\n   Required variables (via `terraform.tfvars` or `-var`):\n   - `github_token`: Personal Access Token (classic) with `repo` scope\n   - `slack_signing_secret`: Signing Secret from the Slack App\n\n   Configure the `slack_webhook_url` output as the Request URL for the `/infra` slash command.\n\n6. **Initialize EC2 with S3 backend** (first time only):\n   ```bash\n   cd devsecops/terraform/aws_ec2\n   source ../env.sh\n   terraform init \\\n     -backend-config=\"bucket=itercraft-terraform-state\" \\\n     -backend-config=\"key=aws_ec2/terraform.tfstate\" \\\n     -backend-config=\"region=eu-west-1\" \\\n     -backend-config=\"dynamodb_table=itercraft-terraform-locks\" \\\n     -backend-config=\"encrypt=true\"\n   ```\n\nThen, use `/infra apply ec2` or `/infra destroy ec2` from Slack.\n\n### API Endpoints\n\n| Method   | URL                                       | Auth          | Description                |\n|----------|-------------------------------------------|---------------|----------------------------|\n| `GET`    | `/healthcheck`                            | Public        | Health status              |\n| `GET`    | `/actuator/health`                        | Public        | Actuator health            |\n| `GET`    | `/actuator/prometheus`                    | Public        | Prometheus metrics         |\n| `GET`    | `/api/events`                             | Public        | SSE stream (real-time)     |\n| `GET`    | `/api/resilience/status`                  | Public        | Circuit breaker metrics    |\n| `GET`    | `/api/resilience/health`                  | Public        | Resilience health status   |\n| `GET`    | `/api/subscriptions`                      | Bearer        | User subscriptions + usage |\n| `GET`    | `/api/services`                           | Bearer        | All available services     |\n| `POST`   | `/api/subscriptions/{serviceCode}`        | Bearer + CSRF | Subscribe to a service     |\n| `DELETE` | `/api/subscriptions/{serviceCode}`        | Bearer + CSRF | Unsubscribe                |\n| `POST`   | `/api/subscriptions/{serviceCode}/usages` | Bearer + CSRF | Add usage                  |\n| `DELETE` | `/api/subscriptions/{serviceCode}/usages` | Bearer + CSRF | Remove usage               |\n| `POST`   | `/api/activities/suggest`                 | Bearer + CSRF | AI activity suggestions (JSON) |\n| `GET`    | `/api/ludotheque/jeux`                    | Bearer        | Game catalog               |\n| `GET`    | `/api/ludotheque/mes-jeux`                | Bearer        | User's game collection     |\n| `GET`    | `/api/ludotheque/references`              | Bearer        | Reference tables           |\n| `POST`   | `/api/ludotheque/mes-jeux`                | Bearer + CSRF | Add game by title (AI)     |\n| `POST`   | `/api/ludotheque/mes-jeux/{jeuId}`        | Bearer + CSRF | Add game to collection     |\n| `DELETE` | `/api/ludotheque/mes-jeux/{jeuId}`        | Bearer + CSRF | Remove game                |\n| `PUT`    | `/api/ludotheque/mes-jeux/{jeuId}/note`   | Bearer + CSRF | Update rating (1-5 stars)  |\n| `POST`   | `/api/ludotheque/suggestion`              | Bearer + CSRF | AI game suggestion         |\n| `GET`    | `/api/maintenance/activities`                | Bearer        | Maintenance activities     |\n| `POST`   | `/api/maintenance/activities`                | Bearer + CSRF | Create new activity        |\n| `POST`   | `/api/maintenance/activities/{code}/start`   | Bearer + CSRF | Start activity timer       |\n| `POST`   | `/api/maintenance/activities/{code}/stop`    | Bearer + CSRF | Stop activity timer        |\n| `GET`    | `/api/maintenance/totals`                    | Bearer        | Aggregated time totals     |\n| `GET`    | `/api/maintenance/activities/{code}/history` | Bearer        | Activity session history   |\n| `DELETE` | `/api/maintenance/sessions/{id}`             | Bearer + CSRF | Delete a session           |\n| `GET`    | `/api/sensors/data?from=...\u0026to=...`          | Bearer        | Sensor data (default: 7 days) |\n\nMutation endpoints require an `X-XSRF-TOKEN` header matching the `XSRF-TOKEN` cookie.\n\n### Resilience Patterns (Circuit Breaker)\n\nThe application uses **Resilience4j** to protect against cascading failures when external services (Météo France, Claude) become slow or unavailable.\n\n#### Circuit Breaker States\n\n```text\nCLOSED → (failures \u003e threshold) → OPEN → (wait duration) → HALF_OPEN → (success) → CLOSED\n                                    ↑                           │\n                                    └───── (failure) ───────────┘\n```\n\n- **CLOSED**: Normal operation, requests pass through\n- **OPEN**: Circuit tripped, requests fail fast with fallback\n- **HALF_OPEN**: Testing recovery, limited requests allowed\n\n#### Configuration\n\n| Service       | Window | Failure Threshold | Wait Duration | Timeout |\n|---------------|--------|-------------------|---------------|---------|\n| Météo France  | 10     | 50%               | 30s           | 10s     |\n| Claude        | 5      | 50%               | 60s           | 60s     |\n\nMétéo France also has automatic retry (3 attempts with exponential backoff).\n\n#### Endpoints\n\n- `GET /api/resilience/status` - Detailed circuit breaker metrics\n- `GET /api/resilience/health` - Overall health (UP, DEGRADED, RECOVERING)\n- `GET /actuator/circuitbreakers` - Native Actuator endpoint\n\n#### Frontend Page\n\nA public page explaining the resilience patterns is available at `/resilience`, showing:\n\n- Circuit breaker concept diagrams\n- Real-time status of all circuit breakers\n- Configuration details\n\n### Build Docker images\n\n```bash\ndocker build -f devsecops/docker/Dockerfile.postgres  -t itercraft-postgres .\ndocker build -f devsecops/docker/Dockerfile            -t itercraft-api .\ndocker build -f devsecops/docker/Dockerfile.front      -t itercraft-front .\ndocker build -f devsecops/docker/Dockerfile.keycloak    -t itercraft-keycloak .\ndocker build -f devsecops/docker/Dockerfile.prometheus  -t itercraft-prometheus .\ndocker build -f devsecops/docker/Dockerfile.grafana     -t itercraft-grafana .\ndocker build -f devsecops/docker/Dockerfile.loki        -t itercraft-loki .\ndocker build -f devsecops/docker/Dockerfile.promtail    -t itercraft-promtail .\ndocker build -f devsecops/docker/Dockerfile.tempo       -t itercraft-tempo .\ndocker build -f devsecops/docker/Dockerfile.falco       -t itercraft-falco .\ndocker build -f devsecops/docker/Dockerfile.falcosidekick -t itercraft-falcosidekick .\n```\n\n### Run - Dev (containers on localhost)\n\nThe backend accesses other services via `localhost` + Docker port mapping.\n`KC_HOSTNAME=localhost` (defined in the Dockerfile) sets the Keycloak issuer to `http://localhost:8180`.\n\n```bash\n# PostgreSQL\ndocker run -p 5432:5432 itercraft-postgres\n\n# Keycloak\ndocker run -p 8180:8180 itercraft-keycloak\n\n# Backend (on host, no -e needed)\ncd itercraft_api \u0026\u0026 mvn spring-boot:run\n\n# Frontend\ncd itercraft_front \u0026\u0026 npm run dev\n```\n\n### Run - Dev (all in containers)\n\nContainers communicate via the Docker network.\nThe backend must know the Docker IP addresses or hostnames of other services.\n\n```bash\n# Create a network\ndocker network create itercraft\n\n# PostgreSQL\ndocker run --network itercraft --name postgres -p 5432:5432 itercraft-postgres\n\n# Keycloak (KC_HOSTNAME=localhost so the issuer matches the browser)\ndocker run --network itercraft --name keycloak -p 8180:8180 itercraft-keycloak\n\n# Backend (DB_HOST and KEYCLOAK_INTERNAL_URL point to Docker hostnames)\ndocker run --network itercraft --name api -p 8080:8080 \\\n  -e DB_HOST=postgres \\\n  -e KEYCLOAK_INTERNAL_URL=http://keycloak:8180 \\\n  -e CORS_ORIGINS=http://localhost:3000 \\\n  -e ANTHROPIC_API_KEY=\u003cyour-key\u003e \\\n  itercraft-api\n\n# Frontend\ndocker run --network itercraft --name front -p 3000:3000 itercraft-front\n\n# Prometheus\ndocker run --network itercraft --name prometheus -p 9090:9090 itercraft-prometheus\n\n# Loki (log aggregation)\ndocker run --network itercraft --name loki -p 3100:3100 itercraft-loki\n\n# Tempo (distributed tracing)\ndocker run --network itercraft --name tempo -p 3200:3200 -p 4317:4317 -p 4318:4318 itercraft-tempo\n\n# Promtail (log collector - needs Docker socket access)\ndocker run --network itercraft --name promtail \\\n  -v /var/lib/docker/containers:/var/lib/docker/containers:ro \\\n  -v /var/run/docker.sock:/var/run/docker.sock:ro \\\n  itercraft-promtail\n\n# Falcosidekick (alert forwarder - start before Falco)\ndocker run --network itercraft --name falcosidekick -p 2801:2801 \\\n  -e SLACK_WEBHOOK_URL=\u003cyour-slack-webhook\u003e \\\n  itercraft-falcosidekick\n\n# Falco (runtime security - needs privileged mode for eBPF)\ndocker run --network itercraft --name falco --privileged \\\n  -v /var/run/docker.sock:/var/run/docker.sock:ro \\\n  -v /dev:/host/dev:ro \\\n  -v /proc:/host/proc:ro \\\n  -v /etc:/host/etc:ro \\\n  itercraft-falco\n\n# Grafana (admin/admin)\ndocker run --network itercraft --name grafana -p 3001:3001 itercraft-grafana\n```\n\n### Run - Production\n\n```bash\ndocker run --network itercraft --name postgres \\\n  -e POSTGRES_PASSWORD=\u003csecret\u003e \\\n  itercraft-postgres\n\ndocker run --network itercraft --name keycloak \\\n  -e KC_HOSTNAME=authent.itercraft.com \\\n  -e KC_HOSTNAME_PORT=-1 \\\n  itercraft-keycloak\n\ndocker run --network itercraft --name api \\\n  -e DB_HOST=postgres \\\n  -e DB_PASSWORD=\u003csecret\u003e \\\n  -e KEYCLOAK_INTERNAL_URL=http://keycloak:8180 \\\n  -e KEYCLOAK_ISSUER_URI=https://authent.itercraft.com/realms/itercraft \\\n  -e CORS_ORIGINS=https://www.itercraft.com \\\n  itercraft-api\n\ndocker run --network itercraft --name front -p 3000:3000 itercraft-front\n\ndocker run --network itercraft --name prometheus -p 9090:9090 itercraft-prometheus\n\ndocker run --network itercraft --name loki itercraft-loki\n\ndocker run --network itercraft --name tempo itercraft-tempo\n\ndocker run --network itercraft --name promtail \\\n  -v /var/lib/docker/containers:/var/lib/docker/containers:ro \\\n  -v /var/run/docker.sock:/var/run/docker.sock:ro \\\n  itercraft-promtail\n\ndocker run --network itercraft --name falcosidekick \\\n  -e SLACK_WEBHOOK_URL=\u003cyour-slack-webhook\u003e \\\n  itercraft-falcosidekick\n\ndocker run --network itercraft --name falco --privileged \\\n  -v /var/run/docker.sock:/var/run/docker.sock:ro \\\n  -v /dev:/host/dev:ro \\\n  -v /proc:/host/proc:ro \\\n  -v /etc:/host/etc:ro \\\n  itercraft-falco\n\ndocker run --network itercraft --name grafana -p 3001:3001 itercraft-grafana\n```\n\n## IoT / MQTT Setup\n\nItercraft includes a secure MQTT broker (Mosquitto) for IoT device connectivity.\n\n### Security Features\n\n- **TLS 1.3** : All connections encrypted with modern cipher suites\n- **Password Authentication** : No anonymous connections allowed\n- **ACL** : Topic-based access control (devices can only publish to their own topics)\n- **DNS-only** : `mqtt.itercraft.com` points directly to EC2 (no Cloudflare proxy for TCP)\n\n### Broker Initial Setup (one-time)\n\n```bash\ncd iot/mosquitto\n\n# 1. Generate CA and server certificates\n./scripts/generate-certs.sh\n\n# 2. Create admin user\n./scripts/add-user.sh admin\n# Enter a strong password when prompted\n\n# 3. Create backend service account\n./scripts/add-user.sh itercraft-backend\n# Enter a strong password when prompted\n```\n\nOutput files:\n\n- `certs/ca.crt` : CA certificate (to distribute to IoT devices)\n- `certs/server.crt` / `server.key` : Server TLS certificate\n- `passwd` : Password file (hashed, safe to commit)\n\n### Device Enrollment Process\n\n#### Step 1: Generate device credentials\n\n```bash\ncd iot/mosquitto\n\n# Create MQTT user for the device\n# Convention: device-\u003ctype\u003e-\u003cserial\u003e\n./scripts/add-user.sh device-esp32-ABC123 \"$(openssl rand -base64 24)\"\n```\n\nSave the generated password securely (e.g., password manager, secrets vault).\n\n#### Step 2: Prepare device provisioning files\n\nCreate a provisioning folder for the device:\n\n```bash\nDEVICE_ID=\"esp32-ABC123\"\nmkdir -p provisioning/$DEVICE_ID\n\n# Copy CA certificate\ncp certs/ca.crt provisioning/$DEVICE_ID/\n\n# Create credentials file\ncat \u003e provisioning/$DEVICE_ID/mqtt_credentials.h \u003c\u003c EOF\n#ifndef MQTT_CREDENTIALS_H\n#define MQTT_CREDENTIALS_H\n\n#define MQTT_BROKER     \"mqtt.itercraft.com\"\n#define MQTT_PORT       8883\n#define MQTT_USER       \"device-$DEVICE_ID\"\n#define MQTT_PASSWORD   \"\u003cpassword-from-step-1\u003e\"\n#define DEVICE_ID       \"$DEVICE_ID\"\n\n// CA Certificate (copy content of ca.crt)\nconst char* ca_cert = R\"EOF(\n-----BEGIN CERTIFICATE-----\n\u003cPASTE CONTENT OF ca.crt HERE\u003e\n-----END CERTIFICATE-----\n)EOF\";\n\n#endif\nEOF\n```\n\n#### Step 3: Flash the device\n\n1. Open the ESP32 project in PlatformIO/Arduino IDE\n2. Copy `mqtt_credentials.h` to the `src/` folder\n3. Build and upload to the device\n4. Monitor Serial output to verify connection\n\n#### Step 4: Verify enrollment\n\n```bash\n# Subscribe to device topics (from server or local with mosquitto-clients)\nmosquitto_sub -h mqtt.itercraft.com -p 8883 \\\n  --cafile certs/ca.crt \\\n  -u admin -P \u003cadmin-password\u003e \\\n  -t \"sensors/esp32-ABC123/#\" -v\n\n# Expected output when device publishes:\n# sensors/esp32-ABC123/temperature 23.5\n# sensors/esp32-ABC123/humidity 65.2\n```\n\n### Topic Structure \u0026 ACL\n\n| Topic Pattern | Access | Description |\n|---------------|--------|-------------|\n| `sensors/\u003cdevice_id\u003e/#` | Device: write | Sensor readings (temperature, humidity, etc.) |\n| `devices/\u003cdevice_id\u003e/status` | Device: write | Heartbeat, battery level, WiFi RSSI |\n| `commands/\u003cdevice_id\u003e/#` | Device: read | Commands from backend (reboot, config update) |\n| `broadcast/#` | All: read | Global announcements (maintenance, updates) |\n\n**ACL enforcement**: A device `device-esp32-ABC123` can only:\n\n- Publish to `sensors/esp32-ABC123/*` and `devices/esp32-ABC123/*`\n- Subscribe to `commands/esp32-ABC123/*` and `broadcast/*`\n\n### ESP32 Reference Implementation\n\n```cpp\n#include \u003cWiFi.h\u003e\n#include \u003cWiFiClientSecure.h\u003e\n#include \u003cPubSubClient.h\u003e\n#include \"mqtt_credentials.h\"  // Generated during enrollment\n\nWiFiClientSecure espClient;\nPubSubClient mqtt(espClient);\n\nvoid connectMQTT() {\n  espClient.setCACert(ca_cert);\n  mqtt.setServer(MQTT_BROKER, MQTT_PORT);\n  mqtt.setCallback(onMessage);\n\n  while (!mqtt.connected()) {\n    Serial.print(\"MQTT connecting...\");\n    if (mqtt.connect(DEVICE_ID, MQTT_USER, MQTT_PASSWORD)) {\n      Serial.println(\"connected\");\n\n      // Subscribe to commands\n      char cmdTopic[64];\n      snprintf(cmdTopic, sizeof(cmdTopic), \"commands/%s/#\", DEVICE_ID);\n      mqtt.subscribe(cmdTopic);\n      mqtt.subscribe(\"broadcast/#\");\n\n      // Publish online status\n      char statusTopic[64];\n      snprintf(statusTopic, sizeof(statusTopic), \"devices/%s/status\", DEVICE_ID);\n      mqtt.publish(statusTopic, \"{\\\"status\\\":\\\"online\\\"}\");\n    } else {\n      Serial.printf(\"failed, rc=%d\\n\", mqtt.state());\n      delay(5000);\n    }\n  }\n}\n\nvoid onMessage(char* topic, byte* payload, unsigned int length) {\n  Serial.printf(\"Message on %s: %.*s\\n\", topic, length, payload);\n  // Handle commands here\n}\n\nvoid publishSensor(const char* type, float value) {\n  char topic[64], msg[32];\n  snprintf(topic, sizeof(topic), \"sensors/%s/%s\", DEVICE_ID, type);\n  snprintf(msg, sizeof(msg), \"%.2f\", value);\n  mqtt.publish(topic, msg);\n}\n\nvoid setup() {\n  Serial.begin(115200);\n  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);\n  while (WiFi.status() != WL_CONNECTED) delay(500);\n  connectMQTT();\n}\n\nvoid loop() {\n  if (!mqtt.connected()) connectMQTT();\n  mqtt.loop();\n\n  // Example: publish temperature every 30s\n  static unsigned long lastPublish = 0;\n  if (millis() - lastPublish \u003e 30000) {\n    publishSensor(\"temperature\", 23.5);\n    publishSensor(\"humidity\", 65.0);\n    lastPublish = millis();\n  }\n}\n```\n\n### Dynamic CA Certificate Fetching\n\nThe CA certificate is automatically uploaded to S3 when Mosquitto starts. Devices can fetch it dynamically:\n\n**URL**: `https://itercraft-mqtt-certs.s3.eu-west-1.amazonaws.com/mqtt/ca.crt`\n\n```cpp\n#include \u003cHTTPClient.h\u003e\n#include \u003cPreferences.h\u003e\n\n#define CA_CERT_URL \"https://itercraft-mqtt-certs.s3.eu-west-1.amazonaws.com/mqtt/ca.crt\"\n\nPreferences prefs;\nString caCert;\n\nbool fetchCACert() {\n  HTTPClient http;\n  http.begin(CA_CERT_URL);\n  int code = http.GET();\n\n  if (code == 200) {\n    caCert = http.getString();\n    // Cache in NVS for offline use\n    prefs.begin(\"mqtt\", false);\n    prefs.putString(\"ca_cert\", caCert);\n    prefs.end();\n    Serial.println(\"CA cert fetched and cached\");\n    return true;\n  }\n  Serial.printf(\"Failed to fetch CA cert: %d\\n\", code);\n  return false;\n}\n\nvoid loadCACert() {\n  prefs.begin(\"mqtt\", true);\n  caCert = prefs.getString(\"ca_cert\", \"\");\n  prefs.end();\n\n  if (caCert.isEmpty()) {\n    fetchCACert();\n  }\n}\n\nvoid setup() {\n  Serial.begin(115200);\n  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);\n  while (WiFi.status() != WL_CONNECTED) delay(500);\n\n  loadCACert();\n  espClient.setCACert(caCert.c_str());\n  connectMQTT();\n}\n```\n\n**Certificate refresh**: Call `fetchCACert()` periodically (e.g., daily) or when MQTT connection fails with TLS errors.\n\n### Device Decommissioning\n\n```bash\ncd iot/mosquitto\n\n# Remove device from password file\n# (mosquitto_passwd doesn't have delete, so recreate without the device)\ngrep -v \"^device-esp32-ABC123:\" passwd \u003e passwd.tmp \u0026\u0026 mv passwd.tmp passwd\n\n# Restart Mosquitto to apply changes\ndocker restart itercraft-mosquitto\n```\n\n### Run Locally (Development)\n\n```bash\ncd iot/mosquitto\ndocker-compose up -d\n\n# Test with mosquitto_pub\nmosquitto_pub -h localhost -p 8883 \\\n  --cafile certs/ca.crt \\\n  -u device-esp32-test -P testpassword \\\n  -t \"sensors/esp32-test/temperature\" \\\n  -m \"25.3\"\n```\n\n### Build \u0026 Push to ECR\n\n```bash\ncd iot/mosquitto\ndocker build -t itercraft-mosquitto .\n\n# Tag and push\naws ecr get-login-password --region eu-west-1 | docker login --username AWS --password-stdin \u003caccount_id\u003e.dkr.ecr.eu-west-1.amazonaws.com\ndocker tag itercraft-mosquitto:latest \u003caccount_id\u003e.dkr.ecr.eu-west-1.amazonaws.com/itercraft_mosquitto:latest\ndocker push \u003caccount_id\u003e.dkr.ecr.eu-west-1.amazonaws.com/itercraft_mosquitto:latest\n```\n\n## License\n\nProprietary - All rights reserved.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneodatsu%2Fitercraft","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fneodatsu%2Fitercraft","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneodatsu%2Fitercraft/lists"}