diff --git a/.env.example b/.env.example index 56a4e0016..37e8ad0db 100644 --- a/.env.example +++ b/.env.example @@ -59,3 +59,6 @@ VITE_BACKEND_API_URL=http://localhost:3170/v1 # Terms Of Service And Privacy Policy Links (Optional) VITE_APP_TOS_LINK=https://docs.hoppscotch.io/support/terms VITE_APP_PRIVACY_POLICY_LINK=https://docs.hoppscotch.io/support/privacy + +# Set to `true` for subpath based access +ENABLE_SUBPATH_BASED_ACCESS=false diff --git a/aio-multiport-setup.Caddyfile b/aio-multiport-setup.Caddyfile new file mode 100644 index 000000000..70ce1c1f2 --- /dev/null +++ b/aio-multiport-setup.Caddyfile @@ -0,0 +1,19 @@ +:3000 { + try_files {path} / + root * /site/selfhost-web + file_server +} + +:3100 { + try_files {path} / + root * /site/sh-admin-multiport-setup + file_server +} + +:3170 { + reverse_proxy localhost:8080 +} + +:80 { + respond 404 +} diff --git a/aio-subpath-access.Caddyfile b/aio-subpath-access.Caddyfile new file mode 100644 index 000000000..46a8436e4 --- /dev/null +++ b/aio-subpath-access.Caddyfile @@ -0,0 +1,37 @@ +:3000 { + respond 404 +} + +:3100 { + respond 404 +} + +:3170 { + reverse_proxy localhost:8080 +} + +:80 { + # Serve the `selfhost-web` SPA by default + root * /site/selfhost-web + file_server + + handle_path /admin* { + root * /site/sh-admin-subpath-access + file_server + + # Ensures any non-existent file in the server is routed to the SPA + try_files {path} / + } + + # Handle requests under `/backend*` path + handle_path /backend* { + reverse_proxy localhost:8080 + } + + # Catch-all route for unknown paths, serves `selfhost-web` SPA + handle { + root * /site/selfhost-web + file_server + try_files {path} / + } +} diff --git a/aio.Caddyfile b/aio.Caddyfile deleted file mode 100644 index 65919f6e8..000000000 --- a/aio.Caddyfile +++ /dev/null @@ -1,11 +0,0 @@ -:3000 { - try_files {path} / - root * /site/selfhost-web - file_server -} - -:3100 { - try_files {path} / - root * /site/sh-admin - file_server -} diff --git a/aio_run.mjs b/aio_run.mjs index 61ac48c97..59c2684be 100644 --- a/aio_run.mjs +++ b/aio_run.mjs @@ -49,7 +49,8 @@ execSync(`npx import-meta-env -x build.env -e build.env -p "/site/**/*"`) fs.rmSync("build.env") -const caddyProcess = runChildProcessWithPrefix("caddy", ["run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"], "App/Admin Dashboard Caddy") +const caddyFileName = process.env.ENABLE_SUBPATH_BASED_ACCESS === 'true' ? 'aio-subpath-access.Caddyfile' : 'aio-multiport-setup.Caddyfile' +const caddyProcess = runChildProcessWithPrefix("caddy", ["run", "--config", `/etc/caddy/${caddyFileName}`, "--adapter", "caddyfile"], "App/Admin Dashboard Caddy") const backendProcess = runChildProcessWithPrefix("pnpm", ["run", "start:prod"], "Backend Server") caddyProcess.on("exit", (code) => { diff --git a/docker-compose.yml b/docker-compose.yml index c9d5d6684..37af3d1fe 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,7 +17,7 @@ services: environment: # Edit the below line to match your PostgresDB URL if you have an outside DB (make sure to update the .env file as well) - DATABASE_URL=postgresql://postgres:testpass@hoppscotch-db:5432/hoppscotch?connect_timeout=300 - - PORT=3170 + - PORT=8080 volumes: # Uncomment the line below when modifying code. Only applicable when using the "dev" target. # - ./packages/hoppscotch-backend/:/usr/src/app @@ -26,6 +26,7 @@ services: hoppscotch-db: condition: service_healthy ports: + - "3180:80" - "3170:3170" # The main hoppscotch app. This will be hosted at port 3000 @@ -42,7 +43,8 @@ services: depends_on: - hoppscotch-backend ports: - - "3000:8080" + - "3080:80" + - "3000:3000" # The Self Host dashboard for managing the app. This will be hosted at port 3100 # NOTE: To do TLS or play around with how the app is hosted, you can look into the Caddyfile for @@ -58,7 +60,8 @@ services: depends_on: - hoppscotch-backend ports: - - "3100:8080" + - "3280:80" + - "3100:3100" # The service that spins up all 3 services at once in one container hoppscotch-aio: @@ -76,6 +79,7 @@ services: - "3000:3000" - "3100:3100" - "3170:3170" + - "3080:80" # The preset DB service, you can delete/comment the below lines if # you are using an external postgres instance diff --git a/packages/hoppscotch-backend/backend.Caddyfile b/packages/hoppscotch-backend/backend.Caddyfile new file mode 100644 index 000000000..d5bd13244 --- /dev/null +++ b/packages/hoppscotch-backend/backend.Caddyfile @@ -0,0 +1,3 @@ +:80 :3170 { + reverse_proxy localhost:8080 +} diff --git a/packages/hoppscotch-backend/prod_run.mjs b/packages/hoppscotch-backend/prod_run.mjs new file mode 100644 index 000000000..2d4a8c1d9 --- /dev/null +++ b/packages/hoppscotch-backend/prod_run.mjs @@ -0,0 +1,66 @@ +#!/usr/local/bin/node +// @ts-check + +import { spawn } from 'child_process'; +import process from 'process'; + +function runChildProcessWithPrefix(command, args, prefix) { + const childProcess = spawn(command, args); + + childProcess.stdout.on('data', (data) => { + const output = data.toString().trim().split('\n'); + output.forEach((line) => { + console.log(`${prefix} | ${line}`); + }); + }); + + childProcess.stderr.on('data', (data) => { + const error = data.toString().trim().split('\n'); + error.forEach((line) => { + console.error(`${prefix} | ${line}`); + }); + }); + + childProcess.on('close', (code) => { + console.log(`${prefix} Child process exited with code ${code}`); + }); + + childProcess.on('error', (stuff) => { + console.error('error'); + console.error(stuff); + }); + + return childProcess; +} + +const caddyProcess = runChildProcessWithPrefix( + 'caddy', + ['run', '--config', '/etc/caddy/backend.Caddyfile', '--adapter', 'caddyfile'], + 'App/Admin Dashboard Caddy', +); +const backendProcess = runChildProcessWithPrefix( + 'pnpm', + ['run', 'start:prod'], + 'Backend Server', +); + +caddyProcess.on('exit', (code) => { + console.log(`Exiting process because Caddy Server exited with code ${code}`); + process.exit(code); +}); + +backendProcess.on('exit', (code) => { + console.log( + `Exiting process because Backend Server exited with code ${code}`, + ); + process.exit(code); +}); + +process.on('SIGINT', () => { + console.log('SIGINT received, exiting...'); + + caddyProcess.kill('SIGINT'); + backendProcess.kill('SIGINT'); + + process.exit(0); +}); diff --git a/packages/hoppscotch-selfhost-web/Caddyfile b/packages/hoppscotch-selfhost-web/Caddyfile index 4d9382b53..dc930d195 100644 --- a/packages/hoppscotch-selfhost-web/Caddyfile +++ b/packages/hoppscotch-selfhost-web/Caddyfile @@ -1,5 +1,5 @@ :8080 { - try_files {path} / - root * /site - file_server + try_files {path} / + root * /site + file_server } diff --git a/packages/hoppscotch-selfhost-web/selfhost-web.Caddyfile b/packages/hoppscotch-selfhost-web/selfhost-web.Caddyfile new file mode 100644 index 000000000..1474530c1 --- /dev/null +++ b/packages/hoppscotch-selfhost-web/selfhost-web.Caddyfile @@ -0,0 +1,5 @@ +:80 :3000 { + try_files {path} / + root * /site + file_server +} diff --git a/packages/hoppscotch-selfhost-web/vite.config.ts b/packages/hoppscotch-selfhost-web/vite.config.ts index 5995fa316..d32f419ae 100644 --- a/packages/hoppscotch-selfhost-web/vite.config.ts +++ b/packages/hoppscotch-selfhost-web/vite.config.ts @@ -223,6 +223,8 @@ export default defineConfig({ /twitter/, /github/, /announcements/, + /admin/, + /backend/, ], }, }), diff --git a/packages/hoppscotch-sh-admin/Caddyfile b/packages/hoppscotch-sh-admin/Caddyfile index 4d9382b53..dc930d195 100644 --- a/packages/hoppscotch-sh-admin/Caddyfile +++ b/packages/hoppscotch-sh-admin/Caddyfile @@ -1,5 +1,5 @@ :8080 { - try_files {path} / - root * /site - file_server + try_files {path} / + root * /site + file_server } diff --git a/packages/hoppscotch-sh-admin/prod_run.mjs b/packages/hoppscotch-sh-admin/prod_run.mjs index 9e386cb98..d571aff55 100755 --- a/packages/hoppscotch-sh-admin/prod_run.mjs +++ b/packages/hoppscotch-sh-admin/prod_run.mjs @@ -1,18 +1,70 @@ #!/usr/local/bin/node -import { execSync } from "child_process" -import fs from "fs" +import { execSync, spawn } from 'child_process'; +import fs from 'fs'; +import process from 'process'; + +function runChildProcessWithPrefix(command, args, prefix) { + const childProcess = spawn(command, args); + + childProcess.stdout.on('data', (data) => { + const output = data.toString().trim().split('\n'); + output.forEach((line) => { + console.log(`${prefix} | ${line}`); + }); + }); + + childProcess.stderr.on('data', (data) => { + const error = data.toString().trim().split('\n'); + error.forEach((line) => { + console.error(`${prefix} | ${line}`); + }); + }); + + childProcess.on('close', (code) => { + console.log(`${prefix} Child process exited with code ${code}`); + }); + + childProcess.on('error', (stuff) => { + console.log('error'); + console.log(stuff); + }); + + return childProcess; +} const envFileContent = Object.entries(process.env) - .filter(([env]) => env.startsWith("VITE_")) - .map(([env, val]) => `${env}=${ - (val.startsWith("\"") && val.endsWith("\"")) - ? val - : `"${val}"` - }`) - .join("\n") + .filter(([env]) => env.startsWith('VITE_')) + .map( + ([env, val]) => + `${env}=${val.startsWith('"') && val.endsWith('"') ? val : `"${val}"`}` + ) + .join('\n'); -fs.writeFileSync("build.env", envFileContent) +fs.writeFileSync('build.env', envFileContent); -execSync(`npx import-meta-env -x build.env -e build.env -p "/site/**/*"`) +execSync(`npx import-meta-env -x build.env -e build.env -p "/site/**/*"`); -fs.rmSync("build.env") +fs.rmSync('build.env'); + +const caddyFileName = + process.env.ENABLE_SUBPATH_BASED_ACCESS === 'true' + ? 'sh-admin-subpath-access.Caddyfile' + : 'sh-admin-multiport-setup.Caddyfile'; +const caddyProcess = runChildProcessWithPrefix( + 'caddy', + ['run', '--config', `/etc/caddy/${caddyFileName}`, '--adapter', 'caddyfile'], + 'App/Admin Dashboard Caddy' +); + +caddyProcess.on('exit', (code) => { + console.log(`Exiting process because Caddy Server exited with code ${code}`); + process.exit(code); +}); + +process.on('SIGINT', () => { + console.log('SIGINT received, exiting...'); + + caddyProcess.kill('SIGINT'); + + process.exit(0); +}); diff --git a/packages/hoppscotch-sh-admin/sh-admin-multiport-setup.Caddyfile b/packages/hoppscotch-sh-admin/sh-admin-multiport-setup.Caddyfile new file mode 100644 index 000000000..3ff07c6b3 --- /dev/null +++ b/packages/hoppscotch-sh-admin/sh-admin-multiport-setup.Caddyfile @@ -0,0 +1,5 @@ +:80 :3100 { + try_files {path} / + root * /site/sh-admin-multiport-setup + file_server +} diff --git a/packages/hoppscotch-sh-admin/sh-admin-subpath-access.Caddyfile b/packages/hoppscotch-sh-admin/sh-admin-subpath-access.Caddyfile new file mode 100644 index 000000000..ac6dd488b --- /dev/null +++ b/packages/hoppscotch-sh-admin/sh-admin-subpath-access.Caddyfile @@ -0,0 +1,11 @@ +:80 :3100 { + handle_path /admin* { + root * /site/sh-admin-subpath-access + file_server + try_files {path} / + } + + handle { + respond 404 + } +} diff --git a/packages/hoppscotch-sh-admin/src/modules/router.ts b/packages/hoppscotch-sh-admin/src/modules/router.ts index 317f30000..f7ab4b735 100644 --- a/packages/hoppscotch-sh-admin/src/modules/router.ts +++ b/packages/hoppscotch-sh-admin/src/modules/router.ts @@ -42,7 +42,7 @@ export const isLoadingInitialRoute = readonly(_isLoadingInitialRoute); export default { onVueAppInit(app) { const router = createRouter({ - history: createWebHistory(), + history: createWebHistory(import.meta.env.BASE_URL), routes, }); diff --git a/packages/hoppscotch-sh-admin/src/pages/_.vue b/packages/hoppscotch-sh-admin/src/pages/_.vue index 7a4ce25e2..43d5e2219 100644 --- a/packages/hoppscotch-sh-admin/src/pages/_.vue +++ b/packages/hoppscotch-sh-admin/src/pages/_.vue @@ -6,7 +6,7 @@ :class="{ 'min-h-screen': statusCode !== 404 }" > props.error?.statusCode ?? 404); const message = computed( diff --git a/prod.Dockerfile b/prod.Dockerfile index 28cbab316..0080d0bb9 100644 --- a/prod.Dockerfile +++ b/prod.Dockerfile @@ -13,16 +13,19 @@ RUN pnpm install -f --offline FROM base_builder as backend +RUN apk add caddy WORKDIR /usr/src/app/packages/hoppscotch-backend RUN pnpm exec prisma generate RUN pnpm run build +COPY --from=base_builder /usr/src/app/packages/hoppscotch-backend/backend.Caddyfile /etc/caddy/backend.Caddyfile # Remove the env file to avoid backend copying it in and using it RUN rm "../../.env" ENV PRODUCTION="true" -ENV PORT=3170 +ENV PORT=8080 ENV APP_PORT=${PORT} ENV DB_URL=${DATABASE_URL} -CMD ["pnpm", "run", "start:prod"] +CMD ["node", "/usr/src/app/packages/hoppscotch-backend/prod_run.mjs"] +EXPOSE 80 EXPOSE 3170 FROM base_builder as fe_builder @@ -31,34 +34,42 @@ RUN pnpm run generate FROM caddy:2-alpine as app WORKDIR /site -COPY --from=fe_builder /usr/src/app/packages/hoppscotch-sh-admin/prod_run.mjs /usr -COPY --from=fe_builder /usr/src/app/packages/hoppscotch-selfhost-web/Caddyfile /etc/caddy/Caddyfile +COPY --from=fe_builder /usr/src/app/packages/hoppscotch-selfhost-web/prod_run.mjs /usr +COPY --from=fe_builder /usr/src/app/packages/hoppscotch-selfhost-web/selfhost-web.Caddyfile /etc/caddy/selfhost-web.Caddyfile COPY --from=fe_builder /usr/src/app/packages/hoppscotch-selfhost-web/dist/ . RUN apk add nodejs npm RUN npm install -g @import-meta-env/cli -EXPOSE 8080 -CMD ["/bin/sh", "-c", "node /usr/prod_run.mjs && caddy run --config /etc/caddy/Caddyfile --adapter caddyfile"] +EXPOSE 80 +EXPOSE 3000 +CMD ["/bin/sh", "-c", "node /usr/prod_run.mjs && caddy run --config /etc/caddy/selfhost-web.Caddyfile --adapter caddyfile"] FROM base_builder as sh_admin_builder WORKDIR /usr/src/app/packages/hoppscotch-sh-admin -RUN pnpm run build +# Generate two builds for `sh-admin`, one based on subpath-access and the regular build +RUN pnpm run build --outDir dist-multiport-setup +RUN pnpm run build --outDir dist-subpath-access --base /admin/ FROM caddy:2-alpine as sh_admin WORKDIR /site COPY --from=sh_admin_builder /usr/src/app/packages/hoppscotch-sh-admin/prod_run.mjs /usr -COPY --from=sh_admin_builder /usr/src/app/packages/hoppscotch-sh-admin/Caddyfile /etc/caddy/Caddyfile -COPY --from=sh_admin_builder /usr/src/app/packages/hoppscotch-sh-admin/dist/ . +COPY --from=sh_admin_builder /usr/src/app/packages/hoppscotch-sh-admin/sh-admin-multiport-setup.Caddyfile /etc/caddy/sh-admin-multiport-setup.Caddyfile +COPY --from=sh_admin_builder /usr/src/app/packages/hoppscotch-sh-admin/sh-admin-subpath-access.Caddyfile /etc/caddy/sh-admin-subpath-access.Caddyfile +COPY --from=sh_admin_builder /usr/src/app/packages/hoppscotch-sh-admin/dist-multiport-setup /site/sh-admin-multiport-setup +COPY --from=sh_admin_builder /usr/src/app/packages/hoppscotch-sh-admin/dist-subpath-access /site/sh-admin-subpath-access RUN apk add nodejs npm RUN npm install -g @import-meta-env/cli -EXPOSE 8080 -CMD ["/bin/sh", "-c", "node /usr/prod_run.mjs && caddy run --config /etc/caddy/Caddyfile --adapter caddyfile"] +EXPOSE 80 +EXPOSE 3100 +CMD ["node","/usr/prod_run.mjs"] FROM backend as aio RUN apk add caddy tini RUN npm install -g @import-meta-env/cli COPY --from=fe_builder /usr/src/app/packages/hoppscotch-selfhost-web/dist /site/selfhost-web -COPY --from=sh_admin_builder /usr/src/app/packages/hoppscotch-sh-admin/dist /site/sh-admin -COPY aio.Caddyfile /etc/caddy/Caddyfile +COPY --from=sh_admin_builder /usr/src/app/packages/hoppscotch-sh-admin/dist-multiport-setup /site/sh-admin-multiport-setup +COPY --from=sh_admin_builder /usr/src/app/packages/hoppscotch-sh-admin/dist-subpath-access /site/sh-admin-subpath-access +COPY aio-multiport-setup.Caddyfile /etc/caddy/aio-multiport-setup.Caddyfile +COPY aio-subpath-access.Caddyfile /etc/caddy/aio-subpath-access.Caddyfile ENTRYPOINT [ "tini", "--" ] RUN apk --no-cache add curl COPY --chmod=755 healthcheck.sh . @@ -67,3 +78,7 @@ CMD ["node", "/usr/src/app/aio_run.mjs"] EXPOSE 3170 EXPOSE 3000 EXPOSE 3100 +EXPOSE 80 + + +