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.
Files changed (531) hide show
  1. package/.env.example +19 -3
  2. package/CONTRIBUTING.md +69 -19
  3. package/LICENSE +661 -0
  4. package/README.md +304 -108
  5. package/apps/api/Dockerfile +6 -8
  6. package/apps/api/package.json +25 -21
  7. package/apps/api/src/app.ts +46 -3
  8. package/apps/api/src/config/env.ts +81 -7
  9. package/apps/api/src/lib/auth-mode.ts +42 -0
  10. package/apps/api/src/lib/auth.ts +105 -0
  11. package/apps/api/src/lib/cache.ts +128 -0
  12. package/apps/api/src/lib/cloud-auth-proxy.ts +310 -0
  13. package/apps/api/src/lib/cloud-client.ts +103 -0
  14. package/apps/api/src/lib/compose-parser.ts +138 -0
  15. package/apps/api/src/lib/controller-helpers.ts +90 -0
  16. package/apps/api/src/lib/email-templates.ts +94 -0
  17. package/apps/api/src/lib/encryption.ts +102 -0
  18. package/apps/api/src/lib/local-user.ts +65 -0
  19. package/apps/api/src/lib/mail.ts +48 -0
  20. package/apps/api/src/lib/notifications.ts +195 -0
  21. package/apps/api/src/lib/openship-cloud.ts +101 -0
  22. package/apps/api/src/lib/resources.ts +96 -0
  23. package/apps/api/src/lib/screenshots.ts +189 -0
  24. package/apps/api/src/lib/ssh-manager.ts +369 -0
  25. package/apps/api/src/lib/ssl-scheduler.ts +109 -0
  26. package/apps/api/src/lib/stack-detector.ts +708 -0
  27. package/apps/api/src/lib/system-debug.ts +10 -0
  28. package/apps/api/src/middleware/auth.ts +49 -13
  29. package/apps/api/src/middleware/error-handler.ts +23 -3
  30. package/apps/api/src/middleware/index.ts +2 -0
  31. package/apps/api/src/middleware/internal-auth.ts +40 -0
  32. package/apps/api/src/middleware/local-only.ts +20 -0
  33. package/apps/api/src/middleware/rate-limiter.ts +9 -0
  34. package/apps/api/src/modules/analytics/analytics.controller.ts +111 -0
  35. package/apps/api/src/modules/analytics/analytics.routes.ts +29 -0
  36. package/apps/api/src/modules/analytics/analytics.schema.ts +37 -0
  37. package/apps/api/src/modules/analytics/analytics.service.ts +264 -0
  38. package/apps/api/src/modules/auth/auth.routes.ts +233 -6
  39. package/apps/api/src/modules/billing/billing.routes.ts +8 -0
  40. package/apps/api/src/modules/cloud/cloud.controller.ts +225 -0
  41. package/apps/api/src/modules/cloud/cloud.routes.ts +40 -0
  42. package/apps/api/src/modules/deployments/build.service.ts +791 -0
  43. package/apps/api/src/modules/deployments/deployment-lifecycle.ts +138 -0
  44. package/apps/api/src/modules/deployments/deployment.controller.ts +324 -16
  45. package/apps/api/src/modules/deployments/deployment.routes.ts +33 -7
  46. package/apps/api/src/modules/deployments/deployment.schema.ts +37 -5
  47. package/apps/api/src/modules/deployments/deployment.service.ts +178 -11
  48. package/apps/api/src/modules/deployments/preflight.ts +280 -0
  49. package/apps/api/src/modules/deployments/prepare.service.ts +252 -0
  50. package/apps/api/src/modules/deployments/session-manager.ts +283 -0
  51. package/apps/api/src/modules/deployments/ssl.service.ts +73 -0
  52. package/apps/api/src/modules/domains/domain.controller.ts +59 -7
  53. package/apps/api/src/modules/domains/domain.routes.ts +20 -5
  54. package/apps/api/src/modules/domains/domain.schema.ts +35 -0
  55. package/apps/api/src/modules/domains/domain.service.ts +375 -9
  56. package/apps/api/src/modules/github/github.auth.ts +397 -0
  57. package/apps/api/src/modules/github/github.controller.ts +387 -0
  58. package/apps/api/src/modules/github/github.local-auth.ts +251 -0
  59. package/apps/api/src/modules/github/github.routes.ts +50 -0
  60. package/apps/api/src/modules/github/github.schema.ts +52 -0
  61. package/apps/api/src/modules/github/github.service.ts +592 -0
  62. package/apps/api/src/modules/github/github.types.ts +158 -0
  63. package/apps/api/src/modules/github/github.webhook.ts +186 -0
  64. package/apps/api/src/modules/github/index.ts +16 -0
  65. package/apps/api/src/modules/health/health.routes.ts +35 -0
  66. package/apps/api/src/modules/projects/project.controller.ts +481 -12
  67. package/apps/api/src/modules/projects/project.routes.ts +63 -6
  68. package/apps/api/src/modules/projects/project.schema.ts +104 -6
  69. package/apps/api/src/modules/projects/project.service.ts +502 -11
  70. package/apps/api/src/modules/settings/settings.controller.ts +55 -0
  71. package/apps/api/src/modules/settings/settings.routes.ts +25 -0
  72. package/apps/api/src/modules/settings/settings.service.ts +49 -0
  73. package/apps/api/src/modules/system/filesystem.controller.ts +66 -0
  74. package/apps/api/src/modules/system/index.ts +1 -0
  75. package/apps/api/src/modules/system/server-check.controller.ts +475 -0
  76. package/apps/api/src/modules/system/setup-session.ts +255 -0
  77. package/apps/api/src/modules/system/setup.controller.ts +128 -0
  78. package/apps/api/src/modules/system/system.routes.ts +42 -0
  79. package/apps/api/src/modules/webhooks/webhook.controller.ts +57 -15
  80. package/apps/api/src/modules/webhooks/webhook.routes.ts +11 -8
  81. package/apps/api/src/modules/webhooks/webhook.service.ts +46 -5
  82. package/apps/api/src/modules/webhooks/webhook.types.ts +35 -0
  83. package/apps/api/tsconfig.json +3 -1
  84. package/apps/cli/package.json +8 -10
  85. package/apps/cli/tsconfig.json +2 -1
  86. package/apps/dashboard/Dockerfile +4 -6
  87. package/apps/dashboard/components.json +20 -0
  88. package/apps/dashboard/next-env.d.ts +6 -0
  89. package/apps/dashboard/next.config.mjs +11 -1
  90. package/apps/dashboard/package.json +25 -13
  91. package/apps/dashboard/postcss.config.mjs +5 -0
  92. package/apps/dashboard/public/android-chrome-192x192.png +0 -0
  93. package/apps/dashboard/public/android-chrome-512x512.png +0 -0
  94. package/apps/dashboard/public/apple-touch-icon.png +0 -0
  95. package/apps/dashboard/public/favicon-16x16.png +0 -0
  96. package/apps/dashboard/public/favicon-32x32.png +0 -0
  97. package/apps/dashboard/public/favicon.ico +0 -0
  98. package/apps/dashboard/public/site.webmanifest +19 -0
  99. package/apps/dashboard/src/app/(auth)/forgot-password/page.tsx +114 -0
  100. package/apps/dashboard/src/app/(auth)/layout.tsx +18 -3
  101. package/apps/dashboard/src/app/(auth)/login/page.tsx +215 -6
  102. package/apps/dashboard/src/app/(auth)/providers.tsx +34 -0
  103. package/apps/dashboard/src/app/(auth)/register/page.tsx +171 -5
  104. package/apps/dashboard/src/app/(auth)/reset-password/page.tsx +172 -0
  105. package/apps/dashboard/src/app/(auth)/verify-email/page.tsx +190 -0
  106. package/apps/dashboard/src/app/(dashboard)/(deployment)/build/[id]/page.tsx +162 -0
  107. package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/components/BuildSummary.tsx +166 -0
  108. package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/components/DnsConfiguration.tsx +119 -0
  109. package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/components/DomainSettings.tsx +291 -0
  110. package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/components/Header.tsx +71 -0
  111. package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/components/Sidebar.tsx +270 -0
  112. package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/components/SkeletonLoader.tsx +87 -0
  113. package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/page.tsx +169 -0
  114. package/apps/dashboard/src/app/(dashboard)/(deployment)/layout.tsx +7 -0
  115. package/apps/dashboard/src/app/(dashboard)/billing/page.tsx +1 -8
  116. package/apps/dashboard/src/app/(dashboard)/deployments/components/CommitDetailsModal.tsx +240 -0
  117. package/apps/dashboard/src/app/(dashboard)/deployments/components/DeploymentCard.tsx +121 -0
  118. package/apps/dashboard/src/app/(dashboard)/deployments/components/DeploymentHeader.tsx +89 -0
  119. package/apps/dashboard/src/app/(dashboard)/deployments/components/DeploymentMenu.tsx +182 -0
  120. package/apps/dashboard/src/app/(dashboard)/deployments/components/DeploymentsContent.tsx +324 -0
  121. package/apps/dashboard/src/app/(dashboard)/deployments/components/DeploymentsFilters.tsx +99 -0
  122. package/apps/dashboard/src/app/(dashboard)/deployments/components/DeploymentsList.tsx +31 -0
  123. package/apps/dashboard/src/app/(dashboard)/deployments/components/EmptyState.tsx +101 -0
  124. package/apps/dashboard/src/app/(dashboard)/deployments/components/LoadingSkeleton.tsx +57 -0
  125. package/apps/dashboard/src/app/(dashboard)/deployments/components/ProjectFilter.tsx +109 -0
  126. package/apps/dashboard/src/app/(dashboard)/deployments/components/index.ts +9 -0
  127. package/apps/dashboard/src/app/(dashboard)/deployments/page.tsx +9 -3
  128. package/apps/dashboard/src/app/(dashboard)/deployments/types.ts +44 -0
  129. package/apps/dashboard/src/app/(dashboard)/deployments/utils.ts +176 -0
  130. package/apps/dashboard/src/app/(dashboard)/domains/page.tsx +1 -6
  131. package/apps/dashboard/src/app/(dashboard)/integrations/page.tsx +321 -0
  132. package/apps/dashboard/src/app/(dashboard)/layout.tsx +22 -32
  133. package/apps/dashboard/src/app/(dashboard)/library/components/ConnectPrompt.tsx +187 -0
  134. package/apps/dashboard/src/app/(dashboard)/library/components/LibrarySidebar.tsx +147 -0
  135. package/apps/dashboard/src/app/(dashboard)/library/components/LoadingSkeleton.tsx +39 -0
  136. package/apps/dashboard/src/app/(dashboard)/library/components/LocalProjects.tsx +548 -0
  137. package/apps/dashboard/src/app/(dashboard)/library/components/RepositoryList.tsx +292 -0
  138. package/apps/dashboard/src/app/(dashboard)/library/components/TemplateGrid.tsx +36 -0
  139. package/apps/dashboard/src/app/(dashboard)/library/components/UrlImport.tsx +74 -0
  140. package/apps/dashboard/src/app/(dashboard)/library/page.tsx +121 -0
  141. package/apps/dashboard/src/app/(dashboard)/library/types.ts +26 -0
  142. package/apps/dashboard/src/app/(dashboard)/monitoring/page.tsx +1 -6
  143. package/apps/dashboard/src/app/(dashboard)/page.tsx +485 -0
  144. package/apps/dashboard/src/app/(dashboard)/projects/[id]/[[...slug]]/layout.tsx +22 -0
  145. package/apps/dashboard/src/app/(dashboard)/projects/[id]/[[...slug]]/page.tsx +286 -0
  146. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/AdvancedSettings.tsx +235 -0
  147. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/BuildSettings.tsx +174 -0
  148. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/DeleteConfirmationDialog.tsx +45 -0
  149. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/DeletionModal.tsx +95 -0
  150. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/Deployments.tsx +11 -0
  151. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/DomainSettings.tsx +523 -0
  152. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/EnvironmentSettings.tsx +246 -0
  153. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/GeneralSettings.tsx +93 -0
  154. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/GitInfo.tsx +261 -0
  155. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/GitSettings.tsx +391 -0
  156. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/LogsSettings.tsx +133 -0
  157. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/ProjectNotFound.tsx +95 -0
  158. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/ProjectsBottomNavigation.tsx +62 -0
  159. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/ResourceSettings.tsx +333 -0
  160. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/SettingSection.tsx +66 -0
  161. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/SettingsNavigation.tsx +27 -0
  162. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/SleepModeSettings.tsx +134 -0
  163. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/ProductionUrl.tsx +25 -0
  164. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/ProjectIdentity.tsx +159 -0
  165. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/ProjectInfo.tsx +58 -0
  166. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/SkeletonLoader.tsx +170 -0
  167. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/StatsCards.tsx +33 -0
  168. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/TopPaths.tsx +56 -0
  169. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/TrafficChart.tsx +157 -0
  170. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/index.ts +14 -0
  171. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/logs/LogsActions.tsx +52 -0
  172. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/logs/ServerLogs.tsx +249 -0
  173. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/logs/TerminalLogs.tsx +682 -0
  174. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/logs/logs.css +127 -0
  175. package/apps/dashboard/src/app/(dashboard)/projects/components/DeploymentCard.tsx +73 -0
  176. package/apps/dashboard/src/app/(dashboard)/projects/components/ProjectCard.tsx +330 -0
  177. package/apps/dashboard/src/app/(dashboard)/projects/page.tsx +326 -4
  178. package/apps/dashboard/src/app/(dashboard)/providers.tsx +23 -0
  179. package/apps/dashboard/src/app/(dashboard)/servers/[serverId]/_components/components-tab.tsx +329 -0
  180. package/apps/dashboard/src/app/(dashboard)/servers/[serverId]/_components/overview-tab.tsx +229 -0
  181. package/apps/dashboard/src/app/(dashboard)/servers/[serverId]/_components/terminal-tab.tsx +38 -0
  182. package/apps/dashboard/src/app/(dashboard)/servers/[serverId]/page.tsx +437 -0
  183. package/apps/dashboard/src/app/(dashboard)/servers/new/_components/checking-state.tsx +15 -0
  184. package/apps/dashboard/src/app/(dashboard)/servers/new/_components/choose-mode.tsx +54 -0
  185. package/apps/dashboard/src/app/(dashboard)/servers/new/_components/component-row.tsx +65 -0
  186. package/apps/dashboard/src/app/(dashboard)/servers/new/_components/error-banner.tsx +13 -0
  187. package/apps/dashboard/src/app/(dashboard)/servers/new/_components/installing-panel.tsx +227 -0
  188. package/apps/dashboard/src/app/(dashboard)/servers/new/_components/results-panel.tsx +90 -0
  189. package/apps/dashboard/src/app/(dashboard)/servers/new/_components/setup-header.tsx +56 -0
  190. package/apps/dashboard/src/app/(dashboard)/servers/new/_components/types.ts +13 -0
  191. package/apps/dashboard/src/app/(dashboard)/servers/new/page.tsx +666 -0
  192. package/apps/dashboard/src/app/(dashboard)/servers/page.tsx +283 -0
  193. package/apps/dashboard/src/app/(dashboard)/settings/_components/BuildPreferences.tsx +110 -0
  194. package/apps/dashboard/src/app/(dashboard)/settings/_components/CloudConnection.tsx +147 -0
  195. package/apps/dashboard/src/app/(dashboard)/settings/_components/InstanceInfo.tsx +54 -0
  196. package/apps/dashboard/src/app/(dashboard)/settings/_components/ServerConnection.tsx +407 -0
  197. package/apps/dashboard/src/app/(dashboard)/settings/_components/SettingsSection.tsx +32 -0
  198. package/apps/dashboard/src/app/(dashboard)/settings/page.tsx +62 -3
  199. package/apps/dashboard/src/app/auth/callback/close/page.tsx +23 -0
  200. package/apps/dashboard/src/app/auth/callback/install/page.tsx +40 -0
  201. package/apps/dashboard/src/app/authorize/page.tsx +137 -0
  202. package/apps/dashboard/src/app/globals.css +57 -3
  203. package/apps/dashboard/src/app/layout.tsx +43 -3
  204. package/apps/dashboard/src/components/auth-shell.tsx +66 -0
  205. package/apps/dashboard/src/components/deployments/types.ts +44 -0
  206. package/apps/dashboard/src/components/global-deployments/index.ts +17 -0
  207. package/apps/dashboard/src/components/i18n-provider.tsx +100 -0
  208. package/apps/dashboard/src/components/import-project/BuildSettings.tsx +376 -0
  209. package/apps/dashboard/src/components/import-project/BuildSkeleton.tsx +161 -0
  210. package/apps/dashboard/src/components/import-project/BuildTerminal.tsx +358 -0
  211. package/apps/dashboard/src/components/import-project/ComposeServices.tsx +455 -0
  212. package/apps/dashboard/src/components/import-project/DeploymentProcessing.tsx +510 -0
  213. package/apps/dashboard/src/components/import-project/DockerSettings.tsx +64 -0
  214. package/apps/dashboard/src/components/import-project/EnvironmentVariables.tsx +481 -0
  215. package/apps/dashboard/src/components/import-project/Frameworks.tsx +90 -0
  216. package/apps/dashboard/src/components/import-project/ProjectSettings.tsx +160 -0
  217. package/apps/dashboard/src/components/import-project/types.ts +82 -0
  218. package/apps/dashboard/src/components/integrations/GithubPermissionModal.tsx +173 -0
  219. package/apps/dashboard/src/components/language-switcher.tsx +36 -0
  220. package/apps/dashboard/src/components/logo.tsx +13 -0
  221. package/apps/dashboard/src/components/network-error-handler.tsx +27 -0
  222. package/apps/dashboard/src/components/oauth-buttons.tsx +81 -0
  223. package/apps/dashboard/src/components/overview/ActivityChart.tsx +247 -0
  224. package/apps/dashboard/src/components/overview/ApiRequestsCard.tsx +111 -0
  225. package/apps/dashboard/src/components/overview/CreditsCard.tsx +137 -0
  226. package/apps/dashboard/src/components/overview/DeploymentsChart.tsx +195 -0
  227. package/apps/dashboard/src/components/overview/EmptyState.tsx +126 -0
  228. package/apps/dashboard/src/components/overview/LoadingSkeleton.tsx +92 -0
  229. package/apps/dashboard/src/components/overview/OverviewHeader.tsx +74 -0
  230. package/apps/dashboard/src/components/overview/OverviewHeroChart.tsx +344 -0
  231. package/apps/dashboard/src/components/overview/ProjectsCard.tsx +134 -0
  232. package/apps/dashboard/src/components/overview/QuickLinks.tsx +119 -0
  233. package/apps/dashboard/src/components/overview/SandboxesCard.tsx +150 -0
  234. package/apps/dashboard/src/components/overview/StatsGrid.tsx +166 -0
  235. package/apps/dashboard/src/components/overview/TokenUsageChart.tsx +206 -0
  236. package/apps/dashboard/src/components/overview/UsageStats.tsx +79 -0
  237. package/apps/dashboard/src/components/overview/index.ts +30 -0
  238. package/apps/dashboard/src/components/overview/types.ts +116 -0
  239. package/apps/dashboard/src/components/page-header.tsx +25 -0
  240. package/apps/dashboard/src/components/project-settings/ServerSideSwitch.jsx +60 -0
  241. package/apps/dashboard/src/components/shared/AlertBox.tsx +39 -0
  242. package/apps/dashboard/src/components/shared/CTASection.tsx +45 -0
  243. package/apps/dashboard/src/components/shared/ErrorState.tsx +176 -0
  244. package/apps/dashboard/src/components/shared/FeatureCard.tsx +20 -0
  245. package/apps/dashboard/src/components/shared/MDXComponents.tsx +24 -0
  246. package/apps/dashboard/src/components/shared/MachineSettingsModal.tsx +286 -0
  247. package/apps/dashboard/src/components/shared/Modal.tsx +39 -0
  248. package/apps/dashboard/src/components/shared/OTPInput.tsx +122 -0
  249. package/apps/dashboard/src/components/shared/PageHero.tsx +21 -0
  250. package/apps/dashboard/src/components/shared/PlatformFeatureCard.tsx +27 -0
  251. package/apps/dashboard/src/components/shared/StatCard.tsx +14 -0
  252. package/apps/dashboard/src/components/shared/ValueCard.tsx +20 -0
  253. package/apps/dashboard/src/components/shared/index.ts +8 -0
  254. package/apps/dashboard/src/components/sidebar.tsx +282 -0
  255. package/apps/dashboard/src/components/theme-provider.tsx +96 -0
  256. package/apps/dashboard/src/components/toast.tsx +106 -0
  257. package/apps/dashboard/src/components/ui/CustomCursor.tsx +107 -0
  258. package/apps/dashboard/src/components/ui/CustomSelect.tsx +126 -0
  259. package/apps/dashboard/src/components/ui/DropdownMenu.tsx +174 -0
  260. package/apps/dashboard/src/components/ui/FileIcon.jsx +59 -0
  261. package/apps/dashboard/src/components/ui/IconPickerModal.tsx +273 -0
  262. package/apps/dashboard/src/components/ui/InfoBanner.jsx +39 -0
  263. package/apps/dashboard/src/components/ui/Logo.tsx +14 -0
  264. package/apps/dashboard/src/components/ui/Modal.tsx +117 -0
  265. package/apps/dashboard/src/components/ui/PageContainer.tsx +12 -0
  266. package/apps/dashboard/src/components/ui/SectionContainer.jsx +7 -0
  267. package/apps/dashboard/src/components/ui/SlidingToggle.tsx +128 -0
  268. package/apps/dashboard/src/components/ui/Toast.tsx +29 -0
  269. package/apps/dashboard/src/components/ui/button.tsx +58 -0
  270. package/apps/dashboard/src/components/ui/card.tsx +60 -0
  271. package/apps/dashboard/src/components/ui/input.tsx +20 -0
  272. package/apps/dashboard/src/components/ui/label.tsx +19 -0
  273. package/apps/dashboard/src/components/ui/select.tsx +10 -0
  274. package/apps/dashboard/src/components/ui/textarea.tsx +9 -0
  275. package/apps/dashboard/src/constants/lang-colors.ts +25 -0
  276. package/apps/dashboard/src/constants/mock.ts +38 -0
  277. package/apps/dashboard/src/context/AuthContext.tsx +91 -0
  278. package/apps/dashboard/src/context/CloudContext.tsx +275 -0
  279. package/apps/dashboard/src/context/DeploymentContext.tsx +71 -0
  280. package/apps/dashboard/src/context/GitHubContext.tsx +288 -0
  281. package/apps/dashboard/src/context/ModalContext.tsx +300 -0
  282. package/apps/dashboard/src/context/NetworkContext.tsx +32 -0
  283. package/apps/dashboard/src/context/ProjectSettingsContext.tsx +727 -0
  284. package/apps/dashboard/src/context/ToastContext.tsx +129 -0
  285. package/apps/dashboard/src/context/deployment/index.ts +3 -0
  286. package/apps/dashboard/src/context/deployment/types.ts +183 -0
  287. package/apps/dashboard/src/context/deployment/useDeploymentBuild.ts +547 -0
  288. package/apps/dashboard/src/context/deployment/useDeploymentConfig.ts +176 -0
  289. package/apps/dashboard/src/hooks/useBuildConnection.ts +334 -0
  290. package/apps/dashboard/src/hooks/useMonitorStream.ts +146 -0
  291. package/apps/dashboard/src/hooks/useProjectData.ts +110 -0
  292. package/apps/dashboard/src/hooks/useSSEConnection.ts +465 -0
  293. package/apps/dashboard/src/hooks/useSSEStream.ts +309 -0
  294. package/apps/dashboard/src/hooks/useSessions.ts +104 -0
  295. package/apps/dashboard/src/hooks/useSetupStream.ts +223 -0
  296. package/apps/dashboard/src/i18n/dictionaries/ar.json +218 -0
  297. package/apps/dashboard/src/i18n/dictionaries/en.json +218 -0
  298. package/apps/dashboard/src/i18n/index.ts +16 -0
  299. package/apps/dashboard/src/lib/api/ai.ts +22 -0
  300. package/apps/dashboard/src/lib/api/auth.ts +13 -0
  301. package/apps/dashboard/src/lib/api/client.ts +199 -0
  302. package/apps/dashboard/src/lib/api/cloud.ts +15 -0
  303. package/apps/dashboard/src/lib/api/deploy.ts +58 -0
  304. package/apps/dashboard/src/lib/api/domains.ts +13 -0
  305. package/apps/dashboard/src/lib/api/endpoints.ts +129 -0
  306. package/apps/dashboard/src/lib/api/github.ts +31 -0
  307. package/apps/dashboard/src/lib/api/icons.ts +12 -0
  308. package/apps/dashboard/src/lib/api/index.ts +41 -0
  309. package/apps/dashboard/src/lib/api/projects.ts +153 -0
  310. package/apps/dashboard/src/lib/api/sandbox.ts +14 -0
  311. package/apps/dashboard/src/lib/api/settings.ts +21 -0
  312. package/apps/dashboard/src/lib/api/system.ts +159 -0
  313. package/apps/dashboard/src/lib/auth-client.ts +54 -0
  314. package/apps/dashboard/src/lib/country.ts +25 -0
  315. package/apps/dashboard/src/lib/server/api.ts +140 -0
  316. package/apps/dashboard/src/lib/server/session.ts +103 -0
  317. package/apps/dashboard/src/lib/sseClient.ts +241 -0
  318. package/apps/dashboard/src/lib/sseMessageProcessors.ts +323 -0
  319. package/apps/dashboard/src/lib/utils.ts +6 -0
  320. package/apps/dashboard/src/proxy.ts +46 -0
  321. package/apps/dashboard/src/styles/Toast.module.css +92 -0
  322. package/apps/dashboard/src/styles/fonts.css +93 -0
  323. package/apps/dashboard/src/styles/theme.css +336 -0
  324. package/apps/dashboard/src/types/research.ts +42 -0
  325. package/apps/dashboard/src/utils/SSEParser.js +150 -0
  326. package/apps/dashboard/src/utils/authWindow.ts +143 -0
  327. package/apps/dashboard/src/utils/codeTheme.ts +127 -0
  328. package/apps/dashboard/src/utils/colors.json +2810 -0
  329. package/apps/dashboard/src/utils/date.ts +121 -0
  330. package/apps/dashboard/src/utils/deployment.ts +128 -0
  331. package/apps/dashboard/src/utils/deploymentPhaseDetector.ts +232 -0
  332. package/apps/dashboard/src/utils/extToLang.js +103 -0
  333. package/apps/dashboard/src/utils/github.ts +62 -0
  334. package/apps/dashboard/src/utils/icons.js +46 -0
  335. package/apps/dashboard/src/utils/repoSlug.ts +110 -0
  336. package/apps/dashboard/src/utils/research-api.ts +163 -0
  337. package/apps/dashboard/src/utils/suggestions.ts +84 -0
  338. package/apps/dashboard/src/utils/theme.js +1 -0
  339. package/apps/dashboard/src/utils/ui-helpers.tsx +30 -0
  340. package/apps/dashboard/src/utils/upload.js +12 -0
  341. package/apps/dashboard/tsconfig.json +27 -5
  342. package/apps/desktop/forge.config.json +36 -0
  343. package/apps/desktop/package.json +26 -0
  344. package/apps/desktop/scripts/dev.mjs +58 -0
  345. package/apps/desktop/src/main/index.ts +675 -0
  346. package/apps/desktop/src/main/types/desktop.d.ts +33 -0
  347. package/apps/desktop/src/preload/index.ts +88 -0
  348. package/apps/desktop/src/renderer/index.html +462 -0
  349. package/apps/desktop/src/renderer/onboarding.js +511 -0
  350. package/apps/desktop/src/renderer/styles.css +1139 -0
  351. package/apps/desktop/tsconfig.json +17 -0
  352. package/apps/web/.source/browser.ts +13 -0
  353. package/apps/web/.source/dynamic.ts +8 -0
  354. package/apps/web/.source/server.ts +22 -0
  355. package/apps/web/.source/source.config.mjs +20 -0
  356. package/apps/web/Dockerfile +4 -6
  357. package/apps/web/content/blog/how-ai-builds-work.mdx +55 -0
  358. package/apps/web/content/blog/introducing-openship.mdx +46 -0
  359. package/apps/web/content/blog/self-hosting-cost-breakdown.mdx +59 -0
  360. package/apps/web/content/docs/_meta.json +14 -0
  361. package/apps/web/content/docs/api.mdx +98 -0
  362. package/apps/web/content/docs/cli.mdx +80 -0
  363. package/apps/web/content/docs/first-deployment.mdx +56 -0
  364. package/apps/web/content/docs/index.mdx +27 -0
  365. package/apps/web/content/docs/installation.mdx +65 -0
  366. package/apps/web/content/docs/quickstart.mdx +45 -0
  367. package/apps/web/next-env.d.ts +6 -0
  368. package/apps/web/next.config.mjs +16 -2
  369. package/apps/web/package.json +19 -12
  370. package/apps/web/postcss.config.mjs +5 -0
  371. package/apps/web/public/android-chrome-192x192.png +0 -0
  372. package/apps/web/public/android-chrome-512x512.png +0 -0
  373. package/apps/web/public/apple-touch-icon.png +0 -0
  374. package/apps/web/public/favicon-16x16.png +0 -0
  375. package/apps/web/public/favicon-32x32.png +0 -0
  376. package/apps/web/public/favicon.ico +0 -0
  377. package/apps/web/public/site.webmanifest +1 -0
  378. package/apps/web/source.config.ts +17 -0
  379. package/apps/web/src/app/(marketing)/layout.tsx +7 -22
  380. package/apps/web/src/app/blog/[...slug]/page.tsx +247 -0
  381. package/apps/web/src/app/blog/blog.css +575 -0
  382. package/apps/web/src/app/blog/layout.tsx +50 -0
  383. package/apps/web/src/app/blog/page.tsx +235 -0
  384. package/apps/web/src/app/docs/[[...slug]]/page.tsx +67 -0
  385. package/apps/web/src/app/docs/layout.tsx +62 -0
  386. package/apps/web/src/app/docs/toc-theme.css +71 -0
  387. package/apps/web/src/app/download/page.tsx +396 -0
  388. package/apps/web/src/app/globals.css +20 -3
  389. package/apps/web/src/app/layout.tsx +57 -3
  390. package/apps/web/src/app/page.tsx +47 -6
  391. package/apps/web/src/app/robots.ts +15 -0
  392. package/apps/web/src/app/sitemap.ts +35 -0
  393. package/apps/web/src/components/blog/share-sidebar.tsx +81 -0
  394. package/apps/web/src/components/blog/writer-sidebar.tsx +81 -0
  395. package/apps/web/src/components/landing/ai-native.tsx +169 -0
  396. package/apps/web/src/components/landing/backups.tsx +139 -0
  397. package/apps/web/src/components/landing/cdn.tsx +109 -0
  398. package/apps/web/src/components/landing/coming-soon.tsx +180 -0
  399. package/apps/web/src/components/landing/dark-section.tsx +166 -0
  400. package/apps/web/src/components/landing/dashboard.tsx +407 -0
  401. package/apps/web/src/components/landing/developer-experience.tsx +224 -0
  402. package/apps/web/src/components/landing/dns-domains.tsx +146 -0
  403. package/apps/web/src/components/landing/download-button.tsx +151 -0
  404. package/apps/web/src/components/landing/features/ai-visual.tsx +48 -0
  405. package/apps/web/src/components/landing/features/cloud-visual.tsx +44 -0
  406. package/apps/web/src/components/landing/features/git-visual.tsx +61 -0
  407. package/apps/web/src/components/landing/features/index.tsx +642 -0
  408. package/apps/web/src/components/landing/features/infra-visual.tsx +69 -0
  409. package/apps/web/src/components/landing/features/rollback-visual.tsx +42 -0
  410. package/apps/web/src/components/landing/features/ssl-visual.tsx +41 -0
  411. package/apps/web/src/components/landing/features/stacks-visual.tsx +54 -0
  412. package/apps/web/src/components/landing/final-cta.tsx +116 -0
  413. package/apps/web/src/components/landing/footer.tsx +145 -0
  414. package/apps/web/src/components/landing/hero.tsx +110 -0
  415. package/apps/web/src/components/landing/how-it-works.tsx +210 -0
  416. package/apps/web/src/components/landing/index.ts +21 -0
  417. package/apps/web/src/components/landing/mail-server.tsx +122 -0
  418. package/apps/web/src/components/landing/navbar.tsx +199 -0
  419. package/apps/web/src/components/landing/no-lockin.tsx +185 -0
  420. package/apps/web/src/components/landing/open-source-cta.tsx +151 -0
  421. package/apps/web/src/components/landing/portability.tsx +190 -0
  422. package/apps/web/src/components/landing/pricing.tsx +263 -0
  423. package/apps/web/src/components/landing/scaling.tsx +209 -0
  424. package/apps/web/src/components/landing/why-openship.tsx +291 -0
  425. package/apps/web/src/hooks/use-gsap-scroll.ts +158 -0
  426. package/apps/web/src/hooks/use-platform.ts +86 -0
  427. package/apps/web/src/lib/source.ts +12 -0
  428. package/apps/web/src/styles/fonts.css +84 -0
  429. package/apps/web/src/styles/landing.css +951 -0
  430. package/apps/web/src/styles/theme.css +227 -0
  431. package/apps/web/tsconfig.json +8 -3
  432. package/bun.lock +3260 -0
  433. package/docker-compose.yml +3 -0
  434. package/docs/screenshots/.gitkeep +0 -0
  435. package/docs/screenshots/screen.png +0 -0
  436. package/openship.code-workspace +7 -0
  437. package/package.json +23 -9
  438. package/packages/adapters/docs/ARCHITECTURE.md +160 -0
  439. package/packages/adapters/docs/BUILD-PIPELINE.md +171 -0
  440. package/packages/adapters/docs/CLOUD.md +239 -0
  441. package/packages/adapters/docs/EXECUTOR.md +174 -0
  442. package/packages/adapters/docs/LOG-STREAMING.md +175 -0
  443. package/packages/adapters/docs/SYSTEM.md +191 -0
  444. package/packages/adapters/package.json +14 -9
  445. package/packages/adapters/src/index.ts +144 -4
  446. package/packages/adapters/src/infra/cloud.ts +42 -0
  447. package/packages/adapters/src/infra/index.ts +9 -0
  448. package/packages/adapters/src/infra/noop.ts +27 -0
  449. package/packages/adapters/src/infra/traefik.ts +297 -0
  450. package/packages/adapters/src/infra/types.ts +36 -0
  451. package/packages/adapters/src/platform.ts +255 -0
  452. package/packages/adapters/src/runtime/bare.ts +567 -0
  453. package/packages/adapters/src/runtime/build-pipeline.ts +237 -0
  454. package/packages/adapters/src/runtime/cloud.ts +791 -0
  455. package/packages/adapters/src/runtime/deploy-pipeline.ts +146 -0
  456. package/packages/adapters/src/runtime/docker.ts +443 -0
  457. package/packages/adapters/src/runtime/index.ts +69 -0
  458. package/packages/adapters/src/runtime/local-build.ts +112 -0
  459. package/packages/adapters/src/runtime/transfer.ts +82 -0
  460. package/packages/adapters/src/runtime/types.ts +138 -0
  461. package/packages/adapters/src/system/catalog.ts +188 -0
  462. package/packages/adapters/src/system/checks.ts +228 -0
  463. package/packages/adapters/src/system/components.ts +45 -0
  464. package/packages/adapters/src/system/debug.ts +18 -0
  465. package/packages/adapters/src/system/environment.ts +168 -0
  466. package/packages/adapters/src/system/errors.ts +34 -0
  467. package/packages/adapters/src/system/executor.ts +600 -0
  468. package/packages/adapters/src/system/index.ts +58 -0
  469. package/packages/adapters/src/system/installer.ts +310 -0
  470. package/packages/adapters/src/system/setup.ts +442 -0
  471. package/packages/adapters/src/system/state.ts +96 -0
  472. package/packages/adapters/src/system/types.ts +116 -0
  473. package/packages/adapters/src/toolchain/catalog.ts +439 -0
  474. package/packages/adapters/src/toolchain/checks.ts +181 -0
  475. package/packages/adapters/src/toolchain/index.ts +38 -0
  476. package/packages/adapters/src/toolchain/installer.ts +189 -0
  477. package/packages/adapters/src/toolchain/types.ts +84 -0
  478. package/packages/adapters/src/types.ts +277 -0
  479. package/packages/adapters/tsconfig.json +3 -1
  480. package/packages/core/package.json +9 -9
  481. package/packages/core/src/constants.ts +44 -1
  482. package/packages/core/src/index.ts +2 -0
  483. package/packages/core/src/stacks.ts +778 -0
  484. package/packages/core/src/system.ts +111 -0
  485. package/packages/core/src/types.ts +22 -1
  486. package/packages/core/src/utils.ts +8 -0
  487. package/packages/core/tsconfig.json +3 -1
  488. package/packages/db/drizzle/0000_init.sql +231 -0
  489. package/packages/db/drizzle/0001_add_server_name.sql +1 -0
  490. package/packages/db/drizzle/meta/_journal.json +20 -0
  491. package/packages/db/drizzle.config.ts +10 -0
  492. package/packages/db/package.json +26 -14
  493. package/packages/db/src/client.ts +119 -0
  494. package/packages/db/src/index.ts +57 -8
  495. package/packages/db/src/repos/account.repo.ts +92 -0
  496. package/packages/db/src/repos/deployment.repo.ts +168 -0
  497. package/packages/db/src/repos/domain.repo.ts +102 -0
  498. package/packages/db/src/repos/git-installation.repo.ts +95 -0
  499. package/packages/db/src/repos/index.ts +43 -0
  500. package/packages/db/src/repos/instance-settings.repo.ts +43 -0
  501. package/packages/db/src/repos/project.repo.ts +159 -0
  502. package/packages/db/src/repos/session.repo.ts +82 -0
  503. package/packages/db/src/repos/settings.repo.ts +51 -0
  504. package/packages/db/src/repos/user.repo.ts +94 -0
  505. package/packages/db/src/schema/auth.ts +56 -0
  506. package/packages/db/src/schema/deployment.ts +87 -0
  507. package/packages/db/src/schema/domain.ts +46 -0
  508. package/packages/db/src/schema/github.ts +24 -0
  509. package/packages/db/src/schema/index.ts +6 -0
  510. package/packages/db/src/schema/project.ts +117 -0
  511. package/packages/db/src/schema/settings.ts +103 -0
  512. package/packages/db/tsconfig.json +3 -1
  513. package/packages/ui/package.json +17 -17
  514. package/packages/ui/src/components/status-dot.tsx +1 -1
  515. package/packages/ui/tsconfig.json +7 -2
  516. package/pnpm-workspace.yaml +0 -1
  517. package/{tooling/tsconfig/base.json → tsconfig.base.json} +0 -1
  518. package/turbo.json +4 -0
  519. package/apps/api/src/modules/auth/auth.controller.ts +0 -26
  520. package/apps/api/src/modules/auth/auth.schema.ts +0 -12
  521. package/apps/api/src/modules/auth/auth.service.ts +0 -23
  522. package/apps/dashboard/src/app/page.tsx +0 -6
  523. package/apps/web/src/app/(marketing)/docs/page.tsx +0 -10
  524. package/packages/adapters/src/base-adapter.ts +0 -49
  525. package/packages/adapters/src/docker-adapter.ts +0 -43
  526. package/packages/adapters/src/oblien-adapter.ts +0 -46
  527. package/packages/adapters/src/registry.ts +0 -22
  528. package/packages/db/prisma/schema.prisma +0 -160
  529. package/tooling/tsconfig/nextjs.json +0 -10
  530. package/tooling/tsconfig/node.json +0 -10
  531. package/tooling/tsconfig/package.json +0 -7
@@ -0,0 +1,567 @@
1
+ /**
2
+ * Bare runtime — lightweight process management without Docker.
3
+ *
4
+ * Runs applications directly on the target server via shell commands.
5
+ * All operations go through a CommandExecutor, so the bare runtime
6
+ * works identically on the local machine and on remote servers via SSH.
7
+ *
8
+ * Good for:
9
+ * - Development environments
10
+ * - Simple single-app servers where Docker is overkill
11
+ * - Edge deployments (low-resource machines)
12
+ * - Desktop app (local development)
13
+ *
14
+ * Process lifecycle:
15
+ * build() → clone repo, install deps, build (via executor or locally)
16
+ * deploy() → spawn a long-running process, track via PID file
17
+ * stop() → SIGTERM → SIGKILL fallback
18
+ * start() → re-spawn the process
19
+ * destroy() → kill + remove working directory
20
+ *
21
+ * buildStrategy support:
22
+ * "server" → clone + build on the target machine (via executor)
23
+ * "local" → clone + build on the API host, then transfer output to target
24
+ *
25
+ * No containers, no images, no Docker dependency at all.
26
+ */
27
+
28
+ import type {
29
+ BuildConfig,
30
+ CommandExecutor,
31
+ DeployConfig,
32
+ BuildResult,
33
+ DeploymentResult,
34
+ LogEntry,
35
+ LogCallback,
36
+ ContainerInfo,
37
+ ResourceUsage,
38
+ } from "../types";
39
+
40
+ import { LocalExecutor } from "../system/executor";
41
+ import { checkToolchainForStack, installTools } from "../toolchain";
42
+ import type { RuntimeAdapter, RuntimeCapability } from "./types";
43
+ import { BuildLogger, runBuildPipeline, sq, type BuildEnvironment } from "./build-pipeline";
44
+ import { runLocalBuild } from "./local-build";
45
+ import { transferLocalDirectory } from "./transfer";
46
+
47
+ // ─── Config ──────────────────────────────────────────────────────────────────
48
+
49
+ export interface BareRuntimeOptions {
50
+ /** Base directory for project working directories (default: /tmp/openship) */
51
+ workDir?: string;
52
+ /** Max time for build commands in ms (default: 10 min) */
53
+ buildTimeout?: number;
54
+ /**
55
+ * Command executor — local or SSH.
56
+ *
57
+ * When provided, ALL commands and file operations are routed through
58
+ * the executor. This is what makes bare runtime work on remote servers.
59
+ * When omitted, a LocalExecutor is created automatically (same machine).
60
+ */
61
+ executor?: CommandExecutor;
62
+ }
63
+
64
+ const DEFAULT_WORK_DIR = "/tmp/openship";
65
+ const DEFAULT_BUILD_TIMEOUT = 10 * 60 * 1000;
66
+
67
+ // ─── Bare runtime ────────────────────────────────────────────────────────────
68
+
69
+ export class BareRuntime implements RuntimeAdapter {
70
+ readonly name = "bare";
71
+ readonly capabilities: ReadonlySet<RuntimeCapability> = new Set<RuntimeCapability>([
72
+ "build",
73
+ "deploy",
74
+ "stop",
75
+ "start",
76
+ "restart",
77
+ "destroy",
78
+ "runtimeLogs",
79
+ "streamLogs",
80
+ "containerIp",
81
+ ]);
82
+
83
+ private readonly workDir: string;
84
+ private readonly buildTimeout: number;
85
+ private executor: CommandExecutor;
86
+ /** True if we created the executor ourselves (must dispose on cleanup) */
87
+ private readonly ownsExecutor: boolean;
88
+ /** Track active builds by sessionId for cancellation */
89
+ private readonly activeBuilds = new Map<string, AbortController>();
90
+
91
+ constructor(opts?: BareRuntimeOptions) {
92
+ this.workDir = opts?.workDir ?? DEFAULT_WORK_DIR;
93
+ this.buildTimeout = opts?.buildTimeout ?? DEFAULT_BUILD_TIMEOUT;
94
+
95
+ if (opts?.executor) {
96
+ this.executor = opts.executor;
97
+ this.ownsExecutor = false;
98
+ } else {
99
+ this.executor = new LocalExecutor();
100
+ this.ownsExecutor = true;
101
+ }
102
+ }
103
+
104
+ supports(cap: RuntimeCapability): boolean {
105
+ return this.capabilities.has(cap);
106
+ }
107
+
108
+ async dispose(): Promise<void> {
109
+ if (this.ownsExecutor) {
110
+ await this.executor.dispose();
111
+ }
112
+ }
113
+
114
+ // ─── Path helpers ────────────────────────────────────────────────────
115
+
116
+ private projectDir(projectId: string): string {
117
+ return `${this.workDir}/${projectId}`;
118
+ }
119
+
120
+ private buildDir(sessionId: string): string {
121
+ return `${this.workDir}/.builds/${sessionId}`;
122
+ }
123
+
124
+ private pidFile(processId: string): string {
125
+ return `${this.workDir}/.pids/${processId}.pid`;
126
+ }
127
+
128
+ private logFile(processId: string): string {
129
+ return `${this.workDir}/.logs/${processId}.log`;
130
+ }
131
+
132
+ private artifactFile(processId: string): string {
133
+ return `${this.workDir}/.artifacts/${processId}.path`;
134
+ }
135
+
136
+ // ─── PID management (via executor) ───────────────────────────────────
137
+
138
+ private async readPid(processId: string): Promise<number | null> {
139
+ try {
140
+ const content = await this.executor.readFile(this.pidFile(processId));
141
+ const pid = parseInt(content.trim(), 10);
142
+ return isNaN(pid) ? null : pid;
143
+ } catch {
144
+ return null;
145
+ }
146
+ }
147
+
148
+ private async writePid(processId: string, pid: number): Promise<void> {
149
+ await this.executor.mkdir(`${this.workDir}/.pids`);
150
+ await this.executor.writeFile(this.pidFile(processId), String(pid));
151
+ }
152
+
153
+ private async removePid(processId: string): Promise<void> {
154
+ await this.executor.rm(this.pidFile(processId));
155
+ }
156
+
157
+ private async readArtifactPath(processId: string): Promise<string | null> {
158
+ try {
159
+ const content = await this.executor.readFile(this.artifactFile(processId));
160
+ const path = content.trim();
161
+ return path || null;
162
+ } catch {
163
+ return null;
164
+ }
165
+ }
166
+
167
+ private async writeArtifactPath(processId: string, artifactPath: string): Promise<void> {
168
+ await this.executor.mkdir(`${this.workDir}/.artifacts`);
169
+ await this.executor.writeFile(this.artifactFile(processId), artifactPath);
170
+ }
171
+
172
+ private async removeArtifactPath(processId: string): Promise<void> {
173
+ await this.executor.rm(this.artifactFile(processId));
174
+ }
175
+
176
+ /** Check if a PID is alive on the target machine. */
177
+ private async isAlive(pid: number): Promise<boolean> {
178
+ try {
179
+ await this.executor.exec(`kill -0 ${pid} 2>/dev/null`);
180
+ return true;
181
+ } catch {
182
+ return false;
183
+ }
184
+ }
185
+
186
+ // ── File transfer ──────────────────────────────────────────────────────
187
+
188
+ /**
189
+ * Transfer files from a local path on the API server into the build/deploy dir.
190
+ *
191
+ * Delegates entirely to the executor — LocalExecutor does cp,
192
+ * SshExecutor does tar+pipe. No branching here.
193
+ */
194
+ async transferFiles(
195
+ localPath: string,
196
+ remotePath: string,
197
+ logger: BuildLogger,
198
+ ): Promise<void> {
199
+ await transferLocalDirectory(
200
+ localPath,
201
+ {
202
+ kind: "executor",
203
+ executor: this.executor,
204
+ path: remotePath,
205
+ },
206
+ logger,
207
+ );
208
+ }
209
+
210
+ // ── Build lifecycle ────────────────────────────────────────────────────
211
+
212
+ async build(config: BuildConfig, logger?: BuildLogger): Promise<BuildResult> {
213
+ const log = logger ?? new BuildLogger();
214
+
215
+ // "local" = build on the API host, then transfer output to the target.
216
+ // "server" (default) = build directly on the target via the executor.
217
+ // When the executor is already local, both modes are equivalent.
218
+ const buildLocally =
219
+ config.buildStrategy === "local" &&
220
+ !(this.executor instanceof LocalExecutor);
221
+
222
+ const abort = new AbortController();
223
+ this.activeBuilds.set(config.sessionId, abort);
224
+
225
+ try {
226
+ if (buildLocally) {
227
+ return await this.buildLocally(config, log, abort);
228
+ }
229
+ return await this.buildOnTarget(config, log, abort);
230
+ } finally {
231
+ this.activeBuilds.delete(config.sessionId);
232
+ }
233
+ }
234
+
235
+ /** Build on the API host, then transfer output to the target server. */
236
+ private async buildLocally(
237
+ config: BuildConfig,
238
+ log: BuildLogger,
239
+ abort: AbortController,
240
+ ): Promise<BuildResult> {
241
+ log.log("Build strategy: local (build on API host, transfer to server)\n");
242
+ const remoteDir = this.projectDir(config.projectId);
243
+
244
+ const result = await runLocalBuild({
245
+ config,
246
+ logger: log,
247
+ abort: abort.signal,
248
+ preflight: async (cfg, plog, localExec) => {
249
+ await this.ensureToolchain(localExec, cfg.stack, plog);
250
+ plog.log("Checking runtime tools on target server...\n");
251
+ await this.ensureToolchain(this.executor, cfg.stack, plog);
252
+ },
253
+ transferOutput: async (buildDir) => {
254
+ await this.executor.rm(remoteDir);
255
+ await this.executor.mkdir(remoteDir);
256
+ await transferLocalDirectory(
257
+ buildDir,
258
+ {
259
+ kind: "executor",
260
+ executor: this.executor,
261
+ path: remoteDir,
262
+ },
263
+ log,
264
+ { excludes: [] },
265
+ );
266
+ },
267
+ });
268
+
269
+ return {
270
+ sessionId: config.sessionId,
271
+ status: result.status,
272
+ imageRef: result.status === "deploying" ? remoteDir : undefined,
273
+ durationMs: result.durationMs,
274
+ };
275
+ }
276
+
277
+ /** Build directly on the target machine via the executor. */
278
+ private async buildOnTarget(
279
+ config: BuildConfig,
280
+ log: BuildLogger,
281
+ abort: AbortController,
282
+ ): Promise<BuildResult> {
283
+ log.log("Build strategy: server (build on target)\n");
284
+ const dir = this.buildDir(config.sessionId);
285
+ await this.executor.rm(dir);
286
+ await this.executor.mkdir(dir);
287
+
288
+ const buildEnv: BuildEnvironment = {
289
+ projectDir: dir,
290
+ exec: async (command, logCb) => {
291
+ if (abort.signal.aborted) throw new Error("Build cancelled");
292
+ const { code } = await this.executor.streamExec(command, logCb);
293
+ if (abort.signal.aborted) throw new Error("Build cancelled");
294
+ if (code !== 0) {
295
+ throw new Error(`Command failed with exit code ${code}`);
296
+ }
297
+ },
298
+ preflight: async (cfg, plog) => {
299
+ if (abort.signal.aborted) throw new Error("Build cancelled");
300
+ await this.ensureToolchain(this.executor, cfg.stack, plog);
301
+ if (cfg.localPath) {
302
+ await this.transferFiles(cfg.localPath, dir, plog);
303
+ }
304
+ },
305
+ };
306
+
307
+ const result = await runBuildPipeline(buildEnv, config, log);
308
+ return {
309
+ sessionId: config.sessionId,
310
+ status: result.status,
311
+ imageRef: result.status === "deploying" ? dir : undefined,
312
+ durationMs: result.durationMs,
313
+ };
314
+ }
315
+
316
+ /**
317
+ * Check that the target executor has the required toolchain for a stack,
318
+ * and install any missing or outdated tools.
319
+ */
320
+ private async ensureToolchain(
321
+ executor: CommandExecutor,
322
+ stack: string,
323
+ plog: BuildLogger,
324
+ ): Promise<void> {
325
+ const toolcheck = await checkToolchainForStack(executor, stack);
326
+ if (toolcheck.ready) return;
327
+
328
+ const requiredTools = toolcheck.tools.filter((tool) => !tool.healthy);
329
+ plog.log(`${requiredTools.map((tool) => tool.message).join("\n")}\n`);
330
+
331
+ const results = await installTools(
332
+ executor,
333
+ requiredTools.map((tool) => tool.name),
334
+ plog.callback,
335
+ Object.fromEntries(
336
+ requiredTools
337
+ .filter((tool) => tool.requiredVersion)
338
+ .map((tool) => [tool.name, tool.requiredVersion!]),
339
+ ),
340
+ );
341
+ const failed = results.filter((r) => !r.success);
342
+ if (failed.length > 0) {
343
+ throw new Error(
344
+ `Failed to install required tools: ${failed.map((f) => `${f.tool} (${f.error})`).join(", ")}`,
345
+ );
346
+ }
347
+ }
348
+
349
+ async cancelBuild(sessionId: string): Promise<void> {
350
+ const abort = this.activeBuilds.get(sessionId);
351
+ if (abort) {
352
+ abort.abort();
353
+ this.activeBuilds.delete(sessionId);
354
+ }
355
+ }
356
+
357
+ async getBuildLogs(sessionId: string): Promise<LogEntry[]> {
358
+ void sessionId;
359
+ return [];
360
+ }
361
+
362
+ // ── Deploy lifecycle ───────────────────────────────────────────────────
363
+
364
+ /**
365
+ * Deploy by spawning a detached background process on the target server.
366
+ *
367
+ * Uses `nohup` + `&` to background the process, redirects output to
368
+ * a log file, and stores the PID. Works identically via local shell
369
+ * or SSH — no node:child_process spawn() needed.
370
+ */
371
+ async deploy(config: DeployConfig, _onLog?: LogCallback): Promise<DeploymentResult> {
372
+ const dir = config.imageRef ?? this.projectDir(config.projectId);
373
+ const logPath = this.logFile(config.deploymentId);
374
+
375
+ // Ensure log directory exists
376
+ await this.executor.mkdir(`${this.workDir}/.logs`);
377
+
378
+ // Build env string
379
+ const envEntries = {
380
+ ...config.envVars,
381
+ PORT: String(config.port),
382
+ NODE_ENV: config.environment === "production" ? "production" : "development",
383
+ };
384
+ const envPrefix = Object.entries(envEntries)
385
+ .map(([k, v]) => `${k}=${sq(v)}`)
386
+ .join(" ");
387
+
388
+ const startCommand = config.startCommand || "npm start";
389
+
390
+ // Spawn detached process in its own process group via setsid.
391
+ // This ensures `kill -- -PGID` can clean up the entire tree.
392
+ const spawnCmd = [
393
+ `cd ${sq(dir)}`,
394
+ `setsid ${envPrefix} nohup ${startCommand} >> ${sq(logPath)} 2>&1 &`,
395
+ `echo $!`,
396
+ ].join(" && ");
397
+
398
+ const pidStr = await this.executor.exec(spawnCmd);
399
+ const pid = parseInt(pidStr.trim(), 10);
400
+
401
+ if (!isNaN(pid) && pid > 0) {
402
+ await this.writePid(config.deploymentId, pid);
403
+ }
404
+ await this.writeArtifactPath(config.deploymentId, dir);
405
+
406
+ return {
407
+ deploymentId: config.deploymentId,
408
+ containerId: config.deploymentId,
409
+ status: "running",
410
+ };
411
+ }
412
+
413
+ async stop(containerId: string): Promise<void> {
414
+ const pid = await this.readPid(containerId);
415
+ if (!pid) return;
416
+
417
+ if (await this.isAlive(pid)) {
418
+ // Kill the entire process group so child processes don't become orphans.
419
+ // PGID equals the leader PID when spawned with setsid.
420
+ await this.executor.exec(`kill -- -${pid} 2>/dev/null || kill ${pid} 2>/dev/null || true`);
421
+
422
+ // Wait up to 10s for graceful shutdown
423
+ const deadline = Date.now() + 10_000;
424
+ while (Date.now() < deadline && (await this.isAlive(pid))) {
425
+ await new Promise((r) => setTimeout(r, 500));
426
+ }
427
+
428
+ // SIGKILL the whole group if still alive
429
+ if (await this.isAlive(pid)) {
430
+ await this.executor.exec(`kill -9 -- -${pid} 2>/dev/null || kill -9 ${pid} 2>/dev/null || true`);
431
+ }
432
+ }
433
+ }
434
+
435
+ async start(containerId: string): Promise<void> {
436
+ const pid = await this.readPid(containerId);
437
+ if (pid && (await this.isAlive(pid))) return;
438
+
439
+ throw new Error(
440
+ "BareRuntime.start() requires a redeploy. Use restart() for running " +
441
+ "processes or trigger a new deployment.",
442
+ );
443
+ }
444
+
445
+ async restart(containerId: string): Promise<void> {
446
+ await this.stop(containerId);
447
+ // The service layer is responsible for calling deploy() again
448
+ }
449
+
450
+ async destroy(containerId: string): Promise<void> {
451
+ if (containerId.includes("/")) {
452
+ await this.executor.rm(containerId);
453
+ return;
454
+ }
455
+
456
+ await this.stop(containerId);
457
+ await this.removePid(containerId);
458
+ await this.executor.rm(this.logFile(containerId));
459
+
460
+ const artifactPath = await this.readArtifactPath(containerId);
461
+ if (artifactPath) {
462
+ await this.executor.rm(artifactPath);
463
+ }
464
+ await this.removeArtifactPath(containerId);
465
+ }
466
+
467
+ // ── Observability ──────────────────────────────────────────────────────
468
+
469
+ async getContainerInfo(containerId: string): Promise<ContainerInfo> {
470
+ const pid = await this.readPid(containerId);
471
+ const running = pid !== null && (await this.isAlive(pid));
472
+
473
+ return {
474
+ containerId,
475
+ status: running ? "running" : "stopped",
476
+ };
477
+ }
478
+
479
+ async getRuntimeLogs(containerId: string, tail?: number): Promise<LogEntry[]> {
480
+ const logPath = this.logFile(containerId);
481
+
482
+ try {
483
+ // Use `tail` command instead of reading entire file — efficient for
484
+ // large log files and works the same over SSH.
485
+ const lines = tail
486
+ ? await this.executor.exec(`tail -n ${tail} ${sq(logPath)}`)
487
+ : await this.executor.readFile(logPath);
488
+
489
+ return lines
490
+ .split("\n")
491
+ .filter(Boolean)
492
+ .map((line) => ({
493
+ timestamp: new Date().toISOString(),
494
+ message: line,
495
+ level: /\b(error|fatal|panic)\b/i.test(line)
496
+ ? ("error" as const)
497
+ : /\bwarn(ing)?\b/i.test(line)
498
+ ? ("warn" as const)
499
+ : ("info" as const),
500
+ }));
501
+ } catch {
502
+ return [];
503
+ }
504
+ }
505
+
506
+ async streamRuntimeLogs(
507
+ containerId: string,
508
+ onLog: LogCallback,
509
+ opts?: { tail?: number },
510
+ ): Promise<() => void> {
511
+ const logPath = this.logFile(containerId);
512
+ const tailN = opts?.tail ?? 100;
513
+
514
+ // Use streamExec with tail -f for real-time streaming
515
+ let stopped = false;
516
+ const promise = this.executor.streamExec(
517
+ `tail -n ${tailN} -f ${sq(logPath)}`,
518
+ (entry) => {
519
+ if (!stopped) onLog(entry);
520
+ },
521
+ );
522
+
523
+ // The stream resolves when tail exits (which normally won't happen
524
+ // until the cleanup function kills it). Swallow errors from kill.
525
+ promise.catch(() => {});
526
+
527
+ return () => {
528
+ stopped = true;
529
+ // Kill the tail process watching this specific log file
530
+ this.executor.exec(`pkill -f ${sq(`tail.*${logPath}`)} 2>/dev/null || true`).catch(() => {});
531
+ };
532
+ }
533
+
534
+ async getUsage(containerId: string): Promise<ResourceUsage> {
535
+ // Try to get basic stats from /proc on Linux
536
+ try {
537
+ const pid = await this.readPid(containerId);
538
+ if (!pid || !(await this.isAlive(pid))) {
539
+ return { cpuPercent: 0, memoryMb: 0, diskMb: 0, networkRxBytes: 0, networkTxBytes: 0 };
540
+ }
541
+
542
+ // RSS from /proc (Linux only — gracefully fails elsewhere)
543
+ const statm = await this.executor.exec(
544
+ `cat /proc/${pid}/statm 2>/dev/null || echo "0 0"`,
545
+ );
546
+ const rssPages = parseInt(statm.split(" ")[1] ?? "0", 10);
547
+ const memoryMb = (rssPages * 4096) / (1024 * 1024);
548
+
549
+ return {
550
+ cpuPercent: 0,
551
+ memoryMb: Math.round(memoryMb * 100) / 100,
552
+ diskMb: 0,
553
+ networkRxBytes: 0,
554
+ networkTxBytes: 0,
555
+ };
556
+ } catch {
557
+ return { cpuPercent: 0, memoryMb: 0, diskMb: 0, networkRxBytes: 0, networkTxBytes: 0 };
558
+ }
559
+ }
560
+
561
+ // ── Network ────────────────────────────────────────────────────────────
562
+
563
+ async getContainerIp(_containerId: string): Promise<string | null> {
564
+ // Bare processes run directly on the target host
565
+ return "127.0.0.1";
566
+ }
567
+ }