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,55 @@
1
+ import type { Context } from "hono";
2
+ import { getUserId } from "../../lib/controller-helpers";
3
+ import { repos } from "@repo/db";
4
+ import { randomBytes } from "node:crypto";
5
+ import { getBuildMode, type BuildMode } from "./settings.service";
6
+
7
+ const VALID_MODES: BuildMode[] = ["auto", "server", "local"];
8
+
9
+ function generateId() {
10
+ return "us_" + randomBytes(12).toString("base64url");
11
+ }
12
+
13
+ /** GET / — return platform settings for the authenticated user */
14
+ export async function get(c: Context) {
15
+ const userId = getUserId(c);
16
+ return c.json({ buildMode: await getBuildMode(userId) });
17
+ }
18
+
19
+ /** PUT / — create or update platform settings */
20
+ export async function upsert(c: Context) {
21
+ const userId = getUserId(c);
22
+ const body = await c.req.json();
23
+
24
+ const buildMode = body.buildMode || "auto";
25
+ if (!VALID_MODES.includes(buildMode)) {
26
+ return c.json({ error: "buildMode must be 'auto', 'server', or 'local'" }, 400);
27
+ }
28
+
29
+ const row = await repos.settings.upsert({
30
+ id: generateId(),
31
+ userId,
32
+ buildMode,
33
+ });
34
+
35
+ return c.json({ buildMode: row.buildMode });
36
+ }
37
+
38
+ /** PATCH /build-mode — update just the build mode preference */
39
+ export async function updateBuildMode(c: Context) {
40
+ const userId = getUserId(c);
41
+ const { buildMode } = await c.req.json();
42
+
43
+ if (!VALID_MODES.includes(buildMode)) {
44
+ return c.json({ error: "buildMode must be 'auto', 'server', or 'local'" }, 400);
45
+ }
46
+
47
+ const existing = await repos.settings.findByUser(userId);
48
+ if (!existing) {
49
+ await repos.settings.upsert({ id: generateId(), userId, buildMode });
50
+ } else {
51
+ await repos.settings.update(userId, { buildMode });
52
+ }
53
+
54
+ return c.json({ buildMode });
55
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Settings routes — mounted at /api/settings in app.ts.
3
+ *
4
+ * All routes require authentication. Manages user platform preferences
5
+ * (build mode, etc.) that sync across devices and to Openship Cloud.
6
+ *
7
+ * System-level settings (SSH creds, server connection) are stored locally
8
+ * in Electron's ConfigStore — they never touch this API.
9
+ */
10
+ import { Hono } from "hono";
11
+ import { authMiddleware } from "../../middleware/auth";
12
+ import * as ctrl from "./settings.controller";
13
+
14
+ export const settingsRoutes = new Hono();
15
+
16
+ settingsRoutes.use("*", authMiddleware);
17
+
18
+ /** GET / — get current user's workspace settings */
19
+ settingsRoutes.get("/", ctrl.get);
20
+
21
+ /** PUT / — create or update workspace settings */
22
+ settingsRoutes.put("/", ctrl.upsert);
23
+
24
+ /** PATCH /build-mode — update only build mode preference */
25
+ settingsRoutes.patch("/build-mode", ctrl.updateBuildMode);
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Settings service — business logic for user platform preferences.
3
+ *
4
+ * Used by:
5
+ * - settings.controller.ts (HTTP layer)
6
+ * - build.service.ts (build strategy resolution)
7
+ */
8
+
9
+ import { repos } from "@repo/db";
10
+ import { STACKS, type StackId, type StackDefinition } from "@repo/core";
11
+ import type { BuildStrategy } from "@repo/adapters";
12
+
13
+ export type BuildMode = "auto" | "server" | "local";
14
+
15
+ /** Get the user's build mode preference (defaults to "auto" if no row exists) */
16
+ export async function getBuildMode(userId: string): Promise<BuildMode> {
17
+ const settings = await repos.settings.findByUser(userId);
18
+ return (settings?.buildMode as BuildMode) ?? "auto";
19
+ }
20
+
21
+ /**
22
+ * Resolve the effective build strategy for a deployment.
23
+ *
24
+ * The per-deploy value sent by the UI is the source of truth.
25
+ * The global user preference is only used as an initial default
26
+ * in the dashboard when preparing a new deploy — it should NOT
27
+ * override an explicit per-deploy choice here.
28
+ *
29
+ * Priority chain:
30
+ * 1. Explicit per-deploy value (always sent by the dashboard)
31
+ * 2. Stack default (STACKS[framework].defaultBuildStrategy)
32
+ * 3. Fallback: "server"
33
+ */
34
+ export async function resolveStrategy(
35
+ _userId: string,
36
+ framework: string | undefined,
37
+ explicit?: BuildStrategy,
38
+ ): Promise<BuildStrategy> {
39
+ // 1. Per-deploy explicit value (source of truth)
40
+ if (explicit) return explicit;
41
+
42
+ // 2. Stack default → 3. Fallback
43
+ const stackId = framework as StackId;
44
+ const stackDef: StackDefinition | undefined =
45
+ stackId && stackId in STACKS
46
+ ? (STACKS[stackId] as StackDefinition)
47
+ : undefined;
48
+ return stackDef?.defaultBuildStrategy ?? "server";
49
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Filesystem controller — browse local directories.
3
+ *
4
+ * Self-hosted only. Provides directory listing with project detection
5
+ * so the UI can let users pick a folder to deploy.
6
+ *
7
+ * Security: This module is only loaded via dynamic import in self-hosted
8
+ * mode. The handler also checks CLOUD_MODE as defense-in-depth.
9
+ */
10
+
11
+ import type { Context } from "hono";
12
+ import { readdir, stat } from "node:fs/promises";
13
+ import { homedir } from "node:os";
14
+ import { join, resolve } from "node:path";
15
+ import { MANIFEST_FILES } from "../../lib/stack-detector";
16
+ import { env } from "../../config";
17
+
18
+ /** package.json + compose files aren't in MANIFEST_FILES (handled separately in stack detector) */
19
+ const PROJECT_MARKERS = new Set([
20
+ "package.json",
21
+ "docker-compose.yml",
22
+ "docker-compose.yaml",
23
+ ...MANIFEST_FILES,
24
+ ]);
25
+
26
+ /** GET /system/browse?path=/some/dir — list child directories */
27
+ export async function browse(c: Context) {
28
+ if (env.CLOUD_MODE) return c.notFound();
29
+
30
+ const raw = c.req.query("path") || homedir();
31
+ const dirPath = resolve(raw);
32
+
33
+ try {
34
+ const st = await stat(dirPath);
35
+ if (!st.isDirectory()) return c.json({ error: "Not a directory" }, 400);
36
+ } catch {
37
+ return c.json({ error: "Directory not found" }, 404);
38
+ }
39
+
40
+ const entries = await readdir(dirPath, { withFileTypes: true });
41
+ const dirs: { name: string; path: string; isProject: boolean }[] = [];
42
+
43
+ await Promise.all(
44
+ entries
45
+ .filter((e) => e.isDirectory() && !e.name.startsWith("."))
46
+ .map(async (e) => {
47
+ const childPath = join(dirPath, e.name);
48
+ let isProject = false;
49
+ try {
50
+ const children = await readdir(childPath);
51
+ isProject = children.some((m) => PROJECT_MARKERS.has(m));
52
+ } catch {
53
+ /* unreadable dir — skip marker check */
54
+ }
55
+ dirs.push({ name: e.name, path: childPath, isProject });
56
+ }),
57
+ );
58
+
59
+ // Projects first, then alphabetical
60
+ dirs.sort((a, b) => {
61
+ if (a.isProject !== b.isProject) return a.isProject ? -1 : 1;
62
+ return a.name.localeCompare(b.name);
63
+ });
64
+
65
+ return c.json({ path: dirPath, directories: dirs });
66
+ }
@@ -0,0 +1 @@
1
+ export { systemRoutes } from "./system.routes";
@@ -0,0 +1,475 @@
1
+ /**
2
+ * Server check & install controller — runs system health checks and
3
+ * component installation against the configured remote server.
4
+ *
5
+ * Uses the shared SSH connection manager so all server interactions go
6
+ * through one cached executor handler with idle TTL + optional
7
+ * persistent mode.
8
+ *
9
+ * Security:
10
+ * - Gated behind localOnly + authMiddleware (no cloud, no unauthenticated)
11
+ * - SSH credentials are read from DB, never from request body
12
+ * - Component names are validated against a known allowlist
13
+ */
14
+
15
+ import type { Context } from "hono";
16
+ import { streamSSE } from "hono/streaming";
17
+ import { env } from "../../config";
18
+ import {
19
+ checkAllComponents,
20
+ checkComponents,
21
+ COMPONENT_INSTALLERS,
22
+ isSshAuthError,
23
+ SYSTEM_COMPONENTS,
24
+ getSystemComponentDefinition,
25
+ } from "@repo/adapters";
26
+ import { formatDuration, systemDebug } from "@/lib/system-debug";
27
+ import { sshManager } from "../../lib/ssh-manager";
28
+ import {
29
+ createSetupSession,
30
+ getSetupSession,
31
+ getActiveSetupSession,
32
+ updateComponentProgress,
33
+ appendSetupLog,
34
+ finishSetupSession,
35
+ subscribeSetupSession,
36
+ } from "./setup-session";
37
+
38
+ function debugSystemRequest(message: string): void {
39
+ systemDebug("system-check", message);
40
+ }
41
+
42
+ // ─── Allowlisted components ──────────────────────────────────────────────────
43
+
44
+ const ALLOWED_COMPONENTS = new Set(
45
+ SYSTEM_COMPONENTS.filter((component) => component.installable).map(
46
+ (component) => component.name,
47
+ ),
48
+ );
49
+
50
+ // ─── Handlers ────────────────────────────────────────────────────────────────
51
+
52
+ /**
53
+ * POST /system/check
54
+ *
55
+ * Run system health checks against the configured remote server.
56
+ * Optionally accepts { components: ["docker", "git"] } to check a subset.
57
+ *
58
+ * Returns: { components: ComponentStatus[], ready: boolean, missing: string[] }
59
+ */
60
+ export async function checkServer(c: Context) {
61
+ if (env.CLOUD_MODE) return c.json({ error: "Not available" }, 404);
62
+
63
+ const startedAt = Date.now();
64
+
65
+ try {
66
+ const body = await c.req.json().catch(() => ({}));
67
+ const requestedComponents = body.components as string[] | undefined;
68
+ debugSystemRequest(
69
+ `check:start ${requestedComponents?.length ? requestedComponents.join(",") : "all"}`,
70
+ );
71
+
72
+ let components;
73
+ if (requestedComponents?.length) {
74
+ // Validate against allowlist
75
+ const valid = requestedComponents.filter((n) => ALLOWED_COMPONENTS.has(n));
76
+ if (valid.length === 0) {
77
+ return c.json({ error: "Invalid component names" }, 400);
78
+ }
79
+ components = await sshManager.withExecutor((executor) =>
80
+ checkComponents(executor, valid),
81
+ );
82
+ } else {
83
+ components = await sshManager.withExecutor((executor) =>
84
+ checkAllComponents(executor),
85
+ );
86
+ }
87
+
88
+ const missing = components
89
+ .filter((c) => !c.healthy)
90
+ .map((c) => c.name);
91
+
92
+ debugSystemRequest(
93
+ `check:done ready=${missing.length === 0} missing=${missing.join(",") || "none"} (${formatDuration(startedAt)})`,
94
+ );
95
+ return c.json({
96
+ components,
97
+ ready: missing.length === 0,
98
+ missing,
99
+ });
100
+ } catch (err) {
101
+ const message =
102
+ err instanceof Error ? err.message : "Failed to connect to server";
103
+ debugSystemRequest(`check:failed ${message} (${formatDuration(startedAt)})`);
104
+ if (
105
+ message === "No server configured" ||
106
+ message === "Invalid SSH auth configuration"
107
+ ) {
108
+ return c.json({ error: "no_server", message }, 400);
109
+ }
110
+ if (isSshAuthError(err)) {
111
+ return c.json({ error: "auth_failed", message }, 400);
112
+ }
113
+ return c.json({ error: "connection_failed", message }, 502);
114
+ }
115
+ }
116
+
117
+ /**
118
+ * POST /system/install
119
+ *
120
+ * Install a specific component on the configured remote server.
121
+ * Body: { component: "docker" | "traefik" | "git" | "node" | "bun", config?: InstallerConfig }
122
+ *
123
+ * Returns: { success: boolean, component: string, version?: string, error?: string }
124
+ */
125
+ export async function installComponent(c: Context) {
126
+ if (env.CLOUD_MODE) return c.json({ error: "Not available" }, 404);
127
+
128
+ const body = await c.req.json().catch(() => ({}));
129
+ const componentName = body.component as string;
130
+
131
+ if (!componentName || !ALLOWED_COMPONENTS.has(componentName)) {
132
+ return c.json({ error: "Invalid or missing component name" }, 400);
133
+ }
134
+
135
+ const installerFn =
136
+ COMPONENT_INSTALLERS[componentName as keyof typeof COMPONENT_INSTALLERS];
137
+ if (!installerFn) {
138
+ return c.json({ error: `No installer for ${componentName}` }, 400);
139
+ }
140
+
141
+ try {
142
+ const logs: string[] = [];
143
+ const installResult = await sshManager.withExecutor((executor) =>
144
+ installerFn(
145
+ executor,
146
+ (log) => logs.push(log.message),
147
+ body.config ?? {},
148
+ ),
149
+ );
150
+
151
+ return c.json({
152
+ ...installResult,
153
+ logs,
154
+ });
155
+ } catch (err) {
156
+ const message =
157
+ err instanceof Error ? err.message : "Installation failed";
158
+ if (
159
+ message === "No server configured" ||
160
+ message === "Invalid SSH auth configuration"
161
+ ) {
162
+ return c.json({ error: "no_server", message }, 400);
163
+ }
164
+ if (isSshAuthError(err)) {
165
+ return c.json({ error: "auth_failed", message }, 400);
166
+ }
167
+ return c.json({ error: "install_failed", message }, 502);
168
+ }
169
+ }
170
+
171
+ /**
172
+ * POST /system/install/stream
173
+ *
174
+ * Install multiple components with real-time SSE log streaming.
175
+ * Body: { components: ["docker", "traefik", ...], config?: InstallerConfig }
176
+ *
177
+ * Returns an SSE stream with events:
178
+ * - progress: component status updates
179
+ * - log: real-time log lines from installers
180
+ * - complete: final result when all installs finish
181
+ * - end: stream terminated
182
+ */
183
+ export async function installStream(c: Context) {
184
+ if (env.CLOUD_MODE) return c.json({ error: "Not available" }, 404);
185
+
186
+ const body = await c.req.json().catch(() => ({}));
187
+ const requestedComponents = body.components as string[] | undefined;
188
+ const config = body.config ?? {};
189
+
190
+ if (!requestedComponents?.length) {
191
+ return c.json({ error: "No components specified" }, 400);
192
+ }
193
+
194
+ // Validate all component names
195
+ const validNames = requestedComponents.filter((n) => ALLOWED_COMPONENTS.has(n));
196
+ if (validNames.length === 0) {
197
+ return c.json({ error: "Invalid component names" }, 400);
198
+ }
199
+
200
+ // Check for already running session
201
+ const existing = getActiveSetupSession();
202
+ if (existing) {
203
+ return c.json({ error: "install_in_progress", sessionId: existing.id }, 409);
204
+ }
205
+
206
+ // Create session
207
+ const componentMeta = validNames.map((name) => {
208
+ const def = getSystemComponentDefinition(name);
209
+ return { name, label: def.label };
210
+ });
211
+ const session = createSetupSession(componentMeta);
212
+
213
+ return streamSSE(c, async (sseStream) => {
214
+ let closed = false;
215
+
216
+ const writer = (event: string, data: string): boolean => {
217
+ if (closed) return false;
218
+ try {
219
+ void sseStream.writeSSE({ event, data });
220
+ return true;
221
+ } catch {
222
+ return false;
223
+ }
224
+ };
225
+
226
+ // Subscribe this connection as the first listener
227
+ const { unsubscribe } = subscribeSetupSession(session.id, writer);
228
+
229
+ // Run installs in background — don't await inline,
230
+ // the SSE stream stays open via the promise below
231
+ const installPromise = (async () => {
232
+ let hasFailure = false;
233
+
234
+ for (const name of validNames) {
235
+ if (closed) break;
236
+
237
+ const installerFn = COMPONENT_INSTALLERS[name as keyof typeof COMPONENT_INSTALLERS];
238
+ if (!installerFn) {
239
+ updateComponentProgress(session.id, name, "failed", `No installer for ${name}`);
240
+ hasFailure = true;
241
+ continue;
242
+ }
243
+
244
+ updateComponentProgress(session.id, name, "installing");
245
+
246
+ try {
247
+ const result = await sshManager.withExecutor((executor) =>
248
+ installerFn(
249
+ executor,
250
+ (log) => appendSetupLog(session.id, name, log.message, log.level),
251
+ config,
252
+ ),
253
+ );
254
+
255
+ if (result.success) {
256
+ appendSetupLog(session.id, name, `${name} installed successfully${result.version ? ` (${result.version})` : ""}`);
257
+ updateComponentProgress(session.id, name, "installed");
258
+ } else {
259
+ appendSetupLog(session.id, name, result.error ?? `${name} installation failed`, "error");
260
+ updateComponentProgress(session.id, name, "failed", result.error);
261
+ hasFailure = true;
262
+ }
263
+ } catch (err) {
264
+ const msg = err instanceof Error ? err.message : String(err);
265
+ appendSetupLog(session.id, name, msg, "error");
266
+ updateComponentProgress(session.id, name, "failed", msg);
267
+ hasFailure = true;
268
+ }
269
+ }
270
+
271
+ finishSetupSession(session.id, hasFailure ? "failed" : "completed");
272
+ })();
273
+
274
+ // Keep the SSE connection open until install finishes or client disconnects
275
+ await new Promise<void>((resolve) => {
276
+ installPromise.then(() => {
277
+ // Give a brief delay for final events to flush
278
+ setTimeout(() => {
279
+ closed = true;
280
+ resolve();
281
+ }, 500);
282
+ });
283
+
284
+ sseStream.onAbort(() => {
285
+ closed = true;
286
+ unsubscribe();
287
+ resolve();
288
+ });
289
+ });
290
+ });
291
+ }
292
+
293
+ /**
294
+ * GET /system/install/session
295
+ *
296
+ * Get the active setup session or a specific session by ID.
297
+ * Query: ?id=setup_xxx (optional — returns active session if omitted)
298
+ *
299
+ * Returns: session state or 404
300
+ */
301
+ export async function getInstallSession(c: Context) {
302
+ if (env.CLOUD_MODE) return c.json({ error: "Not available" }, 404);
303
+
304
+ const sessionId = c.req.query("id");
305
+
306
+ const session = sessionId
307
+ ? getSetupSession(sessionId)
308
+ : getActiveSetupSession();
309
+
310
+ if (!session) {
311
+ return c.json({ active: false }, 200);
312
+ }
313
+
314
+ return c.json({
315
+ active: true,
316
+ sessionId: session.id,
317
+ status: session.status,
318
+ components: session.components,
319
+ startedAt: session.startedAt,
320
+ finishedAt: session.finishedAt,
321
+ });
322
+ }
323
+
324
+ /**
325
+ * GET /system/install/stream
326
+ *
327
+ * Attach to an existing setup session's SSE stream (for page reloads).
328
+ * Query: ?id=setup_xxx
329
+ */
330
+ export async function attachInstallStream(c: Context) {
331
+ if (env.CLOUD_MODE) return c.json({ error: "Not available" }, 404);
332
+
333
+ const sessionId = c.req.query("id");
334
+ const session = sessionId
335
+ ? getSetupSession(sessionId)
336
+ : getActiveSetupSession();
337
+
338
+ if (!session) {
339
+ return c.json({ error: "No active session" }, 404);
340
+ }
341
+
342
+ return streamSSE(c, async (sseStream) => {
343
+ let closed = false;
344
+
345
+ const writer = (event: string, data: string): boolean => {
346
+ if (closed) return false;
347
+ try {
348
+ void sseStream.writeSSE({ event, data });
349
+ return true;
350
+ } catch {
351
+ return false;
352
+ }
353
+ };
354
+
355
+ const { success, unsubscribe } = subscribeSetupSession(session.id, writer);
356
+
357
+ if (!success) {
358
+ await sseStream.writeSSE({ event: "error", data: JSON.stringify({ error: "Session not found" }) });
359
+ return;
360
+ }
361
+
362
+ // If session is already done, subscribe will have replayed + sent end; just close
363
+ if (session.status !== "running") {
364
+ return;
365
+ }
366
+
367
+ await new Promise<void>((resolve) => {
368
+ const checkInterval = setInterval(() => {
369
+ if (closed) {
370
+ clearInterval(checkInterval);
371
+ resolve();
372
+ }
373
+ }, 1000);
374
+
375
+ sseStream.onAbort(() => {
376
+ closed = true;
377
+ unsubscribe();
378
+ clearInterval(checkInterval);
379
+ resolve();
380
+ });
381
+ });
382
+ });
383
+ }
384
+
385
+ // ─── Monitoring ──────────────────────────────────────────────────────────────
386
+
387
+ /**
388
+ * Shell one-liner that gathers CPU, memory, disk, uptime, and load average.
389
+ * Outputs a single JSON line. Designed for Linux servers.
390
+ *
391
+ * Fields:
392
+ * cpu - usage % (100 - idle from /proc/stat snapshot)
393
+ * memTotal - total RAM bytes
394
+ * memUsed - used RAM bytes (total - available)
395
+ * memAvail - available RAM bytes
396
+ * diskTotal - root partition total bytes
397
+ * diskUsed - root partition used bytes
398
+ * diskAvail - root partition available bytes
399
+ * uptime - seconds since boot
400
+ * load1 - 1-min load average
401
+ * load5 - 5-min load average
402
+ * load15 - 15-min load average
403
+ */
404
+ const STATS_COMMAND = [
405
+ // CPU: sample /proc/stat twice (200ms apart) for accurate usage
406
+ 'read cpu0_u cpu0_n cpu0_s cpu0_i cpu0_rest <<< $(head -1 /proc/stat | awk \'{print $2,$3,$4,$5}\');',
407
+ 'sleep 0.2;',
408
+ 'read cpu1_u cpu1_n cpu1_s cpu1_i cpu1_rest <<< $(head -1 /proc/stat | awk \'{print $2,$3,$4,$5}\');',
409
+ 'cpu_d=$(( (cpu1_u-cpu0_u)+(cpu1_n-cpu0_n)+(cpu1_s-cpu0_s)+(cpu1_i-cpu0_i) ));',
410
+ 'cpu_idle=$(( cpu1_i - cpu0_i ));',
411
+ '[ "$cpu_d" -gt 0 ] && cpu_pct=$(( 100 - (cpu_idle * 100 / cpu_d) )) || cpu_pct=0;',
412
+ // Memory
413
+ 'read mem_t mem_a <<< $(awk \'/MemTotal/{t=$2} /MemAvailable/{a=$2} END{print t*1024, a*1024}\' /proc/meminfo);',
414
+ 'mem_u=$((mem_t - mem_a));',
415
+ // Disk
416
+ 'read disk_t disk_u disk_a <<< $(df -B1 / | awk \'NR==2{print $2,$3,$4}\');',
417
+ // Uptime + load
418
+ 'read up_s _ <<< $(cat /proc/uptime);',
419
+ 'read l1 l5 l15 _ _ <<< $(cat /proc/loadavg);',
420
+ // Output JSON
421
+ 'printf \'{"cpu":%d,"memTotal":%s,"memUsed":%s,"memAvail":%s,"diskTotal":%s,"diskUsed":%s,"diskAvail":%s,"uptime":"%s","load1":"%s","load5":"%s","load15":"%s"}\\n\' "$cpu_pct" "$mem_t" "$mem_u" "$mem_a" "$disk_t" "$disk_u" "$disk_a" "$up_s" "$l1" "$l5" "$l15"',
422
+ ].join(" ");
423
+
424
+ /**
425
+ * GET /system/monitor/stream
426
+ *
427
+ * SSE stream that emits system stats every few seconds.
428
+ * Runs a lightweight stats command via SSH on an interval.
429
+ * Stops when the client disconnects.
430
+ */
431
+ export async function monitorStream(c: Context) {
432
+ if (env.CLOUD_MODE) return c.json({ error: "Not available" }, 404);
433
+
434
+ const POLL_INTERVAL = 3_000;
435
+
436
+ return streamSSE(c, async (sseStream) => {
437
+ let closed = false;
438
+ let pollTimer: ReturnType<typeof setInterval> | null = null;
439
+
440
+ const poll = async () => {
441
+ if (closed) return;
442
+ try {
443
+ const raw = await sshManager.withExecutor((executor) =>
444
+ executor.exec(STATS_COMMAND, { timeout: 5_000 }),
445
+ );
446
+ if (closed) return;
447
+ // Validate it's parseable JSON before sending
448
+ JSON.parse(raw);
449
+ await sseStream.writeSSE({ event: "stats", data: raw });
450
+ } catch (err) {
451
+ if (closed) return;
452
+ const msg = err instanceof Error ? err.message : String(err);
453
+ await sseStream.writeSSE({
454
+ event: "error",
455
+ data: JSON.stringify({ error: msg }),
456
+ });
457
+ }
458
+ };
459
+
460
+ // First poll immediately
461
+ await poll();
462
+
463
+ // Then poll on interval
464
+ pollTimer = setInterval(() => void poll(), POLL_INTERVAL);
465
+
466
+ // Wait until client disconnects
467
+ await new Promise<void>((resolve) => {
468
+ sseStream.onAbort(() => {
469
+ closed = true;
470
+ if (pollTimer) clearInterval(pollTimer);
471
+ resolve();
472
+ });
473
+ });
474
+ });
475
+ }