diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..01d90e8 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +letsencrypt/ \ No newline at end of file diff --git a/traefik/README.md b/traefik/README.md new file mode 100644 index 0000000..5a950c7 --- /dev/null +++ b/traefik/README.md @@ -0,0 +1,41 @@ +# Instructions to deploy Huly on a `self-hosted` server with SSL using `Traefik` + +### Prerequisites + +- A domain name pointing to the server +- A server with Docker and Docker Compose installed + +### Steps + +1. Clone the repository + + ```bash + git clone https://github.com/hcengineering/huly-selfhost.git + cd huly-selfhost/traefik + ``` + +2. Run setup.sh + + ```bash + chmod +x setup.sh + ./setup.sh + ``` + +3. Follow the instructions in the setup script to configure your domain name and email address + + ```bash + $ ./setup.sh + Enter the domain name: example.com + Enter the email address: admin@example.com + Setup is complete. Run 'docker compose up -d' to start the services. + ``` + +4. Modify the `docker-compose.yml` file to customize any settings + +5. Start the services + + ```bash + docker compose up -d + ``` + +6. Access Huly at `https://example.com` diff --git a/traefik/docker-compose.yml b/traefik/docker-compose.yml new file mode 100644 index 0000000..dc96f8b --- /dev/null +++ b/traefik/docker-compose.yml @@ -0,0 +1,242 @@ +services: + traefik: + restart: unless-stopped + image: "traefik:v2.10" + container_name: "traefik" + ports: + - "80:80" + - "443:443" + - "3333:3333" + - "3078:3078" + volumes: + - "/var/run/docker.sock:/var/run/docker.sock:ro" + - ./letsencrypt:/letsencrypt + networks: + - traefik-public + command: + - "--log.level=DEBUG" # set to INFO for production + - "--api.insecure=false" + - "--api.dashboard=true" + - "--global.sendAnonymousUsage=false" + - "--global.checkNewVersion=false" + - "--providers.docker=true" + - "--providers.docker.exposedbydefault=false" + - "--providers.docker.network=traefik-public" + - "--entrypoints.web.address=:80" + - "--entrypoints.websecure.address=:443" + - "--entrypoints.transactor.address=:3333" # for transactor wss + - "--entrypoints.collaborator.address=:3078" # for collaborator wss + - "--entrypoints.web.http.redirections.entryPoint.to=websecure" + - "--entrypoints.web.http.redirections.entryPoint.scheme=https" + - "--certificatesresolvers.myresolver.acme.email=admin@example.com" + - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json" + - "--certificatesresolvers.myresolver.acme.tlschallenge=true" + - "--certificatesresolvers.myresolver.acme.caserver=http://acme-staging-v02.api.letsencrypt.org/directory" # For testing, comment out for production + labels: + - "traefik.enable=true" + - "traefik.http.routers.traefik.rule=Host(`example.com`) && (PathPrefix(`/api`) || PathPrefix(`/traefik`))" + - "traefik.http.routers.traefik.service=api@internal" + - "traefik.http.routers.traefik.entrypoints=websecure" + # strip prefix for traefik dashboard + - "traefik.http.routers.traefik.middlewares=strip-prefix-traefik" + - "traefik.http.middlewares.strip-prefix-traefik.stripprefix.prefixes=/traefik" + - "traefik.http.routers.traefik.tls=true" + - "traefik.http.routers.traefik.tls.certresolver=myresolver" + + mongodb: + image: "mongo:7-jammy" + container_name: mongodb + environment: + - PUID=1000 + - PGID=1000 + volumes: + - db:/data/db + restart: unless-stopped + networks: + - internal-services + + elastic: + image: "elasticsearch:7.14.2" + command: | + /bin/sh -c "./bin/elasticsearch-plugin list | grep -q ingest-attachment || yes | ./bin/elasticsearch-plugin install --silent ingest-attachment; + /usr/local/bin/docker-entrypoint.sh eswrapper" + volumes: + - elastic:/usr/share/elasticsearch/data + environment: + - ELASTICSEARCH_PORT_NUMBER=9200 + - BITNAMI_DEBUG=true + - discovery.type=single-node + - ES_JAVA_OPTS=-Xms1024m -Xmx1024m + - http.cors.enabled=true + - http.cors.allow-origin=http://localhost:8082 + healthcheck: + interval: 20s + retries: 10 + test: curl -s http://localhost:9200/_cluster/health | grep -vq '"status":"red"' + restart: unless-stopped + networks: + - internal-services + + minio: + image: "minio/minio" + command: server /data --address ":9000" --console-address ":9001" + volumes: + - files:/data + restart: unless-stopped + networks: + - internal-services + + rekoni: + image: hardcoreeng/rekoni-service:v0.6.245 + environment: + - SECRET=secret + deploy: + resources: + limits: + memory: 500M + restart: unless-stopped + networks: + - internal-services + - traefik-public + labels: + - "traefik.enable=true" + - "traefik.http.routers.rekoni.entrypoints=websecure" + - "traefik.http.services.rekoni.loadbalancer.server.port=4004" + - "traefik.http.routers.rekoni.rule=Host(`example.com`) && PathPrefix(`/rekoni`)" + - "traefik.http.routers.rekoni.middlewares=rekoni-stripprefix" + - "traefik.http.middlewares.rekoni-stripprefix.stripprefix.prefixes=/rekoni" + - "traefik.http.routers.rekoni.tls=true" + - "traefik.http.routers.rekoni.tls.certresolver=myresolver" + + transactor: + image: hardcoreeng/transactor:v0.6.245 + environment: + - SERVER_PORT=3333 + - SERVER_SECRET=secret + - SERVER_CURSOR_MAXTIMEMS=30000 + - ELASTIC_URL=http://elastic:9200 + - ELASTIC_INDEX_NAME=huly_storage_index + - MONGO_URL=mongodb://mongodb:27017 + - METRICS_CONSOLE=false + - METRICS_FILE=metrics.txt + - MINIO_ENDPOINT=minio + - MINIO_ACCESS_KEY=minioadmin + - MINIO_SECRET_KEY=minioadmin + - REKONI_URL=http://rekoni:4004 + - FRONT_URL=http://localhost:8087 + - SERVER_PROVIDER=wss + - ACCOUNTS_URL=http://account:3000 + - LAST_NAME_FIRST=true + - UPLOAD_URL=https://example.com/files + restart: unless-stopped + networks: + - internal-services + - traefik-public + labels: + - "traefik.enable=true" + - "traefik.http.routers.transactor.entrypoints=transactor" + - "traefik.http.routers.transactor.rule=Host(`example.com`)" + - "traefik.http.services.transactor.loadbalancer.server.port=3333" + - "traefik.http.routers.transactor.tls=true" + - "traefik.http.routers.transactor.tls.certresolver=myresolver" + + + collaborator: + image: hardcoreeng/collaborator:v0.6.245 + environment: + - COLLABORATOR_PORT=3078 + - SECRET=secret + - ACCOUNTS_URL=http://account:3000 + - TRANSACTOR_URL=ws://transactor:3333 + - UPLOAD_URL=/files + - MONGO_URL=mongodb://mongodb:27017 + - MINIO_ENDPOINT=minio + - MINIO_ACCESS_KEY=minioadmin + - MINIO_SECRET_KEY=minioadmin + restart: unless-stopped + networks: + - internal-services + - traefik-public + labels: + - "traefik.enable=true" + - "traefik.http.routers.collaborator.entrypoints=collaborator" + - "traefik.http.services.collaborator.loadbalancer.server.port=3078" + - "traefik.http.routers.collaborator.rule=Host(`example.com`)" + - "traefik.http.routers.collaborator.tls=true" + - "traefik.http.routers.collaborator.tls.certresolver=myresolver" + + account: + image: hardcoreeng/account:v0.6.245 + environment: + - SERVER_PORT=3000 + - SERVER_SECRET=secret + - MONGO_URL=mongodb://mongodb:27017 + - TRANSACTOR_URL=ws://transactor:3333 + - ENDPOINT_URL=wss://example.com:3333 # this is the transactor endpoint + - MINIO_ENDPOINT=minio + - MINIO_ACCESS_KEY=minioadmin + - MINIO_SECRET_KEY=minioadmin + - FRONT_URL=http://front:8080 + - INIT_WORKSPACE=demo-tracker + - MODEL_ENABLED=* + - ACCOUNTS_URL=http://localhost:3000 + - ACCOUNT_PORT=3000 + restart: unless-stopped + networks: + - internal-services + - traefik-public + labels: + - "traefik.enable=true" + - "traefik.http.routers.account.entrypoints=websecure" + - "traefik.http.services.account.loadbalancer.server.port=3000" + - "traefik.http.routers.account.rule=Host(`example.com`) && PathPrefix(`/accounts`)" + - "traefik.http.routers.account.middlewares=account-stripprefix" + - "traefik.http.middlewares.account-stripprefix.stripprefix.prefixes=/accounts" + - "traefik.http.routers.account.tls=true" + - "traefik.http.routers.account.tls.certresolver=myresolver" + + front: + image: hardcoreeng/front:v0.6.245 + environment: + - SERVER_PORT=8080 + - SERVER_SECRET=secret + - ACCOUNTS_URL=https://example.com/accounts + - REKONI_URL=https://example.com/rekoni + - CALENDAR_URL=https://example.com:8095 + - GMAIL_URL=https://example.com:8088 + - TELEGRAM_URL=https://example.com:8086 + - UPLOAD_URL=/files + - TRANSACTOR_URL=wss://example.com:3333 + - ELASTIC_URL=http://elastic:9200 + - COLLABORATOR_URL=wss://example.com:3078 + - COLLABORATOR_API_URL=https://example.com:3078 + - MINIO_ENDPOINT=minio + - MINIO_ACCESS_KEY=minioadmin + - MINIO_SECRET_KEY=minioadmin + - MONGO_URL=mongodb://mongodb:27017 + - TITLE=Huly Self Host + - DEFAULT_LANGUAGE=en + - LAST_NAME_FIRST=true + restart: unless-stopped + networks: + - internal-services + - traefik-public + labels: + - "traefik.enable=true" + - "traefik.http.routers.front.entrypoints=websecure" + - "traefik.http.services.front.loadbalancer.server.port=8080" + - "traefik.http.routers.front.rule=Host(`example.com`)" + - "traefik.http.routers.front.tls=true" + - "traefik.http.routers.front.tls.certresolver=myresolver" + +networks: + traefik-public: + name: traefik-public + internal-services: + name: internal-services + +volumes: + db: + letsencrypt: + elastic: + files: diff --git a/traefik/setup.sh b/traefik/setup.sh new file mode 100644 index 0000000..49ab4bc --- /dev/null +++ b/traefik/setup.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# Ask for the domain name +read -p "Enter the domain name: " DOMAIN_NAME +if [ -z "$DOMAIN_NAME" ]; then + echo "DOMAIN_NAME is required" + exit 1 +fi + +# Ask for the email address +read -p "Enter the email address: " LETSENCRYPT_EMAIL +if [ -z "$LETSENCRYPT_EMAIL" ]; then + echo "LETSENCRYPT_EMAIL address is required" + exit 1 +fi + + +export HULY_VERSION="v0.6.245" +export SERVER_ADDRESS=$DOMAIN_NAME +export LETSENCRYPT_EMAIL=$LETSENCRYPT_EMAIL + +# replace the domain name and email address in the docker-compose file +envsubst < template-compose.yml > docker-compose.yml + +echo -e "\033[1;32mSetup is complete. Run 'docker compose up -d' to start the services.\033[0m" + diff --git a/traefik/template-compose.yml b/traefik/template-compose.yml new file mode 100644 index 0000000..0664bbd --- /dev/null +++ b/traefik/template-compose.yml @@ -0,0 +1,242 @@ +services: + traefik: + restart: unless-stopped + image: "traefik:v2.10" + container_name: "traefik" + ports: + - "80:80" + - "443:443" + - "3333:3333" + - "3078:3078" + volumes: + - "/var/run/docker.sock:/var/run/docker.sock:ro" + - ./letsencrypt:/letsencrypt + networks: + - traefik-public + command: + - "--log.level=DEBUG" # set to INFO for production + - "--api.insecure=false" + - "--api.dashboard=true" + - "--global.sendAnonymousUsage=false" + - "--global.checkNewVersion=false" + - "--providers.docker=true" + - "--providers.docker.exposedbydefault=false" + - "--providers.docker.network=traefik-public" + - "--entrypoints.web.address=:80" + - "--entrypoints.websecure.address=:443" + - "--entrypoints.transactor.address=:3333" # for transactor wss + - "--entrypoints.collaborator.address=:3078" # for collaborator wss + - "--entrypoints.web.http.redirections.entryPoint.to=websecure" + - "--entrypoints.web.http.redirections.entryPoint.scheme=https" + - "--certificatesresolvers.myresolver.acme.email=${LETSENCRYPT_EMAIL}" + - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json" + - "--certificatesresolvers.myresolver.acme.tlschallenge=true" + - "--certificatesresolvers.myresolver.acme.caserver=http://acme-staging-v02.api.letsencrypt.org/directory" # For testing, comment out for production + labels: + - "traefik.enable=true" + - "traefik.http.routers.traefik.rule=Host(`${SERVER_ADDRESS}`) && (PathPrefix(`/api`) || PathPrefix(`/traefik`))" + - "traefik.http.routers.traefik.service=api@internal" + - "traefik.http.routers.traefik.entrypoints=websecure" + # strip prefix for traefik dashboard + - "traefik.http.routers.traefik.middlewares=strip-prefix-traefik" + - "traefik.http.middlewares.strip-prefix-traefik.stripprefix.prefixes=/traefik" + - "traefik.http.routers.traefik.tls=true" + - "traefik.http.routers.traefik.tls.certresolver=myresolver" + + mongodb: + image: "mongo:7-jammy" + container_name: mongodb + environment: + - PUID=1000 + - PGID=1000 + volumes: + - db:/data/db + restart: unless-stopped + networks: + - internal-services + + elastic: + image: "elasticsearch:7.14.2" + command: | + /bin/sh -c "./bin/elasticsearch-plugin list | grep -q ingest-attachment || yes | ./bin/elasticsearch-plugin install --silent ingest-attachment; + /usr/local/bin/docker-entrypoint.sh eswrapper" + volumes: + - elastic:/usr/share/elasticsearch/data + environment: + - ELASTICSEARCH_PORT_NUMBER=9200 + - BITNAMI_DEBUG=true + - discovery.type=single-node + - ES_JAVA_OPTS=-Xms1024m -Xmx1024m + - http.cors.enabled=true + - http.cors.allow-origin=http://localhost:8082 + healthcheck: + interval: 20s + retries: 10 + test: curl -s http://localhost:9200/_cluster/health | grep -vq '"status":"red"' + restart: unless-stopped + networks: + - internal-services + + minio: + image: "minio/minio" + command: server /data --address ":9000" --console-address ":9001" + volumes: + - files:/data + restart: unless-stopped + networks: + - internal-services + + rekoni: + image: hardcoreeng/rekoni-service:${HULY_VERSION} + environment: + - SECRET=secret + deploy: + resources: + limits: + memory: 500M + restart: unless-stopped + networks: + - internal-services + - traefik-public + labels: + - "traefik.enable=true" + - "traefik.http.routers.rekoni.entrypoints=websecure" + - "traefik.http.services.rekoni.loadbalancer.server.port=4004" + - "traefik.http.routers.rekoni.rule=Host(`${SERVER_ADDRESS}`) && PathPrefix(`/rekoni`)" + - "traefik.http.routers.rekoni.middlewares=rekoni-stripprefix" + - "traefik.http.middlewares.rekoni-stripprefix.stripprefix.prefixes=/rekoni" + - "traefik.http.routers.rekoni.tls=true" + - "traefik.http.routers.rekoni.tls.certresolver=myresolver" + + transactor: + image: hardcoreeng/transactor:${HULY_VERSION} + environment: + - SERVER_PORT=3333 + - SERVER_SECRET=secret + - SERVER_CURSOR_MAXTIMEMS=30000 + - ELASTIC_URL=http://elastic:9200 + - ELASTIC_INDEX_NAME=huly_storage_index + - MONGO_URL=mongodb://mongodb:27017 + - METRICS_CONSOLE=false + - METRICS_FILE=metrics.txt + - MINIO_ENDPOINT=minio + - MINIO_ACCESS_KEY=minioadmin + - MINIO_SECRET_KEY=minioadmin + - REKONI_URL=http://rekoni:4004 + - FRONT_URL=http://localhost:8087 + - SERVER_PROVIDER=wss + - ACCOUNTS_URL=http://account:3000 + - LAST_NAME_FIRST=true + - UPLOAD_URL=https://${SERVER_ADDRESS}/files + restart: unless-stopped + networks: + - internal-services + - traefik-public + labels: + - "traefik.enable=true" + - "traefik.http.routers.transactor.entrypoints=transactor" + - "traefik.http.routers.transactor.rule=Host(`${SERVER_ADDRESS}`)" + - "traefik.http.services.transactor.loadbalancer.server.port=3333" + - "traefik.http.routers.transactor.tls=true" + - "traefik.http.routers.transactor.tls.certresolver=myresolver" + + + collaborator: + image: hardcoreeng/collaborator:${HULY_VERSION} + environment: + - COLLABORATOR_PORT=3078 + - SECRET=secret + - ACCOUNTS_URL=http://account:3000 + - TRANSACTOR_URL=ws://transactor:3333 + - UPLOAD_URL=/files + - MONGO_URL=mongodb://mongodb:27017 + - MINIO_ENDPOINT=minio + - MINIO_ACCESS_KEY=minioadmin + - MINIO_SECRET_KEY=minioadmin + restart: unless-stopped + networks: + - internal-services + - traefik-public + labels: + - "traefik.enable=true" + - "traefik.http.routers.collaborator.entrypoints=collaborator" + - "traefik.http.services.collaborator.loadbalancer.server.port=3078" + - "traefik.http.routers.collaborator.rule=Host(`${SERVER_ADDRESS}`)" + - "traefik.http.routers.collaborator.tls=true" + - "traefik.http.routers.collaborator.tls.certresolver=myresolver" + + account: + image: hardcoreeng/account:${HULY_VERSION} + environment: + - SERVER_PORT=3000 + - SERVER_SECRET=secret + - MONGO_URL=mongodb://mongodb:27017 + - TRANSACTOR_URL=ws://transactor:3333 + - ENDPOINT_URL=wss://${SERVER_ADDRESS}:3333 # this is the transactor endpoint + - MINIO_ENDPOINT=minio + - MINIO_ACCESS_KEY=minioadmin + - MINIO_SECRET_KEY=minioadmin + - FRONT_URL=http://front:8080 + - INIT_WORKSPACE=demo-tracker + - MODEL_ENABLED=* + - ACCOUNTS_URL=http://localhost:3000 + - ACCOUNT_PORT=3000 + restart: unless-stopped + networks: + - internal-services + - traefik-public + labels: + - "traefik.enable=true" + - "traefik.http.routers.account.entrypoints=websecure" + - "traefik.http.services.account.loadbalancer.server.port=3000" + - "traefik.http.routers.account.rule=Host(`${SERVER_ADDRESS}`) && PathPrefix(`/accounts`)" + - "traefik.http.routers.account.middlewares=account-stripprefix" + - "traefik.http.middlewares.account-stripprefix.stripprefix.prefixes=/accounts" + - "traefik.http.routers.account.tls=true" + - "traefik.http.routers.account.tls.certresolver=myresolver" + + front: + image: hardcoreeng/front:${HULY_VERSION} + environment: + - SERVER_PORT=8080 + - SERVER_SECRET=secret + - ACCOUNTS_URL=https://${SERVER_ADDRESS}/accounts + - REKONI_URL=https://${SERVER_ADDRESS}/rekoni + - CALENDAR_URL=https://${SERVER_ADDRESS}:8095 + - GMAIL_URL=https://${SERVER_ADDRESS}:8088 + - TELEGRAM_URL=https://${SERVER_ADDRESS}:8086 + - UPLOAD_URL=/files + - TRANSACTOR_URL=wss://${SERVER_ADDRESS}:3333 + - ELASTIC_URL=http://elastic:9200 + - COLLABORATOR_URL=wss://${SERVER_ADDRESS}:3078 + - COLLABORATOR_API_URL=https://${SERVER_ADDRESS}:3078 + - MINIO_ENDPOINT=minio + - MINIO_ACCESS_KEY=minioadmin + - MINIO_SECRET_KEY=minioadmin + - MONGO_URL=mongodb://mongodb:27017 + - TITLE=Huly Self Host + - DEFAULT_LANGUAGE=en + - LAST_NAME_FIRST=true + restart: unless-stopped + networks: + - internal-services + - traefik-public + labels: + - "traefik.enable=true" + - "traefik.http.routers.front.entrypoints=websecure" + - "traefik.http.services.front.loadbalancer.server.port=8080" + - "traefik.http.routers.front.rule=Host(`${SERVER_ADDRESS}`)" + - "traefik.http.routers.front.tls=true" + - "traefik.http.routers.front.tls.certresolver=myresolver" + +networks: + traefik-public: + name: traefik-public + internal-services: + name: internal-services + +volumes: + db: + letsencrypt: + elastic: + files: