#!/bin/bash # # Plane Service PATH=$HOME/.docker/cli-plugins:/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin . /federated/lib/helpers.sh # FIXME plane's nginx proxy has a few additional rules that we may # need to port to Traefik: # add_header X-Content-Type-Options "nosniff" always; # add_header Referrer-Policy "no-referrer-when-downgrade" always; # add_header Permissions-Policy "interest-cohort=()" always; # add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; # add_header X-Forwarded-Proto "$scheme"; # add_header X-Forwarded-Host "$host"; # add_header X-Forwarded-For "$proxy_add_x_forwarded_for"; # add_header X-Real-IP "$remote_addr"; config_plane() { echo -ne "\n* Configuring /federated/apps/plane container.." if [ ! -d "/federated/apps/plane" ]; then mkdir -p /federated/apps/plane/data/plane &> /dev/null fi POSTGRES_PASSWORD=$(create_password) EMAIL_PASSWORD="$(cat /federated/apps/panel/.env |grep ^SMTP_PASSWORD= |cut -d= -f2-)" USE_TRAEFIK=true cat >/federated/apps/plane/.env <<'EOF' APP_DOMAIN=plane.@DOMAIN@ IMAGE_VERSION=v0.24.1 VALKEY_RELEASE=7.2.5-alpine RABBITMQ_RELEASE=3.13.6-management-alpine MINIO_RELEASE=RELEASE.2024-12-18T13-15-44Z WEB_REPLICAS=1 SPACE_REPLICAS=1 ADMIN_REPLICAS=1 API_REPLICAS=1 NGINX_PORT=80 WEB_URL=http://${APP_DOMAIN} DEBUG=0 SENTRY_DSN= SENTRY_ENVIRONMENT=production CORS_ALLOWED_ORIGINS=http://${APP_DOMAIN} API_BASE_URL=http://api:8000 #DB SETTINGS PGHOST=postgresql PGDATABASE=plane POSTGRES_USER=plane POSTGRES_PASSWORD=@POSTGRES_PASSWORD@ POSTGRES_DB=plane POSTGRES_PORT=5432 PGDATA=/var/lib/postgresql/data DATABASE_URL= # REDIS SETTINGS REDIS_HOST=plane-redis REDIS_PORT=6379 REDIS_URL= # RabbitMQ Settings RABBITMQ_HOST=plane-mq RABBITMQ_PORT=5672 RABBITMQ_USER=plane RABBITMQ_PASSWORD=plane RABBITMQ_VHOST=plane AMQP_URL= # Secret Key SECRET_KEY=60gp0byfz2dvffa45cxl20p1scy9xbpf6d8c5y0geejgkyp1b5 # DATA STORE SETTINGS USE_MINIO=1 AWS_REGION= AWS_ACCESS_KEY_ID=access-key AWS_SECRET_ACCESS_KEY=secret-key AWS_S3_ENDPOINT_URL=http://plane-minio:9000 AWS_S3_BUCKET_NAME=uploads MINIO_ROOT_USER=access-key MINIO_ROOT_PASSWORD=secret-key BUCKET_NAME=uploads FILE_SIZE_LIMIT=5242880 # Gunicorn Workers GUNICORN_WORKERS=1 # Email EMAIL_HOST=mail.@DOMAIN@ EMAIL_HOST_USER=fcore@@DOMAIN@ EMAIL_HOST_PASSWORD=@EMAIL_PASSWORD@ EMAIL_PORT=587 EMAIL_FROM=admin@@DOMAIN@ EMAIL_USE_TLS=1 EMAIL_USE_SSL=0 # UNCOMMENT `DOCKER_PLATFORM` IF YOU ARE ON `ARM64` AND DOCKER IMAGE IS NOT AVAILABLE FOR RESPECTIVE `IMAGE_VERSION` # DOCKER_PLATFORM=linux/amd64 DOCKERHUB_USER=makeplane PULL_POLICY=if_not_present CUSTOM_BUILD=false EOF cat > /federated/apps/plane/docker-compose.yml <<'EOF' x-app-env: &app-env environment: - NGINX_PORT=${NGINX_PORT:-80} - WEB_URL=${WEB_URL:-http://localhost} - DEBUG=${DEBUG:-0} - SENTRY_DSN=${SENTRY_DSN:-""} - SENTRY_ENVIRONMENT=${SENTRY_ENVIRONMENT:-"production"} - CORS_ALLOWED_ORIGINS=${CORS_ALLOWED_ORIGINS:-} # Gunicorn Workers - GUNICORN_WORKERS=${GUNICORN_WORKERS:-1} #DB SETTINGS - PGHOST=${PGHOST:-postgresql} - PGDATABASE=${PGDATABASE:-plane} - POSTGRES_USER=${POSTGRES_USER:-plane} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-@POSTGRES_PASSWORD@} - POSTGRES_DB=${POSTGRES_DB:-plane} - POSTGRES_PORT=${POSTGRES_PORT:-5432} - PGDATA=${PGDATA:-/var/lib/postgresql/data} - DATABASE_URL=${DATABASE_URL:-postgresql://plane:@POSTGRES_PASSWORD@@postgresql/plane} # REDIS SETTINGS - REDIS_HOST=${REDIS_HOST:-plane-redis} - REDIS_PORT=${REDIS_PORT:-6379} - REDIS_URL=${REDIS_URL:-redis://plane-redis:6379/} # RabbitMQ Settings - RABBITMQ_HOST=${RABBITMQ_HOST:-plane-mq} - RABBITMQ_PORT=${RABBITMQ_PORT:-5672} - RABBITMQ_DEFAULT_USER=${RABBITMQ_USER:-plane} - RABBITMQ_DEFAULT_PASS=${RABBITMQ_PASSWORD:-plane} - RABBITMQ_DEFAULT_VHOST=${RABBITMQ_VHOST:-plane} - RABBITMQ_VHOST=${RABBITMQ_VHOST:-plane} - AMQP_URL=${AMQP_URL:-amqp://plane:plane@plane-mq:5672/plane} # Application secret - SECRET_KEY=${SECRET_KEY:-60gp0byfz2dvffa45cxl20p1scy9xbpf6d8c5y0geejgkyp1b5} # DATA STORE SETTINGS - USE_MINIO=${USE_MINIO:-1} - AWS_REGION=${AWS_REGION:-} - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-"access-key"} - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-"secret-key"} - AWS_S3_ENDPOINT_URL=${AWS_S3_ENDPOINT_URL:-http://plane-minio:9000} - AWS_S3_BUCKET_NAME=${AWS_S3_BUCKET_NAME:-uploads} - MINIO_ROOT_USER=${MINIO_ROOT_USER:-"access-key"} - MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD:-"secret-key"} - BUCKET_NAME=${BUCKET_NAME:-uploads} - FILE_SIZE_LIMIT=${FILE_SIZE_LIMIT:-5242880} # Live server env - API_BASE_URL=${API_BASE_URL:-http://api:8000} services: web: <<: *app-env image: ${DOCKERHUB_USER:-makeplane}/plane-frontend:${IMAGE_VERSION:-stable} platform: ${DOCKER_PLATFORM:-} expose: - 3000 ports: - 3000:3000 env_file: - .env EOF if $USE_TRAEFIK; then cat >> /federated/apps/plane/docker-compose.yml <<'EOF' labels: - "traefik.enable=true" - "traefik.http.routers.plane-web.rule=Host(`plane.@DOMAIN@`)" - "traefik.http.routers.plane-web.entrypoints=websecure" - "traefik.http.routers.plane-web.tls.certresolver=letsencrypt" - "traefik.http.services.plane-web.loadbalancer.server.port=3000" EOF fi cat >> /federated/apps/plane/docker-compose.yml <<'EOF' networks: core: ipv4_address: 192.168.0.50 pull_policy: if_not_present restart: unless-stopped command: node web/server.js web deploy: replicas: ${WEB_REPLICAS:-1} depends_on: - api - worker space: <<: *app-env image: ${DOCKERHUB_USER:-makeplane}/plane-space:${IMAGE_VERSION:-stable} platform: ${DOCKER_PLATFORM:-} env_file: - .env networks: core: ipv4_address: 192.168.0.51 EOF if $USE_TRAEFIK; then cat >> /federated/apps/plane/docker-compose.yml <<'EOF' labels: - "traefik.enable=true" - "traefik.http.routers.plane-space.rule=Host(`plane.@DOMAIN@`) && PathPrefix(`/spaces`)" - "traefik.http.routers.plane-space.entrypoints=websecure" - "traefik.http.routers.plane-space.tls.certresolver=letsencrypt" - "traefik.http.services.plane-space.loadbalancer.server.port=3000" EOF fi cat >> /federated/apps/plane/docker-compose.yml <<'EOF' pull_policy: if_not_present restart: unless-stopped command: node space/server.js space deploy: replicas: ${SPACE_REPLICAS:-1} depends_on: - api - worker - web admin: <<: *app-env image: ${DOCKERHUB_USER:-makeplane}/plane-admin:${IMAGE_VERSION:-stable} platform: ${DOCKER_PLATFORM:-} env_file: - .env networks: core: ipv4_address: 192.168.0.52 EOF if $USE_TRAEFIK; then cat >> /federated/apps/plane/docker-compose.yml <<'EOF' labels: - "traefik.enable=true" - "traefik.http.routers.plane-admin.rule=Host(`plane.@DOMAIN@`) && PathPrefix(`/god-mode`)" - "traefik.http.routers.plane-admin.entrypoints=websecure" - "traefik.http.routers.plane-admin.tls.certresolver=letsencrypt" - "traefik.http.services.plane-admin.loadbalancer.server.port=3000" EOF fi cat >> /federated/apps/plane/docker-compose.yml <<'EOF' pull_policy: if_not_present restart: unless-stopped command: node admin/server.js admin deploy: replicas: ${ADMIN_REPLICAS:-1} depends_on: - api - web live: <<: *app-env image: ${DOCKERHUB_USER:-makeplane}/plane-live:${IMAGE_VERSION:-stable} platform: ${DOCKER_PLATFORM:-} env_file: - .env networks: core: ipv4_address: 192.168.0.53 EOF if $USE_TRAEFIK; then cat >> /federated/apps/plane/docker-compose.yml <<'EOF' labels: - "traefik.enable=true" - "traefik.http.routers.plane-live.rule=Host(`plane.@DOMAIN@`) && PathPrefix(`/live`)" - "traefik.http.routers.plane-live.entrypoints=websecure" - "traefik.http.routers.plane-live.tls.certresolver=letsencrypt" - "traefik.http.services.plane-live.loadbalancer.server.port=3000" EOF fi cat >> /federated/apps/plane/docker-compose.yml <<'EOF' pull_policy: if_not_present restart: unless-stopped command: node live/dist/server.js live deploy: replicas: ${LIVE_REPLICAS:-1} depends_on: - api - web api: <<: *app-env image: ${DOCKERHUB_USER:-makeplane}/plane-backend:${IMAGE_VERSION:-stable} platform: ${DOCKER_PLATFORM:-} env_file: - .env networks: core: ipv4_address: 192.168.0.54 EOF if $USE_TRAEFIK; then cat >> /federated/apps/plane/docker-compose.yml <<'EOF' labels: - "traefik.enable=true" - "traefik.http.routers.plane-api.rule=Host(`plane.@DOMAIN@`) && (PathPrefix(`/api`) || PathPrefix(`/auth`))" - "traefik.http.routers.plane-api.entrypoints=websecure" - "traefik.http.routers.plane-api.tls.certresolver=letsencrypt" - "traefik.http.services.plane-api.loadbalancer.server.port=8000" EOF fi cat >> /federated/apps/plane/docker-compose.yml <<'EOF' pull_policy: if_not_present restart: unless-stopped command: ./bin/docker-entrypoint-api.sh deploy: replicas: ${API_REPLICAS:-1} volumes: - logs_api:/code/plane/logs depends_on: - plane-redis - plane-mq worker: <<: *app-env image: ${DOCKERHUB_USER:-makeplane}/plane-backend:${IMAGE_VERSION:-stable} platform: ${DOCKER_PLATFORM:-} env_file: - .env networks: core: ipv4_address: 192.168.0.55 pull_policy: if_not_present restart: unless-stopped command: ./bin/docker-entrypoint-worker.sh volumes: - logs_worker:/code/plane/logs depends_on: - api - plane-redis - plane-mq beat-worker: <<: *app-env image: ${DOCKERHUB_USER:-makeplane}/plane-backend:${IMAGE_VERSION:-stable} platform: ${DOCKER_PLATFORM:-} env_file: - .env networks: core: ipv4_address: 192.168.0.56 pull_policy: if_not_present restart: unless-stopped command: ./bin/docker-entrypoint-beat.sh volumes: - logs_beat-worker:/code/plane/logs depends_on: - api - plane-redis - plane-mq migrator: <<: *app-env image: ${DOCKERHUB_USER:-makeplane}/plane-backend:${IMAGE_VERSION:-stable} platform: ${DOCKER_PLATFORM:-} env_file: - .env networks: core: ipv4_address: 192.168.0.57 pull_policy: if_not_present restart: "no" command: ./bin/docker-entrypoint-migrator.sh volumes: - logs_migrator:/code/plane/logs depends_on: - plane-redis plane-redis: <<: *app-env image: valkey/valkey:${VALKEY_RELEASE:-7.2.5-alpine} env_file: - .env networks: core: ipv4_address: 192.168.0.58 pull_policy: if_not_present restart: unless-stopped volumes: - redisdata:/data plane-mq: <<: *app-env image: rabbitmq:${RABBITMQ_RELEASE:-3.13.6-management-alpine} env_file: - .env networks: core: ipv4_address: 192.168.0.59 restart: always volumes: - rabbitmq_data:/var/lib/rabbitmq plane-minio: <<: *app-env image: minio/minio:${MINIO_RELEASE:-RELEASE.2024-12-18T13-15-44Z} env_file: - .env networks: core: ipv4_address: 192.168.0.60 EOF if $USE_TRAEFIK; then cat >> /federated/apps/plane/docker-compose.yml <<'EOF' labels: - "traefik.enable=true" - "traefik.http.routers.plane-uploads.rule=Host(`plane.@DOMAIN@`) && PathPrefix(`/uploads`)" - "traefik.http.routers.plane-uploads.entrypoints=websecure" - "traefik.http.routers.plane-uploads.tls.certresolver=letsencrypt" - "traefik.http.services.plane-uploads.loadbalancer.server.port=9000" EOF fi cat >> /federated/apps/plane/docker-compose.yml <<'EOF' pull_policy: if_not_present restart: unless-stopped command: server /export --console-address ":9090" volumes: - uploads:/export EOF if ! $USE_TRAEFIK; then cat >> /federated/apps/plane/docker-compose.yml <<'EOF' proxy: <<: *app-env image: ${DOCKERHUB_USER:-makeplane}/plane-proxy:${IMAGE_VERSION:-stable} env_file: - .env platform: ${DOCKER_PLATFORM:-} pull_policy: if_not_present restart: unless-stopped ports: - ${NGINX_PORT}:80 depends_on: - web - api - space EOF fi cat >> /federated/apps/plane/docker-compose.yml <<'EOF' volumes: redisdata: uploads: logs_api: logs_worker: logs_beat-worker: logs_migrator: rabbitmq_data: networks: core: external: true EOF sed -i -e "s,@DOMAIN@,${DOMAIN},g" \ -e "s,@POSTGRES_PASSWORD@,${POSTGRES_PASSWORD},g" \ -e "s,@EMAIL_PASSWORD@,${EMAIL_PASSWORD},g" \ /federated/apps/plane/docker-compose.yml \ /federated/apps/plane/.env chmod 600 /federated/apps/plane/.env # Create database and user in postgresql SQL="docker exec postgresql psql --csv -U postgres" $SQL -c "CREATE DATABASE plane" &> /dev/null $SQL -c "CREATE USER plane WITH PASSWORD '${POSTGRES_PASSWORD}'" &> /dev/null $SQL -c "GRANT ALL PRIVILEGES ON DATABASE plane TO plane" &> /dev/null unset POSTGRES_PASSWORD # migrator is usually started at the same time as plane - we need to # run it manually once to create the initial database so we can make # modifications to it (like creating the admin user) before plane is # run the regular way pushd /federated/apps/plane docker compose up -d migrator popd # Wait for the migrator to exit -- at that point, the database should be # ready for manipulation echo "Waiting for completion of the initial plane database - this will take some time." while [ -n "$(docker ps -q -f name=plane-migrator-1)" ]; do sleep 1s echo -n . done echo dnf -y --refresh install python-passlib || (apt update ; apt -y install python3-passlib) INSTANCE_ID=$(random xxxxxxxxxxxxxxxxxxxxxxxx) INSTANCE_UUID=$(random xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) USER_UUID=$(random xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) ADMIN_UUID=$(random xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) NOTIFICATION_UUID=$(random xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) PROFILE_UUID=$(random xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) SIGNUP_UUID=$(random xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) TOKEN=$(random xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx) ADMINPASS=$(grep ADMINPASS= /var/lib/cloud/instances/[0-9]*/cloud-config.txt |cut -d= -f2- |cut -d'"' -f2 |tr -d '\\') ENCODED_PASSWORD=$(python3 -c "from passlib.hash import django_pbkdf2_sha256; print(django_pbkdf2_sha256.hash('$ADMINPASS'))") # FIXME don't hardcode 0.24.0 cat >/federated/apps/postgresql/data/var/lib/postgresql/data/plane-initial-user.sql < /dev/null" "7" /federated/bin/start plane echo -ne "done." } uninstall_plane() { docker exec postgresql psql -U postgres -c "DROP DATABASE plane" &> /dev/null docker exec postgresql psql -U postgres -c "DROP USER plane" &> /dev/null rm -rf /federated/apps/plane } email_plane() { echo -ne "* Sending email to customer.." spin & SPINPID=$! cat > /federated/apps/mail/data/root/certs/mailfile <

Plane is now installed on $DOMAIN

Here is your applications chart with on how to access this service:

Applications

Service Link User / Pass Access Docs Description
Plane plane.$DOMAIN admin@$DOMAIN
admin password above
User access is separate from panel. Use the admin account to login and then invite other users Click here Plane is a flexible project management tool that helps teams plan, track, and manage their work more efficiently. It’s built to grow with you, offering features like issue tracking, sprint management, time tracking, knowledge management, analytics and more. These tools help your team stay organized, keep things clear, and stay aligned throughout the project.

Thanks for your support!

Thank you for your support of Federated Computer. We really appreciate it and hope you have a very successful time with Federated Core.

Again, if we can be of any assistance, please don't hesitate to get in touch.

Support: https://support.federated.computer
Phone: (970) 722-8715
Email: support@federated.computer

It's your computer. Let's make it work for you! EOF # Send out e-mail from mail container with details docker exec mail bash -c "mail -r admin@$DOMAIN -a \"Content-type: text/html\" -s \"Application installed on $DOMAIN\" $EMAIL < /root/certs/mailfile" rm /federated/apps/mail/data/root/certs/mailfile kill -9 $SPINPID &> /dev/null echo -ne "done.\n" }