Skip to content

Add support for configurable domain sub-paths #74

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Added `DOMAIN_SUB_PATH` environment variable to allow overriding the default domain subpath. ([#74](https://github.com/sourcebot-dev/sourcebot/pull/74))

## [2.4.3] - 2024-11-18

### Changed
Expand Down
7 changes: 7 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ ENV NEXT_TELEMETRY_DISABLED=1
# @see: https://phase.dev/blog/nextjs-public-runtime-variables/
ARG NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED=BAKED_NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED
ARG NEXT_PUBLIC_SOURCEBOT_VERSION=BAKED_NEXT_PUBLIC_SOURCEBOT_VERSION
# @note: leading "/" is required for the basePath property. @see: https://nextjs.org/docs/app/api-reference/next-config-js/basePath
ARG NEXT_PUBLIC_DOMAIN_SUB_PATH=/BAKED_NEXT_PUBLIC_DOMAIN_SUB_PATH
RUN yarn workspace @sourcebot/web build

# ------ Build Backend ------
Expand Down Expand Up @@ -54,6 +56,11 @@ RUN echo "Sourcebot Version: $SOURCEBOT_VERSION"
# Valid values are: debug, info, warn, error
ENV SOURCEBOT_LOG_LEVEL=info

# Configures the sub-path of the domain to serve Sourcebot from.
# For example, if DOMAIN_SUB_PATH is set to "/sb", Sourcebot
# will serve from http(s)://example.com/sb
ENV DOMAIN_SUB_PATH=/

# @note: This is also set in .env
ENV POSTHOG_KEY=phc_VFn4CkEGHRdlVyOOw8mfkoj1DKVoG6y1007EClvzAnS
ENV NEXT_PUBLIC_POSTHOG_KEY=$POSTHOG_KEY
Expand Down
74 changes: 60 additions & 14 deletions entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -70,23 +70,69 @@ fi

echo -e "\e[34m[Info] Using config file at: '$CONFIG_PATH'.\e[0m"

# Update nextjs public env variables w/o requiring a rebuild.
# Update NextJs public env variables w/o requiring a rebuild.
# @see: https://phase.dev/blog/nextjs-public-runtime-variables/
{
# Infer NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED if it is not set
if [ -z "$NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED" ] && [ ! -z "$SOURCEBOT_TELEMETRY_DISABLED" ]; then
export NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED="$SOURCEBOT_TELEMETRY_DISABLED"
fi

# Infer NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED if it is not set
if [ -z "$NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED" ] && [ ! -z "$SOURCEBOT_TELEMETRY_DISABLED" ]; then
export NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED="$SOURCEBOT_TELEMETRY_DISABLED"
fi
# Infer NEXT_PUBLIC_SOURCEBOT_VERSION if it is not set
if [ -z "$NEXT_PUBLIC_SOURCEBOT_VERSION" ] && [ ! -z "$SOURCEBOT_VERSION" ]; then
export NEXT_PUBLIC_SOURCEBOT_VERSION="$SOURCEBOT_VERSION"
fi

# Infer NEXT_PUBLIC_SOURCEBOT_VERSION if it is not set
if [ -z "$NEXT_PUBLIC_SOURCEBOT_VERSION" ] && [ ! -z "$SOURCEBOT_VERSION" ]; then
export NEXT_PUBLIC_SOURCEBOT_VERSION="$SOURCEBOT_VERSION"
fi
# Iterate over all .js files in .next & public, making substitutions for the `BAKED_` sentinal values
# with their actual desired runtime value.
find /app/packages/web/public /app/packages/web/.next -type f -name "*.js" |
while read file; do
sed -i "s|BAKED_NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED|${NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_SOURCEBOT_VERSION|${NEXT_PUBLIC_SOURCEBOT_VERSION}|g" "$file"
done
}


# Update specifically NEXT_PUBLIC_DOMAIN_SUB_PATH w/o requiring a rebuild.
# Ultimately, the DOMAIN_SUB_PATH sets the `basePath` param in the next.config.mjs.
# Similar to above, we pass in a `BAKED_` sentinal value into next.config.mjs at build
# time. Unlike above, the `basePath` configuration is set in files other than just javascript
# code (e.g., manifest files, css files, etc.), so this section has subtle differences.
#
# @see: https://nextjs.org/docs/app/api-reference/next-config-js/basePath
# @see: https://phase.dev/blog/nextjs-public-runtime-variables/
{
if [ ! -z "$DOMAIN_SUB_PATH" ]; then
# If the sub-path is "/", this creates problems with certain replacements. For example:
# /BAKED_NEXT_PUBLIC_DOMAIN_SUB_PATH/_next/image -> //_next/image (notice the double slash...)
# To get around this, we default to an empty sub-path, which is the default when no sub-path is defined.
if [ "$DOMAIN_SUB_PATH" = "/" ]; then
DOMAIN_SUB_PATH=""

# Otherwise, we need to ensure that the sub-path starts with a slash, since this is a requirement
# for the basePath property. For example, assume DOMAIN_SUB_PATH=/bot, then:
# /BAKED_NEXT_PUBLIC_DOMAIN_SUB_PATH/_next/image -> /bot/_next/image
elif [[ ! "$DOMAIN_SUB_PATH" =~ ^/ ]]; then
DOMAIN_SUB_PATH="/$DOMAIN_SUB_PATH"
fi
fi

if [ ! -z "$DOMAIN_SUB_PATH" ]; then
echo -e "\e[34m[Info] DOMAIN_SUB_PATH was set to "$DOMAIN_SUB_PATH". Overriding default path.\e[0m"
fi

# Always set NEXT_PUBLIC_DOMAIN_SUB_PATH to DOMAIN_SUB_PATH (even if it is empty!!)
export NEXT_PUBLIC_DOMAIN_SUB_PATH="$DOMAIN_SUB_PATH"

# Iterate over _all_ files in the web directory, making substitutions for the `BAKED_` sentinal values
# with their actual desired runtime value.
find /app/packages/web -type f |
while read file; do
# @note: the leading "/" is required here as it is included at build time. See Dockerfile.
sed -i "s|/BAKED_NEXT_PUBLIC_DOMAIN_SUB_PATH|${NEXT_PUBLIC_DOMAIN_SUB_PATH}|g" "$file"
done
}

find /app/packages/web/public /app/packages/web/.next -type f -name "*.js" |
while read file; do
sed -i "s|BAKED_NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED|${NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_SOURCEBOT_VERSION|${NEXT_PUBLIC_SOURCEBOT_VERSION}|g" "$file"
done

# Run supervisord
exec supervisord -c /etc/supervisor/conf.d/supervisord.conf
7 changes: 6 additions & 1 deletion packages/web/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@ const nextConfig = {
];
},
// This is required to support PostHog trailing slash API requests
skipTrailingSlashRedirect: true,
skipTrailingSlashRedirect: true,

// @note: this is evaluated at build time.
...(process.env.NEXT_PUBLIC_DOMAIN_SUB_PATH ? {
basePath: process.env.NEXT_PUBLIC_DOMAIN_SUB_PATH,
} : {})
};

export default nextConfig;
21 changes: 18 additions & 3 deletions packages/web/src/app/api/(client)/client.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
'use client';

import { NEXT_PUBLIC_DOMAIN_SUB_PATH } from "@/lib/environment.client";
import { fileSourceResponseSchema, listRepositoriesResponseSchema, searchResponseSchema } from "@/lib/schemas";
import { FileSourceRequest, FileSourceResponse, ListRepositoriesResponse, SearchRequest, SearchResponse } from "@/lib/types";

export const search = async (body: SearchRequest): Promise<SearchResponse> => {
const result = await fetch(`/api/search`, {
const path = resolveServerPath("/api/search");
const result = await fetch(path, {
method: "POST",
headers: {
"Content-Type": "application/json",
Expand All @@ -14,7 +18,8 @@ export const search = async (body: SearchRequest): Promise<SearchResponse> => {
}

export const fetchFileSource = async (body: FileSourceRequest): Promise<FileSourceResponse> => {
const result = await fetch(`/api/source`, {
const path = resolveServerPath("/api/source");
const result = await fetch(path, {
method: "POST",
headers: {
"Content-Type": "application/json",
Expand All @@ -26,7 +31,8 @@ export const fetchFileSource = async (body: FileSourceRequest): Promise<FileSour
}

export const getRepos = async (): Promise<ListRepositoriesResponse> => {
const result = await fetch('/api/repos', {
const path = resolveServerPath("/api/repos");
const result = await fetch(path, {
method: "GET",
headers: {
"Content-Type": "application/json",
Expand All @@ -35,3 +41,12 @@ export const getRepos = async (): Promise<ListRepositoriesResponse> => {

return listRepositoriesResponseSchema.parse(result);
}

/**
* Given a subpath to a api route on the server (e.g., /api/search),
* returns the full path to that route on the server, taking into account
* the base path (if any).
*/
export const resolveServerPath = (path: string) => {
return `${NEXT_PUBLIC_DOMAIN_SUB_PATH}${path}`;
}
6 changes: 5 additions & 1 deletion packages/web/src/app/posthogProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@
import { NEXT_PUBLIC_POSTHOG_KEY, NEXT_PUBLIC_POSTHOG_UI_HOST, NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED } from '@/lib/environment.client'
import posthog from 'posthog-js'
import { PostHogProvider } from 'posthog-js/react'
import { resolveServerPath } from './api/(client)/client'

if (typeof window !== 'undefined') {
if (!NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED) {
// @see next.config.mjs for path rewrites to the "/ingest" route.
const posthogHostPath = resolveServerPath('/ingest');

posthog.init(NEXT_PUBLIC_POSTHOG_KEY!, {
api_host: "/ingest",
api_host: posthogHostPath,
ui_host: NEXT_PUBLIC_POSTHOG_UI_HOST,
person_profiles: 'identified_only',
capture_pageview: false, // Disable automatic pageview capture
Expand Down
1 change: 1 addition & 0 deletions packages/web/src/lib/environment.client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export const NEXT_PUBLIC_POSTHOG_UI_HOST = getEnv(process.env.NEXT_PUBLIC_POSTHO
export const NEXT_PUBLIC_POSTHOG_ASSET_HOST = getEnv(process.env.NEXT_PUBLIC_POSTHOG_ASSET_HOST);
export const NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED = getEnvBoolean(process.env.NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED, false);
export const NEXT_PUBLIC_SOURCEBOT_VERSION = getEnv(process.env.NEXT_PUBLIC_SOURCEBOT_VERSION, "unknown");
export const NEXT_PUBLIC_DOMAIN_SUB_PATH = getEnv(process.env.NEXT_PUBLIC_DOMAIN_SUB_PATH, "");