diff --git a/apps/proxy/Caddyfile.aio.ce b/apps/proxy/Caddyfile.aio.ce index 9cf6d8dd8e8..2041f0e1231 100644 --- a/apps/proxy/Caddyfile.aio.ce +++ b/apps/proxy/Caddyfile.aio.ce @@ -1,3 +1,14 @@ +{ + {$CERT_EMAIL} + acme_ca {$CERT_ACME_CA:https://acme-v02.api.letsencrypt.org/directory} + {$CERT_ACME_DNS} + servers { + max_header_size 25MB + client_ip_headers X-Forwarded-For X-Real-IP + trusted_proxies static {$TRUSTED_PROXIES:0.0.0.0/0} + } +} + (plane_proxy) { request_body { max_size {$FILE_SIZE_LIMIT} @@ -30,17 +41,6 @@ } } -{ - {$CERT_EMAIL} - acme_ca {$CERT_ACME_CA:https://acme-v02.api.letsencrypt.org/directory} - {$CERT_ACME_DNS} - servers { - max_header_size 25MB - client_ip_headers X-Forwarded-For X-Real-IP - trusted_proxies static {$TRUSTED_PROXIES:0.0.0.0/0} - } -} - {$SITE_ADDRESS} { import plane_proxy } \ No newline at end of file diff --git a/apps/proxy/Caddyfile.ce b/apps/proxy/Caddyfile.ce index 14559f27219..d9b54c99f15 100644 --- a/apps/proxy/Caddyfile.ce +++ b/apps/proxy/Caddyfile.ce @@ -1,39 +1,37 @@ -(plane_proxy) { - request_body { - max_size {$FILE_SIZE_LIMIT} - } - - redir /spaces /spaces/ permanent - reverse_proxy /spaces/* space:3000 - - redir /god-mode /god-mode/ permanent - reverse_proxy /god-mode/* admin:3000 - - reverse_proxy /live/* live:3000 - - reverse_proxy /api/* api:8000 - - reverse_proxy /auth/* api:8000 - - reverse_proxy /static/* api:8000 - - reverse_proxy /{$BUCKET_NAME}/* plane-minio:9000 - reverse_proxy /{$BUCKET_NAME} plane-minio:9000 - - reverse_proxy /* web:3000 +{ + email {$CERT_EMAIL} + acme_ca {$CERT_ACME_CA:https://acme-v02.api.letsencrypt.org/directory} + # acme_dns {$CERT_ACME_DNS} # if Let's Encrypt + + servers { + max_header_size 25MB + client_ip_headers X-Forwarded-For X-Real-IP + trusted_proxies static {$TRUSTED_PROXIES:0.0.0.0/0} + } } -{ - {$CERT_EMAIL} - acme_ca {$CERT_ACME_CA:https://acme-v02.api.letsencrypt.org/directory} - {$CERT_ACME_DNS} - servers { - max_header_size 25MB - client_ip_headers X-Forwarded-For X-Real-IP - trusted_proxies static {$TRUSTED_PROXIES:0.0.0.0/0} - } +(plane_proxy) { + request_body { + max_size {$FILE_SIZE_LIMIT} + } + + redir /spaces /spaces/ permanent + reverse_proxy /spaces/* space:3000 + + redir /god-mode /god-mode/ permanent + reverse_proxy /god-mode/* admin:3000 + + reverse_proxy /live/* live:3000 + reverse_proxy /api/* api:8000 + reverse_proxy /auth/* api:8000 + reverse_proxy /static/* api:8000 + + reverse_proxy /{$BUCKET_NAME}/* plane-minio:9000 + reverse_proxy /{$BUCKET_NAME} plane-minio:9000 + + reverse_proxy /* web:3000 } {$SITE_ADDRESS} { - import plane_proxy + import plane_proxy } \ No newline at end of file diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/active-cycles/header.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/active-cycles/header.tsx index 82fe31cce46..a0281d09889 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/active-cycles/header.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/active-cycles/header.tsx @@ -11,8 +11,6 @@ import { CycleIcon } from "@plane/propel/icons"; import { Breadcrumbs, Header } from "@plane/ui"; // components import { BreadcrumbLink } from "@/components/common/breadcrumb-link"; -// plane web components -import { UpgradeBadge } from "@/plane-web/components/workspace/upgrade-badge"; export const WorkspaceActiveCycleHeader = observer(function WorkspaceActiveCycleHeader() { const { t } = useTranslation(); @@ -29,7 +27,6 @@ export const WorkspaceActiveCycleHeader = observer(function WorkspaceActiveCycle } /> - ); diff --git a/apps/web/ce/components/active-cycles/root.tsx b/apps/web/ce/components/active-cycles/root.tsx index 81805656b30..e73bb7935e7 100644 --- a/apps/web/ce/components/active-cycles/root.tsx +++ b/apps/web/ce/components/active-cycles/root.tsx @@ -4,9 +4,6 @@ * See the LICENSE file for details. */ -// local imports -import { WorkspaceActiveCyclesUpgrade } from "./workspace-active-cycles-upgrade"; - export function WorkspaceActiveCyclesRoot() { - return ; + return null; } diff --git a/apps/web/ce/components/issues/bulk-operations/root.tsx b/apps/web/ce/components/issues/bulk-operations/root.tsx index 567fd12d232..04dbf5a3549 100644 --- a/apps/web/ce/components/issues/bulk-operations/root.tsx +++ b/apps/web/ce/components/issues/bulk-operations/root.tsx @@ -5,8 +5,6 @@ */ import { observer } from "mobx-react"; -// components -import { BulkOperationsUpgradeBanner } from "@/components/issues/bulk-operations/upgrade-banner"; // hooks import { useMultipleSelectStore } from "@/hooks/store/use-multiple-select-store"; import type { TSelectionHelper } from "@/hooks/use-multiple-select"; @@ -16,12 +14,6 @@ type Props = { selectionHelpers: TSelectionHelper; }; -export const IssueBulkOperationsRoot = observer(function IssueBulkOperationsRoot(props: Props) { - const { className, selectionHelpers } = props; - // store hooks - const { isSelectionActive } = useMultipleSelectStore(); - - if (!isSelectionActive || selectionHelpers.isSelectionDisabled) return null; - - return ; +export const IssueBulkOperationsRoot = observer(function IssueBulkOperationsRoot(_props: Props) { + return null; }); diff --git a/apps/web/ce/components/pages/editor/embed/issue-embed-upgrade-card.tsx b/apps/web/ce/components/pages/editor/embed/issue-embed-upgrade-card.tsx index bcd5cebc409..faa64d76c6a 100644 --- a/apps/web/ce/components/pages/editor/embed/issue-embed-upgrade-card.tsx +++ b/apps/web/ce/components/pages/editor/embed/issue-embed-upgrade-card.tsx @@ -4,36 +4,6 @@ * See the LICENSE file for details. */ -// plane imports -import { getButtonStyling } from "@plane/propel/button"; -import { cn } from "@plane/utils"; -// components -import { ProIcon } from "@/components/common/pro-icon"; - -export function IssueEmbedUpgradeCard(props: any) { - return ( -
-
- -

- Embed and access issues in pages seamlessly, upgrade to Plane Pro now. -

-
- - Upgrade - -
- ); +export function IssueEmbedUpgradeCard(_props: any) { + return null; } diff --git a/apps/web/ce/components/workspace/billing/root.tsx b/apps/web/ce/components/workspace/billing/root.tsx index 33100a4372a..a55c0c586a5 100644 --- a/apps/web/ce/components/workspace/billing/root.tsx +++ b/apps/web/ce/components/workspace/billing/root.tsx @@ -4,45 +4,16 @@ * See the LICENSE file for details. */ -import { useState } from "react"; import { observer } from "mobx-react"; // plane imports -import { DEFAULT_PRODUCT_BILLING_FREQUENCY, SUBSCRIPTION_WITH_BILLING_FREQUENCY } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; -import type { TBillingFrequency, TProductBillingFrequency } from "@plane/types"; -import { EProductSubscriptionEnum } from "@plane/types"; // components import { SettingsBoxedControlItem } from "@/components/settings/boxed-control-item"; import { SettingsHeading } from "@/components/settings/heading"; -// local imports -import { PlansComparison } from "./comparison/root"; export const BillingRoot = observer(function BillingRoot() { - const [isCompareAllFeaturesSectionOpen, setIsCompareAllFeaturesSectionOpen] = useState(false); - const [productBillingFrequency, setProductBillingFrequency] = useState( - DEFAULT_PRODUCT_BILLING_FREQUENCY - ); const { t } = useTranslation(); - /** - * Retrieves the billing frequency for a given subscription type - * @param {EProductSubscriptionEnum} subscriptionType - Type of subscription to get frequency for - * @returns {TBillingFrequency | undefined} - Billing frequency if subscription supports it, undefined otherwise - */ - const getBillingFrequency = (subscriptionType: EProductSubscriptionEnum): TBillingFrequency | undefined => - SUBSCRIPTION_WITH_BILLING_FREQUENCY.includes(subscriptionType) - ? productBillingFrequency[subscriptionType] - : undefined; - - /** - * Updates the billing frequency for a specific subscription type - * @param {EProductSubscriptionEnum} subscriptionType - Type of subscription to update - * @param {TBillingFrequency} frequency - New billing frequency to set - * @returns {void} - */ - const setBillingFrequency = (subscriptionType: EProductSubscriptionEnum, frequency: TBillingFrequency): void => - setProductBillingFrequency({ ...productBillingFrequency, [subscriptionType]: frequency }); - return (
@@ -57,15 +28,6 @@ export const BillingRoot = observer(function BillingRoot() { />
-
-

All plans

- -
); }); diff --git a/apps/web/ce/components/workspace/edition-badge.tsx b/apps/web/ce/components/workspace/edition-badge.tsx index 34ef7049876..6cce9a964cf 100644 --- a/apps/web/ce/components/workspace/edition-badge.tsx +++ b/apps/web/ce/components/workspace/edition-badge.tsx @@ -4,7 +4,6 @@ * See the LICENSE file for details. */ -import { useState } from "react"; import { observer } from "mobx-react"; // ui import { useTranslation } from "@plane/i18n"; @@ -12,35 +11,19 @@ import { Tooltip } from "@plane/propel/tooltip"; // hooks import { usePlatformOS } from "@/hooks/use-platform-os"; import packageJson from "package.json"; -// local components -import { PaidPlanUpgradeModal } from "../license"; import { Button } from "@plane/propel/button"; export const WorkspaceEditionBadge = observer(function WorkspaceEditionBadge() { - // states - const [isPaidPlanPurchaseModalOpen, setIsPaidPlanPurchaseModalOpen] = useState(false); // translation const { t } = useTranslation(); // platform const { isMobile } = usePlatformOS(); return ( - <> - setIsPaidPlanPurchaseModalOpen(false)} - /> - - - - + + + ); }); diff --git a/apps/web/ce/components/workspace/sidebar/extended-sidebar-item.tsx b/apps/web/ce/components/workspace/sidebar/extended-sidebar-item.tsx index 4014f8fb28c..d1a4b067719 100644 --- a/apps/web/ce/components/workspace/sidebar/extended-sidebar-item.tsx +++ b/apps/web/ce/components/workspace/sidebar/extended-sidebar-item.tsx @@ -26,7 +26,6 @@ import { useAppTheme } from "@/hooks/store/use-app-theme"; import { useUser, useUserPermissions } from "@/hooks/store/user"; import { useWorkspaceNavigationPreferences } from "@/hooks/use-navigation-preferences"; // local imports -import { UpgradeBadge } from "../upgrade-badge"; import { getSidebarNavigationItemIcon } from "./helper"; type TExtendedSidebarItemProps = { @@ -199,11 +198,6 @@ export const ExtendedSidebarItem = observer(function ExtendedSidebarItem(props:
- {item.key === "active_cycles" && ( -
- -
- )} {isPinned ? ( - {t("sidebar.pro")} -
- ); +export function UpgradeBadge(_props: TUpgradeBadge) { + return null; } diff --git a/apps/web/core/components/estimates/create/stage-one.tsx b/apps/web/core/components/estimates/create/stage-one.tsx index f6b4c999d3e..952a840b529 100644 --- a/apps/web/core/components/estimates/create/stage-one.tsx +++ b/apps/web/core/components/estimates/create/stage-one.tsx @@ -14,7 +14,6 @@ import type { TEstimateSystemKeys } from "@plane/types"; import { convertMinutesToHoursMinutesString } from "@plane/utils"; // plane web imports import { isEstimateSystemEnabled } from "@/plane-web/components/estimates/helper"; -import { UpgradeBadge } from "@/plane-web/components/workspace/upgrade-badge"; import { RadioInput } from "../radio-select"; // local imports @@ -53,7 +52,6 @@ export function EstimateCreateStageOne(props: TEstimateCreateStageOne) { ) : !isEnabled ? (
{t(ESTIMATE_SYSTEMS[currentSystem]?.i18n_name)} -
) : (
{t(ESTIMATE_SYSTEMS[currentSystem]?.i18n_name)}
diff --git a/apps/web/core/components/project/settings/features-list.tsx b/apps/web/core/components/project/settings/features-list.tsx index 8605bf50807..eed99729807 100644 --- a/apps/web/core/components/project/settings/features-list.tsx +++ b/apps/web/core/components/project/settings/features-list.tsx @@ -8,7 +8,6 @@ import { observer } from "mobx-react"; // plane imports import { useTranslation } from "@plane/i18n"; import { setPromiseToast } from "@plane/propel/toast"; -import { Tooltip } from "@plane/propel/tooltip"; import type { IProject } from "@plane/types"; import { CycleIcon, IntakeIcon, ModuleIcon, PageIcon, ViewsIcon } from "@plane/propel/icons"; // components @@ -16,8 +15,6 @@ import { SettingsBoxedControlItem } from "@/components/settings/boxed-control-it import { SettingsHeading } from "@/components/settings/heading"; // hooks import { useProject } from "@/hooks/store/use-project"; -// plane web imports -import { UpgradeBadge } from "@/plane-web/components/workspace/upgrade-badge"; // local imports import { ProjectFeatureToggle } from "./helper"; @@ -34,7 +31,6 @@ const PROJECT_FEATURES_LIST = { title: "Cycles", description: "Timebox work as you see fit per project and change frequency from one period to the next.", icon: , - isPro: false, isEnabled: true, }, modules: { @@ -43,7 +39,6 @@ const PROJECT_FEATURES_LIST = { title: "Modules", description: "Group work into sub-project-like set-ups with their own leads and assignees.", icon: , - isPro: false, isEnabled: true, }, views: { @@ -52,7 +47,6 @@ const PROJECT_FEATURES_LIST = { title: "Views", description: "Save sorts, filters, and display options for later or share them.", icon: , - isPro: false, isEnabled: true, }, pages: { @@ -61,7 +55,6 @@ const PROJECT_FEATURES_LIST = { title: "Pages", description: "Write anything like you write anything.", icon: , - isPro: false, isEnabled: true, }, inbox: { @@ -70,7 +63,6 @@ const PROJECT_FEATURES_LIST = { title: "Intake", description: "Consider and discuss work items before you add them to your project.", icon: , - isPro: false, isEnabled: true, }, }; @@ -119,11 +111,6 @@ export const ProjectFeaturesList = observer(function ProjectFeaturesList(props: title={ {t(featureItem.key)} - {featureItem.isPro && ( - - - - )} } description={t(`${featureItem.key}_description`)} @@ -138,9 +125,6 @@ export const ProjectFeaturesList = observer(function ProjectFeaturesList(props: /> } /> - {/* {currentProjectDetails?.[featureItem.property as keyof IProject] && ( -
{featureItem.renderChildren?.(currentProjectDetails, workspaceSlug)}
- )} */} ))} diff --git a/apps/web/core/components/workspace/sidebar/workspace-menu-item.tsx b/apps/web/core/components/workspace/sidebar/workspace-menu-item.tsx index 0199dc7091d..8b8bd4196ca 100644 --- a/apps/web/core/components/workspace/sidebar/workspace-menu-item.tsx +++ b/apps/web/core/components/workspace/sidebar/workspace-menu-item.tsx @@ -17,8 +17,6 @@ import { SidebarNavItem } from "@/components/sidebar/sidebar-navigation"; // hooks import { useAppTheme } from "@/hooks/store/use-app-theme"; import { useUserPermissions } from "@/hooks/store/user"; -// plane web imports -import { UpgradeBadge } from "@/plane-web/components/workspace/upgrade-badge"; export type SidebarWorkspaceMenuItemProps = { item: { @@ -56,20 +54,17 @@ export const SidebarWorkspaceMenuItem = observer(function SidebarWorkspaceMenuIt const isActive = item.href === pathname; return ( - handleLinkClick()}> - -
- -

{t(item.labelTranslationKey)}

-
-
- -
-
- + handleLinkClick()}> + +
+ +

{t(item.labelTranslationKey)}

+
+
+ ); }); diff --git a/plane-app.backup_20260330_144144/docker-compose.yaml b/plane-app.backup_20260330_144144/docker-compose.yaml new file mode 100644 index 00000000000..a939b92d667 --- /dev/null +++ b/plane-app.backup_20260330_144144/docker-compose.yaml @@ -0,0 +1,255 @@ +x-db-env: &db-env + PGHOST: ${PGHOST:-plane-db} + PGDATABASE: ${PGDATABASE:-plane} + POSTGRES_USER: ${POSTGRES_USER:-plane} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-plane} + POSTGRES_DB: ${POSTGRES_DB:-plane} + POSTGRES_PORT: ${POSTGRES_PORT:-5432} + PGDATA: ${PGDATA:-/var/lib/postgresql/data} + +x-redis-env: &redis-env + REDIS_HOST: ${REDIS_HOST:-plane-redis} + REDIS_PORT: ${REDIS_PORT:-6379} + REDIS_URL: ${REDIS_URL:-redis://plane-redis:6379/} + +x-minio-env: &minio-env + MINIO_ROOT_USER: ${AWS_ACCESS_KEY_ID:-access-key} + MINIO_ROOT_PASSWORD: ${AWS_SECRET_ACCESS_KEY:-secret-key} + +x-aws-s3-env: &aws-s3-env + 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} + +x-proxy-env: &proxy-env + APP_DOMAIN: ${APP_DOMAIN:-localhost} + FILE_SIZE_LIMIT: ${FILE_SIZE_LIMIT:-5242880} + CERT_EMAIL: ${CERT_EMAIL} + CERT_ACME_CA: ${CERT_ACME_CA} + CERT_ACME_DNS: ${CERT_ACME_DNS} + LISTEN_HTTP_PORT: ${LISTEN_HTTP_PORT:-80} + LISTEN_HTTPS_PORT: ${LISTEN_HTTPS_PORT:-443} + BUCKET_NAME: ${AWS_S3_BUCKET_NAME:-uploads} + SITE_ADDRESS: ${SITE_ADDRESS:-:80} + +x-mq-env: &mq-env # 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} + +x-live-env: &live-env + API_BASE_URL: ${API_BASE_URL:-http://api:8000} + LIVE_SERVER_SECRET_KEY: ${LIVE_SERVER_SECRET_KEY:-2FiJk1U2aiVPEQtzLehYGlTSnTnrs7LW} + +x-app-env: &app-env + WEB_URL: ${WEB_URL:-http://localhost} + DEBUG: ${DEBUG:-0} + CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS} + GUNICORN_WORKERS: 1 + USE_MINIO: ${USE_MINIO:-1} + DATABASE_URL: ${DATABASE_URL:-postgresql://plane:plane@plane-db/plane} + SECRET_KEY: ${SECRET_KEY:-60gp0byfz2dvffa45cxl20p1scy9xbpf6d8c5y0geejgkyp1b5} + AMQP_URL: ${AMQP_URL:-amqp://plane:plane@plane-mq:5672/plane} + API_KEY_RATE_LIMIT: ${API_KEY_RATE_LIMIT:-60/minute} + MINIO_ENDPOINT_SSL: ${MINIO_ENDPOINT_SSL:-0} + LIVE_SERVER_SECRET_KEY: ${LIVE_SERVER_SECRET_KEY:-2FiJk1U2aiVPEQtzLehYGlTSnTnrs7LW} + +services: + web: + image: artifacts.plane.so/makeplane/plane-frontend:${APP_RELEASE:-v1.2.3} + deploy: + replicas: ${WEB_REPLICAS:-1} + restart_policy: + condition: any + depends_on: + - api + - worker + + space: + image: artifacts.plane.so/makeplane/plane-space:${APP_RELEASE:-v1.2.3} + deploy: + replicas: ${SPACE_REPLICAS:-1} + restart_policy: + condition: any + depends_on: + - api + - worker + - web + + admin: + image: artifacts.plane.so/makeplane/plane-admin:${APP_RELEASE:-v1.2.3} + deploy: + replicas: ${ADMIN_REPLICAS:-1} + restart_policy: + condition: any + depends_on: + - api + - web + + live: + image: artifacts.plane.so/makeplane/plane-live:${APP_RELEASE:-v1.2.3} + environment: + <<: [*live-env, *redis-env] + deploy: + replicas: ${LIVE_REPLICAS:-1} + restart_policy: + condition: any + depends_on: + - api + - web + + api: + image: artifacts.plane.so/makeplane/plane-backend:${APP_RELEASE:-v1.2.3} + command: ./bin/docker-entrypoint-api.sh + deploy: + replicas: ${API_REPLICAS:-1} + restart_policy: + condition: any + volumes: + - logs_api:/code/plane/logs + environment: + <<: [*app-env, *db-env, *redis-env, *minio-env, *aws-s3-env, *proxy-env] + depends_on: + - plane-db + - plane-redis + - plane-mq + + worker: + image: artifacts.plane.so/makeplane/plane-backend:${APP_RELEASE:-v1.2.3} + command: ./bin/docker-entrypoint-worker.sh + deploy: + replicas: ${WORKER_REPLICAS:-1} + restart_policy: + condition: any + volumes: + - logs_worker:/code/plane/logs + environment: + <<: [*app-env, *db-env, *redis-env, *minio-env, *aws-s3-env, *proxy-env] + depends_on: + - api + - plane-db + - plane-redis + - plane-mq + + beat-worker: + image: artifacts.plane.so/makeplane/plane-backend:${APP_RELEASE:-v1.2.3} + command: ./bin/docker-entrypoint-beat.sh + deploy: + replicas: ${BEAT_WORKER_REPLICAS:-1} + restart_policy: + condition: any + volumes: + - logs_beat-worker:/code/plane/logs + environment: + <<: [*app-env, *db-env, *redis-env, *minio-env, *aws-s3-env, *proxy-env] + depends_on: + - api + - plane-db + - plane-redis + - plane-mq + + migrator: + image: artifacts.plane.so/makeplane/plane-backend:${APP_RELEASE:-v1.2.3} + command: ./bin/docker-entrypoint-migrator.sh + deploy: + replicas: 1 + restart_policy: + condition: on-failure + volumes: + - logs_migrator:/code/plane/logs + environment: + <<: [*app-env, *db-env, *redis-env, *minio-env, *aws-s3-env, *proxy-env] + depends_on: + - plane-db + - plane-redis + + # Comment this if you already have a database running + plane-db: + image: postgres:15.7-alpine + command: postgres -c 'max_connections=1000' + deploy: + replicas: 1 + restart_policy: + condition: any + environment: + <<: *db-env + volumes: + - pgdata:/var/lib/postgresql/data + + plane-redis: + image: valkey/valkey:7.2.11-alpine + deploy: + replicas: 1 + restart_policy: + condition: any + volumes: + - redisdata:/data + + plane-mq: + image: rabbitmq:3.13.6-management-alpine + deploy: + replicas: 1 + restart_policy: + condition: any + environment: + <<: *mq-env + volumes: + - rabbitmq_data:/var/lib/rabbitmq + + # Comment this if you using any external s3 compatible storage + plane-minio: + image: minio/minio:latest + command: server /export --console-address ":9090" + deploy: + replicas: 1 + restart_policy: + condition: any + environment: + <<: *minio-env + volumes: + - uploads:/export + + # Comment this if you already have a reverse proxy running + proxy: + image: artifacts.plane.so/makeplane/plane-proxy:${APP_RELEASE:-v1.2.3} + deploy: + replicas: 1 + restart_policy: + condition: any + environment: + <<: *proxy-env + ports: + - target: 80 + published: ${LISTEN_HTTP_PORT:-80} + protocol: tcp + mode: host + - target: 443 + published: ${LISTEN_HTTPS_PORT:-443} + protocol: tcp + mode: host + volumes: + - proxy_config:/config + - proxy_data:/data + depends_on: + - web + - api + - space + - admin + - live + +volumes: + pgdata: + redisdata: + uploads: + logs_api: + logs_worker: + logs_beat-worker: + logs_migrator: + rabbitmq_data: + proxy_config: + proxy_data: diff --git a/plane-app.backup_20260330_144144/plane.env b/plane-app.backup_20260330_144144/plane.env new file mode 100644 index 00000000000..624c1b701a4 --- /dev/null +++ b/plane-app.backup_20260330_144144/plane.env @@ -0,0 +1,85 @@ +APP_DOMAIN=localhost +APP_RELEASE=v1.2.3 + +WEB_REPLICAS=1 +SPACE_REPLICAS=1 +ADMIN_REPLICAS=1 +API_REPLICAS=1 +WORKER_REPLICAS=1 +BEAT_WORKER_REPLICAS=1 +LIVE_REPLICAS=1 + +LISTEN_HTTP_PORT=80 +LISTEN_HTTPS_PORT=443 + +WEB_URL=http://${APP_DOMAIN} +DEBUG=0 +CORS_ALLOWED_ORIGINS=http://${APP_DOMAIN} +API_BASE_URL=http://api:8000 + +#DB SETTINGS +PGHOST=plane-db +PGDATABASE=plane +POSTGRES_USER=plane +POSTGRES_PASSWORD=plane +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= + +# If SSL Cert to be generated, set CERT_EMAIl="email " +CERT_ACME_CA=https://acme-v02.api.letsencrypt.org/directory +TRUSTED_PROXIES=0.0.0.0/0 +SITE_ADDRESS=:80 +CERT_EMAIL= + + + +# For DNS Challenge based certificate generation, set the CERT_ACME_DNS, CERT_EMAIL +# CERT_ACME_DNS="acme_dns " +CERT_ACME_DNS= + + +# 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 +FILE_SIZE_LIMIT=5242880 + +# Gunicorn Workers +GUNICORN_WORKERS=1 + +# UNCOMMENT `DOCKER_PLATFORM` IF YOU ARE ON `ARM64` AND DOCKER IMAGE IS NOT AVAILABLE FOR RESPECTIVE `APP_RELEASE` +# DOCKER_PLATFORM=linux/amd64 + +# Force HTTPS for handling SSL Termination +MINIO_ENDPOINT_SSL=0 + +# API key rate limit +API_KEY_RATE_LIMIT=60/minute + +# Live server environment variables +# WARNING: You must set a secure value for LIVE_SERVER_SECRET_KEY in production environments. +LIVE_SERVER_SECRET_KEY= +DOCKERHUB_USER=artifacts.plane.so/makeplane +PULL_POLICY=if_not_present +CUSTOM_BUILD=false diff --git a/plane-app/docker-compose.yaml b/plane-app/docker-compose.yaml new file mode 100644 index 00000000000..787ba640aa6 --- /dev/null +++ b/plane-app/docker-compose.yaml @@ -0,0 +1,177 @@ +services: + web: + container_name: web + build: + context: . + dockerfile: ./apps/web/Dockerfile.web + args: + DOCKER_BUILDKIT: 1 + restart: always + depends_on: + - api + + admin: + container_name: admin + build: + context: . + dockerfile: ./apps/admin/Dockerfile.admin + args: + DOCKER_BUILDKIT: 1 + restart: always + depends_on: + - api + - web + + space: + container_name: space + build: + context: . + dockerfile: ./apps/space/Dockerfile.space + args: + DOCKER_BUILDKIT: 1 + restart: always + depends_on: + - api + - web + + api: + container_name: api + build: + context: ./apps/api + dockerfile: Dockerfile.api + args: + DOCKER_BUILDKIT: 1 + restart: always + command: ./bin/docker-entrypoint-api.sh + env_file: + - ./apps/api/.env + depends_on: + - plane-db + - plane-redis + + worker: + container_name: bgworker + build: + context: ./apps/api + dockerfile: Dockerfile.api + args: + DOCKER_BUILDKIT: 1 + restart: always + command: ./bin/docker-entrypoint-worker.sh + env_file: + - ./apps/api/.env + depends_on: + - api + - plane-db + - plane-redis + + beat-worker: + container_name: beatworker + build: + context: ./apps/api + dockerfile: Dockerfile.api + args: + DOCKER_BUILDKIT: 1 + restart: always + command: ./bin/docker-entrypoint-beat.sh + env_file: + - ./apps/api/.env + depends_on: + - api + - plane-db + - plane-redis + + migrator: + container_name: plane-migrator + build: + context: ./apps/api + dockerfile: Dockerfile.api + args: + DOCKER_BUILDKIT: 1 + restart: no + command: ./bin/docker-entrypoint-migrator.sh + env_file: + - ./apps/api/.env + depends_on: + - plane-db + - plane-redis + + live: + container_name: plane-live + build: + context: . + dockerfile: ./apps/live/Dockerfile.live + args: + DOCKER_BUILDKIT: 1 + restart: always + + plane-db: + container_name: plane-db + image: postgres:15.7-alpine + restart: always + command: postgres -c 'max_connections=1000' + volumes: + - pgdata:/var/lib/postgresql/data + env_file: + - .env + environment: + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_DB: ${POSTGRES_DB} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + PGDATA: /var/lib/postgresql/data + + plane-redis: + container_name: plane-redis + image: valkey/valkey:7.2.11-alpine + restart: always + volumes: + - redisdata:/data + + plane-mq: + container_name: plane-mq + image: rabbitmq:3.13.6-management-alpine + restart: always + env_file: + - .env + environment: + RABBITMQ_DEFAULT_USER: ${RABBITMQ_USER} + RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASSWORD} + RABBITMQ_DEFAULT_VHOST: ${RABBITMQ_VHOST} + volumes: + - rabbitmq_data:/var/lib/rabbitmq + + plane-minio: + container_name: plane-minio + image: minio/minio + restart: always + command: server /export --console-address ":9090" + volumes: + - uploads:/export + environment: + MINIO_ROOT_USER: ${AWS_ACCESS_KEY_ID} + MINIO_ROOT_PASSWORD: ${AWS_SECRET_ACCESS_KEY} + + # Comment this if you already have a reverse proxy running + proxy: + container_name: proxy + build: + context: ./apps/proxy + dockerfile: Dockerfile.ce + restart: always + ports: + - ${LISTEN_HTTP_PORT}:80 + - ${LISTEN_HTTPS_PORT}:443 + environment: + FILE_SIZE_LIMIT: ${FILE_SIZE_LIMIT:-5242880} + BUCKET_NAME: ${AWS_S3_BUCKET_NAME:-uploads} + depends_on: + - web + - api + - space + - admin + +volumes: + pgdata: + redisdata: + uploads: + rabbitmq_data: diff --git a/plane-app/ex.pln b/plane-app/ex.pln new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plane-app/plane.env b/plane-app/plane.env new file mode 100644 index 00000000000..c6a8ce36965 --- /dev/null +++ b/plane-app/plane.env @@ -0,0 +1,85 @@ +APP_DOMAIN=82.114.228.228 +APP_RELEASE=v1.2.3 + +WEB_REPLICAS=1 +SPACE_REPLICAS=1 +ADMIN_REPLICAS=1 +API_REPLICAS=1 +WORKER_REPLICAS=1 +BEAT_WORKER_REPLICAS=1 +LIVE_REPLICAS=1 + +LISTEN_HTTP_PORT=80 +LISTEN_HTTPS_PORT=443 + +WEB_URL=http://${APP_DOMAIN} +DEBUG=0 +CORS_ALLOWED_ORIGINS=http://${APP_DOMAIN} +API_BASE_URL=http://api:8000 + +#DB SETTINGS +PGHOST=plane-db +PGDATABASE=plane +POSTGRES_USER=plane +POSTGRES_PASSWORD=plane +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= + +# If SSL Cert to be generated, set CERT_EMAIl="email " +CERT_ACME_CA=https://acme-v02.api.letsencrypt.org/directory +TRUSTED_PROXIES=0.0.0.0/0 +SITE_ADDRESS=:80 +CERT_EMAIL= + + + +# For DNS Challenge based certificate generation, set the CERT_ACME_DNS, CERT_EMAIL +# CERT_ACME_DNS="acme_dns " +CERT_ACME_DNS= + + +# 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 +FILE_SIZE_LIMIT=5242880 + +# Gunicorn Workers +GUNICORN_WORKERS=1 + +# UNCOMMENT `DOCKER_PLATFORM` IF YOU ARE ON `ARM64` AND DOCKER IMAGE IS NOT AVAILABLE FOR RESPECTIVE `APP_RELEASE` +# DOCKER_PLATFORM=linux/amd64 + +# Force HTTPS for handling SSL Termination +MINIO_ENDPOINT_SSL=0 + +# API key rate limit +API_KEY_RATE_LIMIT=60/minute + +# Live server environment variables +# WARNING: You must set a secure value for LIVE_SERVER_SECRET_KEY in production environments. +LIVE_SERVER_SECRET_KEY= +DOCKERHUB_USER=artifacts.plane.so/makeplane +PULL_POLICY=if_not_present +CUSTOM_BUILD=false diff --git a/plane.env b/plane.env new file mode 100644 index 00000000000..f4a8a6bd043 --- /dev/null +++ b/plane.env @@ -0,0 +1,60 @@ +# Database Settings +POSTGRES_USER="plane" +POSTGRES_PASSWORD="plane" +POSTGRES_DB="plane" +PGDATA="/var/lib/postgresql/data" + +# Redis Settings +REDIS_HOST="plane-redis" +REDIS_PORT="6379" + +# RabbitMQ Settings +RABBITMQ_HOST="plane-mq" +RABBITMQ_PORT="5672" +RABBITMQ_USER="plane" +RABBITMQ_PASSWORD="plane" +RABBITMQ_VHOST="plane" + +LISTEN_HTTP_PORT=80 +LISTEN_HTTPS_PORT=443 + +# AWS Settings +AWS_REGION="" +AWS_ACCESS_KEY_ID="access-key" +AWS_SECRET_ACCESS_KEY="secret-key" +AWS_S3_ENDPOINT_URL="http://plane-minio:9000" +# Changing this requires change in the proxy config for uploads if using minio setup +AWS_S3_BUCKET_NAME="uploads" +# Maximum file upload limit +FILE_SIZE_LIMIT=5242880 + +# GPT settings +OPENAI_API_BASE="https://api.openai.com/v1" # deprecated +OPENAI_API_KEY="sk-" # deprecated +GPT_ENGINE="gpt-3.5-turbo" # deprecated + +# Settings related to Docker +DOCKERIZED=1 # deprecated + +# set to 1 If using the pre-configured minio setup +USE_MINIO=1 + +# If SSL Cert to be generated, set CERT_EMAIl="email " +CERT_ACME_CA=https://acme-v02.api.letsencrypt.org/directory +TRUSTED_PROXIES=0.0.0.0/0 +SITE_ADDRESS=:80 +CERT_EMAIL= + +# For DNS Challenge based certificate generation, set the CERT_ACME_DNS, CERT_EMAIL +# CERT_ACME_DNS="acme_dns " +CERT_ACME_DNS= + +# Force HTTPS for handling SSL Termination +MINIO_ENDPOINT_SSL=0 + +# API key rate limit +API_KEY_RATE_LIMIT="60/minute" + +# Custom build settings +APP_RELEASE=local +CUSTOM_BUILD=true diff --git a/setup.sh b/setup.sh index 12fcceae41c..c26fba22b8a 100755 --- a/setup.sh +++ b/setup.sh @@ -1,97 +1,714 @@ #!/bin/bash -# Plane Project Setup Script -# This script prepares the local development environment by setting up all necessary .env files -# https://github.com/makeplane/plane - -# Set colors for output messages -GREEN='\033[0;32m' -BLUE='\033[0;34m' -YELLOW='\033[1;33m' -RED='\033[0;31m' -BOLD='\033[1m' -NC='\033[0m' # No Color - -# Print header -echo -e "${BOLD}${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" -echo -e "${BOLD}${BLUE} Plane - Project Management Tool ${NC}" -echo -e "${BOLD}${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" -echo -e "${BOLD}Setting up your development environment...${NC}\n" - -# Function to handle file copying with error checking -copy_env_file() { - local source=$1 - local destination=$2 - - if [ ! -f "$source" ]; then - echo -e "${RED}Error: Source file $source does not exist.${NC}" +BRANCH=${BRANCH:-master} +SCRIPT_DIR=$PWD +SERVICE_FOLDER=plane-app +PLANE_INSTALL_DIR=$PWD/$SERVICE_FOLDER +export APP_RELEASE=stable +export DOCKERHUB_USER=artifacts.plane.so/makeplane +export PULL_POLICY=${PULL_POLICY:-if_not_present} +export GH_REPO=makeplane/plane +export RELEASE_DOWNLOAD_URL="https://github.com/$GH_REPO/releases/download" +export FALLBACK_DOWNLOAD_URL="https://raw.githubusercontent.com/$GH_REPO/$BRANCH/deployments/cli/community" + +CPU_ARCH=$(uname -m) +OS_NAME=$(uname) +UPPER_CPU_ARCH=$(tr '[:lower:]' '[:upper:]' <<< "$CPU_ARCH") + +mkdir -p $PLANE_INSTALL_DIR/archive +DOCKER_FILE_PATH=$PLANE_INSTALL_DIR/docker-compose.yaml +DOCKER_ENV_PATH=$PLANE_INSTALL_DIR/plane.env + +function print_header() { +clear + +cat <<"EOF" +-------------------------------------------- + ____ _ ///////// +| _ \| | __ _ _ __ ___ ///////// +| |_) | |/ _` | '_ \ / _ \ ///// ///// +| __/| | (_| | | | | __/ ///// ///// +|_| |_|\__,_|_| |_|\___| //// + //// +-------------------------------------------- +Project management tool from the future +-------------------------------------------- +EOF +} + +function spinner() { + local pid=$1 + local delay=.5 + local spinstr='|/-\' + + if ! ps -p "$pid" > /dev/null; then + echo "Invalid PID: $pid" + return 1 + fi + while ps -p "$pid" > /dev/null; do + local temp=${spinstr#?} + printf " [%c] " "$spinstr" >&2 + local spinstr=$temp${spinstr%"$temp"} + sleep $delay + printf "\b\b\b\b\b\b" >&2 + done + printf " \b\b\b\b" >&2 +} + +function checkLatestRelease(){ + echo "Checking for the latest release..." >&2 + local latest_release=$(curl -sSL https://api.github.com/repos/$GH_REPO/releases/latest | grep -o '"tag_name": "[^"]*"' | sed 's/"tag_name": "//;s/"//g') + if [ -z "$latest_release" ]; then + echo "Failed to check for the latest release. Exiting..." >&2 + exit 1 + fi + + echo $latest_release +} + +function initialize(){ + printf "Please wait while we check the availability of Docker images for the selected release ($APP_RELEASE) with ${UPPER_CPU_ARCH} support." >&2 + + if [ "$CUSTOM_BUILD" == "true" ]; then + echo "" >&2 + echo "" >&2 + echo "${UPPER_CPU_ARCH} images are not available for selected release ($APP_RELEASE)." >&2 + echo "build" return 1 fi - cp "$source" "$destination" + local IMAGE_NAME=makeplane/plane-proxy + local IMAGE_TAG=${APP_RELEASE} + docker manifest inspect "${IMAGE_NAME}:${IMAGE_TAG}" | grep -q "\"architecture\": \"${CPU_ARCH}\"" & + local pid=$! + spinner "$pid" + + echo "" >&2 + + wait "$pid" if [ $? -eq 0 ]; then - echo -e "${GREEN}✓${NC} Copied $destination" + echo "Plane supports ${CPU_ARCH}" >&2 + echo "available" + return 0 else - echo -e "${RED}✗${NC} Failed to copy $destination" + echo "" >&2 + echo "" >&2 + echo "${UPPER_CPU_ARCH} images are not available for selected release ($APP_RELEASE)." >&2 + echo "" >&2 + echo "build" return 1 fi } +function getEnvValue() { + local key=$1 + local file=$2 + + if [ -z "$key" ] || [ -z "$file" ]; then + echo "Invalid arguments supplied" + exit 1 + fi + + if [ -f "$file" ]; then + grep -q "^$key=" "$file" + if [ $? -eq 0 ]; then + local value + value=$(grep "^$key=" "$file" | cut -d'=' -f2) + echo "$value" + else + echo "" + fi + fi +} +function updateEnvFile() { + local key=$1 + local value=$2 + local file=$3 + + if [ -z "$key" ] || [ -z "$value" ] || [ -z "$file" ]; then + echo "Invalid arguments supplied" + exit 1 + fi + + if [ -f "$file" ]; then + # check if key exists in the file + grep -q "^$key=" "$file" + if [ $? -ne 0 ]; then + echo "$key=$value" >> "$file" + return + else + if [ "$OS_NAME" == "Darwin" ]; then + value=$(echo "$value" | sed 's/|/\\|/g') + sed -i '' "s|^$key=.*|$key=$value|g" "$file" + else + value=$(echo "$value" | sed 's/\//\\\//g') + sed -i "s/^$key=.*/$key=$value/g" "$file" + fi + fi + else + echo "File not found: $file" + exit 1 + fi +} + +function updateCustomVariables(){ + echo "Updating custom variables..." >&2 + updateEnvFile "DOCKERHUB_USER" "$DOCKERHUB_USER" "$DOCKER_ENV_PATH" + updateEnvFile "APP_RELEASE" "$APP_RELEASE" "$DOCKER_ENV_PATH" + updateEnvFile "PULL_POLICY" "$PULL_POLICY" "$DOCKER_ENV_PATH" + updateEnvFile "CUSTOM_BUILD" "$CUSTOM_BUILD" "$DOCKER_ENV_PATH" + echo "Custom variables updated successfully" >&2 +} + +function syncEnvFile(){ + echo "Syncing environment variables..." >&2 + if [ -f "$PLANE_INSTALL_DIR/plane.env.bak" ]; then + updateCustomVariables + + # READ keys of plane.env and update the values from plane.env.bak + while IFS= read -r line + do + # ignore is the line is empty or starts with # + if [ -z "$line" ] || [[ $line == \#* ]]; then + continue + fi + key=$(echo "$line" | cut -d'=' -f1) + value=$(getEnvValue "$key" "$PLANE_INSTALL_DIR/plane.env.bak") + if [ -n "$value" ]; then + updateEnvFile "$key" "$value" "$DOCKER_ENV_PATH" + fi + done < "$DOCKER_ENV_PATH" + fi + echo "Environment variables synced successfully" >&2 +} + +function buildYourOwnImage(){ + echo "Building images locally..." + + export DOCKERHUB_USER="myplane" + export APP_RELEASE="local" + export PULL_POLICY="never" + CUSTOM_BUILD="true" + + # checkout the code to ~/tmp/plane folder and build the images + local PLANE_TEMP_CODE_DIR=~/tmp/plane + rm -rf $PLANE_TEMP_CODE_DIR + mkdir -p $PLANE_TEMP_CODE_DIR + REPO=https://github.com/$GH_REPO.git + git clone "$REPO" "$PLANE_TEMP_CODE_DIR" --branch "$BRANCH" --single-branch --depth 1 + + cp "$PLANE_TEMP_CODE_DIR/deployments/cli/community/build.yml" "$PLANE_TEMP_CODE_DIR/build.yml" + + cd "$PLANE_TEMP_CODE_DIR" || exit + + /bin/bash -c "$COMPOSE_CMD -f build.yml build --no-cache" >&2 + if [ $? -ne 0 ]; then + echo "Build failed. Exiting..." + exit 1 + fi + echo "Build completed successfully" + echo "" + echo "You can now start the services by running the command: ./setup.sh start" + echo "" +} + +function install() { + echo "Begin Installing Plane" + echo "" + + if [ "$APP_RELEASE" == "stable" ]; then + export APP_RELEASE=$(checkLatestRelease) + fi + + local build_image=$(initialize) + + if [ "$build_image" == "build" ]; then + # ask for confirmation to continue building the images + echo "Do you want to continue with building the Docker images locally?" + read -p "Continue? [y/N]: " confirm + if [[ ! "$confirm" =~ ^[Yy]$ ]]; then + echo "Exiting..." + exit 0 + fi + fi + + if [ "$build_image" == "build" ]; then + download "true" + else + download "false" + fi +} + +function download() { + local LOCAL_BUILD=$1 + cd $SCRIPT_DIR + TS=$(date +%s) + if [ -f "$PLANE_INSTALL_DIR/docker-compose.yaml" ] + then + mv $PLANE_INSTALL_DIR/docker-compose.yaml $PLANE_INSTALL_DIR/archive/$TS.docker-compose.yaml + fi + + RESPONSE=$(curl -sSL -H 'Cache-Control: no-cache, no-store' -w "HTTPSTATUS:%{http_code}" "$RELEASE_DOWNLOAD_URL/$APP_RELEASE/docker-compose.yml?$(date +%s)") + BODY=$(echo "$RESPONSE" | sed -e 's/HTTPSTATUS\:.*//g') + STATUS=$(echo "$RESPONSE" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://') + + if [ "$STATUS" -eq 200 ]; then + echo "$BODY" > $PLANE_INSTALL_DIR/docker-compose.yaml + else + # Fallback to download from the raw github url + RESPONSE=$(curl -sSL -H 'Cache-Control: no-cache, no-store' -w "HTTPSTATUS:%{http_code}" "$FALLBACK_DOWNLOAD_URL/docker-compose.yml?$(date +%s)") + BODY=$(echo "$RESPONSE" | sed -e 's/HTTPSTATUS\:.*//g') + STATUS=$(echo "$RESPONSE" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://') + + if [ "$STATUS" -eq 200 ]; then + echo "$BODY" > $PLANE_INSTALL_DIR/docker-compose.yaml + else + echo "Failed to download docker-compose.yml. HTTP Status: $STATUS" + echo "URL: $RELEASE_DOWNLOAD_URL/$APP_RELEASE/docker-compose.yml" + mv $PLANE_INSTALL_DIR/archive/$TS.docker-compose.yaml $PLANE_INSTALL_DIR/docker-compose.yaml + exit 1 + fi + fi + + RESPONSE=$(curl -sSL -H 'Cache-Control: no-cache, no-store' -w "HTTPSTATUS:%{http_code}" "$RELEASE_DOWNLOAD_URL/$APP_RELEASE/variables.env?$(date +%s)") + BODY=$(echo "$RESPONSE" | sed -e 's/HTTPSTATUS\:.*//g') + STATUS=$(echo "$RESPONSE" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://') + + if [ "$STATUS" -eq 200 ]; then + echo "$BODY" > $PLANE_INSTALL_DIR/variables-upgrade.env + else + # Fallback to download from the raw github url + RESPONSE=$(curl -sSL -H 'Cache-Control: no-cache, no-store' -w "HTTPSTATUS:%{http_code}" "$FALLBACK_DOWNLOAD_URL/variables.env?$(date +%s)") + BODY=$(echo "$RESPONSE" | sed -e 's/HTTPSTATUS\:.*//g') + STATUS=$(echo "$RESPONSE" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://') + + if [ "$STATUS" -eq 200 ]; then + echo "$BODY" > $PLANE_INSTALL_DIR/variables-upgrade.env + else + echo "Failed to download variables.env. HTTP Status: $STATUS" + echo "URL: $RELEASE_DOWNLOAD_URL/$APP_RELEASE/variables.env" + mv $PLANE_INSTALL_DIR/archive/$TS.docker-compose.yaml $PLANE_INSTALL_DIR/docker-compose.yaml + exit 1 + fi + fi + + if [ -f "$DOCKER_ENV_PATH" ]; + then + cp "$DOCKER_ENV_PATH" "$PLANE_INSTALL_DIR/archive/$TS.env" + cp "$DOCKER_ENV_PATH" "$PLANE_INSTALL_DIR/plane.env.bak" + fi + + mv $PLANE_INSTALL_DIR/variables-upgrade.env $DOCKER_ENV_PATH + + syncEnvFile + + if [ "$LOCAL_BUILD" == "true" ]; then + export DOCKERHUB_USER="myplane" + export APP_RELEASE="local" + export PULL_POLICY="never" + CUSTOM_BUILD="true" + + buildYourOwnImage + + if [ $? -ne 0 ]; then + echo "" + echo "Build failed. Exiting..." + exit 1 + fi + updateCustomVariables + else + CUSTOM_BUILD="false" + updateCustomVariables + /bin/bash -c "$COMPOSE_CMD -f $DOCKER_FILE_PATH --env-file=$DOCKER_ENV_PATH pull --policy always" + + if [ $? -ne 0 ]; then + echo "" + echo "Failed to pull the images. Exiting..." + exit 1 + fi + fi + + echo "" + echo "Most recent version of Plane is now available for you to use" + echo "" + echo "In case of 'Upgrade', please check the 'plane.env 'file for any new variables and update them accordingly" + echo "" +} +function startServices() { + /bin/bash -c "$COMPOSE_CMD -f $DOCKER_FILE_PATH --env-file=$DOCKER_ENV_PATH up -d --pull if_not_present --quiet-pull" -# Export character encoding settings for macOS compatibility -export LC_ALL=C -export LC_CTYPE=C -echo -e "${YELLOW}Setting up environment files...${NC}" + local migrator_container_id=$(docker container ls -aq -f "name=$SERVICE_FOLDER-migrator") + if [ -n "$migrator_container_id" ]; then + local idx=0 + while docker inspect --format='{{.State.Status}}' $migrator_container_id | grep -q "running"; do + local message=">> Waiting for Data Migration to finish" + local dots=$(printf '%*s' $idx | tr ' ' '.') + echo -ne "\r$message$dots" + ((idx++)) + sleep 1 + done + fi + printf "\r\033[K" + echo "" + echo " Data Migration completed successfully ✅" + + # if migrator exit status is not 0, show error message and exit + if [ -n "$migrator_container_id" ]; then + local migrator_exit_code=$(docker inspect --format='{{.State.ExitCode}}' $migrator_container_id) + if [ $migrator_exit_code -ne 0 ]; then + echo "Plane Server failed to start ❌" + # stopServices + echo + echo "Please check the logs for the 'migrator' service and resolve the issue(s)." + echo "Stop the services by running the command: ./setup.sh stop" + exit 1 + fi + fi + + local api_container_id=$(docker container ls -q -f "name=$SERVICE_FOLDER-api") + + # Verify container exists + if [ -z "$api_container_id" ]; then + echo " Error: API container not found. Please check if services are running." + exit 1 + fi + + local idx2=0 + local api_ready=true # assume success, flip on timeout + local max_wait_time=300 # 5 minutes timeout + local start_time=$(date +%s) + + echo " Waiting for API Service to be ready..." + while ! docker exec "$api_container_id" python3 -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/', timeout=3)" > /dev/null 2>&1; do + local current_time=$(date +%s) + local elapsed_time=$((current_time - start_time)) + + if [ $elapsed_time -gt $max_wait_time ]; then + echo "" + echo " API Service health check timed out after 5 minutes" + echo " Checking if API container is still running..." + if docker ps | grep -q "$SERVICE_FOLDER-api"; then + echo " API container is running but did not pass the health-check. Continuing without marking it ready." + api_ready=false + break + else + echo " API container is not running. Please check logs." + exit 1 + fi + fi + + local message=">> Waiting for API Service to Start (${elapsed_time}s)" + local dots=$(printf '%*s' $idx2 | tr ' ' '.') + echo -ne "\r$message$dots" + ((idx2++)) + sleep 1 + done + printf "\r\033[K" + if [ "$api_ready" = true ]; then + echo " API Service started successfully ✅" + else + echo " ⚠️ API Service did not respond to health-check – please verify manually." + fi + source "${DOCKER_ENV_PATH}" + echo " Plane Server started successfully ✅" + echo "" + echo " You can access the application at $WEB_URL" + echo "" + +} +function stopServices() { + /bin/bash -c "$COMPOSE_CMD -f $DOCKER_FILE_PATH --env-file=$DOCKER_ENV_PATH down" +} +function restartServices() { + stopServices + startServices +} +function upgrade() { + local latest_release=$(checkLatestRelease) + + echo "" + echo "Current release: $APP_RELEASE" + + if [ "$latest_release" == "$APP_RELEASE" ]; then + echo "" + echo "You are already using the latest release" + exit 0 + fi + + echo "Latest release: $latest_release" + echo "" + + # Check for confirmation to upgrade + echo "Do you want to upgrade to the latest release ($latest_release)?" + read -p "Continue? [y/N]: " confirm + + if [[ ! "$confirm" =~ ^[Yy]$ ]]; then + echo "Exiting..." + exit 0 + fi + + export APP_RELEASE=$latest_release + + echo "Upgrading Plane to the latest release..." + echo "" + + echo "***** STOPPING SERVICES ****" + stopServices + + echo + echo "***** DOWNLOADING STABLE VERSION ****" + install -# Copy all environment example files -services=("" "web" "api" "space" "admin" "live") -success=true + echo "***** PLEASE VALIDATE AND START SERVICES ****" +} +function viewSpecificLogs(){ + local SERVICE_NAME=$1 -for service in "${services[@]}"; do - if [ "$service" == "" ]; then - # Handle root .env file - prefix="./" + if /bin/bash -c "$COMPOSE_CMD -f $DOCKER_FILE_PATH ps | grep -q '$SERVICE_NAME'"; then + echo "Service '$SERVICE_NAME' is running." else - # Handle service .env files in apps folder - prefix="./apps/$service/" + echo "Service '$SERVICE_NAME' is not running." + fi + + /bin/bash -c "$COMPOSE_CMD -f $DOCKER_FILE_PATH logs -f $SERVICE_NAME" +} +function viewLogs(){ + + ARG_SERVICE_NAME=$2 + + if [ -z "$ARG_SERVICE_NAME" ]; + then + echo + echo "Select a Service you want to view the logs for:" + echo " 1) Web" + echo " 2) Space" + echo " 3) API" + echo " 4) Worker" + echo " 5) Beat-Worker" + echo " 6) Migrator" + echo " 7) Proxy" + echo " 8) Redis" + echo " 9) Postgres" + echo " 10) Minio" + echo " 11) RabbitMQ" + echo " 0) Back to Main Menu" + echo + read -p "Service: " DOCKER_SERVICE_NAME + + until (( DOCKER_SERVICE_NAME >= 0 && DOCKER_SERVICE_NAME <= 11 )); do + echo "Invalid selection. Please enter a number between 0 and 11." + read -p "Service: " DOCKER_SERVICE_NAME + done + + if [ -z "$DOCKER_SERVICE_NAME" ]; + then + echo "INVALID SERVICE NAME SUPPLIED" + else + case $DOCKER_SERVICE_NAME in + 1) viewSpecificLogs "web";; + 2) viewSpecificLogs "space";; + 3) viewSpecificLogs "api";; + 4) viewSpecificLogs "worker";; + 5) viewSpecificLogs "beat-worker";; + 6) viewSpecificLogs "migrator";; + 7) viewSpecificLogs "proxy";; + 8) viewSpecificLogs "plane-redis";; + 9) viewSpecificLogs "plane-db";; + 10) viewSpecificLogs "plane-minio";; + 11) viewSpecificLogs "plane-mq";; + 0) askForAction;; + *) echo "INVALID SERVICE NAME SUPPLIED";; + esac + fi + elif [ -n "$ARG_SERVICE_NAME" ]; + then + ARG_SERVICE_NAME=$(echo "$ARG_SERVICE_NAME" | tr '[:upper:]' '[:lower:]') + case $ARG_SERVICE_NAME in + web) viewSpecificLogs "web";; + space) viewSpecificLogs "space";; + api) viewSpecificLogs "api";; + worker) viewSpecificLogs "worker";; + beat-worker) viewSpecificLogs "beat-worker";; + migrator) viewSpecificLogs "migrator";; + proxy) viewSpecificLogs "proxy";; + redis) viewSpecificLogs "plane-redis";; + postgres) viewSpecificLogs "plane-db";; + minio) viewSpecificLogs "plane-minio";; + rabbitmq) viewSpecificLogs "plane-mq";; + *) echo "INVALID SERVICE NAME SUPPLIED";; + esac + else + echo "INVALID SERVICE NAME SUPPLIED" + fi +} +function backup_container_dir() { + local BACKUP_FOLDER=$1 + local CONTAINER_NAME=$2 + local CONTAINER_DATA_DIR=$3 + local SERVICE_FOLDER=$4 + + echo "Backing up $CONTAINER_NAME data..." + local CONTAINER_ID=$(/bin/bash -c "$COMPOSE_CMD -f $DOCKER_FILE_PATH ps -q $CONTAINER_NAME") + if [ -z "$CONTAINER_ID" ]; then + echo "Error: $CONTAINER_NAME container not found. Make sure the services are running." + return 1 + fi + + # Create a temporary directory for the backup + mkdir -p "$BACKUP_FOLDER/$SERVICE_FOLDER" + + # Copy the data directory from the running container + echo "Copying $CONTAINER_NAME data directory..." + docker cp -q "$CONTAINER_ID:$CONTAINER_DATA_DIR/." "$BACKUP_FOLDER/$SERVICE_FOLDER/" + local cp_status=$? + + if [ $cp_status -ne 0 ]; then + echo "Error: Failed to copy $SERVICE_FOLDER data" + rm -rf $BACKUP_FOLDER/$SERVICE_FOLDER + return 1 + fi + + # Create tar.gz of the data + cd "$BACKUP_FOLDER" + tar -czf "${SERVICE_FOLDER}.tar.gz" "$SERVICE_FOLDER/" + local tar_status=$? + if [ $tar_status -eq 0 ]; then + rm -rf "$SERVICE_FOLDER/" + fi + cd - > /dev/null + + if [ $tar_status -ne 0 ]; then + echo "Error: Failed to create tar archive" + return 1 + fi + + echo "Successfully backed up $SERVICE_FOLDER data" +} + +function backupData() { + local datetime=$(date +"%Y%m%d-%H%M") + local BACKUP_FOLDER=$PLANE_INSTALL_DIR/backup/$datetime + mkdir -p "$BACKUP_FOLDER" + + # Check if docker-compose.yml exists + if [ ! -f "$DOCKER_FILE_PATH" ]; then + echo "Error: docker-compose.yml not found at $DOCKER_FILE_PATH" + exit 1 fi - copy_env_file "${prefix}.env.example" "${prefix}.env" || success=false -done + backup_container_dir "$BACKUP_FOLDER" "plane-db" "/var/lib/postgresql/data" "pgdata" || exit 1 + backup_container_dir "$BACKUP_FOLDER" "plane-minio" "/export" "uploads" || exit 1 + backup_container_dir "$BACKUP_FOLDER" "plane-mq" "/var/lib/rabbitmq" "rabbitmq_data" || exit 1 + backup_container_dir "$BACKUP_FOLDER" "plane-redis" "/data" "redisdata" || exit 1 -# Generate SECRET_KEY for Django -if [ -f "./apps/api/.env" ]; then - echo -e "\n${YELLOW}Generating Django SECRET_KEY...${NC}" - SECRET_KEY=$(tr -dc 'a-z0-9' < /dev/urandom | head -c50) + echo "" + echo "Backup completed successfully. Backup files are stored in $BACKUP_FOLDER" + echo "" +} +function askForAction() { + local DEFAULT_ACTION=$1 + + if [ -z "$DEFAULT_ACTION" ]; + then + echo + echo "Select a Action you want to perform:" + echo " 1) Install" + echo " 2) Start" + echo " 3) Stop" + echo " 4) Restart" + echo " 5) Upgrade" + echo " 6) View Logs" + echo " 7) Backup Data" + echo " 8) Exit" + echo + read -p "Action [2]: " ACTION + until [[ -z "$ACTION" || "$ACTION" =~ ^[1-8]$ ]]; do + echo "$ACTION: invalid selection." + read -p "Action [2]: " ACTION + done - if [ -z "$SECRET_KEY" ]; then - echo -e "${RED}Error: Failed to generate SECRET_KEY.${NC}" - echo -e "${RED}Ensure 'tr' and 'head' commands are available on your system.${NC}" - success=false + if [ -z "$ACTION" ]; + then + ACTION=2 + fi + echo + fi + + if [ "$ACTION" == "1" ] || [ "$DEFAULT_ACTION" == "install" ]; + then + install + # askForAction + elif [ "$ACTION" == "2" ] || [ "$DEFAULT_ACTION" == "start" ]; + then + startServices + # askForAction + elif [ "$ACTION" == "3" ] || [ "$DEFAULT_ACTION" == "stop" ]; + then + stopServices + # askForAction + elif [ "$ACTION" == "4" ] || [ "$DEFAULT_ACTION" == "restart" ]; + then + restartServices + # askForAction + elif [ "$ACTION" == "5" ] || [ "$DEFAULT_ACTION" == "upgrade" ]; + then + upgrade + # askForAction + elif [ "$ACTION" == "6" ] || [ "$DEFAULT_ACTION" == "logs" ]; + then + viewLogs "$@" + askForAction + elif [ "$ACTION" == "7" ] || [ "$DEFAULT_ACTION" == "backup" ]; + then + backupData + elif [ "$ACTION" == "8" ] + then + exit 0 else - echo -e "SECRET_KEY=\"$SECRET_KEY\"" >> ./apps/api/.env - echo -e "${GREEN}✓${NC} Added SECRET_KEY to apps/api/.env" + echo "INVALID ACTION SUPPLIED" fi +} + +# if docker-compose is installed +if command -v docker-compose &> /dev/null +then + COMPOSE_CMD="docker-compose" else - echo -e "${RED}✗${NC} apps/api/.env not found. SECRET_KEY not added." - success=false + COMPOSE_CMD="docker compose" fi -# Activate pnpm (version set in package.json) -corepack enable pnpm || success=false -# Install Node dependencies -pnpm install || success=false - -# Summary -echo -e "\n${YELLOW}Setup status:${NC}" -if [ "$success" = true ]; then - echo -e "${GREEN}✓${NC} Environment setup completed successfully!\n" - echo -e "${BOLD}Next steps:${NC}" - echo -e "1. Review the .env files in each folder if needed" - echo -e "2. Start the services with: ${BOLD}docker compose -f docker-compose-local.yml up -d${NC}" - echo -e "\n${GREEN}Happy coding! 🚀${NC}" -else - echo -e "${RED}✗${NC} Some issues occurred during setup. Please check the errors above.\n" - echo -e "For help, visit: ${BLUE}https://github.com/makeplane/plane${NC}" - exit 1 +if [ "$CPU_ARCH" == "x86_64" ] || [ "$CPU_ARCH" == "amd64" ]; then + CPU_ARCH="amd64" +elif [ "$CPU_ARCH" == "aarch64" ] || [ "$CPU_ARCH" == "arm64" ]; then + CPU_ARCH="arm64" +fi + +if [ -f "$DOCKER_ENV_PATH" ]; then + DOCKERHUB_USER=$(getEnvValue "DOCKERHUB_USER" "$DOCKER_ENV_PATH") + APP_RELEASE=$(getEnvValue "APP_RELEASE" "$DOCKER_ENV_PATH") + PULL_POLICY=$(getEnvValue "PULL_POLICY" "$DOCKER_ENV_PATH") + CUSTOM_BUILD=$(getEnvValue "CUSTOM_BUILD" "$DOCKER_ENV_PATH") + + if [ -z "$DOCKERHUB_USER" ]; then + DOCKERHUB_USER=artifacts.plane.so/makeplane + updateEnvFile "DOCKERHUB_USER" "$DOCKERHUB_USER" "$DOCKER_ENV_PATH" + fi + + if [ -z "$APP_RELEASE" ]; then + APP_RELEASE=stable + updateEnvFile "APP_RELEASE" "$APP_RELEASE" "$DOCKER_ENV_PATH" + fi + + if [ -z "$PULL_POLICY" ]; then + PULL_POLICY=if_not_present + updateEnvFile "PULL_POLICY" "$PULL_POLICY" "$DOCKER_ENV_PATH" + fi + + if [ -z "$CUSTOM_BUILD" ]; then + CUSTOM_BUILD=false + updateEnvFile "CUSTOM_BUILD" "$CUSTOM_BUILD" "$DOCKER_ENV_PATH" + fi fi + +print_header +askForAction "$@" diff --git a/setup_lib.sh b/setup_lib.sh new file mode 100644 index 00000000000..12fcceae41c --- /dev/null +++ b/setup_lib.sh @@ -0,0 +1,97 @@ +#!/bin/bash + +# Plane Project Setup Script +# This script prepares the local development environment by setting up all necessary .env files +# https://github.com/makeplane/plane + +# Set colors for output messages +GREEN='\033[0;32m' +BLUE='\033[0;34m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +BOLD='\033[1m' +NC='\033[0m' # No Color + +# Print header +echo -e "${BOLD}${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +echo -e "${BOLD}${BLUE} Plane - Project Management Tool ${NC}" +echo -e "${BOLD}${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +echo -e "${BOLD}Setting up your development environment...${NC}\n" + +# Function to handle file copying with error checking +copy_env_file() { + local source=$1 + local destination=$2 + + if [ ! -f "$source" ]; then + echo -e "${RED}Error: Source file $source does not exist.${NC}" + return 1 + fi + + cp "$source" "$destination" + + if [ $? -eq 0 ]; then + echo -e "${GREEN}✓${NC} Copied $destination" + else + echo -e "${RED}✗${NC} Failed to copy $destination" + return 1 + fi +} + +# Export character encoding settings for macOS compatibility +export LC_ALL=C +export LC_CTYPE=C +echo -e "${YELLOW}Setting up environment files...${NC}" + +# Copy all environment example files +services=("" "web" "api" "space" "admin" "live") +success=true + +for service in "${services[@]}"; do + if [ "$service" == "" ]; then + # Handle root .env file + prefix="./" + else + # Handle service .env files in apps folder + prefix="./apps/$service/" + fi + + copy_env_file "${prefix}.env.example" "${prefix}.env" || success=false +done + +# Generate SECRET_KEY for Django +if [ -f "./apps/api/.env" ]; then + echo -e "\n${YELLOW}Generating Django SECRET_KEY...${NC}" + SECRET_KEY=$(tr -dc 'a-z0-9' < /dev/urandom | head -c50) + + if [ -z "$SECRET_KEY" ]; then + echo -e "${RED}Error: Failed to generate SECRET_KEY.${NC}" + echo -e "${RED}Ensure 'tr' and 'head' commands are available on your system.${NC}" + success=false + else + echo -e "SECRET_KEY=\"$SECRET_KEY\"" >> ./apps/api/.env + echo -e "${GREEN}✓${NC} Added SECRET_KEY to apps/api/.env" + fi +else + echo -e "${RED}✗${NC} apps/api/.env not found. SECRET_KEY not added." + success=false +fi + +# Activate pnpm (version set in package.json) +corepack enable pnpm || success=false +# Install Node dependencies +pnpm install || success=false + +# Summary +echo -e "\n${YELLOW}Setup status:${NC}" +if [ "$success" = true ]; then + echo -e "${GREEN}✓${NC} Environment setup completed successfully!\n" + echo -e "${BOLD}Next steps:${NC}" + echo -e "1. Review the .env files in each folder if needed" + echo -e "2. Start the services with: ${BOLD}docker compose -f docker-compose-local.yml up -d${NC}" + echo -e "\n${GREEN}Happy coding! 🚀${NC}" +else + echo -e "${RED}✗${NC} Some issues occurred during setup. Please check the errors above.\n" + echo -e "For help, visit: ${BLUE}https://github.com/makeplane/plane${NC}" + exit 1 +fi