Loading .env 0 → 100644 +29 −0 Original line number Diff line number Diff line # docker compose COMPOSE_BAKE=true # Server DOMAIN=murenaworkspace.app # mail SMTP_NAME=username SMTP_PASSWORD=123456 SMTP_HOST=smtp.domain.com MAIL_FROM_ADDRESS=no-reply MAIL_DOMAIN=domain.com # database DB_HOST=db.dev.murenaworkspace.app DB_USER=nextcloud DB_PASSWORD=123456 DB_NAME=nextcloud # redis REDIS_HOST=redis.dev.murenaworkspace.app REDIS_HOST_PASSWORD=12456 # nextcloud NEXTCLOUD_DOCKERFILE=slim.Dockerfile NEXTCLOUD_DOCKER_IMG=registry.gitlab.e.foundation/e/infra/ecloud/nextcloud/slim NEXTCLOUD_ADMIN_USER=admin NEXTCLOUD_ADMIN_PASSWORD=@dm1n NEXTCLOUD_TRUSTED_DOMAINS=nginx config/nginx/templates/default.conf.template +1 −1 Original line number Diff line number Diff line Loading @@ -5,7 +5,7 @@ map $arg_v $asset_immutable { } upstream php-handler { server nextcloud:9000; server ${COMPOSE_PROJECT_NAME}_nextcloud:9000; } server { Loading create-cname.sh 0 → 100755 +37 −0 Original line number Diff line number Diff line #!/bin/bash # Script simple pour créer des CNAME via l'API Gandi # Usage: ./create-cname.sh <subdomain> <domain> <target> # Exemple: ./create-cname.sh test example.com target.example.com SUBDOMAIN=$1 DOMAIN=$2 TARGET=$3 GANDI_API_KEY=${GANDI_API_KEY} if [[ -z "$SUBDOMAIN" || -z "$DOMAIN" || -z "$TARGET" || -z "$GANDI_API_KEY" ]]; then echo "Usage: $0 <subdomain> <domain> <target>" echo "Exemple: $0 test example.com target.example.com" echo "GANDI_API_KEY doit être définie en variable d'environnement" exit 1 fi # Créer le record CNAME via l'API Gandi response=$(curl -s -X POST \ "https://api.gandi.net/v5/livedns/domains/$DOMAIN/records" \ -H "Authorization: Apikey $GANDI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "rrset_name": "'$SUBDOMAIN'", "rrset_type": "CNAME", "rrset_values": ["'$TARGET'"], "rrset_ttl": 300 }') if echo "$response" | grep -q "rrset_name"; then echo "✅ CNAME créé avec succès: $SUBDOMAIN.$DOMAIN -> $TARGET" else echo "❌ Erreur lors de la création du CNAME:" echo "$response" exit 1 fi No newline at end of file deploy-crud.sh 0 → 100755 +444 −0 Original line number Diff line number Diff line #!/bin/bash # CRUD script for intelligent Nextcloud deployment # Usage: ./deploy-crud.sh <action> <instance_name> <docker_context> # Actions: create, read, update, delete, list # Example: ./deploy-crud.sh create nc1 remote ACTION=$1 INSTANCE_NAME=$2 DOCKER_CONTEXT=${3:-"default"} show_help() { echo "Usage: $0 <action> <instance_name> [docker_context]" echo "" echo "Actions:" echo " create - Create a new instance" echo " read - View instance status" echo " update - Update an instance" echo " delete - Delete an instance" echo " list - List all instances" echo "" echo "Examples:" echo " $0 create nc1 remote" echo " $0 read nc1 remote" echo " $0 delete nc1 remote" echo " $0 list remote" exit 1 } if [[ -z "$ACTION" ]]; then show_help fi # Fonctions utilitaires get_docker_info() { DOCKER_ENDPOINT=$(docker context inspect $DOCKER_CONTEXT --format '{{.Endpoints.docker.Host}}' 2>/dev/null) if [[ -z "$DOCKER_ENDPOINT" ]]; then echo "❌ Docker context '$DOCKER_CONTEXT' not found" exit 1 fi if [[ "$DOCKER_ENDPOINT" =~ ssh://.*@(.+) ]]; then DOCKER_HOST="${BASH_REMATCH[1]}" if [[ "$DOCKER_HOST" == *".dev."* ]]; then ENV="dev" BASE_DOMAIN=$(echo "$DOCKER_HOST" | sed 's/^docker\.//') TARGET_HOST="$DOCKER_HOST" elif [[ "$DOCKER_HOST" == *".staging."* ]]; then ENV="staging" BASE_DOMAIN=$(echo "$DOCKER_HOST" | sed 's/^docker\.//') TARGET_HOST="$DOCKER_HOST" elif [[ "$DOCKER_HOST" == *"murenaworkspace.com"* ]]; then ENV="prod" BASE_DOMAIN="murenaworkspace.com" TARGET_HOST="$DOCKER_HOST" else ENV="custom" BASE_DOMAIN=$(echo "$DOCKER_HOST" | sed 's/^[^.]*\.//') TARGET_HOST="$DOCKER_HOST" fi else ENV="local" BASE_DOMAIN="localhost" TARGET_HOST="nginx" fi FULL_DOMAIN="${INSTANCE_NAME}.${BASE_DOMAIN}" } create_cname() { if [[ "$ENV" != "local" && -n "$GANDI_API_KEY" ]]; then echo "📡 Creating CNAME $FULL_DOMAIN -> $TARGET_HOST..." if [[ "$BASE_DOMAIN" == "dev.murenaworkspace.app" ]]; then API_DOMAIN="murenaworkspace.app" RECORD_NAME="${INSTANCE_NAME}.dev" else API_DOMAIN="$BASE_DOMAIN" RECORD_NAME="$INSTANCE_NAME" fi response=$(curl -s -X POST \ "https://api.gandi.net/v5/livedns/domains/$API_DOMAIN/records" \ -H "Authorization: Bearer $GANDI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "rrset_name": "'$RECORD_NAME'", "rrset_type": "CNAME", "rrset_values": ["'$TARGET_HOST'."], "rrset_ttl": 300 }') if echo "$response" | grep -q -E "(rrset_name|DNS Record Created)"; then echo "✅ CNAME created successfully" else echo "⚠️ CNAME error: $response" fi else echo "⚠️ CNAME ignored (local or no GANDI_API_KEY)" fi } delete_cname() { if [[ "$ENV" != "local" && -n "$GANDI_API_KEY" ]]; then echo "🗑️ Deleting CNAME $FULL_DOMAIN..." if [[ "$BASE_DOMAIN" == "dev.murenaworkspace.app" ]]; then API_DOMAIN="murenaworkspace.app" RECORD_NAME="${INSTANCE_NAME}.dev" else API_DOMAIN="$BASE_DOMAIN" RECORD_NAME="$INSTANCE_NAME" fi response=$(curl -s -X DELETE \ "https://api.gandi.net/v5/livedns/domains/$API_DOMAIN/records/$RECORD_NAME/CNAME" \ -H "Authorization: Bearer $GANDI_API_KEY") if [[ "$response" == "" ]]; then echo "✅ CNAME deleted successfully" else echo "⚠️ CNAME deletion error: $response" fi fi } find_project_by_instance() { get_docker_info local project_file=".env.${INSTANCE_NAME}-${ENV}" if [[ -f "$project_file" ]]; then PROJECT_NAME="${INSTANCE_NAME}-${ENV}" echo "$PROJECT_NAME" else echo "" fi } # Actions CRUD action_create() { if [[ -z "$INSTANCE_NAME" ]]; then echo "❌ Instance name required for CREATE" show_help fi get_docker_info # Generate unique UID for this project PROJECT_UID=$(echo "$INSTANCE_NAME-$ENV" | md5sum | cut -c1-8) PROJECT_NAME="${INSTANCE_NAME}-${ENV}" # Vérifier la connectivité à l'infrastructure distante echo "🔍 Checking remote infrastructure..." # Tester la connectivité à la base de données if ! docker --context $DOCKER_CONTEXT run --rm alpine/curl -f --connect-timeout 5 http://db.${BASE_DOMAIN}:3306 2>/dev/null; then echo "⚠️ Database not accessible at db.${BASE_DOMAIN}:3306" echo " Continuing anyway, check that Terraform infrastructure is deployed" else echo "✅ Database accessible" fi echo "🚀 Creating Nextcloud instance:" echo " - Instance: $INSTANCE_NAME" echo " - Contexte: $DOCKER_CONTEXT" echo " - Project: $PROJECT_NAME" echo " - Environment: $ENV" echo " - Domain: $FULL_DOMAIN" echo " - Target: $TARGET_HOST" echo "" echo "🔌 Remote infrastructure:" echo " - Database: db.${BASE_DOMAIN}:3306" echo " - Redis: redis.${BASE_DOMAIN}:6379" echo " - Docker: docker.${BASE_DOMAIN}:22" create_cname # Générer port numérique basé sur le hash du nom PORT_SUFFIX=$(echo "$PROJECT_NAME" | md5sum | tr -d 'a-f' | head -c 3) # Créer le .env cat > .env.${PROJECT_NAME} << EOF # Instance: $INSTANCE_NAME # Environment: $ENV # Created: $(date) DOMAIN=${FULL_DOMAIN} NGINX_PORT=8${PORT_SUFFIX:-000} # Database (uses infrastructure deployed via DNS) DB_HOST=db.${BASE_DOMAIN} DB_USER=nextcloud DB_PASSWORD=0aVa8l87PfDtrUlg5hIvK3SH1SnfB1 DB_NAME=nextcloud # Redis (uses infrastructure deployed via DNS) REDIS_HOST=redis.${BASE_DOMAIN} REDIS_PASSWORD=123456 # Nextcloud NEXTCLOUD_DOCKERFILE=slim.Dockerfile NEXTCLOUD_DOCKER_IMG=registry.gitlab.e.foundation/e/infra/ecloud/nextcloud/slim NEXTCLOUD_ADMIN_USER=admin NEXTCLOUD_ADMIN_PASSWORD=@dm1n NEXTCLOUD_TRUSTED_DOMAINS=${FULL_DOMAIN} # Mail SMTP_NAME=${SMTP_NAME:-fd0af045-04cd-471d-b405-3e978bb0b313} SMTP_PASSWORD=${SMTP_PASSWORD:-d3d51629-f9dd-4d1b-bbb4-8998f5b9bd50} SMTP_HOST=${SMTP_HOST:-smtp.tem.scaleway.com} MAIL_FROM_ADDRESS=${MAIL_FROM_ADDRESS:-no-reply} MAIL_DOMAIN=${MAIL_DOMAIN:-${BASE_DOMAIN}} # Traefik / ACME ACME_EMAIL=${ACME_EMAIL:-dev@e.email} EOF echo "📄 Configuration: .env.${PROJECT_NAME}" # Déployer export COMPOSE_PROJECT_NAME=$PROJECT_NAME echo "🐳 Deploying on context '$DOCKER_CONTEXT'..." if docker --context $DOCKER_CONTEXT info --format '{{.Swarm.LocalNodeState}}' 2>/dev/null | grep -q "active"; then echo "🐝 Docker Swarm mode detected" # Créer le volume avec nom explicite VOLUME_NAME="nc_data_${INSTANCE_NAME}_${PROJECT_UID}" echo "📦 Creating volume: $VOLUME_NAME" docker --context $DOCKER_CONTEXT volume create $VOLUME_NAME set -a source .env.${PROJECT_NAME} export INSTANCE_NAME=$INSTANCE_NAME export PROJECT_UID=$PROJECT_UID set +a docker --context $DOCKER_CONTEXT stack deploy -c docker-compose.yml $PROJECT_NAME else echo "🐳 Docker Compose mode detected" DOCKER_CONTEXT=$DOCKER_CONTEXT docker-compose -f docker-compose.yml --env-file .env.${PROJECT_NAME} up -d fi echo "" echo "✅ Instance '$INSTANCE_NAME' created successfully!" echo " 🌍 URL: https://$FULL_DOMAIN" echo " 📋 Project: $PROJECT_NAME" echo " ⚙️ Config: .env.${PROJECT_NAME}" echo "" echo "🔑 Admin login:" echo " 👤 User: $(grep NEXTCLOUD_ADMIN_USER .env.${PROJECT_NAME} | cut -d'=' -f2)" echo " 🔒 Pass: $(grep NEXTCLOUD_ADMIN_PASSWORD .env.${PROJECT_NAME} | cut -d'=' -f2)" } action_read() { if [[ -z "$INSTANCE_NAME" ]]; then echo "❌ Instance name required for READ" show_help fi PROJECT_NAME=$(find_project_by_instance) if [[ -z "$PROJECT_NAME" ]]; then echo "❌ Instance '$INSTANCE_NAME' not found" exit 1 fi get_docker_info echo "📋 Instance '$INSTANCE_NAME' status:" echo " - Project: $PROJECT_NAME" echo " - Domain: $FULL_DOMAIN" echo " - Config: .env.${PROJECT_NAME}" # État des services if docker --context $DOCKER_CONTEXT info --format '{{.Swarm.LocalNodeState}}' 2>/dev/null | grep -q "active"; then echo "" echo "🐝 Docker Swarm services:" docker --context $DOCKER_CONTEXT stack services $PROJECT_NAME echo "" echo "📊 Detailed status:" docker --context $DOCKER_CONTEXT stack ps $PROJECT_NAME else echo "" echo "🐳 Docker Compose services:" DOCKER_CONTEXT=$DOCKER_CONTEXT docker-compose -f docker-compose.yml -p $PROJECT_NAME ps fi } action_delete() { if [[ -z "$INSTANCE_NAME" ]]; then echo "❌ Instance name required for DELETE" show_help fi PROJECT_NAME=$(find_project_by_instance) if [[ -z "$PROJECT_NAME" ]]; then echo "❌ Instance '$INSTANCE_NAME' not found" exit 1 fi get_docker_info echo "🗑️ Deleting instance '$INSTANCE_NAME':" echo " - Project: $PROJECT_NAME" echo " - Domain: $FULL_DOMAIN" # Confirmer la suppression read -p "⚠️ Confirm deletion of '$INSTANCE_NAME'? (yes/no): " confirm if [[ "$confirm" != "yes" ]]; then echo "❌ Deletion cancelled" exit 1 fi # Supprimer les services if docker --context $DOCKER_CONTEXT info --format '{{.Swarm.LocalNodeState}}' 2>/dev/null | grep -q "active"; then echo "🐝 Deleting Docker Swarm stack..." docker --context $DOCKER_CONTEXT stack rm $PROJECT_NAME # Wait for stack to be completely deleted sleep 10 # Delete explicit volume VOLUME_NAME="nc_data_${INSTANCE_NAME}_${PROJECT_UID}" echo "🗑️ Deleting volume: $VOLUME_NAME" docker --context $DOCKER_CONTEXT volume rm $VOLUME_NAME 2>/dev/null || echo "Volume already deleted" else echo "🐳 Deleting Docker Compose services..." DOCKER_CONTEXT=$DOCKER_CONTEXT docker-compose -f docker-compose.yml -p $PROJECT_NAME down -v fi # Supprimer le CNAME delete_cname # Supprimer la config if [[ -f ".env.${PROJECT_NAME}" ]]; then rm ".env.${PROJECT_NAME}" echo "📄 Configuration deleted: .env.${PROJECT_NAME}" fi echo "✅ Instance '$INSTANCE_NAME' deleted successfully!" } action_list() { get_docker_info echo "📋 Instances deployed on '$DOCKER_CONTEXT' ($ENV):" echo "" # Lister depuis les fichiers .env local configs=($(ls .env.*-* 2>/dev/null)) if [[ ${#configs[@]} -eq 0 ]]; then echo " No instances found" return fi for config in "${configs[@]}"; do PROJECT_NAME=$(basename "$config" | sed 's/^\.env\.//') INSTANCE_NAME=$(echo "$PROJECT_NAME" | sed 's/-[^-]*$//') # Extraire le domaine du fichier if [[ -f "$config" ]]; then DOMAIN=$(grep "^DOMAIN=" "$config" | cut -d'=' -f2) CREATED=$(grep "# Created:" "$config" | sed 's/# Created: //') fi echo " 🔹 $INSTANCE_NAME" echo " Projet: $PROJECT_NAME" echo " Domaine: $DOMAIN" echo " Created: ${CREATED:-'N/A'}" echo " Config: $config" echo "" done # Stack/services status if docker --context $DOCKER_CONTEXT info --format '{{.Swarm.LocalNodeState}}' 2>/dev/null | grep -q "active"; then echo "🐝 Active Docker Swarm stacks:" docker --context $DOCKER_CONTEXT stack ls else echo "🐳 Active Docker Compose services:" docker --context $DOCKER_CONTEXT ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" fi } action_update() { if [[ -z "$INSTANCE_NAME" ]]; then echo "❌ Instance name required for UPDATE" show_help fi PROJECT_NAME=$(find_project_by_instance) if [[ -z "$PROJECT_NAME" ]]; then echo "❌ Instance '$INSTANCE_NAME' not found" exit 1 fi echo "🔄 Updating instance '$INSTANCE_NAME':" echo " - Project: $PROJECT_NAME" # Redeploy with existing config export COMPOSE_PROJECT_NAME=$PROJECT_NAME if docker --context $DOCKER_CONTEXT info --format '{{.Swarm.LocalNodeState}}' 2>/dev/null | grep -q "active"; then echo "🐝 Updating Docker Swarm stack..." set -a source .env.${PROJECT_NAME} export INSTANCE_NAME=$INSTANCE_NAME export PROJECT_UID=$PROJECT_UID set +a docker --context $DOCKER_CONTEXT stack deploy -c docker-compose.yml $PROJECT_NAME else echo "🐳 Updating Docker Compose services..." DOCKER_CONTEXT=$DOCKER_CONTEXT docker-compose -f docker-compose.yml --env-file .env.${PROJECT_NAME} up -d fi echo "✅ Instance '$INSTANCE_NAME' updated successfully!" } # Router vers la bonne action case "$ACTION" in create|c) action_create ;; read|r|status) action_read ;; update|u) action_update ;; delete|d|remove) action_delete ;; list|l|ls) action_list ;; help|h|--help|-h) show_help ;; *) echo "❌ Unknown action: $ACTION" show_help ;; esac No newline at end of file docker-compose.yml +49 −49 Original line number Diff line number Diff line services: db: image: postgres:17.4-alpine restart: unless-stopped environment: - POSTGRES_DB=${DB_NAME} - POSTGRES_USER=${DB_USER} - POSTGRES_PASSWORD=${DB_PASSWORD} volumes: - db:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"] interval: 10s timeout: 5s retries: 5 redis: image: redis:7.4-alpine restart: unless-stopped healthcheck: test: [ "CMD", "redis-cli", "--raw", "incr", "ping" ] interval: 10s timeout: 5s retries: 5 nextcloud: image: ${NEXTCLOUD_DOCKER_IMG} build: context: . dockerfile: ${NEXTCLOUD_DOCKERFILE} restart: unless-stopped environment: - POSTGRES_HOST=${DB_HOST} - POSTGRES_USER=${DB_USER} - POSTGRES_PASSWORD=${DB_PASSWORD} - POSTGRES_DB=${DB_NAME} - MYSQL_HOST=${DB_HOST} - MYSQL_USER=${DB_USER} - MYSQL_PASSWORD=${DB_PASSWORD} - MYSQL_DATABASE=${DB_NAME} - REDIS_HOST=${REDIS_HOST} - REDIS_HOST_PASSWORD=${REDIS_PASSWORD} - NEXTCLOUD_ADMIN_USER=${NEXTCLOUD_ADMIN_USER} - NEXTCLOUD_ADMIN_PASSWORD=${NEXTCLOUD_ADMIN_PASSWORD} - NEXTCLOUD_TRUSTED_DOMAINS=${NEXTCLOUD_TRUSTED_DOMAINS} Loading @@ -45,42 +22,65 @@ services: - SMTP_HOST=${SMTP_HOST} - MAIL_FROM_ADDRESS=${MAIL_FROM_ADDRESS} - MAIL_DOMAIN=${MAIL_DOMAIN} - SYSLOG_HOST=syslog - SYSLOG_HOST=syslog.dev.murenaworkspace.app volumes: - nextcloud:/var/www/html depends_on: syslog: condition: service_started db: condition: service_healthy redis: condition: service_healthy syslog: image: jumanjiman/rsyslog deploy: restart_policy: condition: any placement: constraints: - node.role == worker nextcloud-cron: image: ${NEXTCLOUD_DOCKER_IMG} restart: unless-stopped entrypoint: /cron.sh volumes: - nextcloud:/var/www/html depends_on: - nextcloud deploy: restart_policy: condition: any placement: constraints: - node.role == worker nginx: image: nginx:stable-alpine restart: unless-stopped environment: DOMAIN: ${DOMAIN} ports: - "8000:80" COMPOSE_PROJECT_NAME: ${COMPOSE_PROJECT_NAME:-nextcloud} configs: - source: nginx_config target: /etc/nginx/templates/default.conf.template volumes: - ${DEPLOYMENT_PATH:-.}/config/nginx/templates:/etc/nginx/templates - nextcloud:/var/www/html depends_on: - nextcloud deploy: restart_policy: condition: any placement: constraints: - node.role == worker labels: - traefik.enable=true - traefik.http.routers.${COMPOSE_PROJECT_NAME:-nextcloud}.rule=Host(`${DOMAIN}`) - traefik.http.routers.${COMPOSE_PROJECT_NAME:-nextcloud}.entrypoints=websecure - traefik.http.routers.${COMPOSE_PROJECT_NAME:-nextcloud}.tls.certresolver=letsencrypt - traefik.http.routers.${COMPOSE_PROJECT_NAME:-nextcloud}-http.rule=Host(`${DOMAIN}`) - traefik.http.routers.${COMPOSE_PROJECT_NAME:-nextcloud}-http.entrypoints=web - traefik.http.routers.${COMPOSE_PROJECT_NAME:-nextcloud}-http.middlewares=https-redirect - traefik.http.middlewares.https-redirect.redirectscheme.scheme=https - traefik.http.services.${COMPOSE_PROJECT_NAME:-nextcloud}.loadbalancer.server.port=80 volumes: db: nextcloud: external: true name: nc_data_${INSTANCE_NAME}_${PROJECT_UID} networks: default: external: true name: nextcloud-network configs: nginx_config: file: ./config/nginx/templates/default.conf.template No newline at end of file Loading
.env 0 → 100644 +29 −0 Original line number Diff line number Diff line # docker compose COMPOSE_BAKE=true # Server DOMAIN=murenaworkspace.app # mail SMTP_NAME=username SMTP_PASSWORD=123456 SMTP_HOST=smtp.domain.com MAIL_FROM_ADDRESS=no-reply MAIL_DOMAIN=domain.com # database DB_HOST=db.dev.murenaworkspace.app DB_USER=nextcloud DB_PASSWORD=123456 DB_NAME=nextcloud # redis REDIS_HOST=redis.dev.murenaworkspace.app REDIS_HOST_PASSWORD=12456 # nextcloud NEXTCLOUD_DOCKERFILE=slim.Dockerfile NEXTCLOUD_DOCKER_IMG=registry.gitlab.e.foundation/e/infra/ecloud/nextcloud/slim NEXTCLOUD_ADMIN_USER=admin NEXTCLOUD_ADMIN_PASSWORD=@dm1n NEXTCLOUD_TRUSTED_DOMAINS=nginx
config/nginx/templates/default.conf.template +1 −1 Original line number Diff line number Diff line Loading @@ -5,7 +5,7 @@ map $arg_v $asset_immutable { } upstream php-handler { server nextcloud:9000; server ${COMPOSE_PROJECT_NAME}_nextcloud:9000; } server { Loading
create-cname.sh 0 → 100755 +37 −0 Original line number Diff line number Diff line #!/bin/bash # Script simple pour créer des CNAME via l'API Gandi # Usage: ./create-cname.sh <subdomain> <domain> <target> # Exemple: ./create-cname.sh test example.com target.example.com SUBDOMAIN=$1 DOMAIN=$2 TARGET=$3 GANDI_API_KEY=${GANDI_API_KEY} if [[ -z "$SUBDOMAIN" || -z "$DOMAIN" || -z "$TARGET" || -z "$GANDI_API_KEY" ]]; then echo "Usage: $0 <subdomain> <domain> <target>" echo "Exemple: $0 test example.com target.example.com" echo "GANDI_API_KEY doit être définie en variable d'environnement" exit 1 fi # Créer le record CNAME via l'API Gandi response=$(curl -s -X POST \ "https://api.gandi.net/v5/livedns/domains/$DOMAIN/records" \ -H "Authorization: Apikey $GANDI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "rrset_name": "'$SUBDOMAIN'", "rrset_type": "CNAME", "rrset_values": ["'$TARGET'"], "rrset_ttl": 300 }') if echo "$response" | grep -q "rrset_name"; then echo "✅ CNAME créé avec succès: $SUBDOMAIN.$DOMAIN -> $TARGET" else echo "❌ Erreur lors de la création du CNAME:" echo "$response" exit 1 fi No newline at end of file
deploy-crud.sh 0 → 100755 +444 −0 Original line number Diff line number Diff line #!/bin/bash # CRUD script for intelligent Nextcloud deployment # Usage: ./deploy-crud.sh <action> <instance_name> <docker_context> # Actions: create, read, update, delete, list # Example: ./deploy-crud.sh create nc1 remote ACTION=$1 INSTANCE_NAME=$2 DOCKER_CONTEXT=${3:-"default"} show_help() { echo "Usage: $0 <action> <instance_name> [docker_context]" echo "" echo "Actions:" echo " create - Create a new instance" echo " read - View instance status" echo " update - Update an instance" echo " delete - Delete an instance" echo " list - List all instances" echo "" echo "Examples:" echo " $0 create nc1 remote" echo " $0 read nc1 remote" echo " $0 delete nc1 remote" echo " $0 list remote" exit 1 } if [[ -z "$ACTION" ]]; then show_help fi # Fonctions utilitaires get_docker_info() { DOCKER_ENDPOINT=$(docker context inspect $DOCKER_CONTEXT --format '{{.Endpoints.docker.Host}}' 2>/dev/null) if [[ -z "$DOCKER_ENDPOINT" ]]; then echo "❌ Docker context '$DOCKER_CONTEXT' not found" exit 1 fi if [[ "$DOCKER_ENDPOINT" =~ ssh://.*@(.+) ]]; then DOCKER_HOST="${BASH_REMATCH[1]}" if [[ "$DOCKER_HOST" == *".dev."* ]]; then ENV="dev" BASE_DOMAIN=$(echo "$DOCKER_HOST" | sed 's/^docker\.//') TARGET_HOST="$DOCKER_HOST" elif [[ "$DOCKER_HOST" == *".staging."* ]]; then ENV="staging" BASE_DOMAIN=$(echo "$DOCKER_HOST" | sed 's/^docker\.//') TARGET_HOST="$DOCKER_HOST" elif [[ "$DOCKER_HOST" == *"murenaworkspace.com"* ]]; then ENV="prod" BASE_DOMAIN="murenaworkspace.com" TARGET_HOST="$DOCKER_HOST" else ENV="custom" BASE_DOMAIN=$(echo "$DOCKER_HOST" | sed 's/^[^.]*\.//') TARGET_HOST="$DOCKER_HOST" fi else ENV="local" BASE_DOMAIN="localhost" TARGET_HOST="nginx" fi FULL_DOMAIN="${INSTANCE_NAME}.${BASE_DOMAIN}" } create_cname() { if [[ "$ENV" != "local" && -n "$GANDI_API_KEY" ]]; then echo "📡 Creating CNAME $FULL_DOMAIN -> $TARGET_HOST..." if [[ "$BASE_DOMAIN" == "dev.murenaworkspace.app" ]]; then API_DOMAIN="murenaworkspace.app" RECORD_NAME="${INSTANCE_NAME}.dev" else API_DOMAIN="$BASE_DOMAIN" RECORD_NAME="$INSTANCE_NAME" fi response=$(curl -s -X POST \ "https://api.gandi.net/v5/livedns/domains/$API_DOMAIN/records" \ -H "Authorization: Bearer $GANDI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "rrset_name": "'$RECORD_NAME'", "rrset_type": "CNAME", "rrset_values": ["'$TARGET_HOST'."], "rrset_ttl": 300 }') if echo "$response" | grep -q -E "(rrset_name|DNS Record Created)"; then echo "✅ CNAME created successfully" else echo "⚠️ CNAME error: $response" fi else echo "⚠️ CNAME ignored (local or no GANDI_API_KEY)" fi } delete_cname() { if [[ "$ENV" != "local" && -n "$GANDI_API_KEY" ]]; then echo "🗑️ Deleting CNAME $FULL_DOMAIN..." if [[ "$BASE_DOMAIN" == "dev.murenaworkspace.app" ]]; then API_DOMAIN="murenaworkspace.app" RECORD_NAME="${INSTANCE_NAME}.dev" else API_DOMAIN="$BASE_DOMAIN" RECORD_NAME="$INSTANCE_NAME" fi response=$(curl -s -X DELETE \ "https://api.gandi.net/v5/livedns/domains/$API_DOMAIN/records/$RECORD_NAME/CNAME" \ -H "Authorization: Bearer $GANDI_API_KEY") if [[ "$response" == "" ]]; then echo "✅ CNAME deleted successfully" else echo "⚠️ CNAME deletion error: $response" fi fi } find_project_by_instance() { get_docker_info local project_file=".env.${INSTANCE_NAME}-${ENV}" if [[ -f "$project_file" ]]; then PROJECT_NAME="${INSTANCE_NAME}-${ENV}" echo "$PROJECT_NAME" else echo "" fi } # Actions CRUD action_create() { if [[ -z "$INSTANCE_NAME" ]]; then echo "❌ Instance name required for CREATE" show_help fi get_docker_info # Generate unique UID for this project PROJECT_UID=$(echo "$INSTANCE_NAME-$ENV" | md5sum | cut -c1-8) PROJECT_NAME="${INSTANCE_NAME}-${ENV}" # Vérifier la connectivité à l'infrastructure distante echo "🔍 Checking remote infrastructure..." # Tester la connectivité à la base de données if ! docker --context $DOCKER_CONTEXT run --rm alpine/curl -f --connect-timeout 5 http://db.${BASE_DOMAIN}:3306 2>/dev/null; then echo "⚠️ Database not accessible at db.${BASE_DOMAIN}:3306" echo " Continuing anyway, check that Terraform infrastructure is deployed" else echo "✅ Database accessible" fi echo "🚀 Creating Nextcloud instance:" echo " - Instance: $INSTANCE_NAME" echo " - Contexte: $DOCKER_CONTEXT" echo " - Project: $PROJECT_NAME" echo " - Environment: $ENV" echo " - Domain: $FULL_DOMAIN" echo " - Target: $TARGET_HOST" echo "" echo "🔌 Remote infrastructure:" echo " - Database: db.${BASE_DOMAIN}:3306" echo " - Redis: redis.${BASE_DOMAIN}:6379" echo " - Docker: docker.${BASE_DOMAIN}:22" create_cname # Générer port numérique basé sur le hash du nom PORT_SUFFIX=$(echo "$PROJECT_NAME" | md5sum | tr -d 'a-f' | head -c 3) # Créer le .env cat > .env.${PROJECT_NAME} << EOF # Instance: $INSTANCE_NAME # Environment: $ENV # Created: $(date) DOMAIN=${FULL_DOMAIN} NGINX_PORT=8${PORT_SUFFIX:-000} # Database (uses infrastructure deployed via DNS) DB_HOST=db.${BASE_DOMAIN} DB_USER=nextcloud DB_PASSWORD=0aVa8l87PfDtrUlg5hIvK3SH1SnfB1 DB_NAME=nextcloud # Redis (uses infrastructure deployed via DNS) REDIS_HOST=redis.${BASE_DOMAIN} REDIS_PASSWORD=123456 # Nextcloud NEXTCLOUD_DOCKERFILE=slim.Dockerfile NEXTCLOUD_DOCKER_IMG=registry.gitlab.e.foundation/e/infra/ecloud/nextcloud/slim NEXTCLOUD_ADMIN_USER=admin NEXTCLOUD_ADMIN_PASSWORD=@dm1n NEXTCLOUD_TRUSTED_DOMAINS=${FULL_DOMAIN} # Mail SMTP_NAME=${SMTP_NAME:-fd0af045-04cd-471d-b405-3e978bb0b313} SMTP_PASSWORD=${SMTP_PASSWORD:-d3d51629-f9dd-4d1b-bbb4-8998f5b9bd50} SMTP_HOST=${SMTP_HOST:-smtp.tem.scaleway.com} MAIL_FROM_ADDRESS=${MAIL_FROM_ADDRESS:-no-reply} MAIL_DOMAIN=${MAIL_DOMAIN:-${BASE_DOMAIN}} # Traefik / ACME ACME_EMAIL=${ACME_EMAIL:-dev@e.email} EOF echo "📄 Configuration: .env.${PROJECT_NAME}" # Déployer export COMPOSE_PROJECT_NAME=$PROJECT_NAME echo "🐳 Deploying on context '$DOCKER_CONTEXT'..." if docker --context $DOCKER_CONTEXT info --format '{{.Swarm.LocalNodeState}}' 2>/dev/null | grep -q "active"; then echo "🐝 Docker Swarm mode detected" # Créer le volume avec nom explicite VOLUME_NAME="nc_data_${INSTANCE_NAME}_${PROJECT_UID}" echo "📦 Creating volume: $VOLUME_NAME" docker --context $DOCKER_CONTEXT volume create $VOLUME_NAME set -a source .env.${PROJECT_NAME} export INSTANCE_NAME=$INSTANCE_NAME export PROJECT_UID=$PROJECT_UID set +a docker --context $DOCKER_CONTEXT stack deploy -c docker-compose.yml $PROJECT_NAME else echo "🐳 Docker Compose mode detected" DOCKER_CONTEXT=$DOCKER_CONTEXT docker-compose -f docker-compose.yml --env-file .env.${PROJECT_NAME} up -d fi echo "" echo "✅ Instance '$INSTANCE_NAME' created successfully!" echo " 🌍 URL: https://$FULL_DOMAIN" echo " 📋 Project: $PROJECT_NAME" echo " ⚙️ Config: .env.${PROJECT_NAME}" echo "" echo "🔑 Admin login:" echo " 👤 User: $(grep NEXTCLOUD_ADMIN_USER .env.${PROJECT_NAME} | cut -d'=' -f2)" echo " 🔒 Pass: $(grep NEXTCLOUD_ADMIN_PASSWORD .env.${PROJECT_NAME} | cut -d'=' -f2)" } action_read() { if [[ -z "$INSTANCE_NAME" ]]; then echo "❌ Instance name required for READ" show_help fi PROJECT_NAME=$(find_project_by_instance) if [[ -z "$PROJECT_NAME" ]]; then echo "❌ Instance '$INSTANCE_NAME' not found" exit 1 fi get_docker_info echo "📋 Instance '$INSTANCE_NAME' status:" echo " - Project: $PROJECT_NAME" echo " - Domain: $FULL_DOMAIN" echo " - Config: .env.${PROJECT_NAME}" # État des services if docker --context $DOCKER_CONTEXT info --format '{{.Swarm.LocalNodeState}}' 2>/dev/null | grep -q "active"; then echo "" echo "🐝 Docker Swarm services:" docker --context $DOCKER_CONTEXT stack services $PROJECT_NAME echo "" echo "📊 Detailed status:" docker --context $DOCKER_CONTEXT stack ps $PROJECT_NAME else echo "" echo "🐳 Docker Compose services:" DOCKER_CONTEXT=$DOCKER_CONTEXT docker-compose -f docker-compose.yml -p $PROJECT_NAME ps fi } action_delete() { if [[ -z "$INSTANCE_NAME" ]]; then echo "❌ Instance name required for DELETE" show_help fi PROJECT_NAME=$(find_project_by_instance) if [[ -z "$PROJECT_NAME" ]]; then echo "❌ Instance '$INSTANCE_NAME' not found" exit 1 fi get_docker_info echo "🗑️ Deleting instance '$INSTANCE_NAME':" echo " - Project: $PROJECT_NAME" echo " - Domain: $FULL_DOMAIN" # Confirmer la suppression read -p "⚠️ Confirm deletion of '$INSTANCE_NAME'? (yes/no): " confirm if [[ "$confirm" != "yes" ]]; then echo "❌ Deletion cancelled" exit 1 fi # Supprimer les services if docker --context $DOCKER_CONTEXT info --format '{{.Swarm.LocalNodeState}}' 2>/dev/null | grep -q "active"; then echo "🐝 Deleting Docker Swarm stack..." docker --context $DOCKER_CONTEXT stack rm $PROJECT_NAME # Wait for stack to be completely deleted sleep 10 # Delete explicit volume VOLUME_NAME="nc_data_${INSTANCE_NAME}_${PROJECT_UID}" echo "🗑️ Deleting volume: $VOLUME_NAME" docker --context $DOCKER_CONTEXT volume rm $VOLUME_NAME 2>/dev/null || echo "Volume already deleted" else echo "🐳 Deleting Docker Compose services..." DOCKER_CONTEXT=$DOCKER_CONTEXT docker-compose -f docker-compose.yml -p $PROJECT_NAME down -v fi # Supprimer le CNAME delete_cname # Supprimer la config if [[ -f ".env.${PROJECT_NAME}" ]]; then rm ".env.${PROJECT_NAME}" echo "📄 Configuration deleted: .env.${PROJECT_NAME}" fi echo "✅ Instance '$INSTANCE_NAME' deleted successfully!" } action_list() { get_docker_info echo "📋 Instances deployed on '$DOCKER_CONTEXT' ($ENV):" echo "" # Lister depuis les fichiers .env local configs=($(ls .env.*-* 2>/dev/null)) if [[ ${#configs[@]} -eq 0 ]]; then echo " No instances found" return fi for config in "${configs[@]}"; do PROJECT_NAME=$(basename "$config" | sed 's/^\.env\.//') INSTANCE_NAME=$(echo "$PROJECT_NAME" | sed 's/-[^-]*$//') # Extraire le domaine du fichier if [[ -f "$config" ]]; then DOMAIN=$(grep "^DOMAIN=" "$config" | cut -d'=' -f2) CREATED=$(grep "# Created:" "$config" | sed 's/# Created: //') fi echo " 🔹 $INSTANCE_NAME" echo " Projet: $PROJECT_NAME" echo " Domaine: $DOMAIN" echo " Created: ${CREATED:-'N/A'}" echo " Config: $config" echo "" done # Stack/services status if docker --context $DOCKER_CONTEXT info --format '{{.Swarm.LocalNodeState}}' 2>/dev/null | grep -q "active"; then echo "🐝 Active Docker Swarm stacks:" docker --context $DOCKER_CONTEXT stack ls else echo "🐳 Active Docker Compose services:" docker --context $DOCKER_CONTEXT ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" fi } action_update() { if [[ -z "$INSTANCE_NAME" ]]; then echo "❌ Instance name required for UPDATE" show_help fi PROJECT_NAME=$(find_project_by_instance) if [[ -z "$PROJECT_NAME" ]]; then echo "❌ Instance '$INSTANCE_NAME' not found" exit 1 fi echo "🔄 Updating instance '$INSTANCE_NAME':" echo " - Project: $PROJECT_NAME" # Redeploy with existing config export COMPOSE_PROJECT_NAME=$PROJECT_NAME if docker --context $DOCKER_CONTEXT info --format '{{.Swarm.LocalNodeState}}' 2>/dev/null | grep -q "active"; then echo "🐝 Updating Docker Swarm stack..." set -a source .env.${PROJECT_NAME} export INSTANCE_NAME=$INSTANCE_NAME export PROJECT_UID=$PROJECT_UID set +a docker --context $DOCKER_CONTEXT stack deploy -c docker-compose.yml $PROJECT_NAME else echo "🐳 Updating Docker Compose services..." DOCKER_CONTEXT=$DOCKER_CONTEXT docker-compose -f docker-compose.yml --env-file .env.${PROJECT_NAME} up -d fi echo "✅ Instance '$INSTANCE_NAME' updated successfully!" } # Router vers la bonne action case "$ACTION" in create|c) action_create ;; read|r|status) action_read ;; update|u) action_update ;; delete|d|remove) action_delete ;; list|l|ls) action_list ;; help|h|--help|-h) show_help ;; *) echo "❌ Unknown action: $ACTION" show_help ;; esac No newline at end of file
docker-compose.yml +49 −49 Original line number Diff line number Diff line services: db: image: postgres:17.4-alpine restart: unless-stopped environment: - POSTGRES_DB=${DB_NAME} - POSTGRES_USER=${DB_USER} - POSTGRES_PASSWORD=${DB_PASSWORD} volumes: - db:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"] interval: 10s timeout: 5s retries: 5 redis: image: redis:7.4-alpine restart: unless-stopped healthcheck: test: [ "CMD", "redis-cli", "--raw", "incr", "ping" ] interval: 10s timeout: 5s retries: 5 nextcloud: image: ${NEXTCLOUD_DOCKER_IMG} build: context: . dockerfile: ${NEXTCLOUD_DOCKERFILE} restart: unless-stopped environment: - POSTGRES_HOST=${DB_HOST} - POSTGRES_USER=${DB_USER} - POSTGRES_PASSWORD=${DB_PASSWORD} - POSTGRES_DB=${DB_NAME} - MYSQL_HOST=${DB_HOST} - MYSQL_USER=${DB_USER} - MYSQL_PASSWORD=${DB_PASSWORD} - MYSQL_DATABASE=${DB_NAME} - REDIS_HOST=${REDIS_HOST} - REDIS_HOST_PASSWORD=${REDIS_PASSWORD} - NEXTCLOUD_ADMIN_USER=${NEXTCLOUD_ADMIN_USER} - NEXTCLOUD_ADMIN_PASSWORD=${NEXTCLOUD_ADMIN_PASSWORD} - NEXTCLOUD_TRUSTED_DOMAINS=${NEXTCLOUD_TRUSTED_DOMAINS} Loading @@ -45,42 +22,65 @@ services: - SMTP_HOST=${SMTP_HOST} - MAIL_FROM_ADDRESS=${MAIL_FROM_ADDRESS} - MAIL_DOMAIN=${MAIL_DOMAIN} - SYSLOG_HOST=syslog - SYSLOG_HOST=syslog.dev.murenaworkspace.app volumes: - nextcloud:/var/www/html depends_on: syslog: condition: service_started db: condition: service_healthy redis: condition: service_healthy syslog: image: jumanjiman/rsyslog deploy: restart_policy: condition: any placement: constraints: - node.role == worker nextcloud-cron: image: ${NEXTCLOUD_DOCKER_IMG} restart: unless-stopped entrypoint: /cron.sh volumes: - nextcloud:/var/www/html depends_on: - nextcloud deploy: restart_policy: condition: any placement: constraints: - node.role == worker nginx: image: nginx:stable-alpine restart: unless-stopped environment: DOMAIN: ${DOMAIN} ports: - "8000:80" COMPOSE_PROJECT_NAME: ${COMPOSE_PROJECT_NAME:-nextcloud} configs: - source: nginx_config target: /etc/nginx/templates/default.conf.template volumes: - ${DEPLOYMENT_PATH:-.}/config/nginx/templates:/etc/nginx/templates - nextcloud:/var/www/html depends_on: - nextcloud deploy: restart_policy: condition: any placement: constraints: - node.role == worker labels: - traefik.enable=true - traefik.http.routers.${COMPOSE_PROJECT_NAME:-nextcloud}.rule=Host(`${DOMAIN}`) - traefik.http.routers.${COMPOSE_PROJECT_NAME:-nextcloud}.entrypoints=websecure - traefik.http.routers.${COMPOSE_PROJECT_NAME:-nextcloud}.tls.certresolver=letsencrypt - traefik.http.routers.${COMPOSE_PROJECT_NAME:-nextcloud}-http.rule=Host(`${DOMAIN}`) - traefik.http.routers.${COMPOSE_PROJECT_NAME:-nextcloud}-http.entrypoints=web - traefik.http.routers.${COMPOSE_PROJECT_NAME:-nextcloud}-http.middlewares=https-redirect - traefik.http.middlewares.https-redirect.redirectscheme.scheme=https - traefik.http.services.${COMPOSE_PROJECT_NAME:-nextcloud}.loadbalancer.server.port=80 volumes: db: nextcloud: external: true name: nc_data_${INSTANCE_NAME}_${PROJECT_UID} networks: default: external: true name: nextcloud-network configs: nginx_config: file: ./config/nginx/templates/default.conf.template No newline at end of file