From 47f443c300336e376cf189ed8f2007ce38d3ff08 Mon Sep 17 00:00:00 2001 From: Bruno Bernardino Date: Thu, 28 Aug 2025 14:57:51 +0100 Subject: [PATCH] Fix for Evolution CardDav/CalDav They seem to make `GET` requests with `body`, which isn't allowed by the spec and causes Deno to fail. This prevents/ignores that. It also makes the default `docker-compose.yml` "safer" by not exposing the database and container. Finally, it removes a couple of unmaintained "one-click-deploy" buttons and simplifies documentation. --- .do/deploy.template.yaml | 43 ---------------------------------------- README.md | 39 +++++++++++++++++------------------- docker-compose.yml | 8 +++++--- render.yaml | 36 --------------------------------- routes/caldav.tsx | 7 ++++++- routes/carddav.tsx | 7 ++++++- 6 files changed, 35 insertions(+), 105 deletions(-) delete mode 100644 .do/deploy.template.yaml delete mode 100644 render.yaml diff --git a/.do/deploy.template.yaml b/.do/deploy.template.yaml deleted file mode 100644 index 868b077..0000000 --- a/.do/deploy.template.yaml +++ /dev/null @@ -1,43 +0,0 @@ -spec: - name: bewcloud - envs: - - key: BASE_URL - scope: RUN_AND_BUILD_TIME - value: ${app.PUBLIC_URL} - services: - - name: app - dockerfile_path: Dockerfile - git: - branch: main - http_port: 8000 - instance_count: 1 - instance_size_slug: basic-xs - routes: - - path: / - health_check: - http_path: / - source_dir: / - envs: - - key: POSTGRESQL_HOST - scope: RUN_AND_BUILD_TIME - value: ${db.HOSTNAME} - - key: POSTGRESQL_USER - scope: RUN_AND_BUILD_TIME - value: ${db.USERNAME} - - key: POSTGRESQL_PASSWORD - scope: RUN_AND_BUILD_TIME - value: ${db.PASSWORD} - - key: POSTGRESQL_DBNAME - scope: RUN_AND_BUILD_TIME - value: ${db.DATABASE} - - key: POSTGRESQL_PORT - scope: RUN_AND_BUILD_TIME - value: ${db.PORT} - - key: POSTGRESQL_CAFILE - scope: RUN_AND_BUILD_TIME - value: '' - databases: - - name: db - engine: PG - production: false - version: '17' diff --git a/README.md b/README.md index dd1c22c..f898872 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,6 @@ If you're looking for the mobile app, it's at [`bewcloud-mobile`](https://github ## Self-host it! -[![Deploy to DigitalOcean](https://www.deploytodo.com/do-btn-blue.svg)](https://cloud.digitalocean.com/apps/new?repo=https://github.com/bewcloud/bewcloud) - -[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/bewcloud/bewcloud) - [![Buy managed cloud (1 year)](https://img.shields.io/badge/Buy%20managed%20cloud%20(1%20year)-51a4fb?style=for-the-badge)](https://buy.stripe.com/eVa01HgQk0Ap0eseVz) [![Buy managed cloud (1 month)](https://img.shields.io/badge/Buy%20managed%20cloud%20(1%20month)-51a4fb?style=for-the-badge)](https://buy.stripe.com/fZu8wOb5RfIydj56FA1gs0J) @@ -21,27 +17,28 @@ If you're looking for the mobile app, it's at [`bewcloud-mobile`](https://github Or on your own machine, start with these commands: ```sh -$ mkdir data-files data-radicale # local directories for storing user-uploaded files and radicale data -$ sudo chown -R 1993:1993 data-files # solves permission-related issues in the container with uploading files +mkdir data-files data-radicale radicale-config # local directories for storing user-uploaded files, radicale data, and radicale config (these last two are necessary only if you're using CalDav/CardDav/Contacts) ``` -> [!NOTE] -> `1993:1993` below comes from deno's [docker image](https://github.com/denoland/deno_docker/blob/2abfe921484bdc79d11c7187a9d7b59537457c31/ubuntu.dockerfile#L20-L22) where `1993` is the default user id in it. It might change in the future since I don't control it. - Now, download/copy the following configuration files (and tweak their contents as necessary, though no changes should yield a working — but very unsafe — setup): - [`docker-compose.yml`](/docker-compose.yml) - [`.env.sample`](/.env.sample) and save it as `.env` - [`bewcloud.config.sample.ts`](/bewcloud.config.sample.ts) and save it as `bewcloud.config.ts` -- [`radicale-config/config`](/radicale-config/config) and save it as `radicale-config/config` (if you're using CalDav/CardDav/Contacts) +- [`radicale-config/config`](/radicale-config/config) and save it as `radicale-config/config` (necessary only if you're using CalDav/CardDav/Contacts) Finally, run these commands: ```sh -$ docker compose up -d # makes the app available at http://localhost:8000 -$ docker compose run --rm website bash -c "cd /app && make migrate-db" # initializes/updates the database (only needs to be executed the first time and on any data updates) +docker compose up -d # makes the app available at http://localhost:8000 +docker compose run --rm website bash -c "cd /app && make migrate-db" # initializes/updates the database (only needs to be executed the first time and on any data updates) ``` +> [!NOTE] +> If you run into permission issues, you can try running `sudo chown -R 1993:1993 data-files` to fix them. +> +> `1993:1993` above comes from deno's [docker image](https://github.com/denoland/deno_docker/blob/2abfe921484bdc79d11c7187a9d7b59537457c31/ubuntu.dockerfile#L20-L22) where `1993` is the default user id in it. It might change in the future since I don't control it. + If you're interested in building/contributing, check the [Development section below](#development). > [!IMPORTANT] @@ -71,23 +68,23 @@ These are the amazing entities or individuals who are sponsoring this project fo ### Commands ```sh -$ docker compose -f docker-compose.dev.yml up # (optional) runs docker with postgres, locally -$ make migrate-db # runs any missing database migrations -$ make start # runs the app -$ make format # formats the code -$ make test # runs tests +docker compose -f docker-compose.dev.yml up # (optional) runs docker with postgres, locally +make migrate-db # runs any missing database migrations +make start # runs the app +make format # formats the code +make test # runs tests ``` ### Other less-used commands ```sh -$ make exec-db # runs psql inside the postgres container, useful for running direct development queries like `DROP DATABASE "bewcloud"; CREATE DATABASE "bewcloud";` -$ make build # generates all static files for production deploy +make exec-db # runs psql inside the postgres container, useful for running direct development queries like `DROP DATABASE "bewcloud"; CREATE DATABASE "bewcloud";` +make build # generates all static files for production deploy ``` ## Structure -- Routes defined at `routes/`. +- Routes are defined at `routes/`. - Static files are defined at `static/`. - Frontend-only components are defined at `components/`. - Isomorphic components are defined at `islands/`. @@ -101,7 +98,7 @@ Just push to the `main` branch. ## How does Contacts/CardDav and Calendar/CalDav work? -CalDav/CardDav is now available since [v2.3.0](https://github.com/bewcloud/bewcloud/releases/tag/v2.3.0), using [Radicale](https://radicale.org/v3.html) via Docker, which is already _very_ efficient (and battle-tested). The client for CardDav is available since [v2.4.0](https://github.com/bewcloud/bewcloud/releases/tag/v2.3.0) and for CalDav is not yet implemented. [Check this tag/release for custom-made server and clients where it was all mostly working, except for many edge cases](https://github.com/bewcloud/bewcloud/releases/tag/v0.0.1-self-made-carddav-caldav). +CalDav/CardDav is now available since [v2.3.0](https://github.com/bewcloud/bewcloud/releases/tag/v2.3.0), using [Radicale](https://radicale.org/v3.html) via Docker, which is already _very_ efficient (and battle-tested). The "Contacts" client for CardDav is available since [v2.4.0](https://github.com/bewcloud/bewcloud/releases/tag/v2.3.0) and for CalDav is not yet implemented. [Check this tag/release for custom-made server and clients where it was all mostly working, except for many edge cases](https://github.com/bewcloud/bewcloud/releases/tag/v0.0.1-self-made-carddav-caldav). In order to share a calendar, you can either have a shared user, or you can symlink the calendar to the user's own calendar (simply `ln -s //collections/collection-root// //collections/collection-root//`). diff --git a/docker-compose.yml b/docker-compose.yml index c5b8860..28861f0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: website: - image: ghcr.io/bewcloud/bewcloud:v2.4.5 + image: ghcr.io/bewcloud/bewcloud:v2.4.6 restart: always ports: - 127.0.0.1:8000:8000 @@ -21,8 +21,9 @@ services: restart: always volumes: - bewcloud-db:/var/lib/postgresql/data - ports: - - 127.0.0.1:5432:5432 + # NOTE: uncomment below only if you need to connect to the database from outside the container + # ports: + # - 127.0.0.1:5432:5432 ulimits: memlock: soft: -1 @@ -32,6 +33,7 @@ services: # NOTE: If you don't want to use the CardDav/CalDav servers, you can comment/remove this service. radicale: image: tomsquest/docker-radicale:3.5.4.0 + # NOTE: uncomment below only if you need to connect to the CardDav/CalDav servers from outside the container # ports: # - 127.0.0.1:5232:5232 init: true diff --git a/render.yaml b/render.yaml deleted file mode 100644 index a065ee9..0000000 --- a/render.yaml +++ /dev/null @@ -1,36 +0,0 @@ -services: - - type: web - name: bewcloud - env: docker - plan: starter - healthCheckPath: / - envVars: - - key: BASE_URL - fromService: - name: bewcloud - type: web - property: host - - key: POSTGRESQL_HOST - fromDatabase: - name: bewcloud - property: host - - key: POSTGRESQL_USER - fromDatabase: - name: bewcloud - property: user - - key: POSTGRESQL_PASSWORD - fromDatabase: - name: bewcloud - property: password - - key: POSTGRESQL_DBNAME - fromDatabase: - name: bewcloud - property: database - - key: POSTGRESQL_PORT - fromDatabase: - name: bewcloud - property: port - -databases: - - name: bewcloud - plan: basic-256mb diff --git a/routes/caldav.tsx b/routes/caldav.tsx index 739dbba..b4a0b39 100644 --- a/routes/caldav.tsx +++ b/routes/caldav.tsx @@ -31,11 +31,16 @@ export const handler: Handler = async (request, context const requestBodyText = await request.clone().text(); // Remove the `/caldav/` prefix from the hrefs in the request - const parsedRequestBodyText = requestBodyText.replaceAll('/caldav/', `/`).replaceAll( + let parsedRequestBodyText = requestBodyText.replaceAll('/caldav/', `/`).replaceAll( ':href>/caldav/', `:href>/`, ); + // The spec doesn't allow a body for GET or HEAD requests (and Deno fails if you try) + if (request.method === 'GET' || request.method === 'HEAD') { + parsedRequestBodyText = ''; + } + const response = await fetch(`${calendarConfig.calDavUrl}/${path}`, { headers: { ...Object.fromEntries(request.headers.entries()), diff --git a/routes/carddav.tsx b/routes/carddav.tsx index 07994e3..c42aff8 100644 --- a/routes/carddav.tsx +++ b/routes/carddav.tsx @@ -31,11 +31,16 @@ export const handler: Handler = async (request, context const requestBodyText = await request.clone().text(); // Remove the `/carddav/` prefix from the hrefs in the request - const parsedRequestBodyText = requestBodyText.replaceAll('/carddav/', `/`).replaceAll( + let parsedRequestBodyText = requestBodyText.replaceAll('/carddav/', `/`).replaceAll( ':href>/carddav/', `:href>/`, ); + // The spec doesn't allow a body for GET or HEAD requests (and Deno fails if you try) + if (request.method === 'GET' || request.method === 'HEAD') { + parsedRequestBodyText = ''; + } + const response = await fetch(`${contactsConfig.cardDavUrl}/${path}`, { headers: { ...Object.fromEntries(request.headers.entries()),