diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..958b26c9d6 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +node_modules +.git +.gitignore +*.md +dist \ No newline at end of file diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml new file mode 100644 index 0000000000..611d9c5f40 --- /dev/null +++ b/.github/workflows/staging.yml @@ -0,0 +1,78 @@ +name: Staging deployment + +on: + push: + branches: + - dev + - main + +env: + REGISTRY: ghcr.io + IMAGE_NAME: appwrite/homepage + TAG: ${{ github.sha }} + STACK_FILE: docker-stack.yml + NAMESPACE: homepage + REPOSITORY: homepage + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout the repo + uses: actions/checkout@v2 + + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v2 + with: + build-args: | + VERSION=${{ env.TAG }} + context: . + push: true + tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.TAG }} + + deploy: + needs: build + runs-on: ubuntu-latest + steps: + - name: Execute SSH commands + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.STG_SSH_HOST }} + username: ${{ secrets.STG_SSH_USERNAME }} + key: ${{ secrets.STG_SSH_KEY }} + script: | + url="https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/appwrite/${{ env.REPOSITORY }}.git" + if ! git clone "${url}" "${{ env.REPOSITORY }}" 2>/dev/null && [ -d "${{ env.REPOSITORY }}" ] ; then + echo "Clone failed because the folder ${{ env.REPOSITORY }} exists" + fi + + cd ${{ env.REPOSITORY }} + git reset --hard HEAD + git remote set-url origin $url + git fetch origin + git checkout ${{ env.TAG }} + + rm -f .env + echo "_APP_ENV=${{ secrets.STG_APP_ENV }}" >> .env + echo "_APP_VERSION=${{ env.TAG }}" >> .env + echo "_APP_DOMAIN=${{ secrets.STG_APP_DOMAIN }}" >> .env + echo "_APP_MAILGUN_DOMAIN=${{ secrets._APP_MAILGUN_DOMAIN }}" >> .env + echo "_APP_MAILGUN_KEY=${{ secrets._APP_MAILGUN_KEY }}" >> .env + echo "_APP_SMTP_PASSWORD=${{ secrets._APP_SMTP_PASSWORD }}" >> .env + echo "_APP_SMTP_USERNAME=${{ secrets._APP_SMTP_USERNAME }}" >> .env + echo "_APP_SYSTEM_SECURITY_EMAIL_ADDRESS=${{ secrets._APP_SYSTEM_SECURITY_EMAIL_ADDRESS }}" >> .env + echo "_APP_HOMERUN_API_KEY=${{ secrets._APP_HOMERUN_API_KEY }}" >> .env + echo "_APP_LOGGING_PROVIDER=${{ secrets._APP_LOGGING_PROVIDER }}" >> .env + echo "_APP_LOGGING_CONFIG=${{ secrets._APP_LOGGING_CONFIG }}" >> .env + echo "SEMATEXT_TOKEN=${{ secrets.SEMATEXT_TOKEN }}" >> .env + + echo ${{ secrets.GH_REGISTRY_TOKEN }} | docker login ghcr.io --username ${{ github.actor }} --password-stdin + docker-compose -f ${{ env.STACK_FILE }} config + docker stack deploy --with-registry-auth -c <(docker-compose -f ${{ env.STACK_FILE }} config) ${{ env.NAMESPACE }} \ No newline at end of file diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 0000000000..a681be17cb --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,11 @@ +# This configuration file was automatically generated by Gitpod. +# Please adjust to your needs (see https://www.gitpod.io/docs/introduction/learn-gitpod/gitpod-yaml) +# and commit this file to your remote git repository to share the goodness with others. + +# Learn more from ready-to-use templates: https://www.gitpod.io/docs/introduction/getting-started/quickstart + +tasks: + - init: pnpm install && pnpm run build + command: pnpm run dev + + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000..59b6bf3693 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,30 @@ +FROM node:20-slim AS build + +ENV PNPM_HOME="/pnpm" +ENV PATH="$PNPM_HOME:$PATH" + +RUN corepack enable +COPY . /app + +WORKDIR /app + +# RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile +# RUN pnpm run build + +FROM base AS prod-deps +RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile + +FROM base AS build +RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile +RUN pnpm run build + +FROM base +COPY --from=prod-deps /app/node_modules /app/node_modules +COPY --from=build /app/dist /app/dist + +# FROM caddy:alpine as serve +# COPY --from=build /app/dist /app/dist + +# EXPOSE 80 + +# CMD [ "caddy", "file-server", "--root /app/dist"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000000..f1d64b34ce --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,54 @@ +version: '3.8' + +services: + traefik: + image: traefik:2.9 + command: + - --log.level=DEBUG + - --api.insecure=true + - --providers.docker=true + - --providers.docker.watch=true + - --providers.docker.swarmMode=true + - --providers.docker.exposedByDefault=false + - --entrypoints.web.address=:80 + - --entrypoints.websecure.address=:443 + - --entrypoints.web.http.redirections.entrypoint.to=websecure + - --entrypoints.web.http.redirections.entrypoint.scheme=https + - --providers.docker.constraints=Label(`traefik.constraint-label-stack`,`appwrite`) + - --certificatesresolvers.myresolver.acme.httpchallenge=true + - --certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web + - --certificatesresolvers.myresolver.acme.email=$_APP_SYSTEM_SECURITY_EMAIL_ADDRESS + - --certificatesresolvers.myresolver.acme.storage=/letsencrypt/${_APP_DOMAIN}.json + - --accesslog=true + ports: + - 80:80 + - 8080:8080 + volumes: + - /letsencrypt:/letsencrypt + - /var/run/docker.sock:/var/run/docker.sock + networks: + - homepage + + appwrite: + image: homepage-dev + build: + context: . + networks: + - homepage + labels: + - traefik.enable=true + - traefik.constraint-label-stack=appwrite + - traefik.http.services.appwrite_api.loadbalancer.server.port=80 + #http + - traefik.http.routers.appwrite.entrypoints=web + - traefik.http.routers.appwrite.rule=Host(`$_APP_DOMAIN`) || Host(`www.$_APP_DOMAIN`) + - traefik.http.routers.appwrite.service=appwrite_api + # https + - traefik.http.routers.appwrite_secure.entrypoints=websecure + - traefik.http.routers.appwrite_secure.rule=Host(`$_APP_DOMAIN`) || Host(`www.$_APP_DOMAIN`) + - traefik.http.routers.appwrite_secure.service=appwrite_api + - traefik.http.routers.appwrite_secure.tls=true + - traefik.http.routers.appwrite_secure.tls.certresolver=myresolver + +networks: + homepage: \ No newline at end of file diff --git a/docker/docker-stack.stage.yml b/docker/docker-stack.stage.yml new file mode 100644 index 0000000000..6600612b95 --- /dev/null +++ b/docker/docker-stack.stage.yml @@ -0,0 +1,125 @@ +x-logging: &x-logging + logging: + driver: 'json-file' + options: + max-file: '5' + max-size: '20m' + +x-update-config: &x-update-config + update_config: + order: start-first + failure_action: rollback + parallelism: 2 + delay: 5s + rollback_config: + failure_action: pause + monitor: 5s + parallelism: 2 + order: start-first + +version: '3.8' + +services: + traefik: + image: traefik:2.9 + <<: *x-logging + command: + - --log.level=DEBUG + - --api.insecure=true + - --providers.docker=true + - --providers.docker.watch=true + - --providers.docker.swarmMode=true + - --providers.docker.exposedByDefault=false + - --entrypoints.web.address=:80 + - --entrypoints.websecure.address=:443 + - --entrypoints.web.http.redirections.entrypoint.to=websecure + - --entrypoints.web.http.redirections.entrypoint.scheme=https + - --providers.docker.constraints=Label(`traefik.constraint-label-stack`,`appwrite`) + - --certificatesresolvers.myresolver.acme.httpchallenge=true + - --certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web + - --certificatesresolvers.myresolver.acme.email=$_APP_SYSTEM_SECURITY_EMAIL_ADDRESS + - --certificatesresolvers.myresolver.acme.storage=/letsencrypt/${_APP_DOMAIN}.json + - --accesslog=true + ports: + - 80:80 + - 443:443 + - 8080:8080 + volumes: + - /letsencrypt:/letsencrypt + - /var/run/docker.sock:/var/run/docker.sock + networks: + - cloud + deploy: + replicas: 1 + <<: *x-update-config + placement: + max_replicas_per_node: 1 + constraints: + - node.role == manager + + appwrite: + image: ghcr.io/appwrite/homepage:$_APP_VERSION + <<: *x-logging + networks: + - cloud + deploy: + <<: *x-update-config + mode: global + placement: + constraints: + - node.role == worker + labels: + - traefik.enable=true + - traefik.constraint-label-stack=appwrite + - traefik.http.services.appwrite_api.loadbalancer.server.port=80 + #http + - traefik.http.routers.appwrite.entrypoints=web + - traefik.http.routers.appwrite.rule=Host(`$_APP_DOMAIN`) || Host(`www.$_APP_DOMAIN`) + - traefik.http.routers.appwrite.service=appwrite_api + # https + - traefik.http.routers.appwrite_secure.entrypoints=websecure + - traefik.http.routers.appwrite_secure.rule=Host(`$_APP_DOMAIN`) || Host(`www.$_APP_DOMAIN`) + - traefik.http.routers.appwrite_secure.service=appwrite_api + - traefik.http.routers.appwrite_secure.tls=true + - traefik.http.routers.appwrite_secure.tls.certresolver=myresolver + environment: + _APP_SMTP_PORT: 587 + _APP_SMTP_SECURE: tls + _APP_SMTP_HOST: smtp.mailgun.org + _APP_ENV: + _APP_MAILGUN_DOMAIN: + _APP_MAILGUN_KEY: + _APP_SMTP_PASSWORD: + _APP_SMTP_USERNAME: + _APP_HOMERUN_API_KEY: + _APP_LOGGING_PROVIDER: + _APP_LOGGING_CONFIG: + + janitor: + image: appwrite/docker-janitor + deploy: + mode: global + volumes: + - /var/run/docker.sock:/var/run/docker.sock + + sematext-agent: + image: sematext/agent:latest + environment: + REGION: EU + INFRA_TOKEN: $SEMATEXT_TOKEN + deploy: + mode: global + restart_policy: + condition: any + volumes: + - /:/hostfs:ro + - /etc/passwd:/etc/passwd:ro + - /etc/group:/etc/group:ro + - /sys:/host/sys:ro + - /dev:/hostfs/dev:ro + - /var/run:/var/run + - /sys/kernel/debug:/sys/kernel/debug + +networks: + cloud: + driver: overlay \ No newline at end of file