diff --git a/shell.nix b/shell.nix index 8f8623e2c041..4589fb514b9c 100644 --- a/shell.nix +++ b/shell.nix @@ -561,8 +561,20 @@ let set -e cd $(${pkgs.git}/bin/git rev-parse --show-toplevel)/utopia-remix ${pnpm}/bin/pnpm install - ${pnpm}/bin/pnpm exec prisma generate - PORT=8000 DATABASE_URL="postgres://$(whoami):postgres@localhost:5432/utopia" ${pnpm}/bin/pnpm run dev + ${pnpm}/bin/pnpm exec prisma generate > /dev/null + PNPM=${pnpm}/bin/pnpm PORT=8000 DATABASE_URL="postgres://$(whoami):postgres@localhost:5432/utopia" LOCAL=true ./run-remix.sh + '') + (pkgs.writeScriptBin "watch-fga" '' + #!/usr/bin/env bash + set -e + cd $(${pkgs.git}/bin/git rev-parse --show-toplevel)/utopia-remix + ./fga-run.sh + '') + (pkgs.writeScriptBin "fga-create-store" '' + #!/usr/bin/env bash + set -e + cd $(${pkgs.git}/bin/git rev-parse --show-toplevel)/utopia-remix + ./fga-create-store.sh '') ]; @@ -639,6 +651,8 @@ let send-keys -t :9 watch-vscode-build-extension-only C-m \; \ new-window -n "PostgreSQL" \; \ send-keys -t :10 start-postgres C-m \; \ + new-window -n "FGA" \; \ + send-keys -t :11 watch-fga C-m \; \ select-window -t :1 \; '') (pkgs.writeScriptBin "start-full" '' @@ -684,6 +698,8 @@ let send-keys -t :9 watch-vscode-build-extension-only C-m \; \ new-window -n "PostgreSQL" \; \ send-keys -t :10 start-postgres C-m \; \ + new-window -n "FGA" \; \ + send-keys -t :11 watch-fga C-m \; \ select-window -t :1 \; '') (pkgs.writeScriptBin "start-full-webpack" '' diff --git a/utopia-remix/docker-compose.fga.yml b/utopia-remix/docker-compose.fga.yml new file mode 100644 index 000000000000..49741924c6ff --- /dev/null +++ b/utopia-remix/docker-compose.fga.yml @@ -0,0 +1,81 @@ +services: + postgres: + image: postgres:14 + container_name: postgres + command: postgres -c 'max_connections=100' + networks: + - default + ports: + - '54323:5432' + environment: + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=password + healthcheck: + test: ['CMD-SHELL', 'pg_isready -U postgres'] + interval: 5s + timeout: 5s + retries: 5 + + migrate: + depends_on: + postgres: + condition: service_healthy + image: openfga/openfga:latest + container_name: migrate + environment: + - OPENFGA_DATASTORE_ENGINE=postgres + - OPENFGA_DATASTORE_URI=postgres://postgres:password@postgres:5432/postgres?sslmode=disable + command: migrate + networks: + - default + + openfga: + depends_on: + migrate: + condition: service_completed_successfully + image: openfga/openfga:latest + container_name: openfga + command: run + environment: + - OPENFGA_DATASTORE_ENGINE=postgres + - OPENFGA_DATASTORE_URI=postgres://postgres:password@postgres:5432/postgres?sslmode=disable + - OPENFGA_DATASTORE_MAX_OPEN_CONNS=100 #see postgres container + - OPENFGA_PLAYGROUND_ENABLED=true + networks: + - default + ports: + - '8003:8080' #http + # - '8081:8081' #grpc + # - '3002:3000' #playground + # - "2112:2112" #prometheus metrics + healthcheck: + test: ['CMD', '/usr/local/bin/grpc_health_probe', '-addr=openfga:8081'] + interval: 5s + timeout: 30s + retries: 3 + + # Two separate manual services for Docker and Podman because they behave differently :( + utopia-fga-model-docker: + profiles: + - manual + depends_on: + openfga: + condition: service_healthy + image: openfga/cli:latest + container_name: utopia-fga-model-docker + volumes: + - ./fga/model.fga:/model.fga + command: store create --name utopia-local --model /model.fga --api-url http://openfga:8080 + utopia-fga-model-podman: + profiles: + - manual + image: alpine:3.19.1 + container_name: utopia-fga-model-podman + volumes: + - ./fga/model.fga:/model.fga + command: > + sh -c " + wget -q https://github.com/openfga/cli/releases/download/v0.2.7/fga_0.2.7_linux_arm64.tar.gz + tar -xzf fga_0.2.7_linux_arm64.tar.gz + ./fga store create --name utopia-local --model /model.fga --api-url http://openfga:8080 + " diff --git a/utopia-remix/fga-create-store.sh b/utopia-remix/fga-create-store.sh new file mode 100755 index 000000000000..6b6fa4a0f8b2 --- /dev/null +++ b/utopia-remix/fga-create-store.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash + +# Create a new store based on the fga/model.fga file against the local FGA server, +# then parse the generated Store ID and save it into the .env file. + +set -e + +HOST_ENV_KEY="FGA_API_HOST" +STORE_ENV_KEY="FGA_STORE_ID" + +# Only run this if the .env file uses the local FGA store +if ! grep -qE "^[^#]*\b${HOST_ENV_KEY}\b=['\"]?localhost" .env; then + echo "The FGA_API_HOST is either not defined or not set to localhost, so I won't do anything." + exit +fi + +if type docker &> /dev/null; then + RUNTIME=docker +elif type podman &> /dev/null; then + RUNTIME=podman +else + echo "The docker or podman commands are not found." + exit +fi + +SERVICE_NAME="utopia-fga-model-${RUNTIME}" + +echo "* Creating store…" +${RUNTIME} compose -f docker-compose.fga.yml up ${SERVICE_NAME} + +# Note: it would be awesome to just use jq here, but there's no guarantee it +# will be available on the host machine, so this is just doing a simple regex +# match since the JSON output of the FGA CLI is simple enough. +echo "* Parsing logs…" +STORE_ID=$( ${RUNTIME} logs -f ${SERVICE_NAME} | tail -n1 | grep -o '"id":"[^"]*"' | awk -F':' '{print $2}' | tr -d '"' ) + +echo "=> Obtained store id ${STORE_ID}" + +STORE_ENV_LINE="${STORE_ENV_KEY}=\"${STORE_ID}\" # ** GENERATED BY fga-create-store.sh **" + +grep -vE "\b${STORE_ENV_KEY}\b=" .env > .env.temp +mv .env.temp .env +echo "${STORE_ENV_LINE}" >> .env + +echo "=> Updated .env file" diff --git a/utopia-remix/fga-run.sh b/utopia-remix/fga-run.sh new file mode 100755 index 000000000000..29fda8cbdb4f --- /dev/null +++ b/utopia-remix/fga-run.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +# Start the FGA local server with either Docker or Podman. + +set -e + +HOST_ENV_KEY="FGA_API_HOST" +STORE_ENV_KEY="FGA_STORE_ID" + +# Only run this if the .env file uses the local FGA store +if ! grep -qE "^[^#]*\b${HOST_ENV_KEY}\b=['\"]?localhost" .env; then + echo "The FGA_API_HOST is either not defined or not set to localhost, so I won't do anything." + echo "If you want to enable the local FGA server, please add FGA_API_HOST=localhost:8003 to your environment variables." + exit +fi + +if type docker &> /dev/null; then + RUNTIME=docker +elif type podman &> /dev/null; then + RUNTIME=podman +else + echo "The docker or podman commands are not found." + exit +fi + +${RUNTIME} compose -f docker-compose.fga.yml up diff --git a/utopia-remix/fga/model.fga b/utopia-remix/fga/model.fga new file mode 100644 index 000000000000..43085ffc5e1b --- /dev/null +++ b/utopia-remix/fga/model.fga @@ -0,0 +1,26 @@ +model + schema 1.1 + +type user + +type project + relations + define can_change_owner: owner + define can_comment: collaborator or editor or owner + define can_edit: editor or owner + define can_fork: viewer or collaborator or editor or owner + define can_manage: owner + define can_play: viewer or collaborator or editor or owner + define can_request_access: [user:*] + define can_see_live_changes: collaborator or editor or owner + define can_show_presence: collaborator or editor or owner + define can_view: viewer or collaborator or editor or owner + define collaborator: [user, user:*, group#member] + define creator: [user] + define editor: [user, group#member] + define owner: [user] or creator + define viewer: [user, user:*, group#member] + +type group + relations + define member: [user] diff --git a/utopia-remix/run-remix.sh b/utopia-remix/run-remix.sh new file mode 100755 index 000000000000..f1498e1fda3f --- /dev/null +++ b/utopia-remix/run-remix.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +## Please run this script from shell.nix with `watch-remix` or provide the +## PNPM= environment variable with the pnpm executable path. + +set -e + +if [ "${LOCAL}" == 'true' ] ; then + # Make sure the .env file exists + touch .env + + HOST_ENV_KEY="FGA_API_HOST" + STORE_ENV_KEY="FGA_STORE_ID" + + if grep -qE "^[^#]*\b${HOST_ENV_KEY}\b=['\"]?localhost" .env; then + if ! grep -qE "^[^#]*\b${STORE_ENV_KEY}\b=['\"]?.+" .env; then + echo "* FGA is set to run locally, but no FGA_STORE_ID is set. Creating it…" + ./fga-create-store.sh + fi + fi +fi + +${PNPM} run dev