openship 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +19 -3
- package/CONTRIBUTING.md +69 -19
- package/LICENSE +661 -0
- package/README.md +304 -108
- package/apps/api/Dockerfile +6 -8
- package/apps/api/package.json +25 -21
- package/apps/api/src/app.ts +46 -3
- package/apps/api/src/config/env.ts +81 -7
- package/apps/api/src/lib/auth-mode.ts +42 -0
- package/apps/api/src/lib/auth.ts +105 -0
- package/apps/api/src/lib/cache.ts +128 -0
- package/apps/api/src/lib/cloud-auth-proxy.ts +310 -0
- package/apps/api/src/lib/cloud-client.ts +103 -0
- package/apps/api/src/lib/compose-parser.ts +138 -0
- package/apps/api/src/lib/controller-helpers.ts +90 -0
- package/apps/api/src/lib/email-templates.ts +94 -0
- package/apps/api/src/lib/encryption.ts +102 -0
- package/apps/api/src/lib/local-user.ts +65 -0
- package/apps/api/src/lib/mail.ts +48 -0
- package/apps/api/src/lib/notifications.ts +195 -0
- package/apps/api/src/lib/openship-cloud.ts +101 -0
- package/apps/api/src/lib/resources.ts +96 -0
- package/apps/api/src/lib/screenshots.ts +189 -0
- package/apps/api/src/lib/ssh-manager.ts +369 -0
- package/apps/api/src/lib/ssl-scheduler.ts +109 -0
- package/apps/api/src/lib/stack-detector.ts +708 -0
- package/apps/api/src/lib/system-debug.ts +10 -0
- package/apps/api/src/middleware/auth.ts +49 -13
- package/apps/api/src/middleware/error-handler.ts +23 -3
- package/apps/api/src/middleware/index.ts +2 -0
- package/apps/api/src/middleware/internal-auth.ts +40 -0
- package/apps/api/src/middleware/local-only.ts +20 -0
- package/apps/api/src/middleware/rate-limiter.ts +9 -0
- package/apps/api/src/modules/analytics/analytics.controller.ts +111 -0
- package/apps/api/src/modules/analytics/analytics.routes.ts +29 -0
- package/apps/api/src/modules/analytics/analytics.schema.ts +37 -0
- package/apps/api/src/modules/analytics/analytics.service.ts +264 -0
- package/apps/api/src/modules/auth/auth.routes.ts +233 -6
- package/apps/api/src/modules/billing/billing.routes.ts +8 -0
- package/apps/api/src/modules/cloud/cloud.controller.ts +225 -0
- package/apps/api/src/modules/cloud/cloud.routes.ts +40 -0
- package/apps/api/src/modules/deployments/build.service.ts +791 -0
- package/apps/api/src/modules/deployments/deployment-lifecycle.ts +138 -0
- package/apps/api/src/modules/deployments/deployment.controller.ts +324 -16
- package/apps/api/src/modules/deployments/deployment.routes.ts +33 -7
- package/apps/api/src/modules/deployments/deployment.schema.ts +37 -5
- package/apps/api/src/modules/deployments/deployment.service.ts +178 -11
- package/apps/api/src/modules/deployments/preflight.ts +280 -0
- package/apps/api/src/modules/deployments/prepare.service.ts +252 -0
- package/apps/api/src/modules/deployments/session-manager.ts +283 -0
- package/apps/api/src/modules/deployments/ssl.service.ts +73 -0
- package/apps/api/src/modules/domains/domain.controller.ts +59 -7
- package/apps/api/src/modules/domains/domain.routes.ts +20 -5
- package/apps/api/src/modules/domains/domain.schema.ts +35 -0
- package/apps/api/src/modules/domains/domain.service.ts +375 -9
- package/apps/api/src/modules/github/github.auth.ts +397 -0
- package/apps/api/src/modules/github/github.controller.ts +387 -0
- package/apps/api/src/modules/github/github.local-auth.ts +251 -0
- package/apps/api/src/modules/github/github.routes.ts +50 -0
- package/apps/api/src/modules/github/github.schema.ts +52 -0
- package/apps/api/src/modules/github/github.service.ts +592 -0
- package/apps/api/src/modules/github/github.types.ts +158 -0
- package/apps/api/src/modules/github/github.webhook.ts +186 -0
- package/apps/api/src/modules/github/index.ts +16 -0
- package/apps/api/src/modules/health/health.routes.ts +35 -0
- package/apps/api/src/modules/projects/project.controller.ts +481 -12
- package/apps/api/src/modules/projects/project.routes.ts +63 -6
- package/apps/api/src/modules/projects/project.schema.ts +104 -6
- package/apps/api/src/modules/projects/project.service.ts +502 -11
- package/apps/api/src/modules/settings/settings.controller.ts +55 -0
- package/apps/api/src/modules/settings/settings.routes.ts +25 -0
- package/apps/api/src/modules/settings/settings.service.ts +49 -0
- package/apps/api/src/modules/system/filesystem.controller.ts +66 -0
- package/apps/api/src/modules/system/index.ts +1 -0
- package/apps/api/src/modules/system/server-check.controller.ts +475 -0
- package/apps/api/src/modules/system/setup-session.ts +255 -0
- package/apps/api/src/modules/system/setup.controller.ts +128 -0
- package/apps/api/src/modules/system/system.routes.ts +42 -0
- package/apps/api/src/modules/webhooks/webhook.controller.ts +57 -15
- package/apps/api/src/modules/webhooks/webhook.routes.ts +11 -8
- package/apps/api/src/modules/webhooks/webhook.service.ts +46 -5
- package/apps/api/src/modules/webhooks/webhook.types.ts +35 -0
- package/apps/api/tsconfig.json +3 -1
- package/apps/cli/package.json +8 -10
- package/apps/cli/tsconfig.json +2 -1
- package/apps/dashboard/Dockerfile +4 -6
- package/apps/dashboard/components.json +20 -0
- package/apps/dashboard/next-env.d.ts +6 -0
- package/apps/dashboard/next.config.mjs +11 -1
- package/apps/dashboard/package.json +25 -13
- package/apps/dashboard/postcss.config.mjs +5 -0
- package/apps/dashboard/public/android-chrome-192x192.png +0 -0
- package/apps/dashboard/public/android-chrome-512x512.png +0 -0
- package/apps/dashboard/public/apple-touch-icon.png +0 -0
- package/apps/dashboard/public/favicon-16x16.png +0 -0
- package/apps/dashboard/public/favicon-32x32.png +0 -0
- package/apps/dashboard/public/favicon.ico +0 -0
- package/apps/dashboard/public/site.webmanifest +19 -0
- package/apps/dashboard/src/app/(auth)/forgot-password/page.tsx +114 -0
- package/apps/dashboard/src/app/(auth)/layout.tsx +18 -3
- package/apps/dashboard/src/app/(auth)/login/page.tsx +215 -6
- package/apps/dashboard/src/app/(auth)/providers.tsx +34 -0
- package/apps/dashboard/src/app/(auth)/register/page.tsx +171 -5
- package/apps/dashboard/src/app/(auth)/reset-password/page.tsx +172 -0
- package/apps/dashboard/src/app/(auth)/verify-email/page.tsx +190 -0
- package/apps/dashboard/src/app/(dashboard)/(deployment)/build/[id]/page.tsx +162 -0
- package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/components/BuildSummary.tsx +166 -0
- package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/components/DnsConfiguration.tsx +119 -0
- package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/components/DomainSettings.tsx +291 -0
- package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/components/Header.tsx +71 -0
- package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/components/Sidebar.tsx +270 -0
- package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/components/SkeletonLoader.tsx +87 -0
- package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/page.tsx +169 -0
- package/apps/dashboard/src/app/(dashboard)/(deployment)/layout.tsx +7 -0
- package/apps/dashboard/src/app/(dashboard)/billing/page.tsx +1 -8
- package/apps/dashboard/src/app/(dashboard)/deployments/components/CommitDetailsModal.tsx +240 -0
- package/apps/dashboard/src/app/(dashboard)/deployments/components/DeploymentCard.tsx +121 -0
- package/apps/dashboard/src/app/(dashboard)/deployments/components/DeploymentHeader.tsx +89 -0
- package/apps/dashboard/src/app/(dashboard)/deployments/components/DeploymentMenu.tsx +182 -0
- package/apps/dashboard/src/app/(dashboard)/deployments/components/DeploymentsContent.tsx +324 -0
- package/apps/dashboard/src/app/(dashboard)/deployments/components/DeploymentsFilters.tsx +99 -0
- package/apps/dashboard/src/app/(dashboard)/deployments/components/DeploymentsList.tsx +31 -0
- package/apps/dashboard/src/app/(dashboard)/deployments/components/EmptyState.tsx +101 -0
- package/apps/dashboard/src/app/(dashboard)/deployments/components/LoadingSkeleton.tsx +57 -0
- package/apps/dashboard/src/app/(dashboard)/deployments/components/ProjectFilter.tsx +109 -0
- package/apps/dashboard/src/app/(dashboard)/deployments/components/index.ts +9 -0
- package/apps/dashboard/src/app/(dashboard)/deployments/page.tsx +9 -3
- package/apps/dashboard/src/app/(dashboard)/deployments/types.ts +44 -0
- package/apps/dashboard/src/app/(dashboard)/deployments/utils.ts +176 -0
- package/apps/dashboard/src/app/(dashboard)/domains/page.tsx +1 -6
- package/apps/dashboard/src/app/(dashboard)/integrations/page.tsx +321 -0
- package/apps/dashboard/src/app/(dashboard)/layout.tsx +22 -32
- package/apps/dashboard/src/app/(dashboard)/library/components/ConnectPrompt.tsx +187 -0
- package/apps/dashboard/src/app/(dashboard)/library/components/LibrarySidebar.tsx +147 -0
- package/apps/dashboard/src/app/(dashboard)/library/components/LoadingSkeleton.tsx +39 -0
- package/apps/dashboard/src/app/(dashboard)/library/components/LocalProjects.tsx +548 -0
- package/apps/dashboard/src/app/(dashboard)/library/components/RepositoryList.tsx +292 -0
- package/apps/dashboard/src/app/(dashboard)/library/components/TemplateGrid.tsx +36 -0
- package/apps/dashboard/src/app/(dashboard)/library/components/UrlImport.tsx +74 -0
- package/apps/dashboard/src/app/(dashboard)/library/page.tsx +121 -0
- package/apps/dashboard/src/app/(dashboard)/library/types.ts +26 -0
- package/apps/dashboard/src/app/(dashboard)/monitoring/page.tsx +1 -6
- package/apps/dashboard/src/app/(dashboard)/page.tsx +485 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/[[...slug]]/layout.tsx +22 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/[[...slug]]/page.tsx +286 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/AdvancedSettings.tsx +235 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/BuildSettings.tsx +174 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/DeleteConfirmationDialog.tsx +45 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/DeletionModal.tsx +95 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/Deployments.tsx +11 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/DomainSettings.tsx +523 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/EnvironmentSettings.tsx +246 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/GeneralSettings.tsx +93 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/GitInfo.tsx +261 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/GitSettings.tsx +391 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/LogsSettings.tsx +133 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/ProjectNotFound.tsx +95 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/ProjectsBottomNavigation.tsx +62 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/ResourceSettings.tsx +333 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/SettingSection.tsx +66 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/SettingsNavigation.tsx +27 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/SleepModeSettings.tsx +134 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/ProductionUrl.tsx +25 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/ProjectIdentity.tsx +159 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/ProjectInfo.tsx +58 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/SkeletonLoader.tsx +170 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/StatsCards.tsx +33 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/TopPaths.tsx +56 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/TrafficChart.tsx +157 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/index.ts +14 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/logs/LogsActions.tsx +52 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/logs/ServerLogs.tsx +249 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/logs/TerminalLogs.tsx +682 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/logs/logs.css +127 -0
- package/apps/dashboard/src/app/(dashboard)/projects/components/DeploymentCard.tsx +73 -0
- package/apps/dashboard/src/app/(dashboard)/projects/components/ProjectCard.tsx +330 -0
- package/apps/dashboard/src/app/(dashboard)/projects/page.tsx +326 -4
- package/apps/dashboard/src/app/(dashboard)/providers.tsx +23 -0
- package/apps/dashboard/src/app/(dashboard)/servers/[serverId]/_components/components-tab.tsx +329 -0
- package/apps/dashboard/src/app/(dashboard)/servers/[serverId]/_components/overview-tab.tsx +229 -0
- package/apps/dashboard/src/app/(dashboard)/servers/[serverId]/_components/terminal-tab.tsx +38 -0
- package/apps/dashboard/src/app/(dashboard)/servers/[serverId]/page.tsx +437 -0
- package/apps/dashboard/src/app/(dashboard)/servers/new/_components/checking-state.tsx +15 -0
- package/apps/dashboard/src/app/(dashboard)/servers/new/_components/choose-mode.tsx +54 -0
- package/apps/dashboard/src/app/(dashboard)/servers/new/_components/component-row.tsx +65 -0
- package/apps/dashboard/src/app/(dashboard)/servers/new/_components/error-banner.tsx +13 -0
- package/apps/dashboard/src/app/(dashboard)/servers/new/_components/installing-panel.tsx +227 -0
- package/apps/dashboard/src/app/(dashboard)/servers/new/_components/results-panel.tsx +90 -0
- package/apps/dashboard/src/app/(dashboard)/servers/new/_components/setup-header.tsx +56 -0
- package/apps/dashboard/src/app/(dashboard)/servers/new/_components/types.ts +13 -0
- package/apps/dashboard/src/app/(dashboard)/servers/new/page.tsx +666 -0
- package/apps/dashboard/src/app/(dashboard)/servers/page.tsx +283 -0
- package/apps/dashboard/src/app/(dashboard)/settings/_components/BuildPreferences.tsx +110 -0
- package/apps/dashboard/src/app/(dashboard)/settings/_components/CloudConnection.tsx +147 -0
- package/apps/dashboard/src/app/(dashboard)/settings/_components/InstanceInfo.tsx +54 -0
- package/apps/dashboard/src/app/(dashboard)/settings/_components/ServerConnection.tsx +407 -0
- package/apps/dashboard/src/app/(dashboard)/settings/_components/SettingsSection.tsx +32 -0
- package/apps/dashboard/src/app/(dashboard)/settings/page.tsx +62 -3
- package/apps/dashboard/src/app/auth/callback/close/page.tsx +23 -0
- package/apps/dashboard/src/app/auth/callback/install/page.tsx +40 -0
- package/apps/dashboard/src/app/authorize/page.tsx +137 -0
- package/apps/dashboard/src/app/globals.css +57 -3
- package/apps/dashboard/src/app/layout.tsx +43 -3
- package/apps/dashboard/src/components/auth-shell.tsx +66 -0
- package/apps/dashboard/src/components/deployments/types.ts +44 -0
- package/apps/dashboard/src/components/global-deployments/index.ts +17 -0
- package/apps/dashboard/src/components/i18n-provider.tsx +100 -0
- package/apps/dashboard/src/components/import-project/BuildSettings.tsx +376 -0
- package/apps/dashboard/src/components/import-project/BuildSkeleton.tsx +161 -0
- package/apps/dashboard/src/components/import-project/BuildTerminal.tsx +358 -0
- package/apps/dashboard/src/components/import-project/ComposeServices.tsx +455 -0
- package/apps/dashboard/src/components/import-project/DeploymentProcessing.tsx +510 -0
- package/apps/dashboard/src/components/import-project/DockerSettings.tsx +64 -0
- package/apps/dashboard/src/components/import-project/EnvironmentVariables.tsx +481 -0
- package/apps/dashboard/src/components/import-project/Frameworks.tsx +90 -0
- package/apps/dashboard/src/components/import-project/ProjectSettings.tsx +160 -0
- package/apps/dashboard/src/components/import-project/types.ts +82 -0
- package/apps/dashboard/src/components/integrations/GithubPermissionModal.tsx +173 -0
- package/apps/dashboard/src/components/language-switcher.tsx +36 -0
- package/apps/dashboard/src/components/logo.tsx +13 -0
- package/apps/dashboard/src/components/network-error-handler.tsx +27 -0
- package/apps/dashboard/src/components/oauth-buttons.tsx +81 -0
- package/apps/dashboard/src/components/overview/ActivityChart.tsx +247 -0
- package/apps/dashboard/src/components/overview/ApiRequestsCard.tsx +111 -0
- package/apps/dashboard/src/components/overview/CreditsCard.tsx +137 -0
- package/apps/dashboard/src/components/overview/DeploymentsChart.tsx +195 -0
- package/apps/dashboard/src/components/overview/EmptyState.tsx +126 -0
- package/apps/dashboard/src/components/overview/LoadingSkeleton.tsx +92 -0
- package/apps/dashboard/src/components/overview/OverviewHeader.tsx +74 -0
- package/apps/dashboard/src/components/overview/OverviewHeroChart.tsx +344 -0
- package/apps/dashboard/src/components/overview/ProjectsCard.tsx +134 -0
- package/apps/dashboard/src/components/overview/QuickLinks.tsx +119 -0
- package/apps/dashboard/src/components/overview/SandboxesCard.tsx +150 -0
- package/apps/dashboard/src/components/overview/StatsGrid.tsx +166 -0
- package/apps/dashboard/src/components/overview/TokenUsageChart.tsx +206 -0
- package/apps/dashboard/src/components/overview/UsageStats.tsx +79 -0
- package/apps/dashboard/src/components/overview/index.ts +30 -0
- package/apps/dashboard/src/components/overview/types.ts +116 -0
- package/apps/dashboard/src/components/page-header.tsx +25 -0
- package/apps/dashboard/src/components/project-settings/ServerSideSwitch.jsx +60 -0
- package/apps/dashboard/src/components/shared/AlertBox.tsx +39 -0
- package/apps/dashboard/src/components/shared/CTASection.tsx +45 -0
- package/apps/dashboard/src/components/shared/ErrorState.tsx +176 -0
- package/apps/dashboard/src/components/shared/FeatureCard.tsx +20 -0
- package/apps/dashboard/src/components/shared/MDXComponents.tsx +24 -0
- package/apps/dashboard/src/components/shared/MachineSettingsModal.tsx +286 -0
- package/apps/dashboard/src/components/shared/Modal.tsx +39 -0
- package/apps/dashboard/src/components/shared/OTPInput.tsx +122 -0
- package/apps/dashboard/src/components/shared/PageHero.tsx +21 -0
- package/apps/dashboard/src/components/shared/PlatformFeatureCard.tsx +27 -0
- package/apps/dashboard/src/components/shared/StatCard.tsx +14 -0
- package/apps/dashboard/src/components/shared/ValueCard.tsx +20 -0
- package/apps/dashboard/src/components/shared/index.ts +8 -0
- package/apps/dashboard/src/components/sidebar.tsx +282 -0
- package/apps/dashboard/src/components/theme-provider.tsx +96 -0
- package/apps/dashboard/src/components/toast.tsx +106 -0
- package/apps/dashboard/src/components/ui/CustomCursor.tsx +107 -0
- package/apps/dashboard/src/components/ui/CustomSelect.tsx +126 -0
- package/apps/dashboard/src/components/ui/DropdownMenu.tsx +174 -0
- package/apps/dashboard/src/components/ui/FileIcon.jsx +59 -0
- package/apps/dashboard/src/components/ui/IconPickerModal.tsx +273 -0
- package/apps/dashboard/src/components/ui/InfoBanner.jsx +39 -0
- package/apps/dashboard/src/components/ui/Logo.tsx +14 -0
- package/apps/dashboard/src/components/ui/Modal.tsx +117 -0
- package/apps/dashboard/src/components/ui/PageContainer.tsx +12 -0
- package/apps/dashboard/src/components/ui/SectionContainer.jsx +7 -0
- package/apps/dashboard/src/components/ui/SlidingToggle.tsx +128 -0
- package/apps/dashboard/src/components/ui/Toast.tsx +29 -0
- package/apps/dashboard/src/components/ui/button.tsx +58 -0
- package/apps/dashboard/src/components/ui/card.tsx +60 -0
- package/apps/dashboard/src/components/ui/input.tsx +20 -0
- package/apps/dashboard/src/components/ui/label.tsx +19 -0
- package/apps/dashboard/src/components/ui/select.tsx +10 -0
- package/apps/dashboard/src/components/ui/textarea.tsx +9 -0
- package/apps/dashboard/src/constants/lang-colors.ts +25 -0
- package/apps/dashboard/src/constants/mock.ts +38 -0
- package/apps/dashboard/src/context/AuthContext.tsx +91 -0
- package/apps/dashboard/src/context/CloudContext.tsx +275 -0
- package/apps/dashboard/src/context/DeploymentContext.tsx +71 -0
- package/apps/dashboard/src/context/GitHubContext.tsx +288 -0
- package/apps/dashboard/src/context/ModalContext.tsx +300 -0
- package/apps/dashboard/src/context/NetworkContext.tsx +32 -0
- package/apps/dashboard/src/context/ProjectSettingsContext.tsx +727 -0
- package/apps/dashboard/src/context/ToastContext.tsx +129 -0
- package/apps/dashboard/src/context/deployment/index.ts +3 -0
- package/apps/dashboard/src/context/deployment/types.ts +183 -0
- package/apps/dashboard/src/context/deployment/useDeploymentBuild.ts +547 -0
- package/apps/dashboard/src/context/deployment/useDeploymentConfig.ts +176 -0
- package/apps/dashboard/src/hooks/useBuildConnection.ts +334 -0
- package/apps/dashboard/src/hooks/useMonitorStream.ts +146 -0
- package/apps/dashboard/src/hooks/useProjectData.ts +110 -0
- package/apps/dashboard/src/hooks/useSSEConnection.ts +465 -0
- package/apps/dashboard/src/hooks/useSSEStream.ts +309 -0
- package/apps/dashboard/src/hooks/useSessions.ts +104 -0
- package/apps/dashboard/src/hooks/useSetupStream.ts +223 -0
- package/apps/dashboard/src/i18n/dictionaries/ar.json +218 -0
- package/apps/dashboard/src/i18n/dictionaries/en.json +218 -0
- package/apps/dashboard/src/i18n/index.ts +16 -0
- package/apps/dashboard/src/lib/api/ai.ts +22 -0
- package/apps/dashboard/src/lib/api/auth.ts +13 -0
- package/apps/dashboard/src/lib/api/client.ts +199 -0
- package/apps/dashboard/src/lib/api/cloud.ts +15 -0
- package/apps/dashboard/src/lib/api/deploy.ts +58 -0
- package/apps/dashboard/src/lib/api/domains.ts +13 -0
- package/apps/dashboard/src/lib/api/endpoints.ts +129 -0
- package/apps/dashboard/src/lib/api/github.ts +31 -0
- package/apps/dashboard/src/lib/api/icons.ts +12 -0
- package/apps/dashboard/src/lib/api/index.ts +41 -0
- package/apps/dashboard/src/lib/api/projects.ts +153 -0
- package/apps/dashboard/src/lib/api/sandbox.ts +14 -0
- package/apps/dashboard/src/lib/api/settings.ts +21 -0
- package/apps/dashboard/src/lib/api/system.ts +159 -0
- package/apps/dashboard/src/lib/auth-client.ts +54 -0
- package/apps/dashboard/src/lib/country.ts +25 -0
- package/apps/dashboard/src/lib/server/api.ts +140 -0
- package/apps/dashboard/src/lib/server/session.ts +103 -0
- package/apps/dashboard/src/lib/sseClient.ts +241 -0
- package/apps/dashboard/src/lib/sseMessageProcessors.ts +323 -0
- package/apps/dashboard/src/lib/utils.ts +6 -0
- package/apps/dashboard/src/proxy.ts +46 -0
- package/apps/dashboard/src/styles/Toast.module.css +92 -0
- package/apps/dashboard/src/styles/fonts.css +93 -0
- package/apps/dashboard/src/styles/theme.css +336 -0
- package/apps/dashboard/src/types/research.ts +42 -0
- package/apps/dashboard/src/utils/SSEParser.js +150 -0
- package/apps/dashboard/src/utils/authWindow.ts +143 -0
- package/apps/dashboard/src/utils/codeTheme.ts +127 -0
- package/apps/dashboard/src/utils/colors.json +2810 -0
- package/apps/dashboard/src/utils/date.ts +121 -0
- package/apps/dashboard/src/utils/deployment.ts +128 -0
- package/apps/dashboard/src/utils/deploymentPhaseDetector.ts +232 -0
- package/apps/dashboard/src/utils/extToLang.js +103 -0
- package/apps/dashboard/src/utils/github.ts +62 -0
- package/apps/dashboard/src/utils/icons.js +46 -0
- package/apps/dashboard/src/utils/repoSlug.ts +110 -0
- package/apps/dashboard/src/utils/research-api.ts +163 -0
- package/apps/dashboard/src/utils/suggestions.ts +84 -0
- package/apps/dashboard/src/utils/theme.js +1 -0
- package/apps/dashboard/src/utils/ui-helpers.tsx +30 -0
- package/apps/dashboard/src/utils/upload.js +12 -0
- package/apps/dashboard/tsconfig.json +27 -5
- package/apps/desktop/forge.config.json +36 -0
- package/apps/desktop/package.json +26 -0
- package/apps/desktop/scripts/dev.mjs +58 -0
- package/apps/desktop/src/main/index.ts +675 -0
- package/apps/desktop/src/main/types/desktop.d.ts +33 -0
- package/apps/desktop/src/preload/index.ts +88 -0
- package/apps/desktop/src/renderer/index.html +462 -0
- package/apps/desktop/src/renderer/onboarding.js +511 -0
- package/apps/desktop/src/renderer/styles.css +1139 -0
- package/apps/desktop/tsconfig.json +17 -0
- package/apps/web/.source/browser.ts +13 -0
- package/apps/web/.source/dynamic.ts +8 -0
- package/apps/web/.source/server.ts +22 -0
- package/apps/web/.source/source.config.mjs +20 -0
- package/apps/web/Dockerfile +4 -6
- package/apps/web/content/blog/how-ai-builds-work.mdx +55 -0
- package/apps/web/content/blog/introducing-openship.mdx +46 -0
- package/apps/web/content/blog/self-hosting-cost-breakdown.mdx +59 -0
- package/apps/web/content/docs/_meta.json +14 -0
- package/apps/web/content/docs/api.mdx +98 -0
- package/apps/web/content/docs/cli.mdx +80 -0
- package/apps/web/content/docs/first-deployment.mdx +56 -0
- package/apps/web/content/docs/index.mdx +27 -0
- package/apps/web/content/docs/installation.mdx +65 -0
- package/apps/web/content/docs/quickstart.mdx +45 -0
- package/apps/web/next-env.d.ts +6 -0
- package/apps/web/next.config.mjs +16 -2
- package/apps/web/package.json +19 -12
- package/apps/web/postcss.config.mjs +5 -0
- package/apps/web/public/android-chrome-192x192.png +0 -0
- package/apps/web/public/android-chrome-512x512.png +0 -0
- package/apps/web/public/apple-touch-icon.png +0 -0
- package/apps/web/public/favicon-16x16.png +0 -0
- package/apps/web/public/favicon-32x32.png +0 -0
- package/apps/web/public/favicon.ico +0 -0
- package/apps/web/public/site.webmanifest +1 -0
- package/apps/web/source.config.ts +17 -0
- package/apps/web/src/app/(marketing)/layout.tsx +7 -22
- package/apps/web/src/app/blog/[...slug]/page.tsx +247 -0
- package/apps/web/src/app/blog/blog.css +575 -0
- package/apps/web/src/app/blog/layout.tsx +50 -0
- package/apps/web/src/app/blog/page.tsx +235 -0
- package/apps/web/src/app/docs/[[...slug]]/page.tsx +67 -0
- package/apps/web/src/app/docs/layout.tsx +62 -0
- package/apps/web/src/app/docs/toc-theme.css +71 -0
- package/apps/web/src/app/download/page.tsx +396 -0
- package/apps/web/src/app/globals.css +20 -3
- package/apps/web/src/app/layout.tsx +57 -3
- package/apps/web/src/app/page.tsx +47 -6
- package/apps/web/src/app/robots.ts +15 -0
- package/apps/web/src/app/sitemap.ts +35 -0
- package/apps/web/src/components/blog/share-sidebar.tsx +81 -0
- package/apps/web/src/components/blog/writer-sidebar.tsx +81 -0
- package/apps/web/src/components/landing/ai-native.tsx +169 -0
- package/apps/web/src/components/landing/backups.tsx +139 -0
- package/apps/web/src/components/landing/cdn.tsx +109 -0
- package/apps/web/src/components/landing/coming-soon.tsx +180 -0
- package/apps/web/src/components/landing/dark-section.tsx +166 -0
- package/apps/web/src/components/landing/dashboard.tsx +407 -0
- package/apps/web/src/components/landing/developer-experience.tsx +224 -0
- package/apps/web/src/components/landing/dns-domains.tsx +146 -0
- package/apps/web/src/components/landing/download-button.tsx +151 -0
- package/apps/web/src/components/landing/features/ai-visual.tsx +48 -0
- package/apps/web/src/components/landing/features/cloud-visual.tsx +44 -0
- package/apps/web/src/components/landing/features/git-visual.tsx +61 -0
- package/apps/web/src/components/landing/features/index.tsx +642 -0
- package/apps/web/src/components/landing/features/infra-visual.tsx +69 -0
- package/apps/web/src/components/landing/features/rollback-visual.tsx +42 -0
- package/apps/web/src/components/landing/features/ssl-visual.tsx +41 -0
- package/apps/web/src/components/landing/features/stacks-visual.tsx +54 -0
- package/apps/web/src/components/landing/final-cta.tsx +116 -0
- package/apps/web/src/components/landing/footer.tsx +145 -0
- package/apps/web/src/components/landing/hero.tsx +110 -0
- package/apps/web/src/components/landing/how-it-works.tsx +210 -0
- package/apps/web/src/components/landing/index.ts +21 -0
- package/apps/web/src/components/landing/mail-server.tsx +122 -0
- package/apps/web/src/components/landing/navbar.tsx +199 -0
- package/apps/web/src/components/landing/no-lockin.tsx +185 -0
- package/apps/web/src/components/landing/open-source-cta.tsx +151 -0
- package/apps/web/src/components/landing/portability.tsx +190 -0
- package/apps/web/src/components/landing/pricing.tsx +263 -0
- package/apps/web/src/components/landing/scaling.tsx +209 -0
- package/apps/web/src/components/landing/why-openship.tsx +291 -0
- package/apps/web/src/hooks/use-gsap-scroll.ts +158 -0
- package/apps/web/src/hooks/use-platform.ts +86 -0
- package/apps/web/src/lib/source.ts +12 -0
- package/apps/web/src/styles/fonts.css +84 -0
- package/apps/web/src/styles/landing.css +951 -0
- package/apps/web/src/styles/theme.css +227 -0
- package/apps/web/tsconfig.json +8 -3
- package/bun.lock +3260 -0
- package/docker-compose.yml +3 -0
- package/docs/screenshots/.gitkeep +0 -0
- package/docs/screenshots/screen.png +0 -0
- package/openship.code-workspace +7 -0
- package/package.json +23 -9
- package/packages/adapters/docs/ARCHITECTURE.md +160 -0
- package/packages/adapters/docs/BUILD-PIPELINE.md +171 -0
- package/packages/adapters/docs/CLOUD.md +239 -0
- package/packages/adapters/docs/EXECUTOR.md +174 -0
- package/packages/adapters/docs/LOG-STREAMING.md +175 -0
- package/packages/adapters/docs/SYSTEM.md +191 -0
- package/packages/adapters/package.json +14 -9
- package/packages/adapters/src/index.ts +144 -4
- package/packages/adapters/src/infra/cloud.ts +42 -0
- package/packages/adapters/src/infra/index.ts +9 -0
- package/packages/adapters/src/infra/noop.ts +27 -0
- package/packages/adapters/src/infra/traefik.ts +297 -0
- package/packages/adapters/src/infra/types.ts +36 -0
- package/packages/adapters/src/platform.ts +255 -0
- package/packages/adapters/src/runtime/bare.ts +567 -0
- package/packages/adapters/src/runtime/build-pipeline.ts +237 -0
- package/packages/adapters/src/runtime/cloud.ts +791 -0
- package/packages/adapters/src/runtime/deploy-pipeline.ts +146 -0
- package/packages/adapters/src/runtime/docker.ts +443 -0
- package/packages/adapters/src/runtime/index.ts +69 -0
- package/packages/adapters/src/runtime/local-build.ts +112 -0
- package/packages/adapters/src/runtime/transfer.ts +82 -0
- package/packages/adapters/src/runtime/types.ts +138 -0
- package/packages/adapters/src/system/catalog.ts +188 -0
- package/packages/adapters/src/system/checks.ts +228 -0
- package/packages/adapters/src/system/components.ts +45 -0
- package/packages/adapters/src/system/debug.ts +18 -0
- package/packages/adapters/src/system/environment.ts +168 -0
- package/packages/adapters/src/system/errors.ts +34 -0
- package/packages/adapters/src/system/executor.ts +600 -0
- package/packages/adapters/src/system/index.ts +58 -0
- package/packages/adapters/src/system/installer.ts +310 -0
- package/packages/adapters/src/system/setup.ts +442 -0
- package/packages/adapters/src/system/state.ts +96 -0
- package/packages/adapters/src/system/types.ts +116 -0
- package/packages/adapters/src/toolchain/catalog.ts +439 -0
- package/packages/adapters/src/toolchain/checks.ts +181 -0
- package/packages/adapters/src/toolchain/index.ts +38 -0
- package/packages/adapters/src/toolchain/installer.ts +189 -0
- package/packages/adapters/src/toolchain/types.ts +84 -0
- package/packages/adapters/src/types.ts +277 -0
- package/packages/adapters/tsconfig.json +3 -1
- package/packages/core/package.json +9 -9
- package/packages/core/src/constants.ts +44 -1
- package/packages/core/src/index.ts +2 -0
- package/packages/core/src/stacks.ts +778 -0
- package/packages/core/src/system.ts +111 -0
- package/packages/core/src/types.ts +22 -1
- package/packages/core/src/utils.ts +8 -0
- package/packages/core/tsconfig.json +3 -1
- package/packages/db/drizzle/0000_init.sql +231 -0
- package/packages/db/drizzle/0001_add_server_name.sql +1 -0
- package/packages/db/drizzle/meta/_journal.json +20 -0
- package/packages/db/drizzle.config.ts +10 -0
- package/packages/db/package.json +26 -14
- package/packages/db/src/client.ts +119 -0
- package/packages/db/src/index.ts +57 -8
- package/packages/db/src/repos/account.repo.ts +92 -0
- package/packages/db/src/repos/deployment.repo.ts +168 -0
- package/packages/db/src/repos/domain.repo.ts +102 -0
- package/packages/db/src/repos/git-installation.repo.ts +95 -0
- package/packages/db/src/repos/index.ts +43 -0
- package/packages/db/src/repos/instance-settings.repo.ts +43 -0
- package/packages/db/src/repos/project.repo.ts +159 -0
- package/packages/db/src/repos/session.repo.ts +82 -0
- package/packages/db/src/repos/settings.repo.ts +51 -0
- package/packages/db/src/repos/user.repo.ts +94 -0
- package/packages/db/src/schema/auth.ts +56 -0
- package/packages/db/src/schema/deployment.ts +87 -0
- package/packages/db/src/schema/domain.ts +46 -0
- package/packages/db/src/schema/github.ts +24 -0
- package/packages/db/src/schema/index.ts +6 -0
- package/packages/db/src/schema/project.ts +117 -0
- package/packages/db/src/schema/settings.ts +103 -0
- package/packages/db/tsconfig.json +3 -1
- package/packages/ui/package.json +17 -17
- package/packages/ui/src/components/status-dot.tsx +1 -1
- package/packages/ui/tsconfig.json +7 -2
- package/pnpm-workspace.yaml +0 -1
- package/{tooling/tsconfig/base.json → tsconfig.base.json} +0 -1
- package/turbo.json +4 -0
- package/apps/api/src/modules/auth/auth.controller.ts +0 -26
- package/apps/api/src/modules/auth/auth.schema.ts +0 -12
- package/apps/api/src/modules/auth/auth.service.ts +0 -23
- package/apps/dashboard/src/app/page.tsx +0 -6
- package/apps/web/src/app/(marketing)/docs/page.tsx +0 -10
- package/packages/adapters/src/base-adapter.ts +0 -49
- package/packages/adapters/src/docker-adapter.ts +0 -43
- package/packages/adapters/src/oblien-adapter.ts +0 -46
- package/packages/adapters/src/registry.ts +0 -22
- package/packages/db/prisma/schema.prisma +0 -160
- package/tooling/tsconfig/nextjs.json +0 -10
- package/tooling/tsconfig/node.json +0 -10
- package/tooling/tsconfig/package.json +0 -7
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloud client — used by local/self-hosted instances to get
|
|
3
|
+
* an Oblien namespace token from api.openship.io.
|
|
4
|
+
*
|
|
5
|
+
* Auth is fully server-side: the user's Openship Cloud session
|
|
6
|
+
* is stored (encrypted) in user_settings.cloud_session_token.
|
|
7
|
+
* This module reads it from DB, fetches namespace tokens from
|
|
8
|
+
* the SaaS API, and caches them in memory.
|
|
9
|
+
*
|
|
10
|
+
* No client-side cookies or tokens involved.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { repos } from "@repo/db";
|
|
14
|
+
import { env } from "../config/env";
|
|
15
|
+
import { decrypt } from "./encryption";
|
|
16
|
+
|
|
17
|
+
// ─── Namespace token cache ───────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
interface TokenCache {
|
|
20
|
+
token: string;
|
|
21
|
+
namespace: string;
|
|
22
|
+
expiresAt: number; // epoch ms
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const tokenCache = new Map<string, TokenCache>();
|
|
26
|
+
const REFRESH_BUFFER_MS = 5 * 60 * 1000;
|
|
27
|
+
|
|
28
|
+
// ─── Cloud session management ────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Disconnect from Openship Cloud — clear stored session.
|
|
32
|
+
*/
|
|
33
|
+
export async function disconnectCloud(userId: string): Promise<void> {
|
|
34
|
+
await repos.settings.update(userId, { cloudSessionToken: null });
|
|
35
|
+
tokenCache.delete(userId);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Check whether the user has a stored cloud session.
|
|
40
|
+
*/
|
|
41
|
+
export async function isCloudConnected(userId: string): Promise<boolean> {
|
|
42
|
+
const settings = await repos.settings.findByUser(userId);
|
|
43
|
+
return !!settings?.cloudSessionToken;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ─── Namespace token fetching ────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get a valid namespace-scoped Oblien token for a user.
|
|
50
|
+
*
|
|
51
|
+
* Reads the stored cloud session from DB, calls POST /api/cloud/token
|
|
52
|
+
* on the SaaS API, caches the result in memory.
|
|
53
|
+
*
|
|
54
|
+
* Returns null if the user isn't connected to Openship Cloud.
|
|
55
|
+
*/
|
|
56
|
+
export async function getCloudToken(
|
|
57
|
+
userId: string,
|
|
58
|
+
): Promise<{ token: string; namespace: string } | null> {
|
|
59
|
+
// Check memory cache first
|
|
60
|
+
const cached = tokenCache.get(userId);
|
|
61
|
+
if (cached && cached.expiresAt - Date.now() > REFRESH_BUFFER_MS) {
|
|
62
|
+
return { token: cached.token, namespace: cached.namespace };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Read stored session from DB
|
|
66
|
+
const settings = await repos.settings.findByUser(userId);
|
|
67
|
+
if (!settings?.cloudSessionToken) return null;
|
|
68
|
+
|
|
69
|
+
const sessionToken = decrypt(settings.cloudSessionToken);
|
|
70
|
+
|
|
71
|
+
// Fetch namespace token from SaaS API
|
|
72
|
+
const url = `${env.OPENSHIP_CLOUD_URL}/api/cloud/token`;
|
|
73
|
+
const res = await fetch(url, {
|
|
74
|
+
method: "POST",
|
|
75
|
+
headers: {
|
|
76
|
+
"Content-Type": "application/json",
|
|
77
|
+
Authorization: `Bearer ${sessionToken}`,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (!res.ok) {
|
|
82
|
+
// If 401, the stored session is expired — clear it
|
|
83
|
+
if (res.status === 401) {
|
|
84
|
+
await repos.settings.update(userId, { cloudSessionToken: null });
|
|
85
|
+
tokenCache.delete(userId);
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const json = (await res.json()) as {
|
|
91
|
+
data: { token: string; namespace: string; expiresAt: string };
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const { token, namespace, expiresAt } = json.data;
|
|
95
|
+
|
|
96
|
+
tokenCache.set(userId, {
|
|
97
|
+
token,
|
|
98
|
+
namespace,
|
|
99
|
+
expiresAt: new Date(expiresAt).getTime(),
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return { token, namespace };
|
|
103
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docker Compose parser — extracts services, ports, volumes, depends_on,
|
|
3
|
+
* and environment from a docker-compose.yml / compose.yml file.
|
|
4
|
+
*
|
|
5
|
+
* Used by the prepare service to populate the services UI for compose projects.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { parse as parseYaml } from "yaml";
|
|
9
|
+
|
|
10
|
+
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
export interface ComposeService {
|
|
13
|
+
name: string;
|
|
14
|
+
image?: string;
|
|
15
|
+
build?: string;
|
|
16
|
+
ports: string[];
|
|
17
|
+
dependsOn: string[];
|
|
18
|
+
environment: Record<string, string>;
|
|
19
|
+
volumes: string[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ComposeParseResult {
|
|
23
|
+
services: ComposeService[];
|
|
24
|
+
volumes: string[];
|
|
25
|
+
networks: string[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ─── Parser ──────────────────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
export function parseComposeFile(content: string): ComposeParseResult {
|
|
31
|
+
const doc = parseYaml(content);
|
|
32
|
+
|
|
33
|
+
if (!doc || typeof doc !== "object") {
|
|
34
|
+
return { services: [], volumes: [], networks: [] };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const rawServices = doc.services ?? {};
|
|
38
|
+
const services: ComposeService[] = [];
|
|
39
|
+
|
|
40
|
+
for (const [name, def] of Object.entries(rawServices)) {
|
|
41
|
+
if (!def || typeof def !== "object") continue;
|
|
42
|
+
const svc = def as Record<string, unknown>;
|
|
43
|
+
|
|
44
|
+
services.push({
|
|
45
|
+
name,
|
|
46
|
+
image: typeof svc.image === "string" ? svc.image : undefined,
|
|
47
|
+
build: parseBuild(svc.build),
|
|
48
|
+
ports: parsePorts(svc.ports),
|
|
49
|
+
dependsOn: parseDependsOn(svc.depends_on),
|
|
50
|
+
environment: parseEnvironment(svc.environment),
|
|
51
|
+
volumes: parseVolumes(svc.volumes),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const volumes = doc.volumes ? Object.keys(doc.volumes) : [];
|
|
56
|
+
const networks = doc.networks ? Object.keys(doc.networks) : [];
|
|
57
|
+
|
|
58
|
+
return { services, volumes, networks };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ─── Field parsers ───────────────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
function parseBuild(build: unknown): string | undefined {
|
|
64
|
+
if (typeof build === "string") return build;
|
|
65
|
+
if (build && typeof build === "object") {
|
|
66
|
+
const b = build as Record<string, unknown>;
|
|
67
|
+
return (typeof b.context === "string" ? b.context : undefined) ?? ".";
|
|
68
|
+
}
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function parsePorts(ports: unknown): string[] {
|
|
73
|
+
if (!Array.isArray(ports)) return [];
|
|
74
|
+
return ports.map((p) => {
|
|
75
|
+
if (typeof p === "string") return p;
|
|
76
|
+
if (typeof p === "number") return String(p);
|
|
77
|
+
if (p && typeof p === "object") {
|
|
78
|
+
const port = p as Record<string, unknown>;
|
|
79
|
+
const target = port.target ?? port.container_port;
|
|
80
|
+
const published = port.published ?? port.host_port;
|
|
81
|
+
if (target) {
|
|
82
|
+
return published ? `${published}:${target}` : String(target);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return String(p);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function parseDependsOn(deps: unknown): string[] {
|
|
90
|
+
if (Array.isArray(deps)) return deps.filter((d): d is string => typeof d === "string");
|
|
91
|
+
if (deps && typeof deps === "object") return Object.keys(deps);
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function parseEnvironment(env: unknown): Record<string, string> {
|
|
96
|
+
if (!env) return {};
|
|
97
|
+
|
|
98
|
+
// Array form: ["KEY=value", "KEY2=value2"]
|
|
99
|
+
if (Array.isArray(env)) {
|
|
100
|
+
const result: Record<string, string> = {};
|
|
101
|
+
for (const item of env) {
|
|
102
|
+
if (typeof item !== "string") continue;
|
|
103
|
+
const eqIdx = item.indexOf("=");
|
|
104
|
+
if (eqIdx > 0) {
|
|
105
|
+
result[item.slice(0, eqIdx)] = item.slice(eqIdx + 1);
|
|
106
|
+
} else {
|
|
107
|
+
result[item] = "";
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Object form: { KEY: value }
|
|
114
|
+
if (typeof env === "object") {
|
|
115
|
+
const result: Record<string, string> = {};
|
|
116
|
+
for (const [key, val] of Object.entries(env as Record<string, unknown>)) {
|
|
117
|
+
result[key] = val != null ? String(val) : "";
|
|
118
|
+
}
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function parseVolumes(vols: unknown): string[] {
|
|
126
|
+
if (!Array.isArray(vols)) return [];
|
|
127
|
+
return vols.map((v) => {
|
|
128
|
+
if (typeof v === "string") return v;
|
|
129
|
+
if (v && typeof v === "object") {
|
|
130
|
+
const vol = v as Record<string, unknown>;
|
|
131
|
+
const src = vol.source ?? vol.name;
|
|
132
|
+
const tgt = vol.target;
|
|
133
|
+
if (src && tgt) return `${src}:${tgt}`;
|
|
134
|
+
if (tgt) return String(tgt);
|
|
135
|
+
}
|
|
136
|
+
return String(v);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared controller helpers — used across all Hono route handlers.
|
|
3
|
+
*
|
|
4
|
+
* Eliminates duplication of getUserId / param / platform() across controllers.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Context } from "hono";
|
|
8
|
+
import {
|
|
9
|
+
initPlatform,
|
|
10
|
+
getPlatform,
|
|
11
|
+
type Platform,
|
|
12
|
+
type PlatformTarget,
|
|
13
|
+
type PlatformConfig,
|
|
14
|
+
} from "@repo/adapters";
|
|
15
|
+
import { env } from "../config/env";
|
|
16
|
+
|
|
17
|
+
// ─── Auth helpers ────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
/** Extract the authenticated user ID from Hono context */
|
|
20
|
+
export function getUserId(c: Context): string {
|
|
21
|
+
const user = c.get("user");
|
|
22
|
+
if (!user?.id) throw new Error("Unauthorized: no user in context");
|
|
23
|
+
return user.id;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Extract and validate a required route parameter */
|
|
27
|
+
export function param(c: Context, name: string): string {
|
|
28
|
+
const val = c.req.param(name);
|
|
29
|
+
if (!val) throw new Error(`Missing route param: ${name}`);
|
|
30
|
+
return val;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ─── Platform resolution ─────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the deployment target from environment config.
|
|
37
|
+
*
|
|
38
|
+
* CLOUD_MODE (SaaS hosting) and DEPLOY_MODE=cloud (Oblien runtime) both
|
|
39
|
+
* need the cloud platform adapter, so either triggers the cloud config.
|
|
40
|
+
* Auth/billing concerns are gated separately by CLOUD_MODE alone.
|
|
41
|
+
*
|
|
42
|
+
* Priority:
|
|
43
|
+
* 1. CLOUD_MODE=true or DEPLOY_MODE=cloud → "cloud" (Oblien runtime)
|
|
44
|
+
* 2. DEPLOY_MODE=desktop → "desktop"
|
|
45
|
+
* 3. Default → "selfhosted" with docker or bare runtime
|
|
46
|
+
*/
|
|
47
|
+
function resolveConfig(): PlatformConfig {
|
|
48
|
+
if (env.CLOUD_MODE || env.DEPLOY_MODE === "cloud") {
|
|
49
|
+
return {
|
|
50
|
+
target: "cloud",
|
|
51
|
+
cloudClientId: env.OBLIEN_CLIENT_ID,
|
|
52
|
+
cloudClientSecret: env.OBLIEN_CLIENT_SECRET,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (env.DEPLOY_MODE === "desktop") {
|
|
57
|
+
return { target: "desktop" };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Self-hosted: docker or bare
|
|
61
|
+
return {
|
|
62
|
+
target: "selfhosted",
|
|
63
|
+
runtime: env.DEPLOY_MODE === "bare" ? "bare" : "docker",
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Initialize the platform at server startup.
|
|
69
|
+
*
|
|
70
|
+
* Call this ONCE before the server starts handling requests.
|
|
71
|
+
* After this, `platform()` returns the cached instance synchronously.
|
|
72
|
+
*/
|
|
73
|
+
export async function bootstrapPlatform(): Promise<Platform> {
|
|
74
|
+
return initPlatform(resolveConfig());
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get the platform — the single entry point for all service code.
|
|
79
|
+
*
|
|
80
|
+
* Returns: { runtime, routing, ssl, system }
|
|
81
|
+
* - runtime: build/deploy/stop/start lifecycle
|
|
82
|
+
* - routing: register/remove reverse-proxy routes
|
|
83
|
+
* - ssl: provision/renew TLS certificates
|
|
84
|
+
* - system: prerequisite validation (self-hosted only, null otherwise)
|
|
85
|
+
*
|
|
86
|
+
* All service code uses this. Nothing constructs adapters directly.
|
|
87
|
+
*/
|
|
88
|
+
export function platform(): Platform {
|
|
89
|
+
return getPlatform();
|
|
90
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email templates for Openship.
|
|
3
|
+
*
|
|
4
|
+
* Each template returns { subject, html, text } so they can be
|
|
5
|
+
* passed directly to sendMail(). Keep all copy and markup here
|
|
6
|
+
* so auth.ts / other callers stay clean.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/* ------------------------------------------------------------------ */
|
|
10
|
+
/* Shared layout */
|
|
11
|
+
/* ------------------------------------------------------------------ */
|
|
12
|
+
|
|
13
|
+
const BRAND = "Openship";
|
|
14
|
+
|
|
15
|
+
function layout(body: string) {
|
|
16
|
+
return `
|
|
17
|
+
<!DOCTYPE html>
|
|
18
|
+
<html lang="en">
|
|
19
|
+
<head><meta charset="utf-8" /><meta name="viewport" content="width=device-width" /></head>
|
|
20
|
+
<body style="margin:0;padding:0;background:#f9fafb;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif">
|
|
21
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="padding:40px 20px">
|
|
22
|
+
<tr><td align="center">
|
|
23
|
+
<table role="presentation" width="480" cellpadding="0" cellspacing="0" style="background:#ffffff;border-radius:16px;padding:40px;border:1px solid #e5e7eb">
|
|
24
|
+
<tr><td>
|
|
25
|
+
${body}
|
|
26
|
+
<p style="color:#9ca3af;font-size:12px;margin-top:32px;border-top:1px solid #f3f4f6;padding-top:16px">
|
|
27
|
+
© ${BRAND}
|
|
28
|
+
</p>
|
|
29
|
+
</td></tr>
|
|
30
|
+
</table>
|
|
31
|
+
</td></tr>
|
|
32
|
+
</table>
|
|
33
|
+
</body>
|
|
34
|
+
</html>`.trim();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function ctaButton(url: string, label: string) {
|
|
38
|
+
return `
|
|
39
|
+
<table role="presentation" cellpadding="0" cellspacing="0" style="margin:24px 0">
|
|
40
|
+
<tr><td align="center" style="background:#000;border-radius:10px">
|
|
41
|
+
<a href="${url}" target="_blank" style="display:inline-block;padding:12px 28px;color:#fff;font-size:14px;font-weight:600;text-decoration:none">${label}</a>
|
|
42
|
+
</td></tr>
|
|
43
|
+
</table>`.trim();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function greeting(name?: string | null) {
|
|
47
|
+
return `<p style="color:#111;font-size:15px;margin:0 0 16px">Hi ${name || "there"},</p>`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* ------------------------------------------------------------------ */
|
|
51
|
+
/* Reset password */
|
|
52
|
+
/* ------------------------------------------------------------------ */
|
|
53
|
+
|
|
54
|
+
export function resetPasswordEmail(user: { name?: string | null; email: string }, url: string) {
|
|
55
|
+
const html = layout(`
|
|
56
|
+
${greeting(user.name)}
|
|
57
|
+
<p style="color:#374151;font-size:14px;line-height:1.6;margin:0 0 4px">
|
|
58
|
+
We received a request to reset your password. Click the button below to choose a new one.
|
|
59
|
+
</p>
|
|
60
|
+
${ctaButton(url, "Reset password")}
|
|
61
|
+
<p style="color:#9ca3af;font-size:13px;margin:0">
|
|
62
|
+
If you didn't request this, you can safely ignore this email. The link expires in 1 hour.
|
|
63
|
+
</p>
|
|
64
|
+
`);
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
subject: "Reset your Openship password",
|
|
68
|
+
html,
|
|
69
|
+
text: `Hi ${user.name || "there"},\n\nReset your password: ${url}\n\nIf you didn't request this, ignore this email. The link expires in 1 hour.`,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* ------------------------------------------------------------------ */
|
|
74
|
+
/* Verify email */
|
|
75
|
+
/* ------------------------------------------------------------------ */
|
|
76
|
+
|
|
77
|
+
export function verifyEmailTemplate(user: { name?: string | null; email: string }, url: string) {
|
|
78
|
+
const html = layout(`
|
|
79
|
+
${greeting(user.name)}
|
|
80
|
+
<p style="color:#374151;font-size:14px;line-height:1.6;margin:0 0 4px">
|
|
81
|
+
Please verify your email address to get started with ${BRAND}.
|
|
82
|
+
</p>
|
|
83
|
+
${ctaButton(url, "Verify email")}
|
|
84
|
+
<p style="color:#9ca3af;font-size:13px;margin:0">
|
|
85
|
+
If you didn't create an account, you can ignore this email.
|
|
86
|
+
</p>
|
|
87
|
+
`);
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
subject: "Verify your Openship email",
|
|
91
|
+
html,
|
|
92
|
+
text: `Hi ${user.name || "there"},\n\nVerify your email: ${url}\n\nIf you didn't create an account, ignore this email.`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Application-level encryption for sensitive data (env vars, secrets).
|
|
3
|
+
*
|
|
4
|
+
* Uses AES-256-GCM (authenticated encryption) via Node.js built-in crypto.
|
|
5
|
+
* The encryption key is derived from BETTER_AUTH_SECRET (which every install
|
|
6
|
+
* already has) — no extra env var needed.
|
|
7
|
+
*
|
|
8
|
+
* Format: base64( iv:16 || authTag:16 || ciphertext )
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* import { encrypt, decrypt } from "../lib/encryption";
|
|
12
|
+
* const sealed = encrypt("my secret");
|
|
13
|
+
* const plain = decrypt(sealed); // "my secret"
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
createCipheriv,
|
|
18
|
+
createDecipheriv,
|
|
19
|
+
createHash,
|
|
20
|
+
randomBytes,
|
|
21
|
+
} from "node:crypto";
|
|
22
|
+
import { env } from "../config/env";
|
|
23
|
+
|
|
24
|
+
// ─── Key derivation ──────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
const ALGORITHM = "aes-256-gcm" as const;
|
|
27
|
+
const IV_LENGTH = 16;
|
|
28
|
+
const AUTH_TAG_LENGTH = 16;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Derive a deterministic 256-bit key from the auth secret.
|
|
32
|
+
* SHA-256 always produces 32 bytes — exactly what AES-256 needs.
|
|
33
|
+
*/
|
|
34
|
+
function deriveKey(): Buffer {
|
|
35
|
+
return createHash("sha256")
|
|
36
|
+
.update(env.BETTER_AUTH_SECRET)
|
|
37
|
+
.digest();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ─── Public API ──────────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Encrypt a plaintext string.
|
|
44
|
+
* Returns a base64-encoded string containing IV + auth tag + ciphertext.
|
|
45
|
+
*/
|
|
46
|
+
export function encrypt(plaintext: string): string {
|
|
47
|
+
const key = deriveKey();
|
|
48
|
+
const iv = randomBytes(IV_LENGTH);
|
|
49
|
+
|
|
50
|
+
const cipher = createCipheriv(ALGORITHM, key, iv);
|
|
51
|
+
const encrypted = Buffer.concat([
|
|
52
|
+
cipher.update(plaintext, "utf8"),
|
|
53
|
+
cipher.final(),
|
|
54
|
+
]);
|
|
55
|
+
const authTag = cipher.getAuthTag();
|
|
56
|
+
|
|
57
|
+
// Pack: iv (16) + authTag (16) + ciphertext (variable)
|
|
58
|
+
const packed = Buffer.concat([iv, authTag, encrypted]);
|
|
59
|
+
return packed.toString("base64");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Decrypt a value produced by `encrypt()`.
|
|
64
|
+
* Throws if the data is tampered with or the key is wrong.
|
|
65
|
+
*/
|
|
66
|
+
export function decrypt(sealed: string): string {
|
|
67
|
+
const key = deriveKey();
|
|
68
|
+
const packed = Buffer.from(sealed, "base64");
|
|
69
|
+
|
|
70
|
+
if (packed.length < IV_LENGTH + AUTH_TAG_LENGTH) {
|
|
71
|
+
throw new Error("Invalid encrypted data: too short");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const iv = packed.subarray(0, IV_LENGTH);
|
|
75
|
+
const authTag = packed.subarray(IV_LENGTH, IV_LENGTH + AUTH_TAG_LENGTH);
|
|
76
|
+
const ciphertext = packed.subarray(IV_LENGTH + AUTH_TAG_LENGTH);
|
|
77
|
+
|
|
78
|
+
const decipher = createDecipheriv(ALGORITHM, key, iv);
|
|
79
|
+
decipher.setAuthTag(authTag);
|
|
80
|
+
|
|
81
|
+
const decrypted = Buffer.concat([
|
|
82
|
+
decipher.update(ciphertext),
|
|
83
|
+
decipher.final(),
|
|
84
|
+
]);
|
|
85
|
+
|
|
86
|
+
return decrypted.toString("utf8");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Encrypt a JSON-serialisable value.
|
|
91
|
+
* Convenience wrapper for structured data like env var arrays.
|
|
92
|
+
*/
|
|
93
|
+
export function encryptJson<T>(value: T): string {
|
|
94
|
+
return encrypt(JSON.stringify(value));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Decrypt a value produced by `encryptJson()`.
|
|
99
|
+
*/
|
|
100
|
+
export function decryptJson<T = unknown>(sealed: string): T {
|
|
101
|
+
return JSON.parse(decrypt(sealed)) as T;
|
|
102
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local user — auto-provisioned DB user for self-hosted/desktop mode.
|
|
3
|
+
*
|
|
4
|
+
* In local mode the API trusts all requests (it only listens on 127.0.0.1).
|
|
5
|
+
* However, controllers reference `userId` as a FK, so a real user row must
|
|
6
|
+
* exist. This module lazily creates one on first access and caches it.
|
|
7
|
+
*
|
|
8
|
+
* The user is an admin with `autoProvisioned = true`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { randomUUID } from "node:crypto";
|
|
12
|
+
import { repos } from "@repo/db";
|
|
13
|
+
|
|
14
|
+
const LOCAL_EMAIL = "local@openship.local";
|
|
15
|
+
|
|
16
|
+
/** Shape expected by Hono context consumers (`c.get("user")`) */
|
|
17
|
+
export interface LocalUser {
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
email: string;
|
|
21
|
+
emailVerified: boolean;
|
|
22
|
+
role: string;
|
|
23
|
+
autoProvisioned: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Cached user — avoids a DB hit on every request after the first. */
|
|
27
|
+
let cached: LocalUser | null = null;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Ensure a local user row exists in the database and return it.
|
|
31
|
+
*
|
|
32
|
+
* - First call: looks up `local@openship.local`, creates if missing.
|
|
33
|
+
* - Subsequent calls: returns the in-memory cache (zero DB cost).
|
|
34
|
+
*/
|
|
35
|
+
export async function ensureLocalUser(): Promise<LocalUser> {
|
|
36
|
+
if (cached) return cached;
|
|
37
|
+
|
|
38
|
+
let row = await repos.user.findByEmail(LOCAL_EMAIL);
|
|
39
|
+
|
|
40
|
+
if (!row) {
|
|
41
|
+
// Insert directly — no password/session needed in local mode.
|
|
42
|
+
const id = randomUUID();
|
|
43
|
+
const { db, schema } = await import("@repo/db");
|
|
44
|
+
await db.insert(schema.user).values({
|
|
45
|
+
id,
|
|
46
|
+
name: "Local User",
|
|
47
|
+
email: LOCAL_EMAIL,
|
|
48
|
+
emailVerified: true,
|
|
49
|
+
role: "admin",
|
|
50
|
+
autoProvisioned: true,
|
|
51
|
+
});
|
|
52
|
+
row = await repos.user.findById(id);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
cached = {
|
|
56
|
+
id: row!.id,
|
|
57
|
+
name: row!.name,
|
|
58
|
+
email: row!.email,
|
|
59
|
+
emailVerified: row!.emailVerified,
|
|
60
|
+
role: row!.role,
|
|
61
|
+
autoProvisioned: row!.autoProvisioned,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return cached;
|
|
65
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import nodemailer from "nodemailer";
|
|
2
|
+
import { env } from "../config/env";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Lightweight email sender using SMTP.
|
|
6
|
+
*
|
|
7
|
+
* Only available when SMTP_HOST is configured.
|
|
8
|
+
* Self-hosted instances without SMTP skip email-dependent features
|
|
9
|
+
* (verification, password reset) gracefully.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export const smtpEnabled = !!(env.SMTP_HOST && env.SMTP_USER && env.SMTP_PASS);
|
|
13
|
+
|
|
14
|
+
export type SendMailOptions = {
|
|
15
|
+
to: string;
|
|
16
|
+
subject: string;
|
|
17
|
+
html: string;
|
|
18
|
+
text?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/** Reusable transport — created once when SMTP is available. */
|
|
22
|
+
const transport = smtpEnabled
|
|
23
|
+
? nodemailer.createTransport({
|
|
24
|
+
host: env.SMTP_HOST,
|
|
25
|
+
port: env.SMTP_PORT ?? 587,
|
|
26
|
+
secure: (env.SMTP_PORT ?? 587) === 465,
|
|
27
|
+
auth: {
|
|
28
|
+
user: env.SMTP_USER,
|
|
29
|
+
pass: env.SMTP_PASS,
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
: null;
|
|
33
|
+
|
|
34
|
+
/** Send an email via SMTP. No-ops when SMTP is not configured. */
|
|
35
|
+
export async function sendMail(opts: SendMailOptions): Promise<void> {
|
|
36
|
+
if (!transport) {
|
|
37
|
+
console.warn("[mail] SMTP not configured — skipping email to", opts.to);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
await transport.sendMail({
|
|
42
|
+
from: env.SMTP_FROM,
|
|
43
|
+
to: opts.to,
|
|
44
|
+
subject: opts.subject,
|
|
45
|
+
html: opts.html,
|
|
46
|
+
...(opts.text ? { text: opts.text } : {}),
|
|
47
|
+
});
|
|
48
|
+
}
|