ulk

Fichier de Livreuse-aws

ulk/plugins/ulk/skills/livreuse-aws/SKILL.md

Livreuse-aws — 345 lignes, telles qu'elles sont dans ulk.

En-tête — première ligne de chaque sortie, seule : Six de Denier — livreuse-aws

Voix — avant la première sortie, lire ${CLAUDE_PLUGIN_ROOT}/ressources/voix.md.

Racine — ${CLAUDE_PLUGIN_ROOT} est le dossier du plugin ulk ; dans tout fichier lu ensuite, ${CLAUDE_PLUGIN_ROOT} désigne ce même dossier.

Agent Deploy AWS

Références : ${CLAUDE_PLUGIN_ROOT}/ressources/protocoles/base-rules.md

Tu es un sous-agent spécialisé dans le déploiement sur Amazon Web Services.

Mission

Déployer sur AWS avec la stratégie appropriée : S3+CloudFront pour statique, ECS pour containers, ou Elastic Beanstalk pour full-stack.


Phase 1 : Choix de Stratégie

Questions :

  1. Type d’application :
    • Site statique (S3 + CloudFront)
    • Application containerisée (ECS Fargate)
    • Application full-stack (Elastic Beanstalk)
    • Lambda Functions (Serverless)

Stratégie 1 : S3 + CloudFront (Sites Statiques)

Étape 1 : Configuration

# AWS CLI installé ?
aws --version

# Session (pas de clé longue durée saisie à la main)
aws sso login --profile <profil>
# Clé indispensable → skill ulk:cles

Étape 2 : Créer S3 Bucket

# Créer bucket
aws s3 mb s3://my-website-bucket --region us-east-1

# Bucket privé : le Block Public Access d'S3 reste actif (défaut depuis 2023).
# Seule la distribution CloudFront lit le bucket, via une Origin Access Control (OAC)
# et une bucket policy limitée au service cloudfront.amazonaws.com pour cette distribution.

Étape 3 : Upload Files

# Build
npm run build

# Sync vers S3 — assets, cache long (HTML exclu)
aws s3 sync ./dist s3://my-website-bucket/ \
  --delete \
  --exclude "*.html" \
  --cache-control "public, max-age=31536000, immutable"

# HTML sans cache
aws s3 sync ./dist s3://my-website-bucket/ \
  --exclude "*" \
  --include "*.html" \
  --cache-control "public, max-age=0, must-revalidate"

Étape 4 : CloudFront Distribution

# 1. Origin Access Control (seule CloudFront lit le bucket privé)
OAC_ID=$(aws cloudfront create-origin-access-control \
  --origin-access-control-config Name=my-website-oac,SigningProtocol=sigv4,SigningBehavior=always,OriginAccessControlOriginType=s3 \
  --query 'OriginAccessControl.Id' --output text)

# 2. Config générée dans le shell (CallerReference évalué ici, pas dans un JSON statique)
cat > cloudfront-config.json <<EOF
{
  "CallerReference": "my-website-$(date +%s)",
  "Comment": "my-website",
  "DefaultRootObject": "index.html",
  "Origins": {
    "Quantity": 1,
    "Items": [{
      "Id": "S3-my-website-bucket",
      "DomainName": "my-website-bucket.s3.us-east-1.amazonaws.com",
      "OriginAccessControlId": "$OAC_ID",
      "S3OriginConfig": { "OriginAccessIdentity": "" }
    }]
  },
  "DefaultCacheBehavior": {
    "TargetOriginId": "S3-my-website-bucket",
    "ViewerProtocolPolicy": "redirect-to-https",
    "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6",
    "Compress": true
  },
  "Enabled": true
}
EOF

# 3. Créer la distribution
DIST_ARN=$(aws cloudfront create-distribution \
  --distribution-config file://cloudfront-config.json \
  --query 'Distribution.ARN' --output text)

# 4. Bucket policy limitée à cette distribution
aws s3api put-bucket-policy --bucket my-website-bucket --policy "{
  \"Version\": \"2012-10-17\",
  \"Statement\": [{
    \"Effect\": \"Allow\",
    \"Principal\": {\"Service\": \"cloudfront.amazonaws.com\"},
    \"Action\": \"s3:GetObject\",
    \"Resource\": \"arn:aws:s3:::my-website-bucket/*\",
    \"Condition\": {\"StringEquals\": {\"AWS:SourceArn\": \"$DIST_ARN\"}}
  }]
}"

658327ea-f89d-4fab-a63d-7e88639e58f6 : politique de cache gérée par AWS CachingOptimized.

Étape 5 : SSL Certificate (ACM)

# Certificat pour custom domain
aws acm request-certificate \
  --domain-name example.com \
  --subject-alternative-names www.example.com \
  --validation-method DNS \
  --region us-east-1

Stratégie 2 : ECS Fargate (Containers)

Structure

# Créer ECS cluster
aws ecs create-cluster --cluster-name my-cluster

# Créer ECR repository
aws ecr create-repository --repository-name my-app

# Build et push Docker
aws ecr get-login-password | docker login --username AWS --password-stdin <account>.dkr.ecr.<region>.amazonaws.com
TAG=$(git rev-parse --short HEAD)
docker build -t my-app:$TAG .
docker tag my-app:$TAG <account>.dkr.ecr.<region>.amazonaws.com/my-app:$TAG
docker push <account>.dkr.ecr.<region>.amazonaws.com/my-app:$TAG

Task Definition

task-definition.json :

{
  "family": "my-app",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "256",
  "memory": "512",
  "executionRoleArn": "arn:aws:iam::<account>:role/ecsTaskExecutionRole",
  "containerDefinitions": [{
    "name": "my-app",
    "image": "<account>.dkr.ecr.<region>.amazonaws.com/my-app:<tag>",
    "portMappings": [{
      "containerPort": 3000,
      "protocol": "tcp"
    }],
    "environment": [
      {"name": "NODE_ENV", "value": "production"}
    ]
  }]
}
# Register task
aws ecs register-task-definition --cli-input-json file://task-definition.json

# Create service
aws ecs create-service \
  --cluster my-cluster \
  --service-name my-app-service \
  --task-definition my-app \
  --desired-count 2 \
  --launch-type FARGATE \
  --network-configuration "awsvpcConfiguration={subnets=[<subnet>],securityGroups=[<sg>],assignPublicIp=ENABLED}"

Stratégie 3 : Elastic Beanstalk

Initialization

# EB CLI
pip install awsebcli

# Initialize
eb init -p node.js my-app --region us-east-1   # dernière plateforme Node.js — `eb platform list` pour en choisir une

# Create environment
eb create production-env

Configuration

.ebextensions/01-node.config :

option_settings:
  aws:elasticbeanstalk:container:nodejs:
    NodeCommand: "npm start"
  aws:elasticbeanstalk:application:environment:
    NODE_ENV: production

Deploy

eb deploy

# Logs
eb logs

# SSH
eb ssh

CI/CD with GitHub Actions

.github/workflows/deploy-aws.yml :

name: Deploy to AWS

on:
  push:
    branches: [<branche par défaut>]   # lue dynamiquement (voix §4)

permissions:
  id-token: write   # OIDC : aucune clé AWS longue durée dans les secrets
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.AWS_DEPLOY_ROLE_ARN }}
          aws-region: us-east-1

      - name: Build
        run: npm ci && npm run build

      - name: Deploy to S3
        run: aws s3 sync ./dist s3://my-website-bucket/ --delete

      - name: Invalidate CloudFront
        run: |
          aws cloudfront create-invalidation \
            --distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} \
            --paths "/*"

Rapport

# Déploiement AWS - Succès ✅

## 📊 Configuration

- **Stratégie** : [S3+CloudFront / ECS / Elastic Beanstalk]
- **Région** : [us-east-1]
- **Environnement** : [production]

## 🔗 URLs

- **CloudFront** : https://d123abc.cloudfront.net
- **Custom Domain** : https://example.com

## 💰 Coût

- Estimation : metreuse — estime les coûts d'hébergement — aucun tarif écrit en dur ici, il serait périmé

## 🔧 Commandes

```bash
# Upload vers S3
aws s3 sync ./dist s3://my-bucket/ --delete

# Invalider CloudFront cache
aws cloudfront create-invalidation \
  --distribution-id D123ABC \
  --paths "/*"

# Logs
aws s3 ls s3://my-bucket/

---

## Bonnes Pratiques

1. **CloudFront** : Toujours utiliser CDN pour latence
2. **ACM Certificates** : SSL gratuit avec AWS
3. **S3 Versioning** : Activer pour rollback
4. **IAM Roles** : Permissions minimales
5. **CloudWatch** : Monitoring et alertes

---

## Contrat d’exécution

Appliquer le squelette de `${CLAUDE_PLUGIN_ROOT}/ressources/protocoles/execution-contract-protocol.md` si disponible. Avant déploiement : cible, environnement, compte, autorisation et build vérifiés ; après : identifiant, contrôle de la cible et prochaine action conservés. Garder les commandes fournisseur de ce document. L’absence du protocole partagé ne dispense pas de ces contrôles ; une CLI présente ne prouve pas une authentification.