From eb25aa19d9c76e1d9adb6d7689e2475c65b3bfb0 Mon Sep 17 00:00:00 2001 From: Fendy Date: Wed, 15 Jul 2026 23:28:04 +0800 Subject: [PATCH] migrate from github --- .env.example | 68 +++++++ .gitignore | 12 ++ Dockerfile.proto-gen | 11 ++ docker-compose.dev.yml | 324 +++++++++++++++++++++++++++++++++ docker-compose.yml | 251 +++++++++++++++++++++++++ nginx/conf.d/default.conf.tmpl | 130 +++++++++++++ nginx/nginx.conf.tmpl | 34 ++++ rabbitmq/rabbitmq.conf.tmpl | 19 ++ redis/redis.conf.tmpl | 3 + script/configure.ps1 | 111 +++++++++++ script/configure.sh | 119 ++++++++++++ script/gen_proto.ps1 | 20 ++ script/gen_proto.sh | 18 ++ script/prepare.ps1 | 86 +++++++++ script/prepare.sh | 75 ++++++++ script/start.ps1 | 61 +++++++ script/start.sh | 91 +++++++++ 17 files changed, 1433 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 Dockerfile.proto-gen create mode 100644 docker-compose.dev.yml create mode 100644 docker-compose.yml create mode 100644 nginx/conf.d/default.conf.tmpl create mode 100644 nginx/nginx.conf.tmpl create mode 100644 rabbitmq/rabbitmq.conf.tmpl create mode 100644 redis/redis.conf.tmpl create mode 100644 script/configure.ps1 create mode 100755 script/configure.sh create mode 100644 script/gen_proto.ps1 create mode 100755 script/gen_proto.sh create mode 100644 script/prepare.ps1 create mode 100755 script/prepare.sh create mode 100644 script/start.ps1 create mode 100755 script/start.sh diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..f972e07 --- /dev/null +++ b/.env.example @@ -0,0 +1,68 @@ +# host exposed ports +NGINX_HTTP_PORT=8080 +NGINX_HTTPS_PORT=8443 +BACKEND_GRPC_HOST_PORT=9090 +BACKEND_DEBUG_HOST_PORT=2345 +POSTGRES_HOST_PORT=5432 +REDIS_HOST_PORT=6379 +RABBITMQ_AMQP_HOST_PORT=5672 +CONSUL_HOST_PORT=8500 +ELASTICSEARCH_HOST_PORT=9200 + +# internal service ports +BACKEND_GRPC_PORT=9090 +BACKEND_GRPC_WEB_PORT=8080 +POSTGRES_PORT=5432 +REDIS_PORT=6379 +RABBITMQ_AMQP_PORT=5672 +RABBITMQ_MANAGEMENT_PORT=15672 +CONSUL_PORT=8500 +COLLAB_CORE_PORT=1234 +COLLAB_GATEWAY_PORT=1234 +MINIO_API_PORT=9000 +MINIO_CONSOLE_PORT=9001 +ELASTICSEARCH_PORT=9200 +REDIS_DB=0 + +# internal service hosts +POSTGRES_HOST=postgres +REDIS_HOST=redis +CONSUL_HOST=consul +RABBITMQ_HOST=rabbitmq +BACKEND_HOST=backend +FRONTEND_HOST=frontend +COLLAB_HOST=collab +COLLAB_CORE_HOST=collab-core +MINIO_HOST=minio +ELASTICSEARCH_HOST=elasticsearch + +# credentials & secrets +POSTGRES_USER=root +POSTGRES_PASSWORD=your_password +POSTGRES_DB=yoresee_doc_db +REDIS_PASSWORD=your_redis_password +RABBITMQ_DEFAULT_USER=guest +RABBITMQ_DEFAULT_PASS=guest +CONSUL_ROOT_TOKEN=yoresee_doc_root_token +BACKEND_INTERNAL_RPC_KEY=yoresee_doc_internal_key +JWT_SECRET=yoresee_doc_jwt_secret_key +MINIO_ROOT_USER=minioadmin +MINIO_ROOT_PASSWORD=minioadmin + +# application config +VITE_API_BASE_URL=http://localhost:8080 +VITE_GRPC_WEB_ENDPOINT=/grpc +MINIO_BUCKET=yoresee-doc +# Keep this aligned with VITE_API_BASE_URL. configure.sh will derive it as ${VITE_API_BASE_URL}/minio. +MINIO_BROWSER_REDIRECT_URL=http://localhost:8080/minio +DIRTY_DOC_TOPIC=collab.dirty_docs +DIRTY_DOC_MQ=rabbitmq +NOTIFICATION_MQ=rabbitmq +SEARCH_SYNC_MQ=rabbitmq +DIRTY_DOC_NOTIFY_THRESHOLD=5 + +# elasticsearch +ELASTICSEARCH_ENABLED=true +ELASTICSEARCH_USERNAME= +ELASTICSEARCH_PASSWORD= +ELASTICSEARCH_INDEX_PREFIX=yoresee_doc diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b90a414 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# ignore config files +nginx/nginx.conf +nginx/conf.d/default.conf + +redis/redis.conf + +rabbitmq/rabbitmq.conf +.env + +# ignore log files by suffix +*.logs +*.log diff --git a/Dockerfile.proto-gen b/Dockerfile.proto-gen new file mode 100644 index 0000000..1938df2 --- /dev/null +++ b/Dockerfile.proto-gen @@ -0,0 +1,11 @@ +FROM bufbuild/buf:latest + +WORKDIR /workspace/proto + +ENTRYPOINT ["sh", "-c", "\ + buf generate --template buf.gen.backend.yaml && \ + buf generate --template buf.gen.collab-go.yaml && \ + buf generate --template buf.gen.frontend.yaml && \ + buf generate --template buf.gen.node.yaml && \ + echo 'Proto generation complete.' \ +"] diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..1dd03e9 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,324 @@ +services: + proto-gen: + build: + context: ../proto + dockerfile: ../deploy/Dockerfile.proto-gen + image: yoresee_doc_proto_gen:latest + container_name: yoresee_doc_proto_gen + volumes: + - ../proto:/workspace/proto + - ../backend/pkg/gen:/workspace/backend/pkg/gen + - ../collab-go/pkg/gen:/workspace/collab-go/pkg/gen + - ../frontend/src/gen:/workspace/frontend/src/gen + - ../collab/src/gen:/workspace/collab/src/gen + + nginx: + image: nginx:alpine + container_name: yoresee_doc_nginx + ports: + - "${NGINX_HTTP_PORT:-8080}:80" + - "${NGINX_HTTPS_PORT:-8443}:443" + volumes: + - ./nginx/nginx.conf:/etc/nginx/nginx.conf + - ./nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf + - ./logs/nginx:/var/log/nginx + depends_on: + - frontend + - backend + - collab + - minio + networks: + - yoresee_doc_network + restart: unless-stopped + + frontend: + build: + context: ../frontend + dockerfile: Dockerfile + target: dev + image: yoresee_doc_frontend_dev:latest + container_name: yoresee_doc_frontend + volumes: + - ../frontend:/app + - /app/node_modules + environment: + - NODE_ENV=development + - VITE_API_BASE_URL=${VITE_API_BASE_URL:-http://localhost:8080} + - VITE_GRPC_WEB_ENDPOINT=${VITE_GRPC_WEB_ENDPOINT:-/grpc} + networks: + - yoresee_doc_network + restart: unless-stopped + + postgres: + image: postgres:15-alpine + container_name: yoresee_doc_postgres + environment: + POSTGRES_USER: ${POSTGRES_USER:-root} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-your_password} + POSTGRES_DB: ${POSTGRES_DB:-yoresee_doc_db} + ports: + - "${POSTGRES_HOST_PORT:-5432}:${POSTGRES_PORT:-5432}" + volumes: + - yoresee_doc_postgres_data:/var/lib/postgresql/data + networks: + - yoresee_doc_network + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-root} -d ${POSTGRES_DB:-yoresee_doc_db}"] + interval: 5s + timeout: 3s + retries: 5 + restart: unless-stopped + + redis: + image: redis:7-alpine + container_name: yoresee_doc_redis + environment: + REDIS_PASSWORD: ${REDIS_PASSWORD:-your_redis_password} + ports: + - "${REDIS_HOST_PORT:-6379}:${REDIS_PORT:-6379}" + volumes: + - yoresee_doc_redis_data:/data + - ./redis/redis.conf:/usr/local/etc/redis/redis.conf + command: redis-server /usr/local/etc/redis/redis.conf + networks: + - yoresee_doc_network + healthcheck: + test: ["CMD", "redis-cli", "--raw", "incr", "ping"] + interval: 5s + timeout: 3s + retries: 5 + restart: unless-stopped + + consul: + image: hashicorp/consul:1.16 + container_name: yoresee_doc_consul + command: "agent -dev -client=0.0.0.0 -ui" + environment: + CONSUL_LOCAL_CONFIG: >- + {"acl":{"enabled":true,"default_policy":"deny","down_policy":"extend-cache","enable_token_persistence":true,"tokens":{"initial_management":"${CONSUL_ROOT_TOKEN:-yoresee_doc_root_token}"}}} + ports: + - "${CONSUL_HOST_PORT:-8500}:${CONSUL_PORT:-8500}" + volumes: + - yoresee_doc_consul_data:/consul/data + networks: + - yoresee_doc_network + restart: unless-stopped + + rabbitmq: + image: rabbitmq:3-management + container_name: yoresee_doc_rabbitmq + environment: + RABBITMQ_DEFAULT_USER: ${RABBITMQ_DEFAULT_USER:-guest} + RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS:-guest} + ports: + - "${RABBITMQ_AMQP_HOST_PORT:-5672}:${RABBITMQ_AMQP_PORT:-5672}" + volumes: + - yoresee_doc_rabbitmq_data:/var/lib/rabbitmq + - ./rabbitmq/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf:ro + - ./logs/rabbitmq:/var/log/rabbitmq + networks: + - yoresee_doc_network + healthcheck: + test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"] + interval: 5s + timeout: 3s + retries: 5 + restart: unless-stopped + + minio: + image: minio/minio:latest + container_name: yoresee_doc_minio + command: server /data --console-address ":${MINIO_CONSOLE_PORT:-9001}" + environment: + MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin} + MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin} + MINIO_BROWSER_REDIRECT_URL: ${MINIO_BROWSER_REDIRECT_URL:-http://localhost:8080/minio} + volumes: + - yoresee_doc_minio_data:/data + networks: + - yoresee_doc_network + restart: unless-stopped + + elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch:8.15.3 + container_name: yoresee_doc_elasticsearch + environment: + - discovery.type=single-node + - xpack.security.enabled=false + - xpack.security.enrollment.enabled=false + - ES_JAVA_OPTS=-Xms512m -Xmx512m + ports: + - "${ELASTICSEARCH_HOST_PORT:-9200}:${ELASTICSEARCH_PORT:-9200}" + volumes: + - yoresee_doc_elasticsearch_data:/usr/share/elasticsearch/data + networks: + - yoresee_doc_network + restart: unless-stopped + + backend: + build: + context: ../backend + dockerfile: Dockerfile + target: dev + image: yoresee_doc_backend_dev:latest + container_name: yoresee_doc_backend + volumes: + - ../backend:/app + ports: + - "${BACKEND_DEBUG_HOST_PORT:-2345}:2345" + - "${BACKEND_GRPC_HOST_PORT:-9090}:${BACKEND_GRPC_PORT:-9090}" + environment: + - BACKEND_INTERNAL_RPC_KEY=${BACKEND_INTERNAL_RPC_KEY:-yoresee_doc_internal_key} + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + rabbitmq: + condition: service_healthy + minio: + condition: service_started + elasticsearch: + condition: service_started + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "http://${BACKEND_HOST:-backend}:${BACKEND_GRPC_PORT:-9090}/health"] + interval: 5s + timeout: 3s + retries: 20 + start_period: 30s + networks: + - yoresee_doc_network + restart: unless-stopped + + collab-core: + build: + context: .. + dockerfile: collab/Dockerfile + image: yoresee_doc_collab_core:latest + container_name: yoresee_doc_collab_core + environment: + - REDIS_HOST=${REDIS_HOST:-redis} + - REDIS_PORT=${REDIS_PORT:-6379} + - REDIS_PASSWORD=${REDIS_PASSWORD:-your_redis_password} + - REDIS_DB=${REDIS_DB:-0} + - BACKEND_ADDR=${BACKEND_HOST:-backend}:${BACKEND_GRPC_PORT:-9090} + - DIRTY_DOC_MQ=${DIRTY_DOC_MQ:-rabbitmq} + - DIRTY_DOC_TOPIC=${DIRTY_DOC_TOPIC:-collab.dirty_docs} + - RABBITMQ_URL=amqp://${RABBITMQ_DEFAULT_USER:-guest}:${RABBITMQ_DEFAULT_PASS:-guest}@${RABBITMQ_HOST:-rabbitmq}:${RABBITMQ_AMQP_PORT:-5672}/ + - INTERNAL_RPC_KEY=${BACKEND_INTERNAL_RPC_KEY:-yoresee_doc_internal_key} + - DIRTY_DOC_NOTIFY_THRESHOLD=${DIRTY_DOC_NOTIFY_THRESHOLD:-5} + depends_on: + redis: + condition: service_healthy + backend: + condition: service_healthy + rabbitmq: + condition: service_healthy + networks: + - yoresee_doc_network + volumes: + - ../collab:/app + restart: unless-stopped + + collab: + build: + context: ../collab-go + dockerfile: Dockerfile + image: yoresee_doc_collab_gateway:latest + container_name: yoresee_doc_collab + environment: + - ADDR=:${COLLAB_GATEWAY_PORT:-1234} + - JWT_SECRET=${JWT_SECRET:-yoresee_doc_jwt_secret_key} + - COLLAB_CORE_URL=ws://${COLLAB_CORE_HOST:-collab-core}:${COLLAB_CORE_PORT:-1234} + - BACKEND_GRPC_ADDR=${BACKEND_HOST:-backend}:${BACKEND_GRPC_PORT:-9090} + - INTERNAL_RPC_KEY=${BACKEND_INTERNAL_RPC_KEY:-yoresee_doc_internal_key} + volumes: + - ../collab-go:/app + depends_on: + - collab-core + networks: + - yoresee_doc_network + restart: unless-stopped + + snapshot-worker: + build: + context: ../backend + dockerfile: Dockerfile + target: dev + image: yoresee_doc_snapshot_worker_dev:latest + container_name: yoresee_doc_snapshot_worker + volumes: + - ../backend:/app + environment: + - COLLAB_CORE_HTTP=http://${COLLAB_CORE_HOST:-collab-core}:${COLLAB_CORE_PORT:-1234} + command: sh -c "go mod download && go run ./cmd/snapshot-worker" + depends_on: + rabbitmq: + condition: service_healthy + redis: + condition: service_healthy + elasticsearch: + condition: service_started + networks: + - yoresee_doc_network + restart: unless-stopped + + notification-worker: + build: + context: ../backend + dockerfile: Dockerfile + target: dev + image: yoresee_doc_notification_worker_dev:latest + container_name: yoresee_doc_notification_worker + volumes: + - ../backend:/app + environment: + - NOTIFICATION_MQ=${NOTIFICATION_MQ:-rabbitmq} + command: sh -c "go mod download && go run ./cmd/notification-worker" + depends_on: + rabbitmq: + condition: service_healthy + redis: + condition: service_healthy + postgres: + condition: service_healthy + networks: + - yoresee_doc_network + restart: unless-stopped + + search-sync-worker: + build: + context: ../backend + dockerfile: Dockerfile + target: dev + image: yoresee_doc_search_sync_worker_dev:latest + container_name: yoresee_doc_search_sync_worker + volumes: + - ../backend:/app + environment: + - SEARCH_SYNC_MQ=${SEARCH_SYNC_MQ:-rabbitmq} + command: sh -c "go mod download && go run ./cmd/search-sync-worker" + depends_on: + rabbitmq: + condition: service_healthy + redis: + condition: service_healthy + postgres: + condition: service_healthy + elasticsearch: + condition: service_started + networks: + - yoresee_doc_network + restart: unless-stopped + +volumes: + yoresee_doc_postgres_data: + yoresee_doc_redis_data: + yoresee_doc_consul_data: + yoresee_doc_rabbitmq_data: + yoresee_doc_minio_data: + yoresee_doc_elasticsearch_data: + +networks: + yoresee_doc_network: + driver: bridge diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..10141f4 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,251 @@ +services: + nginx: + image: nginx:alpine + container_name: yoresee_doc_nginx + ports: + - "${NGINX_HTTP_PORT:-8080}:80" + - "${NGINX_HTTPS_PORT:-8443}:443" + volumes: + - ./nginx/nginx.conf:/etc/nginx/nginx.conf + - ./nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf + - ./logs/nginx:/var/log/nginx + depends_on: + - frontend + - backend + - collab + - minio + networks: + - yoresee_doc_network + restart: unless-stopped + + frontend: + build: + context: .. + dockerfile: frontend/Dockerfile.prod + target: runner + image: yoresee_doc_frontend_runner:latest + container_name: yoresee_doc_frontend + environment: + - NODE_ENV=production + - VITE_API_BASE_URL=${VITE_API_BASE_URL:-http://localhost:8080} + - VITE_GRPC_WEB_ENDPOINT=${VITE_GRPC_WEB_ENDPOINT:-/grpc} + networks: + - yoresee_doc_network + restart: unless-stopped + + postgres: + image: postgres:15-alpine + container_name: yoresee_doc_postgres + environment: + POSTGRES_USER: ${POSTGRES_USER:-root} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-your_password} + POSTGRES_DB: ${POSTGRES_DB:-yoresee_doc_db} + volumes: + - yoresee_doc_postgres_data:/var/lib/postgresql/data + networks: + - yoresee_doc_network + restart: unless-stopped + + redis: + image: redis:7-alpine + container_name: yoresee_doc_redis + environment: + REDIS_PASSWORD: ${REDIS_PASSWORD:-your_redis_password} + volumes: + - yoresee_doc_redis_data:/data + - ./redis/redis.conf:/usr/local/etc/redis/redis.conf + command: redis-server /usr/local/etc/redis/redis.conf + networks: + - yoresee_doc_network + restart: unless-stopped + + consul: + image: hashicorp/consul:1.16 + container_name: yoresee_doc_consul + command: "agent -dev -client=0.0.0.0 -ui" + environment: + CONSUL_LOCAL_CONFIG: >- + {"acl":{"enabled":true,"default_policy":"deny","down_policy":"extend-cache","enable_token_persistence":true,"tokens":{"initial_management":"${CONSUL_ROOT_TOKEN:-yoresee_doc_root_token}"}}} + ports: + - "${CONSUL_HOST_PORT:-8500}:${CONSUL_PORT:-8500}" + volumes: + - yoresee_doc_consul_data:/consul/data + networks: + - yoresee_doc_network + restart: unless-stopped + + rabbitmq: + image: rabbitmq:3-management + container_name: yoresee_doc_rabbitmq + environment: + RABBITMQ_DEFAULT_USER: ${RABBITMQ_DEFAULT_USER:-guest} + RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS:-guest} + volumes: + - yoresee_doc_rabbitmq_data:/var/lib/rabbitmq + - ./rabbitmq/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf:ro + - ./logs/rabbitmq:/var/log/rabbitmq + networks: + - yoresee_doc_network + restart: unless-stopped + + minio: + image: minio/minio:latest + container_name: yoresee_doc_minio + command: server /data --console-address ":${MINIO_CONSOLE_PORT:-9001}" + environment: + MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin} + MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin} + MINIO_BROWSER_REDIRECT_URL: ${MINIO_BROWSER_REDIRECT_URL:-http://localhost:8080/minio} + volumes: + - yoresee_doc_minio_data:/data + networks: + - yoresee_doc_network + restart: unless-stopped + + elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch:8.15.3 + container_name: yoresee_doc_elasticsearch + environment: + - discovery.type=single-node + - xpack.security.enabled=false + - xpack.security.enrollment.enabled=false + - ES_JAVA_OPTS=-Xms512m -Xmx512m + volumes: + - yoresee_doc_elasticsearch_data:/usr/share/elasticsearch/data + networks: + - yoresee_doc_network + restart: unless-stopped + + backend: + build: + context: .. + dockerfile: backend/Dockerfile.prod + target: runner + image: yoresee_doc_backend_runner:latest + container_name: yoresee_doc_backend + volumes: + - ../backend/config.toml:/app/config.toml + environment: + - BACKEND_INTERNAL_RPC_KEY=${BACKEND_INTERNAL_RPC_KEY:-yoresee_doc_internal_key} + ports: + - "${BACKEND_GRPC_HOST_PORT:-9090}:${BACKEND_GRPC_PORT:-9090}" + depends_on: + - postgres + - redis + - consul + - rabbitmq + - minio + - elasticsearch + networks: + - yoresee_doc_network + restart: unless-stopped + + collab-core: + build: + context: .. + dockerfile: collab/Dockerfile.prod + image: yoresee_doc_collab_core:latest + container_name: yoresee_doc_collab_core + environment: + - REDIS_HOST=${REDIS_HOST:-redis} + - REDIS_PORT=${REDIS_PORT:-6379} + - REDIS_PASSWORD=${REDIS_PASSWORD:-your_redis_password} + - REDIS_DB=${REDIS_DB:-0} + - BACKEND_ADDR=${BACKEND_HOST:-backend}:${BACKEND_GRPC_PORT:-9090} + - DIRTY_DOC_MQ=${DIRTY_DOC_MQ:-rabbitmq} + - DIRTY_DOC_TOPIC=${DIRTY_DOC_TOPIC:-collab.dirty_docs} + - RABBITMQ_URL=amqp://${RABBITMQ_DEFAULT_USER:-guest}:${RABBITMQ_DEFAULT_PASS:-guest}@${RABBITMQ_HOST:-rabbitmq}:${RABBITMQ_AMQP_PORT:-5672}/ + - INTERNAL_RPC_KEY=${BACKEND_INTERNAL_RPC_KEY:-yoresee_doc_internal_key} + - DIRTY_DOC_NOTIFY_THRESHOLD=${DIRTY_DOC_NOTIFY_THRESHOLD:-200} + depends_on: + - redis + - backend + - rabbitmq + networks: + - yoresee_doc_network + restart: unless-stopped + + collab: + build: + context: .. + dockerfile: collab-go/Dockerfile.prod + image: yoresee_doc_collab_gateway:latest + container_name: yoresee_doc_collab + environment: + - ADDR=:${COLLAB_GATEWAY_PORT:-1234} + - JWT_SECRET=${JWT_SECRET:-yoresee_doc_jwt_secret_key} + - COLLAB_CORE_URL=ws://${COLLAB_CORE_HOST:-collab-core}:${COLLAB_CORE_PORT:-1234} + - BACKEND_GRPC_ADDR=${BACKEND_HOST:-backend}:${BACKEND_GRPC_PORT:-9090} + - INTERNAL_RPC_KEY=${BACKEND_INTERNAL_RPC_KEY:-yoresee_doc_internal_key} + depends_on: + - collab-core + networks: + - yoresee_doc_network + restart: unless-stopped + + snapshot-worker: + build: + context: .. + dockerfile: backend/Dockerfile.snapshot.prod + image: yoresee_doc_snapshot_worker:latest + container_name: yoresee_doc_snapshot_worker + volumes: + - ../backend/config.toml:/app/config.toml + environment: + - COLLAB_CORE_HTTP=http://${COLLAB_CORE_HOST:-collab-core}:${COLLAB_CORE_PORT:-1234} + depends_on: + - rabbitmq + - redis + - elasticsearch + networks: + - yoresee_doc_network + restart: unless-stopped + + notification-worker: + build: + context: .. + dockerfile: backend/Dockerfile.notification.prod + image: yoresee_doc_notification_worker:latest + container_name: yoresee_doc_notification_worker + volumes: + - ../backend/config.toml:/app/config.toml + environment: + - NOTIFICATION_MQ=${NOTIFICATION_MQ:-rabbitmq} + depends_on: + - rabbitmq + - redis + - postgres + networks: + - yoresee_doc_network + restart: unless-stopped + + search-sync-worker: + build: + context: .. + dockerfile: backend/Dockerfile.search-sync.prod + image: yoresee_doc_search_sync_worker:latest + container_name: yoresee_doc_search_sync_worker + volumes: + - ../backend/config.toml:/app/config.toml + environment: + - SEARCH_SYNC_MQ=${SEARCH_SYNC_MQ:-rabbitmq} + depends_on: + - rabbitmq + - redis + - postgres + - elasticsearch + networks: + - yoresee_doc_network + restart: unless-stopped + +volumes: + yoresee_doc_postgres_data: + yoresee_doc_redis_data: + yoresee_doc_consul_data: + yoresee_doc_rabbitmq_data: + yoresee_doc_minio_data: + yoresee_doc_elasticsearch_data: + +networks: + yoresee_doc_network: + driver: bridge diff --git a/nginx/conf.d/default.conf.tmpl b/nginx/conf.d/default.conf.tmpl new file mode 100644 index 0000000..ef86e74 --- /dev/null +++ b/nginx/conf.d/default.conf.tmpl @@ -0,0 +1,130 @@ +server { + listen 80; + server_name localhost; + client_max_body_size 5m; +# Uncomment the following block (Line6 to Line24) to enable redirect HTTP to HTTPS +# Redirect HTTP to HTTPS +# Using 8443 port because that is what is exposed in docker-compose +# location / { +# return 301 https://$host:8443$request_uri; +# } +# } + +# server { +# listen 443 ssl; +# server_name localhost; + +# ssl_certificate /etc/nginx/ssl/server.crt; +# ssl_certificate_key /etc/nginx/ssl/server.key; + +# ssl_session_cache shared:SSL:1m; +# ssl_session_timeout 5m; + +# ssl_ciphers HIGH:!aNULL:!MD5; +# ssl_prefer_server_ciphers on; + + # gRPC endpoint + location /grpc/ { + if ($request_method = OPTIONS) { + add_header 'Access-Control-Allow-Origin' "$http_origin" always; + add_header 'Access-Control-Allow-Credentials' 'true' always; + add_header 'Access-Control-Allow-Methods' 'POST,GET,OPTIONS' always; + add_header 'Access-Control-Allow-Headers' 'Accept,Accept-Language,Content-Type,Content-Length,X-Grpc-Web,X-User-Agent,Grpc-Timeout,Authorization,Connect-Protocol-Version' always; + return 204; + } + proxy_pass http://${BACKEND_HOST}:${BACKEND_GRPC_WEB_PORT}/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Grpc-Web $http_x_grpc_web; + proxy_set_header X-User-Agent $http_x_user_agent; + } + + # MinIO Console + location = /minio { + return 301 /minio/; + } + + location /minio/ { + rewrite ^/minio/(.*)$ /$1 break; + proxy_pass http://${MINIO_HOST}:${MINIO_CONSOLE_PORT}; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Prefix /minio; + proxy_redirect off; + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + } + + # Public storage path + location = /storage { + return 301 /storage/; + } + + location /storage/ { + rewrite ^/storage/(.*)$ /${MINIO_BUCKET}/$1 break; + proxy_pass http://${MINIO_HOST}:${MINIO_API_PORT}; + proxy_set_header Host ${MINIO_HOST}:${MINIO_API_PORT}; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # Frontend (dev/prod on same port) + location / { + proxy_pass http://${FRONTEND_HOST}:80; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # Vite HMR WebSocket support + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + } + + # New collab WebSocket path + location = /ws/doc { + return 301 /ws/doc/; + } + + location /ws/doc/ { + proxy_pass http://${COLLAB_HOST}:${COLLAB_GATEWAY_PORT}; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + } + + # RabbitMQ Management Interface + location /rabbitmq/ { + proxy_pass http://${RABBITMQ_HOST}:${RABBITMQ_MANAGEMENT_PORT}/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_redirect off; + + # Handle WebSocket connections for management UI + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + + + error_page 404 /404.html; + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} diff --git a/nginx/nginx.conf.tmpl b/nginx/nginx.conf.tmpl new file mode 100644 index 0000000..8c49e8c --- /dev/null +++ b/nginx/nginx.conf.tmpl @@ -0,0 +1,34 @@ +user nginx; +worker_processes auto; + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + #tcp_nopush on; + + keepalive_timeout 65; + + #gzip on; + + map $http_upgrade $connection_upgrade { + default upgrade; + '' close; + } + + include /etc/nginx/conf.d/*.conf; +} diff --git a/rabbitmq/rabbitmq.conf.tmpl b/rabbitmq/rabbitmq.conf.tmpl new file mode 100644 index 0000000..ae5bf7d --- /dev/null +++ b/rabbitmq/rabbitmq.conf.tmpl @@ -0,0 +1,19 @@ +# RabbitMQ Configuration File - Template + +# Default user and password +default_user = ${RABBITMQ_DEFAULT_USER} +default_pass = ${RABBITMQ_DEFAULT_PASS} + +# Management plugin +management.tcp.port = ${RABBITMQ_MANAGEMENT_PORT} + +# Networking +listeners.tcp.default = ${RABBITMQ_AMQP_PORT} + +# Memory-based DB configuration +vm_memory_high_watermark.relative = 0.6 + +# Logging +log.file.level = info + +# Keep RabbitMQ default auth mechanisms (PLAIN/AMQPLAIN) for broad client compatibility. diff --git a/redis/redis.conf.tmpl b/redis/redis.conf.tmpl new file mode 100644 index 0000000..58fbd77 --- /dev/null +++ b/redis/redis.conf.tmpl @@ -0,0 +1,3 @@ +bind 0.0.0.0 +protected-mode yes +requirepass ${REDIS_PASSWORD} diff --git a/script/configure.ps1 b/script/configure.ps1 new file mode 100644 index 0000000..2a088a2 --- /dev/null +++ b/script/configure.ps1 @@ -0,0 +1,111 @@ +#Requires -Version 5.1 +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition +$BasePath = (Resolve-Path "$ScriptDir\..\.." ).Path +$DeployDir = "$BasePath\deploy" +$EnvFile = "$DeployDir\.env" +$EnvExampleFile = "$DeployDir\.env.example" + +if (-not (Test-Path $EnvExampleFile)) { + Write-Error "Missing required file: $EnvExampleFile" + exit 1 +} + +if (-not (Test-Path $EnvFile)) { + Copy-Item $EnvExampleFile $EnvFile +} + +# Load key=value pairs from a .env file into a hashtable (last value wins) +function Read-EnvFile([string]$Path) { + $map = @{} + foreach ($line in (Get-Content $Path)) { + if ($line -match '^\s*#' -or $line.Trim() -eq '') { continue } + $idx = $line.IndexOf('=') + if ($idx -lt 1) { continue } + $map[$line.Substring(0, $idx).Trim()] = $line.Substring($idx + 1) + } + return $map +} + +# Write or update a single key in the .env file +function Update-EnvKey([string]$Key, [string]$Value) { + $lines = Get-Content $EnvFile + $updated = $false + $result = foreach ($line in $lines) { + if ($line -match "^$Key=") { "$Key=$Value"; $updated = $true } + else { $line } + } + if (-not $updated) { $result += "$Key=$Value" } + $result | Set-Content $EnvFile +} + +# Prompt the user for a value, showing the current default in brackets +function Prompt-Value([string]$Key, [string]$Label, [string]$Fallback) { + $current = if ($script:cfg.ContainsKey($Key) -and $script:cfg[$Key] -ne '') { + $script:cfg[$Key] + } else { $Fallback } + + $input = Read-Host "$Label [$current]" + if ([string]::IsNullOrEmpty($input)) { $input = $current } + + $script:cfg[$Key] = $input + Update-EnvKey $Key $input +} + +# Merge example defaults then overlay with actual .env values +$script:cfg = Read-EnvFile $EnvExampleFile +foreach ($kv in (Read-EnvFile $EnvFile).GetEnumerator()) { + $script:cfg[$kv.Key] = $kv.Value +} + +Write-Host "== Yoresee Deploy Config Init ==" +Write-Host "Press Enter to keep the default value in brackets." + +# Host exposed ports +Prompt-Value 'NGINX_HTTP_PORT' 'Host port for Nginx HTTP' '8080' +Prompt-Value 'NGINX_HTTPS_PORT' 'Host port for Nginx HTTPS' '8443' +Prompt-Value 'BACKEND_GRPC_HOST_PORT' 'Host port for backend gRPC' '9090' +Prompt-Value 'BACKEND_DEBUG_HOST_PORT' 'Host port for backend debug (dev only)' '2345' +Prompt-Value 'POSTGRES_HOST_PORT' 'Host port for Postgres (dev only)' '5432' +Prompt-Value 'REDIS_HOST_PORT' 'Host port for Redis (dev only)' '6379' +Prompt-Value 'RABBITMQ_AMQP_HOST_PORT' 'Host port for RabbitMQ AMQP (dev only)' '5672' +Prompt-Value 'CONSUL_HOST_PORT' 'Host port for Consul UI/API' '8500' +Prompt-Value 'ELASTICSEARCH_HOST_PORT' 'Host port for Elasticsearch (dev only)' '9200' + +# Credentials and app secrets +Prompt-Value 'POSTGRES_USER' 'Postgres user' 'root' +Prompt-Value 'POSTGRES_PASSWORD' 'Postgres password' 'your_password' +Prompt-Value 'POSTGRES_DB' 'Postgres database' 'yoresee_doc_db' +Prompt-Value 'REDIS_PASSWORD' 'Redis password' 'your_redis_password' +Prompt-Value 'RABBITMQ_DEFAULT_USER' 'RabbitMQ user' 'guest' +Prompt-Value 'RABBITMQ_DEFAULT_PASS' 'RabbitMQ password' 'guest' +Prompt-Value 'MINIO_ROOT_USER' 'MinIO root user' 'minioadmin' +Prompt-Value 'MINIO_ROOT_PASSWORD' 'MinIO root password' 'minioadmin' +Prompt-Value 'CONSUL_ROOT_TOKEN' 'Consul root token' 'yoresee_doc_root_token' +Prompt-Value 'BACKEND_INTERNAL_RPC_KEY' 'Internal RPC key' 'yoresee_doc_internal_key' +Prompt-Value 'JWT_SECRET' 'JWT secret' 'yoresee_doc_jwt_secret_key' + +# App behaviour +$defaultApiUrl = "http://localhost:$($script:cfg['NGINX_HTTP_PORT'])" +Prompt-Value 'VITE_API_BASE_URL' 'Frontend API base URL' $defaultApiUrl +Prompt-Value 'VITE_GRPC_WEB_ENDPOINT' 'Frontend gRPC-web endpoint' '/grpc' +Prompt-Value 'DIRTY_DOC_NOTIFY_THRESHOLD' 'Dirty doc notify threshold' '5' + +# Derived value — keep MinIO redirect bound to API base URL +$minioRedirect = "$($script:cfg['VITE_API_BASE_URL'].TrimEnd('/'))/minio" +Update-EnvKey 'MINIO_BROWSER_REDIRECT_URL' $minioRedirect +Write-Host "MinIO browser redirect URL (derived): $minioRedirect" + +Prompt-Value 'ELASTICSEARCH_ENABLED' 'Enable Elasticsearch' 'true' +Prompt-Value 'ELASTICSEARCH_INDEX_PREFIX' 'Elasticsearch index prefix' 'yoresee_doc' +Prompt-Value 'ELASTICSEARCH_USERNAME' 'Elasticsearch username (optional)' '' +Prompt-Value 'ELASTICSEARCH_PASSWORD' 'Elasticsearch password (optional)' '' + +Write-Host "Written: $EnvFile" + +& powershell -ExecutionPolicy Bypass -File "$ScriptDir\prepare.ps1" + +Write-Host "Initialization completed." +Write-Host "You can start services with: powershell -ExecutionPolicy Bypass -File deploy/script/start.ps1 dev up" diff --git a/script/configure.sh b/script/configure.sh new file mode 100755 index 0000000..edd3509 --- /dev/null +++ b/script/configure.sh @@ -0,0 +1,119 @@ +#!/bin/bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +BASE_PATH="$(cd "$SCRIPT_DIR/../.." && pwd)" +DEPLOY_DIR="$BASE_PATH/deploy" +ENV_FILE="$DEPLOY_DIR/.env" +ENV_EXAMPLE_FILE="$DEPLOY_DIR/.env.example" + +if [ ! -t 0 ]; then + echo "Interactive terminal is required." + echo "Fallback: copy deploy/.env.example to deploy/.env then run bash deploy/script/prepare.sh" + exit 1 +fi + +if [ ! -f "$ENV_EXAMPLE_FILE" ]; then + echo "Missing required file: $ENV_EXAMPLE_FILE" + exit 1 +fi + +if [ ! -f "$ENV_FILE" ]; then + cp "$ENV_EXAMPLE_FILE" "$ENV_FILE" +fi + +set -a +# shellcheck disable=SC1090 +source "$ENV_EXAMPLE_FILE" +# shellcheck disable=SC1090 +source "$ENV_FILE" +set +a + +update_env_key() { + local key="$1" + local value="$2" + local tmp_file="" + + tmp_file="$(mktemp)" + awk -v key="$key" -v val="$value" ' + BEGIN { updated = 0 } + $0 ~ "^" key "=" { + print key "=" val + updated = 1 + next + } + { print } + END { + if (!updated) { + print key "=" val + } + } + ' "$ENV_FILE" > "$tmp_file" + mv "$tmp_file" "$ENV_FILE" +} + +prompt_value() { + local key="$1" + local label="$2" + local fallback="$3" + local current_value="" + local input="" + + current_value="${!key:-$fallback}" + read -r -p "$label [$current_value]: " input + if [ -z "$input" ]; then + input="$current_value" + fi + + printf -v "$key" '%s' "$input" + update_env_key "$key" "$input" +} + +echo "== Yoresee Deploy Config Init ==" +echo "Press Enter to keep the default value in brackets." + +# Host exposed ports +prompt_value NGINX_HTTP_PORT "Host port for Nginx HTTP" "8080" +prompt_value NGINX_HTTPS_PORT "Host port for Nginx HTTPS" "8443" +prompt_value BACKEND_GRPC_HOST_PORT "Host port for backend gRPC" "9090" +prompt_value BACKEND_DEBUG_HOST_PORT "Host port for backend debug (dev only)" "2345" +prompt_value POSTGRES_HOST_PORT "Host port for Postgres (dev only)" "5432" +prompt_value REDIS_HOST_PORT "Host port for Redis (dev only)" "6379" +prompt_value RABBITMQ_AMQP_HOST_PORT "Host port for RabbitMQ AMQP (dev only)" "5672" +prompt_value CONSUL_HOST_PORT "Host port for Consul UI/API" "8500" +prompt_value ELASTICSEARCH_HOST_PORT "Host port for Elasticsearch (dev only)" "9200" + +# Credentials and app secrets +prompt_value POSTGRES_USER "Postgres user" "root" +prompt_value POSTGRES_PASSWORD "Postgres password" "your_password" +prompt_value POSTGRES_DB "Postgres database" "yoresee_doc_db" +prompt_value REDIS_PASSWORD "Redis password" "your_redis_password" +prompt_value RABBITMQ_DEFAULT_USER "RabbitMQ user" "guest" +prompt_value RABBITMQ_DEFAULT_PASS "RabbitMQ password" "guest" +prompt_value MINIO_ROOT_USER "MinIO root user" "minioadmin" +prompt_value MINIO_ROOT_PASSWORD "MinIO root password" "minioadmin" +prompt_value CONSUL_ROOT_TOKEN "Consul root token" "yoresee_doc_root_token" +prompt_value BACKEND_INTERNAL_RPC_KEY "Internal RPC key" "yoresee_doc_internal_key" +prompt_value JWT_SECRET "JWT secret" "yoresee_doc_jwt_secret_key" + +# App behavior +prompt_value VITE_API_BASE_URL "Frontend API base URL" "http://localhost:${NGINX_HTTP_PORT}" +prompt_value VITE_GRPC_WEB_ENDPOINT "Frontend gRPC-web endpoint" "/grpc" +prompt_value DIRTY_DOC_NOTIFY_THRESHOLD "Dirty doc notify threshold" "5" + +# Keep MinIO browser redirect bound to API base URL to avoid host drift in deployments. +MINIO_BROWSER_REDIRECT_URL="${VITE_API_BASE_URL%/}/minio" +update_env_key MINIO_BROWSER_REDIRECT_URL "$MINIO_BROWSER_REDIRECT_URL" +echo "MinIO browser redirect URL (derived): $MINIO_BROWSER_REDIRECT_URL" + +prompt_value ELASTICSEARCH_ENABLED "Enable Elasticsearch" "true" +prompt_value ELASTICSEARCH_INDEX_PREFIX "Elasticsearch index prefix" "yoresee_doc" +prompt_value ELASTICSEARCH_USERNAME "Elasticsearch username (optional)" "" +prompt_value ELASTICSEARCH_PASSWORD "Elasticsearch password (optional)" "" + +echo "Written: $ENV_FILE" + +bash "$SCRIPT_DIR/prepare.sh" + +echo "Initialization completed." +echo "You can start services with: bash deploy/script/start.sh dev up" diff --git a/script/gen_proto.ps1 b/script/gen_proto.ps1 new file mode 100644 index 0000000..defcbf6 --- /dev/null +++ b/script/gen_proto.ps1 @@ -0,0 +1,20 @@ +#Requires -Version 5.1 +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition +$DeployDir = Resolve-Path "$ScriptDir\.." + +if (Get-Command buf -ErrorAction SilentlyContinue) { + Write-Host "buf found locally, generating directly..." + Push-Location "$DeployDir\..\proto" + buf generate --template buf.gen.backend.yaml + buf generate --template buf.gen.collab-go.yaml + buf generate --template buf.gen.frontend.yaml + buf generate --template buf.gen.node.yaml + Pop-Location + Write-Host "Proto generation complete." +} else { + Write-Host "buf not found locally, using proto-gen container..." + docker compose -f "$DeployDir\docker-compose.dev.yml" run --rm proto-gen +} diff --git a/script/gen_proto.sh b/script/gen_proto.sh new file mode 100755 index 0000000..229f989 --- /dev/null +++ b/script/gen_proto.sh @@ -0,0 +1,18 @@ +#!/bin/bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DEPLOY_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +if command -v buf &>/dev/null; then + echo "buf found locally, generating directly..." + cd "$DEPLOY_DIR/../proto" + buf generate --template buf.gen.backend.yaml + buf generate --template buf.gen.collab-go.yaml + buf generate --template buf.gen.frontend.yaml + buf generate --template buf.gen.node.yaml + echo "Proto generation complete." +else + echo "buf not found locally, using proto-gen container..." + docker compose -f "$DEPLOY_DIR/docker-compose.dev.yml" run --rm proto-gen +fi diff --git a/script/prepare.ps1 b/script/prepare.ps1 new file mode 100644 index 0000000..a2eb2aa --- /dev/null +++ b/script/prepare.ps1 @@ -0,0 +1,86 @@ +#Requires -Version 5.1 +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition +$BasePath = (Resolve-Path "$ScriptDir\..\.." ).Path +$DeployDir = "$BasePath\deploy" +$EnvExampleFile = "$DeployDir\.env.example" +$EnvFile = "$DeployDir\.env" + +if (-not (Test-Path $EnvExampleFile)) { + Write-Error "Missing required file: $EnvExampleFile" + exit 1 +} + +if (-not (Test-Path $EnvFile)) { + Write-Host "deploy\.env not found, creating from .env.example" + Copy-Item $EnvExampleFile $EnvFile +} + +# Load key=value pairs from a .env file into a hashtable (last value wins) +function Read-EnvFile([string]$Path) { + $map = @{} + foreach ($line in (Get-Content $Path)) { + if ($line -match '^\s*#' -or $line.Trim() -eq '') { continue } + $idx = $line.IndexOf('=') + if ($idx -lt 1) { continue } + $map[$line.Substring(0, $idx).Trim()] = $line.Substring($idx + 1) + } + return $map +} + +# Expand ${VAR} placeholders in a template string using the provided env hashtable +function Expand-Template([string]$Text, [hashtable]$Env) { + return [regex]::Replace($Text, '\$\{([A-Z0-9_]+)\}', { + param($m) + $key = $m.Groups[1].Value + if ($Env.ContainsKey($key)) { $Env[$key] } else { '' } + }) +} + +# Render a template file to an output path, substituting ${VAR} with env values +function Render-Template([string]$Template, [string]$Output, [hashtable]$Env) { + if (-not (Test-Path $Template)) { + Write-Error "Template not found: $Template" + return + } + + $outDir = Split-Path -Parent $Output + if (-not (Test-Path $outDir)) { New-Item -ItemType Directory -Force -Path $outDir | Out-Null } + + # If a directory exists at the output path, back it up + if (Test-Path $Output -PathType Container) { + $backup = "$Output.backup.$([DateTimeOffset]::UtcNow.ToUnixTimeSeconds())" + Move-Item $Output $backup + Write-Host "Detected directory at $Output, moved to $backup" + } + + $content = Get-Content $Template -Raw + $rendered = Expand-Template $content $Env + [System.IO.File]::WriteAllText($Output, $rendered) +} + +# Merge example defaults then overlay with actual .env values +$env_map = Read-EnvFile $EnvExampleFile +foreach ($kv in (Read-EnvFile $EnvFile).GetEnumerator()) { + $env_map[$kv.Key] = $kv.Value +} + +$templateMappings = @( + @{ Template = "$BasePath\backend\config.toml.tmpl"; Output = "$BasePath\backend\config.toml" } + @{ Template = "$BasePath\frontend\nginx.conf.tmpl"; Output = "$BasePath\frontend\nginx.conf" } + @{ Template = "$DeployDir\nginx\nginx.conf.tmpl"; Output = "$DeployDir\nginx\nginx.conf" } + @{ Template = "$DeployDir\nginx\conf.d\default.conf.tmpl"; Output = "$DeployDir\nginx\conf.d\default.conf" } + @{ Template = "$DeployDir\redis\redis.conf.tmpl"; Output = "$DeployDir\redis\redis.conf" } + @{ Template = "$DeployDir\rabbitmq\rabbitmq.conf.tmpl"; Output = "$DeployDir\rabbitmq\rabbitmq.conf" } +) + +Write-Host "Rendering configuration files from templates..." +foreach ($m in $templateMappings) { + Render-Template $m.Template $m.Output $env_map + $rel = $m.Output.Replace("$BasePath\", '') + Write-Host " - $rel" +} + +Write-Host "Configuration preparation completed!" diff --git a/script/prepare.sh b/script/prepare.sh new file mode 100755 index 0000000..99c519a --- /dev/null +++ b/script/prepare.sh @@ -0,0 +1,75 @@ +#!/bin/bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +BASE_PATH="$(cd "$SCRIPT_DIR/../.." && pwd)" +DEPLOY_DIR="$BASE_PATH/deploy" +ENV_EXAMPLE_FILE="$DEPLOY_DIR/.env.example" +ENV_FILE="$DEPLOY_DIR/.env" + +if [ ! -f "$ENV_EXAMPLE_FILE" ]; then + echo "Missing required file: $ENV_EXAMPLE_FILE" + exit 1 +fi + +if [ ! -f "$ENV_FILE" ]; then + echo "deploy/.env not found, creating from .env.example" + cp "$ENV_EXAMPLE_FILE" "$ENV_FILE" +fi + +set -a +# shellcheck disable=SC1090 +source "$ENV_EXAMPLE_FILE" +# shellcheck disable=SC1090 +source "$ENV_FILE" +set +a + +prepare_target_file() { + local target="$1" + if [ -d "$target" ]; then + local backup="${target}.backup.$(date +%s)" + mv "$target" "$backup" + echo "Detected directory at $target, moved to $backup" + fi +} + +render_template() { + local template="$1" + local output="$2" + local tmp_file="" + + if [ ! -f "$template" ]; then + echo "Template not found: $template" + return 1 + fi + + mkdir -p "$(dirname "$output")" + prepare_target_file "$output" + + tmp_file="$(mktemp)" + perl -pe 's/\$\{([A-Z0-9_]+)\}/defined $ENV{$1} ? $ENV{$1} : ""/ge' "$template" > "$tmp_file" + mv "$tmp_file" "$output" +} + +TEMPLATE_MAPPINGS=( + "$BASE_PATH/backend/config.toml.tmpl:$BASE_PATH/backend/config.toml" + "$BASE_PATH/frontend/nginx.conf.tmpl:$BASE_PATH/frontend/nginx.conf" + "$DEPLOY_DIR/nginx/nginx.conf.tmpl:$DEPLOY_DIR/nginx/nginx.conf" + "$DEPLOY_DIR/nginx/conf.d/default.conf.tmpl:$DEPLOY_DIR/nginx/conf.d/default.conf" + "$DEPLOY_DIR/redis/redis.conf.tmpl:$DEPLOY_DIR/redis/redis.conf" + "$DEPLOY_DIR/rabbitmq/rabbitmq.conf.tmpl:$DEPLOY_DIR/rabbitmq/rabbitmq.conf" +) + +echo "Rendering configuration files from templates..." +for mapping in "${TEMPLATE_MAPPINGS[@]}"; do + template="${mapping%%:*}" + output="${mapping#*:}" + render_template "$template" "$output" + rel_output="${output#$BASE_PATH/}" + if [ "$rel_output" = "$output" ]; then + rel_output="$output" + fi + echo " - $rel_output" +done + +echo "Configuration preparation completed!" diff --git a/script/start.ps1 b/script/start.ps1 new file mode 100644 index 0000000..29bed55 --- /dev/null +++ b/script/start.ps1 @@ -0,0 +1,61 @@ +#Requires -Version 5.1 +param( + [Parameter(Mandatory)][ValidateSet('dev','release')][string]$Environment, + [Parameter(Mandatory)][ValidateSet('rebuild','clear','restart','up','build')][string]$Action +) +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition +$BasePath = (Resolve-Path "$ScriptDir\..\.." ).Path +$DeployDir = "$BasePath\deploy" + +Write-Host "Environment: $Environment" +Write-Host "Action: $Action" +Write-Host "Base path: $BasePath" + +$ComposeFile = if ($Environment -eq 'dev') { 'docker-compose.dev.yml' } else { 'docker-compose.yml' } +Write-Host "Compose file: $ComposeFile" + +if ($Environment -eq 'dev') { + Write-Host "Generating proto code..." + docker compose -f "$DeployDir\$ComposeFile" run --rm proto-gen + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } +} + +Push-Location $DeployDir +try { + switch ($Action) { + 'rebuild' { + Write-Host "Rebuilding and starting services..." + docker compose -f $ComposeFile down -v + docker compose -f $ComposeFile up -d --build + } + 'clear' { + Write-Host "Clearing containers and volumes..." + docker compose -f $ComposeFile down -v + } + 'restart' { + Write-Host "Restarting services..." + docker compose -f $ComposeFile down + docker compose -f $ComposeFile up -d + } + 'up' { + Write-Host "Starting services..." + docker compose -f $ComposeFile up -d + } + 'build' { + Write-Host "Building images..." + docker compose -f $ComposeFile build + } + } +} finally { + Pop-Location +} + +if ($LASTEXITCODE -ne 0) { + Write-Error "Operation failed." + exit $LASTEXITCODE +} + +Write-Host "Operation completed successfully." diff --git a/script/start.sh b/script/start.sh new file mode 100755 index 0000000..4ff8899 --- /dev/null +++ b/script/start.sh @@ -0,0 +1,91 @@ +#!/bin/bash + +# Check parameter count +if [ $# -lt 2 ]; then + echo "Usage: $0 " + echo " dev|release: Use docker-compose.dev.yml or docker-compose.yml" + echo " rebuild: Clean containers and volumes, then rebuild and start" + echo " clear: Clean containers and volumes" + echo " restart: Stop and restart services" + echo " up: Start services" + echo " build: Build images without starting" + exit 1 +fi + +ENVIRONMENT=$1 +ACTION=$2 + +# Parameter validation +if [[ "$ENVIRONMENT" != "dev" && "$ENVIRONMENT" != "release" ]]; then + echo "Error: First argument must be 'dev' or 'release'" + exit 1 +fi + +if [[ "$ACTION" != "rebuild" && "$ACTION" != "clear" && "$ACTION" != "restart" && "$ACTION" != "up" && "$ACTION" != "build" ]]; then + echo "Error: Second argument must be 'rebuild', 'clear', 'restart', 'up', or 'build'" + exit 1 +fi + +ORIGINAL_DIR=$(pwd) +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +BASE_PATH="$(cd "$SCRIPT_DIR/../.." && pwd)" + +echo "Environment: $ENVIRONMENT" +echo "Action: $ACTION" +echo "Script directory: $SCRIPT_DIR" +echo "Base path: $BASE_PATH" + +cd "$BASE_PATH"/deploy/ + +# Select compose file based on environment +if [ "$ENVIRONMENT" = "dev" ]; then + COMPOSE_FILE="docker-compose.dev.yml" +else + COMPOSE_FILE="docker-compose.yml" +fi + +echo "Using compose file: $COMPOSE_FILE" + +if [ "$ENVIRONMENT" = "dev" ]; then + echo "Generating proto code..." + docker compose -f "$COMPOSE_FILE" run --rm proto-gen +fi + +case "$ACTION" in + "rebuild") + echo "Rebuilding and starting services..." + docker compose -f "$COMPOSE_FILE" down -v + docker compose -f "$COMPOSE_FILE" up -d --build + ;; + "clear") + echo "Clearing containers and volumes..." + docker compose -f "$COMPOSE_FILE" down -v + ;; + "restart") + echo "Restarting services..." + docker compose -f "$COMPOSE_FILE" down + docker compose -f "$COMPOSE_FILE" up -d + ;; + "up") + echo "Starting services..." + docker compose -f "$COMPOSE_FILE" up -d + ;; + "build") + echo "Building images..." + docker compose -f "$COMPOSE_FILE" build + ;; + *) + echo "Unknown action: $ACTION" + cd "$ORIGINAL_DIR" + exit 1 + ;; +esac + +cd "$ORIGINAL_DIR" + +if [ $? -eq 0 ]; then + echo "Operation completed successfully." +else + echo "Operation failed." + exit 1 +fi