diff --git a/.github/workflows/backend.yaml b/.github/workflows/backend.yaml index ea63340a..1c343a05 100644 --- a/.github/workflows/backend.yaml +++ b/.github/workflows/backend.yaml @@ -11,6 +11,10 @@ on: - .github/** - backend/** +defaults: + run: + working-directory: ./backend + env: REGISTRY: ghcr.io IMAGE_NAME: backend @@ -30,7 +34,7 @@ jobs: - name: Run unit tests run: | - cd ./backend && go test ./... -v -race -cover + go test ./... -v -race -cover - name: Provide image name and version run: | @@ -41,52 +45,52 @@ jobs: - name: Build image run: | - cd ./backend && docker build . --file Dockerfile --target production --tag $IMAGE_ID:$IMAGE_VERSION --tag $IMAGE_ID:latest - - cd: - runs-on: ubuntu-latest - - # This job will be invoked only on default branch - if: ${{ always() && format('refs/heads/{0}', github.event.repository.default_branch) == github.ref }} - - permissions: - packages: write - contents: read - - needs: - - ci - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Provide image name and version - run: | - IMAGE_ID=$(echo $REGISTRY/${{ github.repository_owner }}/$IMAGE_NAME | tr '[A-Z]' '[a-z]') - IMAGE_VERSION=${{ github.sha }} - echo "IMAGE_ID=$IMAGE_ID" >> "$GITHUB_ENV" - echo "IMAGE_VERSION=$IMAGE_VERSION" >> "$GITHUB_ENV" - - - name: Build image - run: | - cd ./backend && docker build . --file Dockerfile --target production --tag $IMAGE_ID:$IMAGE_VERSION --tag $IMAGE_ID:latest - - - name: Log in to registry - run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin - - - name: Push image - run: | - docker push $IMAGE_ID:$IMAGE_VERSION - docker push $IMAGE_ID:latest - - - uses: actions/setup-node@v3 - with: - node-version: "18" - - - name: deploy to production - env: - LIARA_TOKEN: ${{ secrets.LIARA_API_TOKEN }} - LIARA_BACKEND_APP_NAME: ${{secrets.LIARA_BACKEND_APP_NAME}} - run: | - npm i -g @liara/cli@7 - liara deploy --image $IMAGE_ID:$IMAGE_VERSION --platform=docker --port="80" --app="$LIARA_BACKEND_APP_NAME" --api-token="$LIARA_TOKEN" --detach + docker build . --file Dockerfile --target production --tag $IMAGE_ID:$IMAGE_VERSION --tag $IMAGE_ID:latest + + # cd: + # runs-on: ubuntu-latest + + # # This job will be invoked only on default branch + # if: ${{ always() && format('refs/heads/{0}', github.event.repository.default_branch) == github.ref }} + + # permissions: + # packages: write + # contents: read + + # needs: + # - ci + + # steps: + # - name: Checkout code + # uses: actions/checkout@v4 + + # - name: Provide image name and version + # run: | + # IMAGE_ID=$(echo $REGISTRY/${{ github.repository_owner }}/$IMAGE_NAME | tr '[A-Z]' '[a-z]') + # IMAGE_VERSION=${{ github.sha }} + # echo "IMAGE_ID=$IMAGE_ID" >> "$GITHUB_ENV" + # echo "IMAGE_VERSION=$IMAGE_VERSION" >> "$GITHUB_ENV" + + # - name: Build image + # run: | + # docker build . --file Dockerfile --target production --tag $IMAGE_ID:$IMAGE_VERSION --tag $IMAGE_ID:latest + + # - name: Log in to registry + # run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + + # - name: Push image + # run: | + # docker push $IMAGE_ID:$IMAGE_VERSION + # docker push $IMAGE_ID:latest + + # - uses: actions/setup-node@v3 + # with: + # node-version: "18" + + # - name: deploy to production + # env: + # LIARA_TOKEN: ${{ secrets.LIARA_API_TOKEN }} + # LIARA_BACKEND_APP_NAME: ${{secrets.LIARA_BACKEND_APP_NAME}} + # run: | + # npm i -g @liara/cli@7 + # liara deploy --image $IMAGE_ID:$IMAGE_VERSION --platform=docker --port="80" --app="$LIARA_BACKEND_APP_NAME" --api-token="$LIARA_TOKEN" --detach diff --git a/.github/workflows/frontend.yaml b/.github/workflows/frontend.yaml index dfff995a..16a1bb1d 100644 --- a/.github/workflows/frontend.yaml +++ b/.github/workflows/frontend.yaml @@ -11,6 +11,10 @@ on: - .github/** - frontend/** +defaults: + run: + working-directory: ./frontend + env: REGISTRY: ghcr.io IMAGE_NAME: frontend @@ -36,52 +40,52 @@ jobs: - name: Build image run: | - cd ./frontend && docker build . --file Dockerfile --target production --tag $IMAGE_ID:$IMAGE_VERSION --tag $IMAGE_ID:latest - - cd: - runs-on: ubuntu-latest - - # This job will be invoked only on default branch - if: ${{ always() && format('refs/heads/{0}', github.event.repository.default_branch) == github.ref }} - - permissions: - packages: write - contents: read - - needs: - - ci - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Provide image name and version - run: | - IMAGE_ID=$(echo $REGISTRY/${{ github.repository_owner }}/$IMAGE_NAME | tr '[A-Z]' '[a-z]') - IMAGE_VERSION=${{ github.sha }} - echo "IMAGE_ID=$IMAGE_ID" >> "$GITHUB_ENV" - echo "IMAGE_VERSION=$IMAGE_VERSION" >> "$GITHUB_ENV" - - - name: Build image - run: | - cd ./frontend && docker build . --file Dockerfile --target production --tag $IMAGE_ID:$IMAGE_VERSION --tag $IMAGE_ID:latest - - - name: Log in to registry - run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin - - - name: Push image - run: | - docker push $IMAGE_ID:$IMAGE_VERSION - docker push $IMAGE_ID:latest - - - uses: actions/setup-node@v3 - with: - node-version: "18" - - - name: deploy to production - env: - LIARA_TOKEN: ${{ secrets.LIARA_API_TOKEN }} - LIARA_FRONTEND_APP_NAME: ${{secrets.LIARA_FRONTEND_APP_NAME}} - run: | - npm i -g @liara/cli@7 - liara deploy --image $IMAGE_ID:$IMAGE_VERSION --platform=docker --port="3000" --app="$LIARA_FRONTEND_APP_NAME" --api-token="$LIARA_TOKEN" --detach + docker build . --file Dockerfile --target production --tag $IMAGE_ID:$IMAGE_VERSION --tag $IMAGE_ID:latest + + # cd: + # runs-on: ubuntu-latest + + # # This job will be invoked only on default branch + # if: ${{ always() && format('refs/heads/{0}', github.event.repository.default_branch) == github.ref }} + + # permissions: + # packages: write + # contents: read + + # needs: + # - ci + + # steps: + # - name: Checkout code + # uses: actions/checkout@v4 + + # - name: Provide image name and version + # run: | + # IMAGE_ID=$(echo $REGISTRY/${{ github.repository_owner }}/$IMAGE_NAME | tr '[A-Z]' '[a-z]') + # IMAGE_VERSION=${{ github.sha }} + # echo "IMAGE_ID=$IMAGE_ID" >> "$GITHUB_ENV" + # echo "IMAGE_VERSION=$IMAGE_VERSION" >> "$GITHUB_ENV" + + # - name: Build image + # run: | + # docker build . --file Dockerfile --target production --tag $IMAGE_ID:$IMAGE_VERSION --tag $IMAGE_ID:latest + + # - name: Log in to registry + # run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + + # - name: Push image + # run: | + # docker push $IMAGE_ID:$IMAGE_VERSION + # docker push $IMAGE_ID:latest + + # - uses: actions/setup-node@v3 + # with: + # node-version: "18" + + # - name: deploy to production + # env: + # LIARA_TOKEN: ${{ secrets.LIARA_API_TOKEN }} + # LIARA_FRONTEND_APP_NAME: ${{secrets.LIARA_FRONTEND_APP_NAME}} + # run: | + # npm i -g @liara/cli@7 + # liara deploy --image $IMAGE_ID:$IMAGE_VERSION --platform=docker --port="3000" --app="$LIARA_FRONTEND_APP_NAME" --api-token="$LIARA_TOKEN" --detach diff --git a/.github/workflows/infrastructure.yaml b/.github/workflows/infrastructure.yaml new file mode 100644 index 00000000..1ce824a6 --- /dev/null +++ b/.github/workflows/infrastructure.yaml @@ -0,0 +1,147 @@ +name: Infrastructure CI and CD +on: + push: + branches: + - main + paths: + - .github/** + - infrastructure/** + pull_request: + paths: + - .github/** + - infrastructure/** + +defaults: + run: + working-directory: ./infrastructure + +env: + TF_VAR_project_name: tarhche + TF_VAR_instance_name: backend + + EC2_SSH_ADDRESS: ${{ secrets.EC2_SSH_ADDRESS }} + +jobs: + ci: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_REGION }} + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + + - name: Terraform Format + id: fmt + run: terraform fmt -check + + - name: Terraform Init + id: init + run: terraform init + + - name: Terraform Validate + id: validate + run: terraform validate -no-color + + - name: Terraform Plan + run: terraform plan -no-color -input=false + continue-on-error: false + + cd: + runs-on: ubuntu-latest + + # This job will be invoked only on default branch + if: ${{ always() && format('refs/heads/{0}', github.event.repository.default_branch) == github.ref }} + + needs: + - ci + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_REGION }} + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + + - name: Terraform Apply + run: terraform apply -auto-approve -input=false + continue-on-error: false + + - name: Deploy services + run: | + # setup ssh key + echo "${{ secrets.EC2_SSH_PRIVATE_KEY }}" | base64 --decode > ~/ec2-key.pem + chmod 400 ~/ec2-key.pem + + # copy files + scp -i ~/ec2-key.pem -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ./* ubuntu@${{ secrets.EC2_PUBLIC_IP }}:/opt/deployment/ + + # connect and deploy services + ssh -i ~/ec2-key.pem -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ubuntu@${{ secrets.EC2_PUBLIC_IP }} << 'EOF' + VOLUME_PATH="${{ secrets.VOLUME_PATH }}" + + MONGO_USERNAME="${{ secrets.MONGO_USERNAME }}" + MONGO_PASSWORD="${{ secrets.MONGO_PASSWORD }}" + + DASHBOARD_MONGO_USERNAME="${{ secrets.DASHBOARD_MONGO_USERNAME }}" + DASHBOARD_MONGO_PASSWORD="${{ secrets.DASHBOARD_MONGO_PASSWORD }}" + DASHBOARD_MONGO_MONGODB_URL="mongodb://${{ secrets.MONGO_USERNAME }}:${{ secrets.MONGO_PASSWORD }}@mongodb:27017" + + BACKEND_NATS_URL="${{ secrets.BACKEND_NATS_URL }}" + BACKEND_PRIVATE_KEY="${{ secrets.BACKEND_PRIVATE_KEY }}" + + BACKEND_MONGO_HOST="mongodb" + BACKEND_MONGO_PORT="27017" + BACKEND_MONGO_SCHEME="mongodb" + BACKEND_MONGO_DATABASE_NAME="${{ secrets.BACKEND_MONGO_DATABASE_NAME }}" + BACKEND_MONGO_USERNAME="${{ secrets.MONGO_USERNAME }}" + BACKEND_MONGO_PASSWORD="${{ secrets.MONGO_PASSWORD }}" + + BACKEND_MAIL_SMTP_PASSWORD="${{ secrets.BACKEND_MAIL_SMTP_PASSWORD }}" + BACKEND_MAIL_SMTP_HOST="${{ secrets.BACKEND_MAIL_SMTP_HOST }}" + BACKEND_MAIL_SMTP_FROM="${{ secrets.BACKEND_MAIL_SMTP_FROM }}" + BACKEND_MAIL_SMTP_USERNAME="${{ secrets.BACKEND_MAIL_SMTP_USERNAME }}" + BACKEND_MAIL_SMTP_PORT="${{ secrets.BACKEND_MAIL_SMTP_PORT }}" + + BACKEND_S3_ENDPOINT="${{ secrets.BACKEND_S3_ENDPOINT }}" + BACKEND_S3_SECRET_KEY="${{ secrets.BACKEND_S3_SECRET_KEY }}" + BACKEND_S3_ACCESS_KEY="${{ secrets.BACKEND_S3_ACCESS_KEY }}" + BACKEND_S3_USE_SSL="${{ secrets.BACKEND_S3_USE_SSL }}" + BACKEND_S3_BUCKET_NAME="${{ secrets.BACKEND_S3_BUCKET_NAME }}" + + APP_IMAGE="${{ secrets.APP_IMAGE }}" + + PORTAINER_ADMIN_PASSWORD="${{ secrets.PORTAINER_ADMIN_PASSWORD }}" + + FRONTEND_IMAGE="${{ secrets.FRONTEND_IMAGE }}" + NEXT_PUBLIC_EXTERNAL_BACKEND_BASE_URL="${{ secrets.NEXT_PUBLIC_EXTERNAL_BACKEND_BASE_URL }}" + INTERNAL_BACKEND_BASE_URL="${{ secrets.INTERNAL_BACKEND_BASE_URL }}" + NEXT_PUBLIC_FILES_BASE_URL="${{ secrets.NEXT_PUBLIC_FILES_BASE_URL }}" + + # Run Docker Compose + cd /opt/deployment/ + + docker compose \ + -f compose.mongodb.yaml \ + -f compose.nats.yaml \ + -f compose.docker.yaml \ + -f compose.backend.yaml \ + -f compose.frontend.yaml \ + -f compose.proxy.yaml \ + up -d + EOF diff --git a/.gitignore b/.gitignore index c899b176..2fc028ce 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ -.idea .DS_Store +.vscode +.idea /private /private.pub diff --git a/backend/.gitignore b/backend/.gitignore index 070fa0bd..1a637777 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1,3 +1,4 @@ +.DS_Store .vscode .idea /tmp \ No newline at end of file diff --git a/compose.yaml b/compose.yaml index 62d60e81..70b34230 100644 --- a/compose.yaml +++ b/compose.yaml @@ -4,6 +4,7 @@ x-app: &app context: ./backend dockerfile: Dockerfile target: develop + restart: unless-stopped depends_on: - mongodb - minio @@ -36,6 +37,7 @@ services: context: ./frontend dockerfile: Dockerfile target: develop + restart: unless-stopped depends_on: - app ports: @@ -57,8 +59,6 @@ services: runner: <<: *app - volumes: - - podman-data:/tmp ports: - "8001:80" command: > @@ -66,7 +66,7 @@ services: mongodb: image: mongo - restart: always + restart: unless-stopped ports: - "27017:27017" environment: @@ -75,6 +75,7 @@ services: mongodashboard: image: mongo-express + restart: unless-stopped depends_on: - mongodb ports: @@ -86,6 +87,7 @@ services: minio: image: minio/minio + restart: unless-stopped ports: - "9000:9000" - "9001:9001" @@ -116,17 +118,25 @@ services: ports: - "8082:80" - podman: - image: quay.io/podman/stable - volumes: - - podman-data:/tmp - security_opt: - - label=disable - devices: - - /dev/fuse - user: podman - command: > - podman system service --time=0 unix:///tmp/podman.sock + docker: + image: docker:27-dind + restart: unless-stopped + privileged: true + ports: + - "2375:2375" + environment: + DOCKER_TLS_CERTDIR: "" # disable certs -volumes: - podman-data: + portainer: + image: portainer/portainer-ce + restart: unless-stopped + depends_on: + - docker + ports: + - "9100:9000" + - "9443:9443" + command: > + --admin-password="$$2a$$12$$4xcOa82Ni5rjgQF.v.JWi.i71OyUm3fwmfWiumgJHIAPGU.uOw3qu" + # username: admin + # password: admin-password + # standalone docker API url: docker:2375 diff --git a/infrastructure/.gitignore b/infrastructure/.gitignore new file mode 100644 index 00000000..15044452 --- /dev/null +++ b/infrastructure/.gitignore @@ -0,0 +1,13 @@ +.DS_Store +.vscode +.idea +/tmp + +# SSH keys +/*.pem +/*.pub + +# Terraform files +*.tfstate +*.tfstate.backup +.terraform/ diff --git a/infrastructure/.terraform.lock.hcl b/infrastructure/.terraform.lock.hcl new file mode 100644 index 00000000..d3e4f5ce --- /dev/null +++ b/infrastructure/.terraform.lock.hcl @@ -0,0 +1,24 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "5.82.1" + hashes = [ + "h1:QTOtDMehUfiD3wDbbDuXYuTqGgLDkKK9Agkd5NCUEic=", + "zh:0fde8533282973f1f5d33b2c4f82d962a2c78860d39b42ac20a9ce399f06f62c", + "zh:1fd1a252bffe91668f35be8eac4e0a980f022120254eae1674c3c05049aff88a", + "zh:31bbd380cd7d74bf9a8c961fc64da4222bed40ffbdb27b011e637fa8b2d33641", + "zh:333ee400cf6f62fa199dc1270bf8efac6ffe56659f86918070b8351b8636e03b", + "zh:42ea9fee0a152d344d548eab43583299a13bcd73fae9e53e7e1a708720ac1315", + "zh:4b78f25a8cda3316eb56aa01909a403ec2f325a2eb0512c9a73966068c26cf29", + "zh:5e9cf9a275eda8f7940a41e32abe0b92ba76b5744def4af5124b343b5f33eb94", + "zh:6a46c8630c16b9e1338c2daed6006118db951420108b58b8b886403c69317439", + "zh:6efe11cf1a01f98a8d8043cdcd8c0ee5fe93a0e582c2b69ebb73ea073f5068c3", + "zh:88ab5c768c7d8133dab94eff48071e764424ad2b7cfeee5abe6d5bb16e4b85c6", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:a614beb312574342b27dbc34d65b450997f63fa3e948d0d30f441e4f69337380", + "zh:c1f486e27130610a9b64cacb0bd928009c433d62b3be515488185e6467b4aa1f", + "zh:dccd166e89e1a02e7ce658df3c42d040edec4b09c6f7906aa5743938518148b1", + "zh:e75a3ae0fb42b7ea5a0bb5dffd8f8468004c9700fcc934eb04c264fda2ba9984", + ] +} diff --git a/infrastructure/Makefile b/infrastructure/Makefile new file mode 100644 index 00000000..7d6a734f --- /dev/null +++ b/infrastructure/Makefile @@ -0,0 +1,110 @@ +export TF_VAR_project_name = tarhche +export TF_VAR_instance_name = backend + +export EC2_SSH_ADDRESS = +export VOLUME_PATH = ./tmp/volume_01 + +export MONGO_USERNAME = test +export MONGO_PASSWORD = test‍ + +export DASHBOARD_MONGO_USERNAME = username +export DASHBOARD_MONGO_PASSWORD = password +export DASHBOARD_MONGO_MONGODB_URL = mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@mongodb:27017 + +export BACKEND_NATS_URL = +export BACKEND_PRIVATE_KEY = + +export BACKEND_MONGO_HOST = mongodb +export BACKEND_MONGO_PORT = 27017 +export BACKEND_MONGO_SCHEME = mongodb +export BACKEND_MONGO_DATABASE_NAME = test +export BACKEND_MONGO_USERNAME = ${MONGO_USERNAME} +export BACKEND_MONGO_PASSWORD = ${MONGO_PASSWORD} + +export BACKEND_MAIL_SMTP_PASSWORD = +export BACKEND_MAIL_SMTP_HOST = +export BACKEND_MAIL_SMTP_FROM = +export BACKEND_MAIL_SMTP_USERNAME = +export BACKEND_MAIL_SMTP_PORT = + +export BACKEND_S3_ENDPOINT = +export BACKEND_S3_SECRET_KEY = +export BACKEND_S3_ACCESS_KEY = +export BACKEND_S3_USE_SSL = false +export BACKEND_S3_BUCKET_NAME = + +export APP_IMAGE = ghcr.io/tarhche/backend:latest + +# username: admin +# password: admin-password (in bcrypt, a dollar-sign should be escaped by an arbitrary dollar-sign ($ --> $$)) +export PORTAINER_ADMIN_PASSWORD = $$2a$$12$$4xcOa82Ni5rjgQF.v.JWi.i71OyUm3fwmfWiumgJHIAPGU.uOw3qu + +export FRONTEND_IMAGE = ghcr.io/tarhche/frontend:latest +export NEXT_PUBLIC_EXTERNAL_BACKEND_BASE_URL = +export INTERNAL_BACKEND_BASE_URL = +export NEXT_PUBLIC_FILES_BASE_URL = + +validate: + terraform validate + +fmt: + terraform fmt + +init: + terraform init + +state: + terraform state list + +plan: + terraform plan + +apply: + terraform apply + rm -f terraform.tfstate *.tfstate.* + +public_key: + ssh-keygen -y -f ssh-private-key.pem > ssh-public-key.pub + +ssh: + ssh -i "ssh-private-key.pem" ${EC2_SSH_ADDRESS} + +up: + docker compose \ + -f compose.mongodb.yaml \ + -f compose.nats.yaml \ + -f compose.docker.yaml \ + -f compose.backend.yaml \ + -f compose.frontend.yaml \ + -f compose.proxy.yaml \ + up -d + +down: + docker compose \ + -f compose.mongodb.yaml \ + -f compose.nats.yaml \ + -f compose.docker.yaml \ + -f compose.backend.yaml \ + -f compose.frontend.yaml \ + -f compose.proxy.yaml \ + down --volumes --remove-orphans + +ps: + docker compose \ + -f compose.mongodb.yaml \ + -f compose.nats.yaml \ + -f compose.docker.yaml \ + -f compose.backend.yaml \ + -f compose.frontend.yaml \ + -f compose.proxy.yaml \ + ps -a + +logs%: + docker compose \ + -f compose.mongodb.yaml \ + -f compose.nats.yaml \ + -f compose.docker.yaml \ + -f compose.backend.yaml \ + -f compose.frontend.yaml \ + -f compose.proxy.yaml \ + logs $* diff --git a/infrastructure/compose.backend.yaml b/infrastructure/compose.backend.yaml new file mode 100644 index 00000000..ca5bab0b --- /dev/null +++ b/infrastructure/compose.backend.yaml @@ -0,0 +1,49 @@ +# app placement +x-app: &app + restart: unless-stopped + environment: + PRIVATE_KEY: ${BACKEND_PRIVATE_KEY} + S3_ENDPOINT: ${BACKEND_S3_ENDPOINT} + S3_ACCESS_KEY: ${BACKEND_S3_ACCESS_KEY} + S3_SECRET_KEY: ${BACKEND_S3_SECRET_KEY} + S3_BUCKET_NAME: ${BACKEND_S3_BUCKET_NAME} + S3_USE_SSL: ${BACKEND_S3_USE_SSL} + MONGO_SCHEME: ${BACKEND_MONGO_SCHEME} + MONGO_USERNAME: ${BACKEND_MONGO_USERNAME} + MONGO_PASSWORD: ${BACKEND_MONGO_PASSWORD} + MONGO_HOST: ${BACKEND_MONGO_HOST} + MONGO_PORT: ${BACKEND_MONGO_PORT} + MONGO_DATABASE_NAME: ${BACKEND_MONGO_DATABASE_NAME} + MAIL_SMTP_FROM: ${BACKEND_MAIL_SMTP_FROM} + MAIL_SMTP_USERNAME: ${BACKEND_MAIL_SMTP_USERNAME} + MAIL_SMTP_PASSWORD: ${BACKEND_MAIL_SMTP_PASSWORD} + MAIL_SMTP_HOST: ${BACKEND_MAIL_SMTP_HOST} + MAIL_SMTP_PORT: ${BACKEND_MAIL_SMTP_PORT} + NATS_URL: ${BACKEND_NATS_URL} + +services: + app: + <<: *app + image: ${APP_IMAGE} + networks: + - backend + - frontend + - mongodb + - nats + - docker + deploy: + mode: replicated + replicas: 2 + endpoint_mode: vip + +networks: + backend: + name: backend + frontend: + name: frontend + mongodb: + name: mongodb + nats: + name: nats + docker: + name: docker diff --git a/infrastructure/compose.docker.yaml b/infrastructure/compose.docker.yaml new file mode 100644 index 00000000..b896e0c2 --- /dev/null +++ b/infrastructure/compose.docker.yaml @@ -0,0 +1,24 @@ +services: + docker: + image: docker:27-dind + restart: unless-stopped + networks: + - docker + runtime: sysbox-runc + # privileged: true # uncomment this to test locally and comment runtime. + environment: + DOCKER_TLS_CERTDIR: "" # disable certs + + portainer: + image: portainer/portainer-ce + restart: unless-stopped + networks: + - docker + depends_on: + - docker + command: > + --admin-password="${PORTAINER_ADMIN_PASSWORD}" + +networks: + docker: + name: docker diff --git a/infrastructure/compose.frontend.yaml b/infrastructure/compose.frontend.yaml new file mode 100644 index 00000000..15e92213 --- /dev/null +++ b/infrastructure/compose.frontend.yaml @@ -0,0 +1,21 @@ +services: + frontend: + image: ${FRONTEND_IMAGE} + restart: unless-stopped + networks: + - frontend + - backend + deploy: + mode: replicated + replicas: 2 + endpoint_mode: vip + environment: + NEXT_PUBLIC_EXTERNAL_BACKEND_BASE_URL: ${NEXT_PUBLIC_EXTERNAL_BACKEND_BASE_URL} + INTERNAL_BACKEND_BASE_URL: ${INTERNAL_BACKEND_BASE_URL} + NEXT_PUBLIC_FILES_BASE_URL: ${NEXT_PUBLIC_FILES_BASE_URL} + +networks: + frontend: + name: frontend + backend: + name: backend diff --git a/infrastructure/compose.mongodb.yaml b/infrastructure/compose.mongodb.yaml new file mode 100644 index 00000000..24a7727c --- /dev/null +++ b/infrastructure/compose.mongodb.yaml @@ -0,0 +1,27 @@ +services: + mongodb: + image: mongo:8.0 + restart: unless-stopped + networks: + - mongodb + environment: + MONGO_INITDB_ROOT_USERNAME: ${MONGO_USERNAME} + MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD} + volumes: + - ./${VOLUME_PATH}/mongodb:/data + + mongodashboard: + image: mongo-express + restart: unless-stopped + networks: + - mongodb + depends_on: + - mongodb + environment: + ME_CONFIG_BASICAUTH_USERNAME: ${DASHBOARD_MONGO_USERNAME} + ME_CONFIG_BASICAUTH_PASSWORD: ${DASHBOARD_MONGO_PASSWORD} + ME_CONFIG_MONGODB_URL: ${DASHBOARD_MONGO_MONGODB_URL} + +networks: + mongodb: + name: mongodb diff --git a/infrastructure/compose.nats.yaml b/infrastructure/compose.nats.yaml new file mode 100644 index 00000000..81cc7097 --- /dev/null +++ b/infrastructure/compose.nats.yaml @@ -0,0 +1,43 @@ +services: + nats: + image: nats:2.10 + restart: unless-stopped + networks: + - nats + volumes: + - ./${VOLUME_PATH}/nats:/data + command: "--jetstream --store_dir /data --cluster_name NATS --cluster nats://0.0.0.0:6222 --http_port 8222" + nats-1: + image: nats:2.10 + restart: unless-stopped + networks: + - nats + depends_on: + - nats + volumes: + - ./${VOLUME_PATH}/nats-1:/data + command: "--jetstream --store_dir /data --cluster_name NATS --cluster nats://0.0.0.0:6222 --routes=nats://ruser:T0pS3cr3t@nats:6222" + nats-2: + image: nats:2.10 + restart: unless-stopped + networks: + - nats + depends_on: + - nats + volumes: + - ./${VOLUME_PATH}/nats-2:/data + command: "--jetstream --store_dir /data --cluster_name NATS --cluster nats://0.0.0.0:6222 --routes=nats://ruser:T0pS3cr3t@nats:6222" + nats-3: + image: nats:2.10 + restart: unless-stopped + networks: + - nats + depends_on: + - nats + volumes: + - ./${VOLUME_PATH}/nats-3:/data + command: "--jetstream --store_dir /data --cluster_name NATS --cluster nats://0.0.0.0:6222 --routes=nats://ruser:T0pS3cr3t@nats:6222" + +networks: + nats: + name: nats diff --git a/infrastructure/compose.proxy.yaml b/infrastructure/compose.proxy.yaml new file mode 100644 index 00000000..ff2907ce --- /dev/null +++ b/infrastructure/compose.proxy.yaml @@ -0,0 +1,27 @@ +services: + proxy: + build: + dockerfile: Dockerfile + context: ./proxy + restart: unless-stopped + networks: + - backend + - frontend + - mongodb + - docker + deploy: + mode: replicated + replicas: 1 + endpoint_mode: vip + ports: + - 80:80 + +networks: + backend: + name: backend + frontend: + name: frontend + mongodb: + name: mongodb + docker: + name: docker diff --git a/infrastructure/proxy/Dockerfile b/infrastructure/proxy/Dockerfile new file mode 100644 index 00000000..070ba3e8 --- /dev/null +++ b/infrastructure/proxy/Dockerfile @@ -0,0 +1,5 @@ +FROM nginx:1.26-alpine + +COPY ./nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 diff --git a/infrastructure/proxy/nginx.conf b/infrastructure/proxy/nginx.conf new file mode 100644 index 00000000..919886cd --- /dev/null +++ b/infrastructure/proxy/nginx.conf @@ -0,0 +1,56 @@ +# Server block to catch-all unmatched subdomains +server { + listen 80 default_server; + + server_name "_"; + + location / { + proxy_pass http://frontend:3000; + 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; + } +} + +# Server block for backend subdomain +server { + listen 80; + server_name "backend.*"; + + location / { + proxy_pass http://app: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; + } +} + +# Server block for dockerdashboard subdomain +server { + listen 80; + server_name "dockerdashboard.*"; + + location / { + proxy_pass http://portainer:9000; + 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; + } +} + +# Server block for mongodashboard subdomain +server { + listen 80; + server_name "mongodashboard.*"; + + location / { + proxy_pass http://mongodashboard:8081; + 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; + } +} diff --git a/infrastructure/resources.tf b/infrastructure/resources.tf new file mode 100644 index 00000000..f9911832 --- /dev/null +++ b/infrastructure/resources.tf @@ -0,0 +1,239 @@ +provider "aws" { + region = "eu-central-1" +} + +variable "project_name" { + description = "Project tag given to each deployed Instance" + type = string +} + +variable "instance_name" { + description = "instance_name" + type = string +} + +import { + to = aws_security_group.backend + id = "sg-0c4446cdf14777251" +} + +resource "aws_security_group" "backend" { + name = var.instance_name + description = "Allow HTTP, HTTPS, and SSH inbound traffic" + + tags = { + project_name = var.project_name + } + + # Allow SSH (port 22) from any IP address + ingress { + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + # Allow HTTP (port 80) from any IP address + ingress { + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] # Allow HTTP from anywhere + } + + # Allow HTTPS (port 443) from any IP address + ingress { + from_port = 443 + to_port = 443 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + # Allow all outbound traffic + egress { + from_port = 0 + to_port = 0 + protocol = "-1" # all protocols + cidr_blocks = ["0.0.0.0/0"] + } +} + +import { + to = aws_ebs_volume.backend + id = "vol-0d2bab5e75ac580e9" +} + +resource "aws_ebs_volume" "backend" { + availability_zone = aws_instance.backend.availability_zone + encrypted = false + size = 10 + + tags = { + project_name = var.project_name + } +} + +import { + to = aws_volume_attachment.backend + id = "/dev/xvdf:vol-0d2bab5e75ac580e9:${aws_instance.backend.id}" +} + +resource "aws_volume_attachment" "backend" { + device_name = "/dev/xvdf" + instance_id = aws_instance.backend.id + volume_id = aws_ebs_volume.backend.id +} + +import { + to = aws_instance.backend + id = "i-026c60a5a3cdec06e" +} + +resource "aws_instance" "backend" { + ami = "ami-0a628e1e89aaedf80" # Canonical, Ubuntu, 24.04, amd64 noble image + instance_type = "t2.micro" + key_name = "backend" + availability_zone = "eu-central-1b" + + user_data = <<-EOT + #!/bin/bash + + # volumes + sudo mkfs.ext4 /dev/xvdf + sudo mkdir /volume_01 + sudo mount /dev/xvdf /volume_01 + sudo echo "/dev/xvdf /volume_01 ext4 defaults,nofail 0 0" | sudo tee -a /etc/fstab + + # tools + sudo apt install -y wget python3 ca-certificates curl htop jq vim + + # Add Docker's official GPG key: + sudo install -m 0755 -d /etc/apt/keyrings + sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc + sudo chmod a+r /etc/apt/keyrings/docker.asc + + # Add the repository to Apt sources: + echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ + $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ + sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + sudo apt-get update + + # install docker and sysbox + sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + wget https://downloads.nestybox.com/sysbox/releases/v0.6.5/sysbox-ce_0.6.5-0.linux_amd64.deb + sudo apt install -y ./sysbox-ce_0.6.5-0.linux_amd64.deb + rm ./sysbox-ce_0.6.5-0.linux_amd64.deb + + # setup + sudo systemctl enable docker.service + sudo systemctl start docker.service + sudo usermod -a -G docker ubuntu + id ubuntu + newgrp docker + docker swarm init --advertise-addr 192.168.99.100 + EOT + + root_block_device { + delete_on_termination = true + encrypted = false + volume_size = 20 + volume_type = "gp3" + + tags = { + project_name = var.project_name + } + } + + security_groups = [ + aws_security_group.backend.name + ] + + tags = { + project_name = var.project_name + } +} + +import { + to = aws_eip.backend + id = "eipalloc-0adaac6f91269c716" +} + +resource "aws_eip" "backend" { + instance = aws_instance.backend.id + domain = "vpc" + + tags = { + project_name = var.project_name + } +} + +import { + to = aws_route53_zone.tarhche_com + id = "Z0951095A7CDVGITDCUP" +} + +resource "aws_route53_zone" "tarhche_com" { + name = "tarhche.com" + force_destroy = false + comment = "" + + tags = { + project_name = var.project_name + } +} + +import { + to = aws_route53_record.a_record_tarhche_com + id = "${aws_route53_zone.tarhche_com.id}_tarhche.com_A" +} + +resource "aws_route53_record" "a_record_tarhche_com" { + zone_id = aws_route53_zone.tarhche_com.id + name = "tarhche.com" + type = "A" + ttl = 300 + records = ["3.125.118.7"] +} + +import { + to = aws_route53_zone.tarhche_ir + id = "Z07817351L3HY3TPTD5IU" +} + +resource "aws_route53_zone" "tarhche_ir" { + name = "tarhche.ir" + force_destroy = false + comment = "" + + tags = { + project_name = var.project_name + } +} + +import { + to = aws_route53_record.a_record_tarhche_ir + id = "${aws_route53_zone.tarhche_ir.id}_tarhche.ir_A" +} + +resource "aws_route53_record" "a_record_tarhche_ir" { + zone_id = aws_route53_zone.tarhche_ir.id + name = "tarhche.ir" + type = "A" + ttl = 300 + records = ["3.125.118.7"] +} + +import { + to = aws_s3_bucket.tarhche-backend + id = "tarhche-backend" +} + +resource "aws_s3_bucket" "tarhche-backend" { + bucket = "tarhche-backend" + force_destroy = false + + tags = { + project_name = var.project_name + } +}