Merge pull request #115 from IlyaGulya/support/ws-as-separate-paths

traefik: expose websockets on the same domain under `/ws/` path instead of separate ports
This commit is contained in:
Alexander Onnikov
2025-01-21 17:12:42 +07:00
committed by GitHub
3 changed files with 201 additions and 133 deletions
+2 -1
View File
@@ -3,4 +3,5 @@ traefik/docker-compose.yaml
nginx.conf
nginx.conf.bak
huly.conf
.huly.secret
.huly.secret
.idea
+91 -38
View File
@@ -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.411
container_name: ses
ports:
- 3335:3335
expose:
- 3335
environment:
- SOURCE=<EMAIL_FROM>
- ACCESS_KEY=<SES_ACCESS_KEY>
@@ -103,26 +141,28 @@ 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
@@ -151,11 +191,11 @@ Huly audio and video calls are created on top of LiveKit insfrastructure. In ord
```yaml
front:
...
# ...
environment:
- LIVEKIT_WS=<LIVEKIT_HOST>
- 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.
2. Configure user access to the application as needed.
**URI Example:**
- `http://huly.mydomain.com:3000/auth/openid/callback`
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._
+108 -94
View File
@@ -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: