diff options
| -rw-r--r-- | .github/workflows/docker.yml | 2 | ||||
| -rw-r--r-- | apps/cli/index.ts | 2 | ||||
| -rw-r--r-- | apps/web/app/dashboard/admin/page.tsx | 55 | ||||
| -rw-r--r-- | apps/web/lib/clientConfig.tsx | 2 | ||||
| -rw-r--r-- | apps/workers/index.ts | 4 | ||||
| -rw-r--r-- | docker/Dockerfile | 9 | ||||
| -rw-r--r-- | docs/docs/03-configuration.md | 25 | ||||
| -rw-r--r-- | packages/shared/config.ts | 7 |
8 files changed, 92 insertions, 14 deletions
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index b9e83afa..d6de38fd 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -32,6 +32,7 @@ jobs: uses: docker/build-push-action@v5 with: context: . + build-args: SERVER_VERSION=nightly file: docker/Dockerfile target: ${{ matrix.package }} platforms: linux/amd64,linux/arm64 @@ -45,6 +46,7 @@ jobs: if: github.event_name == 'release' with: context: . + build-args: SERVER_VERSION=${{ github.event.release.name }} file: docker/Dockerfile target: ${{ matrix.package }} platforms: linux/amd64,linux/arm64 diff --git a/apps/cli/index.ts b/apps/cli/index.ts index 03699e55..4d0adafb 100644 --- a/apps/cli/index.ts +++ b/apps/cli/index.ts @@ -19,7 +19,7 @@ const program = new Command() .makeOptionMandatory(true) .env("HOARDER_SERVER_ADDR"), ) - .version("0.1.0"); + .version(process.env.SERVER_VERSION ?? "nightly"); program.addCommand(bookmarkCmd); program.addCommand(whoamiCmd); diff --git a/apps/web/app/dashboard/admin/page.tsx b/apps/web/app/dashboard/admin/page.tsx index eb80cb03..0db1130f 100644 --- a/apps/web/app/dashboard/admin/page.tsx +++ b/apps/web/app/dashboard/admin/page.tsx @@ -13,11 +13,58 @@ import { TableRow, } from "@/components/ui/table"; import { toast } from "@/components/ui/use-toast"; +import { useClientConfig } from "@/lib/clientConfig"; import { api } from "@/lib/trpc"; -import { keepPreviousData } from "@tanstack/react-query"; +import { keepPreviousData, useQuery } from "@tanstack/react-query"; import { Trash } from "lucide-react"; import { useSession } from "next-auth/react"; +const REPO_LATEST_RELEASE_API = + "https://api.github.com/repos/mohamedbassem/hoarder-app/releases/latest"; +const REPO_RELEASE_PAGE = + "https://github.com/MohamedBassem/hoarder-app/releases"; + +function useLatestRelease() { + const { data } = useQuery({ + queryKey: ["latest-release"], + queryFn: async () => { + const res = await fetch(REPO_LATEST_RELEASE_API); + if (!res.ok) { + return undefined; + } + const data = (await res.json()) as { name: string }; + return data.name; + }, + staleTime: 60 * 60 * 1000, + enabled: !useClientConfig().disableNewReleaseCheck, + }); + return data; +} + +function ReleaseInfo() { + const currentRelease = useClientConfig().serverVersion ?? "not set"; + const latestRelease = useLatestRelease(); + + let newRelease; + if (latestRelease && currentRelease != latestRelease) { + newRelease = ( + <a + href={REPO_RELEASE_PAGE} + target="_blank" + className="text-blue-500" + rel="noreferrer" + > + (New release available: {latestRelease}) + </a> + ); + } + return ( + <p className="text-nowrap"> + {currentRelease} {newRelease} + </p> + ); +} + function ActionsSection() { const { mutate: recrawlLinks, isPending: isRecrawlPending } = api.admin.recrawlAllLinks.useMutation({ @@ -95,6 +142,12 @@ function ServerStatsSection() { <TableCell>Num Bookmarks</TableCell> <TableCell>{serverStats.numBookmarks}</TableCell> </TableRow> + <TableRow> + <TableCell className="w-2/3">Server Version</TableCell> + <TableCell> + <ReleaseInfo /> + </TableCell> + </TableRow> </TableBody> </Table> <Separator /> diff --git a/apps/web/lib/clientConfig.tsx b/apps/web/lib/clientConfig.tsx index d63b169c..50e9774d 100644 --- a/apps/web/lib/clientConfig.tsx +++ b/apps/web/lib/clientConfig.tsx @@ -7,6 +7,8 @@ export const ClientConfigCtx = createContext<ClientConfig>({ auth: { disableSignups: false, }, + serverVersion: undefined, + disableNewReleaseCheck: true, }); export function useClientConfig() { diff --git a/apps/workers/index.ts b/apps/workers/index.ts index 24bdc67b..687d9ced 100644 --- a/apps/workers/index.ts +++ b/apps/workers/index.ts @@ -1,11 +1,15 @@ import "dotenv/config"; +import serverConfig from "@hoarder/shared/config"; +import logger from "@hoarder/shared/logger"; + import { CrawlerWorker } from "./crawlerWorker"; import { shutdownPromise } from "./exit"; import { OpenAiWorker } from "./openaiWorker"; import { SearchIndexingWorker } from "./searchWorker"; async function main() { + logger.info(`Workers version: ${serverConfig.serverVersion ?? "not set"}`); const [crawler, openai, search] = [ await CrawlerWorker.build(), OpenAiWorker.build(), diff --git a/docker/Dockerfile b/docker/Dockerfile index 8551bbc7..948e70ef 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -34,6 +34,9 @@ RUN pnpm next experimental-compile FROM --platform=$BUILDPLATFORM node:21-alpine AS web WORKDIR /app +ARG SERVER_VERSION=nightly +ENV SERVER_VERSION=${SERVER_VERSION} + ENV NODE_ENV production ENV NEXT_TELEMETRY_DISABLED 1 @@ -72,6 +75,9 @@ RUN --mount=type=cache,id=pnpm_workers,target=/pnpm/store pnpm deploy --node-lin FROM --platform=$BUILDPLATFORM node:21-alpine AS workers WORKDIR /app +ARG SERVER_VERSION=nightly +ENV SERVER_VERSION=${SERVER_VERSION} + COPY --from=workers_builder /prod apps/workers RUN corepack enable @@ -93,6 +99,9 @@ RUN --mount=type=cache,id=pnpm_cli,target=/pnpm/store pnpm deploy --node-linker= FROM --platform=$BUILDPLATFORM node:21-alpine AS cli WORKDIR /app +ARG SERVER_VERSION=nightly +ENV SERVER_VERSION=${SERVER_VERSION} + COPY --from=cli_builder /prod apps/cli RUN corepack enable diff --git a/docs/docs/03-configuration.md b/docs/docs/03-configuration.md index 1cb1da76..8bf8a069 100644 --- a/docs/docs/03-configuration.md +++ b/docs/docs/03-configuration.md @@ -2,18 +2,19 @@ The app is mainly configured by environment variables. All the used environment variables are listed in [packages/shared/config.ts](https://github.com/MohamedBassem/hoarder-app/blob/main/packages/shared/config.ts). The most important ones are: -| Name | Required | Default | Description | -| ----------------- | ------------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | -| DATA_DIR | Yes | Not set | The path for the persistent data directory. This is where the db and the uploaded assets live. | -| NEXTAUTH_URL | Yes | Not set | Should point to the address of your server. The app will function without it, but will redirect you to wrong addresses on signout for example. | -| NEXTAUTH_SECRET | Yes | Not set | Random string used to sign the JWT tokens. Generate one with `openssl rand -base64 36`. | -| REDIS_HOST | Yes | localhost | The address of redis used by background jobs | -| REDIS_PORT | Yes | 6379 | The port of redis used by background jobs | -| REDIS_DB_IDX | No | Not set | The db idx to use with redis. It defaults to 0 (in the client) so you don't usually need to set it unless you explicitly want another db. | -| MEILI_ADDR | No | Not set | The address of meilisearch. If not set, Search will be disabled. E.g. (`http://meilisearch:7700`) | -| MEILI_MASTER_KEY | Only in Prod and if search is enabled | Not set | The master key configured for meilisearch. Not needed in development environment. Generate one with `openssl rand -base64 36` | -| DISABLE_SIGNUPS | No | false | If enabled, no new signups will be allowed and the signup button will be disabled in the UI | -| MAX_ASSET_SIZE_MB | No | 4 | Sets the maximum allowed asset size (in MB) to be uploaded | +| Name | Required | Default | Description | +| ------------------------- | ------------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| DATA_DIR | Yes | Not set | The path for the persistent data directory. This is where the db and the uploaded assets live. | +| NEXTAUTH_URL | Yes | Not set | Should point to the address of your server. The app will function without it, but will redirect you to wrong addresses on signout for example. | +| NEXTAUTH_SECRET | Yes | Not set | Random string used to sign the JWT tokens. Generate one with `openssl rand -base64 36`. | +| REDIS_HOST | Yes | localhost | The address of redis used by background jobs | +| REDIS_PORT | Yes | 6379 | The port of redis used by background jobs | +| REDIS_DB_IDX | No | Not set | The db idx to use with redis. It defaults to 0 (in the client) so you don't usually need to set it unless you explicitly want another db. | +| MEILI_ADDR | No | Not set | The address of meilisearch. If not set, Search will be disabled. E.g. (`http://meilisearch:7700`) | +| MEILI_MASTER_KEY | Only in Prod and if search is enabled | Not set | The master key configured for meilisearch. Not needed in development environment. Generate one with `openssl rand -base64 36` | +| DISABLE_SIGNUPS | No | false | If enabled, no new signups will be allowed and the signup button will be disabled in the UI | +| MAX_ASSET_SIZE_MB | No | 4 | Sets the maximum allowed asset size (in MB) to be uploaded | +| DISABLE_NEW_RELEASE_CHECK | No | false | If set to true, latest release check will be disabled in the admin panel. | ## Inference Configs (For automatic tagging) diff --git a/packages/shared/config.ts b/packages/shared/config.ts index c1cc371a..11140c3b 100644 --- a/packages/shared/config.ts +++ b/packages/shared/config.ts @@ -29,6 +29,9 @@ const allEnv = z.object({ DATA_DIR: z.string().default(""), MAX_ASSET_SIZE_MB: z.coerce.number().default(4), INFERENCE_LANG: z.string().default("english"), + // Build only flag + SERVER_VERSION: z.string().optional(), + DISABLE_NEW_RELEASE_CHECK: stringBool("false"), }); const serverConfigSchema = allEnv.transform((val) => { @@ -69,6 +72,8 @@ const serverConfigSchema = allEnv.transform((val) => { : undefined, dataDir: val.DATA_DIR, maxAssetSizeMb: val.MAX_ASSET_SIZE_MB, + serverVersion: val.SERVER_VERSION, + disableNewReleaseCheck: val.DISABLE_NEW_RELEASE_CHECK, }; }); @@ -79,6 +84,8 @@ export const clientConfig = { auth: { disableSignups: serverConfig.auth.disableSignups, }, + serverVersion: serverConfig.serverVersion, + disableNewReleaseCheck: serverConfig.disableNewReleaseCheck, }; export type ClientConfig = typeof clientConfig; |
