From 32fc73e9588683a2bde4a9a7090dad25635f327d Mon Sep 17 00:00:00 2001 From: Ilya Gulya Date: Sat, 28 Dec 2024 21:24:43 +0500 Subject: [PATCH 1/2] traefik: expose websockets on the same domain under `/ws/` path instead of separate ports --- traefik/template-compose.yaml | 202 ++++++++++++++++++---------------- 1 file changed, 108 insertions(+), 94 deletions(-) diff --git a/traefik/template-compose.yaml b/traefik/template-compose.yaml index 7f36621..4c6fa93 100644 --- a/traefik/template-compose.yaml +++ b/traefik/template-compose.yaml @@ -1,3 +1,12 @@ +x-common-env: &common-env + SERVER_SECRET: ${HULY_SECRET} + SECRET: ${HULY_SECRET} + STORAGE_CONFIG: minio|minio?accessKey=minioadmin&secretKey=minioadmin + MONGO_URL: mongodb://mongodb:27017 + DB_URL: mongodb://mongodb:27017 + ACCOUNTS_URL: http://account:3000 + STATS_URL: http://stats:4900 + services: traefik: restart: unless-stopped @@ -6,8 +15,6 @@ services: ports: - "80:80" - "443:443" - - "3333:3333" - - "3078:3078" volumes: - "/var/run/docker.sock:/var/run/docker.sock:ro" - ./letsencrypt:/letsencrypt @@ -24,8 +31,6 @@ services: - "--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}" @@ -44,19 +49,29 @@ services: - "traefik.http.routers.traefik.tls.certresolver=myresolver" mongodb: - image: "mongo:7-jammy" + image: mongo:7-jammy container_name: mongodb + restart: unless-stopped environment: - PUID=1000 - PGID=1000 volumes: - db:/data/db + networks: + - internal-services + + minio: + image: minio/minio restart: unless-stopped + command: server /data --address ":9000" --console-address ":9001" + volumes: + - files:/data networks: - internal-services elastic: - image: "elasticsearch:7.14.2" + image: elasticsearch:7.14.2 + restart: unless-stopped 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" @@ -73,28 +88,18 @@ services: 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} + restart: unless-stopped environment: - - SECRET=${HULY_SECRET} + <<: *common-env deploy: resources: limits: memory: 500M - restart: unless-stopped networks: - internal-services - traefik-public @@ -110,64 +115,85 @@ services: transactor: image: hardcoreeng/transactor:${HULY_VERSION} - environment: - - SERVER_PORT=3333 - - SERVER_SECRET=${HULY_SECRET} - - SERVER_CURSOR_MAXTIMEMS=30000 - - DB_URL=mongodb://mongodb:27017 - - MONGO_URL=mongodb://mongodb:27017 - - STORAGE_CONFIG=minio|minio?accessKey=minioadmin&secretKey=minioadmin - - FRONT_URL=http://localhost:8087 - - ACCOUNTS_URL=http://account:3000 - - FULLTEXT_URL=http://fulltext:4700 - - STATS_URL=http://stats:4900 - - LAST_NAME_FIRST=true restart: unless-stopped + environment: + <<: *common-env + SERVER_PORT: 3333 + SERVER_CURSOR_MAXTIMEMS: 30000 + FRONT_URL: http://localhost:8087 + FULLTEXT_URL: http://fulltext:4700 + LAST_NAME_FIRST: true 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" + # WebSocket route + - "traefik.http.routers.transactor-ws.entrypoints=websecure" + - "traefik.http.routers.transactor-ws.rule=Host(`${SERVER_ADDRESS}`) && PathPrefix(`/ws/transactor`)" + - "traefik.http.routers.transactor-ws.tls=true" + - "traefik.http.routers.transactor-ws.tls.certresolver=myresolver" + - "traefik.http.services.transactor-ws.loadbalancer.server.port=3333" + - "traefik.http.routers.transactor-ws.service=transactor-ws" + + # Strip WebSocket prefix + - "traefik.http.routers.transactor-ws.middlewares=strip-transactor-ws-prefix" + - "traefik.http.middlewares.strip-transactor-ws-prefix.stripprefix.prefixes=/ws/transactor" + + # HTTP route for non-WebSocket traffic + - "traefik.http.routers.transactor.entrypoints=websecure" + - "traefik.http.routers.transactor.rule=Host(`${SERVER_ADDRESS}`) && PathPrefix(`/transactor`)" - "traefik.http.routers.transactor.tls=true" - "traefik.http.routers.transactor.tls.certresolver=myresolver" + - "traefik.http.services.transactor.loadbalancer.server.port=3333" + + # Strip HTTP prefix + - "traefik.http.routers.transactor.middlewares=strip-transactor-prefix" + - "traefik.http.middlewares.strip-transactor-prefix.stripprefix.prefixes=/transactor" collaborator: image: hardcoreeng/collaborator:${HULY_VERSION} - environment: - - COLLABORATOR_PORT=3078 - - SECRET=${HULY_SECRET} - - ACCOUNTS_URL=http://account:3000 - - STATS_URL=http://stats:4900 - - STORAGE_CONFIG=minio|minio?accessKey=minioadmin&secretKey=minioadmin restart: unless-stopped + environment: + <<: *common-env + COLLABORATOR_PORT: 3078 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}`)" + # WebSocket route + - "traefik.http.routers.collaborator-ws.entrypoints=websecure" + - "traefik.http.routers.collaborator-ws.rule=Host(`${SERVER_ADDRESS}`) && PathPrefix(`/ws/collaborator`)" + - "traefik.http.routers.collaborator-ws.tls=true" + - "traefik.http.routers.collaborator-ws.tls.certresolver=myresolver" + - "traefik.http.services.collaborator-ws.loadbalancer.server.port=3078" + - "traefik.http.routers.collaborator-ws.service=collaborator-ws" + + # Strip WebSocket prefix + - "traefik.http.routers.collaborator-ws.middlewares=strip-collaborator-ws-prefix" + - "traefik.http.middlewares.strip-collaborator-ws-prefix.stripprefix.prefixes=/ws/collaborator" + # HTTP route for non-WebSocket traffic + - "traefik.http.routers.collaborator.entrypoints=websecure" + - "traefik.http.routers.collaborator.rule=Host(`${SERVER_ADDRESS}`) && PathPrefix(`/collaborator`)" - "traefik.http.routers.collaborator.tls=true" - "traefik.http.routers.collaborator.tls.certresolver=myresolver" + - "traefik.http.services.collaborator.loadbalancer.server.port=3078" + + # Strip HTTP prefix + - "traefik.http.routers.collaborator.middlewares=strip-collaborator-prefix" + - "traefik.http.middlewares.strip-collaborator-prefix.stripprefix.prefixes=/collaborator" account: image: hardcoreeng/account:${HULY_VERSION} - environment: - - SERVER_PORT=3000 - - SERVER_SECRET=${HULY_SECRET} - - DB_URL=mongodb://mongodb:27017 - - TRANSACTOR_URL=ws://transactor:3333;wss://${SERVER_ADDRESS}:3333 - - STORAGE_CONFIG=minio|minio?accessKey=minioadmin&secretKey=minioadmin - - FRONT_URL=http://front:8080 - - STATS_URL=http://stats:4900 - - MODEL_ENABLED=* - - ACCOUNTS_URL=http://account:3000 - - ACCOUNT_PORT=3000 restart: unless-stopped + environment: + <<: *common-env + SERVER_PORT: 3000 + TRANSACTOR_URL: ws://transactor:3333;wss://${SERVER_ADDRESS}/ws/transactor + FRONT_URL: http://front:8080 + MODEL_ENABLED: "*" + ACCOUNT_PORT: 3000 networks: - internal-services - traefik-public @@ -183,46 +209,38 @@ services: workspace: image: hardcoreeng/workspace:${HULY_VERSION} - environment: - - SERVER_SECRET=${HULY_SECRET} - - DB_URL=mongodb://mongodb:27017 - - MONGO_URL=mongodb://mongodb:27017 - - TRANSACTOR_URL=ws://transactor:3333;wss://${SERVER_ADDRESS}:3333 - - STORAGE_CONFIG=minio|minio?accessKey=minioadmin&secretKey=minioadmin - - MODEL_ENABLED=* - - ACCOUNTS_URL=http://account:3000 - - STATS_URL=http://stats:4900 restart: unless-stopped + environment: + <<: *common-env + TRANSACTOR_URL: ws://transactor:3333;wss://${SERVER_ADDRESS}/ws/transactor + MODEL_ENABLED: "*" networks: - internal-services front: image: hardcoreeng/front:${HULY_VERSION} - environment: - - SERVER_PORT=8080 - - SERVER_SECRET=${HULY_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 - - STATS_URL=https://${SERVER_ADDRESS}/stats - - UPLOAD_URL=/files - - ELASTIC_URL=http://elastic:9200 - - COLLABORATOR_URL=wss://${SERVER_ADDRESS}:3078 - - STORAGE_CONFIG=minio|minio?accessKey=minioadmin&secretKey=minioadmin - - MONGO_URL=mongodb://mongodb:27017 - - TITLE=Huly Self Host - - DEFAULT_LANGUAGE=en - - LAST_NAME_FIRST=true - - DESKTOP_UPDATES_CHANNEL=selfhost restart: unless-stopped + environment: + <<: *common-env + SERVER_PORT: 8080 + 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 + STATS_URL: https://${SERVER_ADDRESS}/stats + UPLOAD_URL: /files + ELASTIC_URL: http://elastic:9200 + COLLABORATOR_URL: wss://${SERVER_ADDRESS}/ws/collaborator + TITLE: Huly Self Host + DEFAULT_LANGUAGE: en + LAST_NAME_FIRST: true + DESKTOP_UPDATES_CHANNEL: selfhost networks: - internal-services - traefik-public labels: - "traefik.enable=true" - - "traefik.port=80" - "traefik.http.routers.front.entrypoints=websecure" - "traefik.http.services.front.loadbalancer.server.port=8080" - "traefik.http.routers.front.rule=Host(`${SERVER_ADDRESS}`)" @@ -231,25 +249,21 @@ services: fulltext: image: hardcoreeng/fulltext:${HULY_VERSION} - environment: - - SERVER_SECRET=${HULY_SECRET} - - DB_URL=mongodb://mongodb:27017 - - FULLTEXT_DB_URL=http://elastic:9200 - - ELASTIC_INDEX_NAME=huly_storage_index - - STORAGE_CONFIG=minio|minio?accessKey=minioadmin&secretKey=minioadmin - - REKONI_URL=http://rekoni:4004 - - ACCOUNTS_URL=http://account:3000 - - STATS_URL=http://stats:4900 restart: unless-stopped + environment: + <<: *common-env + FULLTEXT_DB_URL: http://elastic:9200 + ELASTIC_INDEX_NAME: huly_storage_index + REKONI_URL: http://rekoni:4004 networks: - internal-services stats: image: hardcoreeng/stats:${HULY_VERSION} - environment: - - PORT=4900 - - SERVER_SECRET=${HULY_SECRET} restart: unless-stopped + environment: + <<: *common-env + PORT: 4900 networks: - internal-services - traefik-public @@ -271,6 +285,6 @@ networks: volumes: db: - letsencrypt: elastic: files: + letsencrypt: \ No newline at end of file From 7db3c2bac28fdc2b19670ef8db3422e0a49cb216 Mon Sep 17 00:00:00 2001 From: Ilya Gulya Date: Sat, 28 Dec 2024 21:33:19 +0500 Subject: [PATCH 2/2] ses: fix README --- .gitignore | 3 +- README.md | 133 +++++++++++++++++++++++++++++++++++++---------------- 2 files changed, 95 insertions(+), 41 deletions(-) diff --git a/.gitignore b/.gitignore index 34c6225..8fb5138 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ traefik/docker-compose.yaml nginx.conf nginx.conf.bak huly.conf -.huly.secret \ No newline at end of file +.huly.secret +.idea diff --git a/README.md b/README.md index 280025c..31f4652 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,18 @@ # Huly Self-Hosted -Please use this README if you want to deploy Huly on your server with `docker compose`. I'm using a Basic Droplet on Digital Ocean with Ubuntu 23.10, but these instructions can be easily adapted for any Linux distribution. +Please use this README if you want to deploy Huly on your server with `docker compose`. I'm using a Basic Droplet on +Digital Ocean with Ubuntu 23.10, but these instructions can be easily adapted for any Linux distribution. > [!NOTE] -> Huly is quite resource-heavy, so I recommend using a Droplet with 2 vCPUs and 4GB of RAM. Droplets with less RAM may stop responding or fail. +> Huly is quite resource-heavy, so I recommend using a Droplet with 2 vCPUs and 4GB of RAM. Droplets with less RAM may +> stop responding or fail. If you prefer Kubernetes deployment, there is a sample Kubernetes configuration under [kube](kube) directory. ## Installing `nginx` and `docker` -First, let's install `nginx` and `docker` using the commands below if you have not already installed them on your machine. +First, let's install `nginx` and `docker` using the commands below if you have not already installed them on your +machine. ```bash sudo apt update @@ -26,18 +29,23 @@ git clone https://github.com/hcengineering/huly-selfhost.git cd huly-selfhost ./setup.sh ``` + This will generate a [huly.conf](./huly.conf) file with your chosen values and create your nginx config. To add the generated configuration to your Nginx setup, run the following: + ```bash sudo ln -s $(pwd)/nginx.conf /etc/nginx/sites-enabled/huly.conf ``` + > [!NOTE] -> If you change `HOST_ADDRESS`, `SECURE`, `HTTP_PORT` or `HTTP_BIND` be sure to update your [nginx.conf](./nginx.conf) by running: +> If you change `HOST_ADDRESS`, `SECURE`, `HTTP_PORT` or `HTTP_BIND` be sure to update your [nginx.conf](./nginx.conf) +> by running: > ```bash > ./nginx.sh > ``` ->You can safely execute this script after adding your custom configurations like ssl. It will only overwrite the necessary settings. +>You can safely execute this script after adding your custom configurations like ssl. It will only overwrite the +> necessary settings. Finally, let's reload `nginx` and start Huly with `docker compose`. @@ -51,18 +59,25 @@ Now, launch your web browser and enjoy Huly! ## Generating Public and Private VAPID keys for front-end You'll need `Node.js` installed on your machine. Installing `npm` on Debian based distro: + ``` sudo apt-get install npm ``` + Install web-push using npm + ``` sudo npm install -g web-push ``` + Generate VAPID Keys. Run the following command to generate a VAPID key pair: + ``` web-push generate-vapid-keys ``` + It will generate both keys that looks like this: + ``` ======================================= @@ -74,9 +89,11 @@ asdfsadfasdfsfd ======================================= ``` + Keep these keys secure, as you will need them to set up your push notification service on the server. Add these keys into `compose.yaml` in section `services:front:environment`: + ``` - PUSH_PUBLIC_KEY=your public key - PUSH_PRIVATE_KEY=your private key @@ -86,14 +103,35 @@ Add these keys into `compose.yaml` in section `services:front:environment`: 1. Setup Amazon Simple Email Service in AWS: https://docs.aws.amazon.com/ses/latest/dg/setting-up.html -2. Add email address you'll use to send notifications into "SOURCE", SES access such as ACCESS_KEY, SECRET_KEY, REGION +2. [Create new policy](https://us-east-1.console.aws.amazon.com/iam/home?region=eu-central-1#/policies/create) with + following permissions: + ``` + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "ses:SendEmail", + "ses:SendRawEmail" + ], + "Resource": "*" + } + ] + } + ``` + +3. [Create separate IAM user](https://us-east-1.console.aws.amazon.com/iam/home?region=eu-central-1#/users/create) for + SES API access. Assign previously created policy to this user during creation. + +4. Add email address you'll use to send notifications into "SOURCE", SES access such as ACCESS_KEY, SECRET_KEY, REGION ```yaml ses: - image: hardcoreeng/ses:v0.6.295 + image: hardcoreeng/ses:v0.6.377 container_name: ses - ports: - - 3335:3335 + expose: + - 3335 environment: - SOURCE= - ACCESS_KEY= @@ -103,33 +141,35 @@ Add these keys into `compose.yaml` in section `services:front:environment`: restart: unless-stopped ``` -3. Add SES container URL into `transactor` and `account` containers: +5. Add SES container URL into `transactor` and `account` containers: ```yaml account: - ... + # ... environment: - SES_URL=http://ses:3335 - ... + # ... transactor: - ... + # ... environment: - SES_URL=http://ses:3335 - ... + # ... ``` -4. In `Settings -> Notifications` setup email notifications for events you need to be notified for. It's a user's setting not a company wide, meaning each user has to setup their own notification rules. +6. In `Settings -> Notifications` setup email notifications for events you need to be notified for. It's a user's + setting not a company wide, meaning each user has to setup their own notification rules. ## Love Service (Audio & Video calls) -Huly audio and video calls are created on top of LiveKit insfrastructure. In order to use Love service in your self-hosted Huly, perform the following steps: +Huly audio and video calls are created on top of LiveKit insfrastructure. In order to use Love service in your +self-hosted Huly, perform the following steps: 1. Set up [LiveKit Cloud](https://cloud.livekit.io) account 2. Add `love` container to the docker-compose.yaml ```yaml love: - image: hardcoreeng/love:v0.6.295 + image: hardcoreeng/love:v0.6.377 container_name: love ports: - 8096:8096 @@ -151,11 +191,11 @@ Huly audio and video calls are created on top of LiveKit insfrastructure. In ord ```yaml front: - ... + # ... environment: - LIVEKIT_WS= - LOVE_ENDPOINT=http://love:8096 - ... + # ... ``` ## Configure OpenID Connect (OIDC) @@ -163,15 +203,19 @@ Huly audio and video calls are created on top of LiveKit insfrastructure. In ord You can configure a Huly instance to authorize users (sign-in/sign-up) using an OpenID Connect identity provider (IdP). ### On the IdP side -1. Create a new OpenID application. - * Use `{huly_account_svc}/auth/openid/callback` as the sign-in redirect URI. The `huly_account_svc` is the hostname for the account service of the deployment, which should be accessible externally from the client/browser side. In the provided example setup, the account service runs on port 3000. - **URI Example:** - - `http://huly.mydomain.com:3000/auth/openid/callback` +1. Create a new OpenID application. + * Use `{huly_account_svc}/auth/openid/callback` as the sign-in redirect URI. The `huly_account_svc` is the hostname + for the account service of the deployment, which should be accessible externally from the client/browser side. In + the provided example setup, the account service runs on port 3000. + + **URI Example:** + - `http://huly.mydomain.com:3000/auth/openid/callback` -2. Configure user access to the application as needed. +2. Configure user access to the application as needed. ### On the Huly side + For the account service, set the following environment variables as provided by the IdP: * OPENID_CLIENT_ID @@ -184,52 +228,61 @@ Ensure you have configured or add the following environment variable to the fron You will need to expose your account service port (e.g. 3000) in your nginx.conf. -Note: Once all the required environment variables are configured, you will see an additional button on the sign-in/sign-up pages. +Note: Once all the required environment variables are configured, you will see an additional button on the +sign-in/sign-up pages. ## Configure GitHub OAuth You can also configure a Huly instance to use GitHub OAuth for user authorization (sign-in/sign-up). ### On the GitHub side -1. Create a new GitHub OAuth application. - * Use `{huly_account_svc}/auth/github/callback` as the sign-in redirect URI. The `huly_account_svc` is the hostname for the account service of the deployment, which should be accessible externally from the client/browser side. In the provided example setup, the account service runs on port 3000. - **URI Example:** - - `http://huly.mydomain.com:3000/auth/github/callback` +1. Create a new GitHub OAuth application. + * Use `{huly_account_svc}/auth/github/callback` as the sign-in redirect URI. The `huly_account_svc` is the hostname + for the account service of the deployment, which should be accessible externally from the client/browser side. In + the provided example setup, the account service runs on port 3000. + + **URI Example:** + - `http://huly.mydomain.com:3000/auth/github/callback` ### On the Huly side + Specify the following environment variables for the account service: -* `GITHUB_CLIENT_ID` -* `GITHUB_CLIENT_SECRET` +* `GITHUB_CLIENT_ID` +* `GITHUB_CLIENT_SECRET` Ensure you have configured or add the following environment variable to the front service: -* `ACCOUNTS_URL` (The URL of the account service, accessible from the client side.) +* `ACCOUNTS_URL` (The URL of the account service, accessible from the client side.) You will need to expose your account service port (e.g. 3000) in your nginx.conf. Notes: + * The `ISSUER` environment variable is not required for GitHub OAuth. -* Once all the required environment variables are configured, you will see an additional button on the sign-in/sign-up pages. +* Once all the required environment variables are configured, you will see an additional button on the sign-in/sign-up + pages. ## Disable Sign-Up -You can disable public sign-ups for a deployment. When configured, sign-ups will only be permitted through an invite link to a specific workspace. +You can disable public sign-ups for a deployment. When configured, sign-ups will only be permitted through an invite +link to a specific workspace. To implement this, set the following environment variable for both the front and account services: ```yaml account: - ... + # ... environment: - DISABLE_SIGNUP=true - ... - front: - ... + # ... + front: + # ... environment: - DISABLE_SIGNUP=true - ... + # ... ``` -_Note: When setting up a new deployment, either create the initial account before disabling sign-ups or use the development tool to create the first account._ +_Note: When setting up a new deployment, either create the initial account before disabling sign-ups or use the +development tool to create the first account._