From d6de380e028d372ae58cbe5c36bdc1945628941f Mon Sep 17 00:00:00 2001 From: Quentin G Date: Wed, 27 Mar 2024 21:28:03 +0100 Subject: [PATCH] feat: add one liner install command (#4613) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add one liner * fix: interactive issue & add support for both linux & mac * feat: move quick start documentation * feat: catch errors * feat: check if directory exists * feat: default to yes for prompt * feat: open in browser * fix: format * feat: do not expose STORAGE_LOCAL_PATH env but handle the case where it would be set * fix: db reset command wasn't working out of the box * Update install.sh Co-authored-by: Darek Desu <4459421+darekdesu@users.noreply.github.com> * feat: harden the whole UX with one-liner * fix: small logical order adjustment * Update packages/twenty-docs/docs/start/self-hosting/docker-compose.mdx --------- Co-authored-by: Darek Desu <4459421+darekdesu@users.noreply.github.com> Co-authored-by: FΓ©lix Malfait --- install.sh | 149 ++++++++++++++++++ packages/twenty-docker/prod/.env.example | 6 + .../twenty-docker/prod/docker-compose.yml | 15 +- .../twenty-docker/prod/twenty/entrypoint.sh | 4 +- .../start/self-hosting/docker-compose.mdx | 92 ++++------- 5 files changed, 192 insertions(+), 74 deletions(-) create mode 100755 install.sh diff --git a/install.sh b/install.sh new file mode 100755 index 0000000000..7d03a201c8 --- /dev/null +++ b/install.sh @@ -0,0 +1,149 @@ +#!/bin/bash + +echo "πŸ”§ Checking dependencies..." +if ! command -v docker &>/dev/null; then + echo -e "\t❌ Docker is not installed or not in PATH. Please install Docker first.\n\t\tSee https://docs.docker.com/get-docker/" + exit 1 +fi +# Check if docker compose plugin is installed +if ! docker compose version &>/dev/null; then + echo -e "\t❌ Docker Compose is not installed or not in PATH (n.b. docker-compose is deprecated)\n\t\tUpdate docker or install docker-compose-plugin\n\t\tOn Linux: sudo apt-get install docker-compose-plugin\n\t\tSee https://docs.docker.com/compose/install/" + exit 1 +fi +# Check if docker is started +if ! docker info &>/dev/null; then + echo -e "\t❌ Docker is not running.\n\t\tPlease start Docker Desktop, Docker or check documentation at https://docs.docker.com/config/daemon/start/" + exit 1 +fi +if ! command -v curl &>/dev/null; then + echo -e "\t❌ Curl is not installed or not in PATH.\n\t\tOn macOS: brew install curl\n\t\tOn Linux: sudo apt install curl" + exit 1 +fi + +# Catch errors +set -e +function on_exit { + # $? is the exit status of the last command executed + local exit_status=$? + if [ $exit_status -ne 0 ]; then + echo "❌ Something went wrong, exiting: $exit_status" + fi +} +trap on_exit EXIT + +# Use environment variables VERSION and BRANCH, with defaults if not set +version=${VERSION:-$(curl -s https://api.github.com/repos/twentyhq/twenty/releases/latest | grep '"tag_name":' | cut -d '"' -f 4)} +branch=${BRANCH:-main} + +echo "πŸš€ Using version $version and branch $branch" + +dir_name="twenty" +function ask_directory { + read -p "πŸ“ Enter the directory name to setup the project (default: $dir_name): " answer + if [ -n "$answer" ]; then + dir_name=$answer + fi +} + +ask_directory + +while [ -d "$dir_name" ]; do + read -p "🚫 Directory '$dir_name' already exists. Do you want to overwrite it? (y/N) " answer + if [ "$answer" = "y" ]; then + break + else + ask_directory + fi +done + +# Create a directory named twenty +echo "πŸ“ Creating directory '$dir_name'" +mkdir -p "$dir_name" && cd "$dir_name" || { echo "❌ Failed to create/access directory '$dir_name'"; exit 1; } + +# Copy the twenty/packages/twenty-docker/prod/docker-compose.yml file in it +echo "\tβ€’ Copying docker-compose.yml" +curl -sLo docker-compose.yml https://raw.githubusercontent.com/twentyhq/twenty/$branch/packages/twenty-docker/prod/docker-compose.yml + +# Copy twenty/packages/twenty-docker/prod/.env.example to .env +echo "\tβ€’ Setting up .env file" +curl -sLo .env https://raw.githubusercontent.com/twentyhq/twenty/$branch/packages/twenty-docker/prod/.env.example + +# Replace TAG=latest by TAG= +if [[ $(uname) == "Darwin" ]]; then + # Running on macOS + sed -i '' "s/TAG=latest/TAG=$version/g" .env +else + # Assuming Linux + sed -i'' "s/TAG=latest/TAG=$version/g" .env +fi + +# Generate random strings for secrets +echo "ACCESS_TOKEN_SECRET=$(openssl rand -base64 32)" >>.env +echo "LOGIN_TOKEN_SECRET=$(openssl rand -base64 32)" >>.env +echo "REFRESH_TOKEN_SECRET=$(openssl rand -base64 32)" >>.env +echo "FILE_TOKEN_SECRET=$(openssl rand -base64 32)" >>.env + +echo "\tβ€’ .env configuration completed" + +port=3000 +# Check if command nc is available +if command -v nc &> /dev/null; then + # Check if port 3000 is already in use, propose to change it + while nc -zv localhost $port &>/dev/null; do + read -p "🚫 Port $port is already in use. Do you want to use another port? (Y/n) " answer + if [ "$answer" = "n" ]; then + continue + fi + read -p "Enter a new port number: " new_port + if [[ $(uname) == "Darwin" ]]; then + sed -i '' "s/$port:$port/$new_port:$port/g" docker-compose.yml + else + sed -i'' "s/$port:$port/$new_port:$port/g" docker-compose.yml + fi + port=$new_port + done +fi + +# Ask user if he wants to start the project +read -p "πŸš€ Do you want to start the project now? (Y/n) " answer +if [ "$answer" = "n" ]; then + echo "βœ… Project setup completed. Run 'docker-compose up -d' to start." + exit 0 +else + echo "🐳 Starting Docker containers..." + docker compose up -d + # Check if port is listening + echo -n "Waiting for server to start..." + while [ ! $(docker inspect --format='{{.State.Health.Status}}' twenty-server-1) = "healthy" ]; do + echo -n "." + sleep 1 + done + echo "" + echo "βœ… Server is up and running" +fi + +function ask_open_browser { + read -p "🌐 Do you want to open the project in your browser? (Y/n) " answer + if [ "$answer" = "n" ]; then + echo "βœ… Setup completed. Access your project at http://localhost:$port" + exit 0 + fi +} + +# Ask user if he wants to open the project +# Running on macOS +if [[ $(uname) == "Darwin" ]]; then + ask_open_browser + + open "http://localhost:$port" +# Assuming Linux +else + # xdg-open is not installed, we could be running in a non gui environment + if command -v xdg-open >/dev/null 2>&1; then + ask_open_browser + + xdg-open "http://localhost:$port" + else + echo "βœ… Setup completed. Your project is available at http://localhost:$port" + fi +fi diff --git a/packages/twenty-docker/prod/.env.example b/packages/twenty-docker/prod/.env.example index 9b13368b11..f9bf606369 100644 --- a/packages/twenty-docker/prod/.env.example +++ b/packages/twenty-docker/prod/.env.example @@ -15,3 +15,9 @@ SERVER_URL=http://localhost:3000 # FILE_TOKEN_SECRET=replace_me_with_a_random_string_refresh SIGN_IN_PREFILLED=true + +STORAGE_TYPE=local + +# STORAGE_S3_REGION=eu-west3 +# STORAGE_S3_NAME=my-bucket +# STORAGE_S3_ENDPOINT= diff --git a/packages/twenty-docker/prod/docker-compose.yml b/packages/twenty-docker/prod/docker-compose.yml index bd857b53a6..edfcef2a54 100644 --- a/packages/twenty-docker/prod/docker-compose.yml +++ b/packages/twenty-docker/prod/docker-compose.yml @@ -1,11 +1,10 @@ -version: '3.8' name: twenty services: server: image: twentycrm/twenty:${TAG} volumes: - - server-local-data:/app/.local-storage + - server-local-data:/app/${STORAGE_LOCAL_PATH:-.local-storage} ports: - "3000:3000" environment: @@ -17,8 +16,10 @@ services: ENABLE_DB_MIGRATIONS: true SIGN_IN_PREFILLED: ${SIGN_IN_PREFILLED} - STORAGE_TYPE: local - STORAGE_LOCAL_PATH: .local-storage + STORAGE_TYPE: ${STORAGE_TYPE} + STORAGE_S3_REGION: ${STORAGE_S3_REGION} + STORAGE_S3_NAME: ${STORAGE_S3_NAME} + STORAGE_S3_ENDPOINT: ${STORAGE_S3_ENDPOINT} ACCESS_TOKEN_SECRET: ${ACCESS_TOKEN_SECRET} LOGIN_TOKEN_SECRET: ${LOGIN_TOKEN_SECRET} REFRESH_TOKEN_SECRET: ${REFRESH_TOKEN_SECRET} @@ -27,7 +28,7 @@ services: db: condition: service_healthy healthcheck: - test: ["CMD-SHELL", "curl --silent --fail http://localhost:3000/healthz | jq -e '.status == \"ok\"' > /dev/null || exit 1"] + test: curl --fail http://localhost:3000/healthz interval: 5s timeout: 5s retries: 10 @@ -39,10 +40,8 @@ services: - db-data:/bitnami/postgresql environment: POSTGRES_PASSWORD: ${POSTGRES_ADMIN_PASSWORD} - #POSTGRES_USER: ${POSTGRES_USER} - #POSTGRES_DB: ${POSTGRES_DB} healthcheck: - test: ["CMD-SHELL", "pg_isready -U twenty -d default"] + test: pg_isready -U twenty -d default interval: 5s timeout: 5s retries: 10 diff --git a/packages/twenty-docker/prod/twenty/entrypoint.sh b/packages/twenty-docker/prod/twenty/entrypoint.sh index a6167afcae..b3bb5cad57 100755 --- a/packages/twenty-docker/prod/twenty/entrypoint.sh +++ b/packages/twenty-docker/prod/twenty/entrypoint.sh @@ -1,7 +1,7 @@ #!/bin/sh # Check if the initialization has already been done and that we enabled automatic migration -if [ "${ENABLE_DB_MIGRATIONS}" = "true" ] && [ ! -f /app/${STORAGE_LOCAL_PATH}/db_initialized ]; then +if [ "${ENABLE_DB_MIGRATIONS}" = "true" ] && [ ! -f /app/${STORAGE_LOCAL_PATH:-.local-storage}/db_initialized ]; then echo "Running database setup and migrations..." # Run setup and migration scripts @@ -10,7 +10,7 @@ if [ "${ENABLE_DB_MIGRATIONS}" = "true" ] && [ ! -f /app/${STORAGE_LOCAL_PATH}/d # Mark initialization as done echo "Successfuly migrated DB!" - touch /app/${STORAGE_LOCAL_PATH}/db_initialized + touch /app/${STORAGE_LOCAL_PATH:-.local-storage}/db_initialized fi # Continue with the original Docker command diff --git a/packages/twenty-docs/docs/start/self-hosting/docker-compose.mdx b/packages/twenty-docs/docs/start/self-hosting/docker-compose.mdx index 60b1137ad0..5af8d930da 100644 --- a/packages/twenty-docs/docs/start/self-hosting/docker-compose.mdx +++ b/packages/twenty-docs/docs/start/self-hosting/docker-compose.mdx @@ -6,7 +6,23 @@ sidebar_custom_props: --- # Step by step instructions: -1. Copy the [.env.example](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/.env.example) into a `.env` in the same directory where your `docker-compose.yml` file will be +## One command installation + +Install the project with the command below. By default, it installs the latest version from the main branch. +```bash +bash <(curl -sL https://git.new/20) +``` + +## Custom Installation: + +Set VERSION for a specific docker image version, BRANCH for a specific clone branch: +```bash +VERSION=x.y.z BRANCH=branch-name bash <(curl -sL https://raw.githubusercontent.com/twentyhq/twenty/main/install.sh) +``` + +## Manual installation + +1. Copy the [.env.example](https://github.com/twentyhq/twenty/blob/main/packages/twenty-docker/prod/.env.example) into a `.env` in the same directory where your `docker-compose.yml` file will be 2. Run the command `openssl rand -base64 32` three times, make note of the string for each 3. In your .env file, replace the three "replace_me_with_a_random_string_access" with the three random strings you just generated. @@ -17,78 +33,26 @@ REFRESH_TOKEN_SECRET=replace_me_with_a_random_string_refresh FILE_TOKEN_SECRET=replace_me_with_a_random_string_refresh ``` -4. Create a `docker-compose.yml` file from the example below. +4. Copy the [docker-compose.yml](https://github.com/twentyhq/twenty/blob/main/packages/twenty-docker/prod/docker-compose.yml) in the same directory as your `.env` file. 5. Run the command `docker-compose up -d` -6. Go to http://localhost:3001 and see your docker instance. +6. Go to http://localhost:3000 and see your docker instance. ## Troubleshooting ### Not able to login -If you encounter errors, (not able to log into the application after inputting an email) after the inital setup, try running `docker exec -it twenty-backend-1 yarn nx database:reset` and see if that solves your issue. +If you encounter errors, (not able to log into the application after inputting an email) after the inital setup, try running the following commands and see if that solves your issue. +``` +docker exec -it twenty-server-1 yarn +docker exec -it twenty-server-1 yarn nx database:reset +``` ### Cannot connect to server, running behind a reverse proxy -Complete step three and four with : +Complete step three and four with: -3. Add `SERVER_URL=https://` to your `.env` -4. Uncomment `SERVER_URL=${SERVER_URL}` in your `docker-compose.yml` +3. Update `SERVER_URL=https://` in your `.env` -## Production docker containers +### Persistence -Prebuilt images for both Postgres, frontend, and back-end can be found on [docker hub](https://hub.docker.com/r/twentycrm/). Note that the Postgres container will not persist data if your server is not configured to be stateful (for example Heroku). You probably want to configure a special stateful resource for the database. - -## Environment Variables - -- Copy this `.env.example` file into a `.env` in the same directory as your `docker-compose.yml` file -- Find the `.env.example` [here](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/.env.example). - -## Docker Compose file - -We will soon update the documentation with an up-to-date docker compose file. -Here is one that was proposed on Discord by a community member: - - -```yaml -version: "3.9" -services: - - twenty: - image: twentycrm/twenty-front:${TAG} - ports: - - 3001:3000 - environment: - - SIGN_IN_PREFILLED=${SIGN_IN_PREFILLED} - - REACT_APP_SERVER_BASE_URL=${LOCAL_SERVER_URL} - depends_on: - - backend - - backend: - image: twentycrm/twenty-server:${TAG} - ports: - - 3000:3000 - environment: - - SIGN_IN_PREFILLED=${SIGN_IN_PREFILLED} - - PG_DATABASE_URL=${PG_DATABASE_URL} - - FRONT_BASE_URL=${FRONT_BASE_URL} - - PORT=3000 - - STORAGE_TYPE=local - - STORAGE_LOCAL_PATH=.local-storage - - ACCESS_TOKEN_SECRET=${ACCESS_TOKEN_SECRET} - - LOGIN_TOKEN_SECRET=${LOGIN_TOKEN_SECRET} - - REFRESH_TOKEN_SECRET=${REFRESH_TOKEN_SECRET} - - FILE_TOKEN_SECRET=${FILE_TOKEN_SECRET} - # Uncomment if behind a reverse proxy - # - SERVER_URL=${SERVER_URL} - depends_on: - - db - - db: - image: twentycrm/twenty-postgres:${TAG} - volumes: - - twenty-db-data:/bitnami/postgresql - environment: - - POSTGRES_PASSWORD=${POSTGRES_ADMIN_PASSWORD} -volumes: - twenty-db-data: -``` +By default the docker-compose will create volumes for the Database and local storage of the Server. Note that the containers will not persist data if your server is not configured to be stateful (for example Heroku). You probably want to configure a special stateful resource for this purpose.