From 811d940f2bc22ca71cf0e91ae984bf952fa8910a Mon Sep 17 00:00:00 2001 From: Thomas Camlong Date: Mon, 13 Nov 2023 20:04:44 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=B3=20Fix=20issues=20with=20dockerfile?= =?UTF-8?q?=20(#1611)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add `wait $PID` to be able to send SIG_ABORT * Move to docker `entrypoint` * Change default NEXTAUTH_URL * Add `VOLUME` instruction * corrected a typo * 🐳 Fix docker TCP not working Fixes Lost docker connection via TCP with 0.14.0 update #1577 * 🚧 Improve dockerfile and start script and fix permission issue by adding new user with permission to read / write to /data folder * 🐛 Cleanup changes, Local db:migrate script not working, CI failed * ✨ Image properties customization (#1590) * 🌐 New Crowdin updates (#1572) * ✨ Add notice page for readonly db * Misc docker changes * 🐳 Add `homarr` as `USER` * 🐛 Unable to use user homarr because db.sqlite file is already owned by root --------- Co-authored-by: Lumilias <10852161+Lumilias@users.noreply.github.com> Co-authored-by: Meier Lukas Co-authored-by: Manuel <30572287+manuel-rw@users.noreply.github.com> Co-authored-by: Manuel --- Dockerfile | 16 +- data/default.json | 513 ++++++++++++++++++ scripts/run.sh | 4 +- .../Onboarding/database-not-writeable.tsx | 42 ++ src/middleware.ts | 6 - src/pages/onboard.tsx | 116 +++- src/tools/config/getFallbackConfig.ts | 2 +- 7 files changed, 657 insertions(+), 42 deletions(-) create mode 100644 data/default.json create mode 100644 src/components/Onboarding/database-not-writeable.tsx diff --git a/Dockerfile b/Dockerfile index 22dd3ccd3..b5a9e2675 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,6 @@ FROM node:20.5-slim WORKDIR /app -ARG UID=1001 -ARG GID=1001 -RUN groupadd -g $GID homarr-group -RUN useradd -r -u $UID -g $GID homarr - # Define node.js environment variables ARG PORT=7575 @@ -28,10 +23,9 @@ COPY ./drizzle/migrate ./migrate COPY ./tsconfig.json ./migrate/tsconfig.json RUN mkdir /data -RUN chown -R homarr:homarr-group /data # Install dependencies -RUN apt-get update -y && apt-get install -y openssl wget +RUN apt update && apt install -y openssl wget # Move node_modules to temp location to avoid overwriting RUN mv node_modules _node_modules @@ -54,13 +48,13 @@ EXPOSE $PORT ENV PORT=${PORT} ENV DATABASE_URL "file:/data/db.sqlite" -ENV NEXTAUTH_URL "http://localhost:3000" +ENV NEXTAUTH_URL "http://localhost:7575" ENV PORT 7575 ENV NEXTAUTH_SECRET NOT_IN_USE_BECAUSE_JWTS_ARE_UNUSED HEALTHCHECK --interval=10s --timeout=5s --start-period=5s --retries=3 \ CMD wget --no-verbose --tries=1 --spider http://localhost:${PORT} || exit 1 -USER homarr - -CMD ["sh", "./scripts/run.sh"] \ No newline at end of file +VOLUME [ "/app/data/configs" ] +VOLUME [ "/data" ] +ENTRYPOINT ["sh", "./scripts/run.sh"] diff --git a/data/default.json b/data/default.json new file mode 100644 index 000000000..910d736f7 --- /dev/null +++ b/data/default.json @@ -0,0 +1,513 @@ +{ + "schemaVersion": 1, + "configProperties": { + "name": "default" + }, + "categories": [], + "wrappers": [ + { + "id": "default", + "position": 0 + } + ], + "apps": [ + { + "id": "5df743d9-5cb1-457c-85d2-64ff86855652", + "name": "Documentation", + "url": "https://homarr.dev", + "behaviour": { + "onClickUrl": "https://homarr.dev", + "externalUrl": "https://homarr.dev", + "isOpeningNewTab": true + }, + "network": { + "enabledStatusChecker": false, + "statusCodes": [ + "200" + ] + }, + "appearance": { + "iconUrl": "/imgs/logo/logo.png", + "appNameStatus": "normal", + "positionAppName": "column", + "lineClampAppName": 1 + }, + "integration": { + "type": null, + "properties": [] + }, + "area": { + "type": "wrapper", + "properties": { + "id": "default" + } + }, + "shape": { + "md": { + "location": { + "x": 5, + "y": 1 + }, + "size": { + "width": 1, + "height": 1 + } + }, + "sm": { + "location": { + "x": 0, + "y": 1 + }, + "size": { + "width": 1, + "height": 2 + } + }, + "lg": { + "location": { + "x": 6, + "y": 1 + }, + "size": { + "width": 2, + "height": 2 + } + } + } + }, + { + "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a337", + "name": "Discord", + "url": "https://discord.com/invite/aCsmEV5RgA", + "behaviour": { + "onClickUrl": "https://discord.com/invite/aCsmEV5RgA", + "isOpeningNewTab": true, + "externalUrl": "https://discord.com/invite/aCsmEV5RgA", + "tooltipDescription": "Join our Discord server! We're waiting for your ideas and feedback. " + }, + "network": { + "enabledStatusChecker": false, + "statusCodes": [ + "200" + ] + }, + "appearance": { + "iconUrl": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/discord.png", + "appNameStatus": "normal", + "positionAppName": "row-reverse", + "lineClampAppName": 1 + }, + "integration": { + "type": null, + "properties": [] + }, + "area": { + "type": "wrapper", + "properties": { + "id": "default" + } + }, + "shape": { + "md": { + "location": { + "x": 3, + "y": 1 + }, + "size": { + "width": 1, + "height": 1 + } + }, + "sm": { + "location": { + "x": 1, + "y": 4 + }, + "size": { + "width": 1, + "height": 1 + } + }, + "lg": { + "location": { + "x": 4, + "y": 0 + }, + "size": { + "width": 2, + "height": 1 + } + } + } + }, + { + "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a330", + "name": "Contribute", + "url": "https://github.com/ajnart/homarr", + "behaviour": { + "onClickUrl": "https://github.com/ajnart/homarr", + "externalUrl": "https://github.com/ajnart/homarr", + "isOpeningNewTab": true, + "tooltipDescription": "" + }, + "network": { + "enabledStatusChecker": false, + "statusCodes": [] + }, + "appearance": { + "iconUrl": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/github.png", + "appNameStatus": "normal", + "positionAppName": "row-reverse", + "lineClampAppName": 2 + }, + "integration": { + "type": null, + "properties": [] + }, + "area": { + "type": "wrapper", + "properties": { + "id": "default" + } + }, + "shape": { + "md": { + "location": { + "x": 3, + "y": 2 + }, + "size": { + "width": 2, + "height": 1 + } + }, + "sm": { + "location": { + "x": 1, + "y": 3 + }, + "size": { + "width": 2, + "height": 1 + } + }, + "lg": { + "location": { + "x": 2, + "y": 0 + }, + "size": { + "width": 2, + "height": 1 + } + } + } + }, + { + "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a990", + "name": "Donate", + "url": "https://ko-fi.com/ajnart", + "behaviour": { + "onClickUrl": "https://ko-fi.com/ajnart", + "externalUrl": "https://ko-fi.com/ajnart", + "isOpeningNewTab": true, + "tooltipDescription": "Please consider making a donation" + }, + "network": { + "enabledStatusChecker": false, + "statusCodes": [ + "200" + ] + }, + "appearance": { + "iconUrl": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/ko-fi.png", + "appNameStatus": "normal", + "positionAppName": "row-reverse", + "lineClampAppName": 1 + }, + "integration": { + "type": null, + "properties": [] + }, + "area": { + "type": "wrapper", + "properties": { + "id": "default" + } + }, + "shape": { + "md": { + "location": { + "x": 4, + "y": 1 + }, + "size": { + "width": 1, + "height": 1 + } + }, + "sm": { + "location": { + "x": 2, + "y": 4 + }, + "size": { + "width": 1, + "height": 1 + } + }, + "lg": { + "location": { + "x": 6, + "y": 0 + }, + "size": { + "width": 2, + "height": 1 + } + } + } + } + ], + "widgets": [ + { + "id": "e3004052-6b83-480e-b458-56e8ccdca5f0", + "type": "weather", + "properties": { + "displayInFahrenheit": false, + "location": { + "name": "Paris", + "latitude": 48.85341, + "longitude": 2.3488 + }, + "displayCityName": true + }, + "area": { + "type": "wrapper", + "properties": { + "id": "default" + } + }, + "shape": { + "md": { + "location": { + "x": 5, + "y": 0 + }, + "size": { + "width": 1, + "height": 1 + } + }, + "sm": { + "location": { + "x": 2, + "y": 0 + }, + "size": { + "width": 1, + "height": 1 + } + }, + "lg": { + "location": { + "x": 0, + "y": 0 + }, + "size": { + "width": 2, + "height": 1 + } + } + } + }, + { + "id": "971aa859-8570-49a1-8d34-dd5c7b3638d1", + "type": "date", + "properties": { + "display24HourFormat": true, + "dateFormat": "hide", + "enableTimezone": false, + "timezoneLocation": { + "name": "Paris", + "latitude": 48.85341, + "longitude": 2.3488 + }, + "titleState": "city" + }, + "area": { + "type": "wrapper", + "properties": { + "id": "default" + } + }, + "shape": { + "sm": { + "location": { + "x": 1, + "y": 0 + }, + "size": { + "width": 1, + "height": 1 + } + }, + "md": { + "location": { + "x": 4, + "y": 0 + }, + "size": { + "width": 1, + "height": 1 + } + }, + "lg": { + "location": { + "x": 8, + "y": 0 + }, + "size": { + "width": 2, + "height": 1 + } + } + } + }, + { + "id": "f252768d-9e69-491b-b6b4-8cad04fa30e8", + "type": "date", + "properties": { + "display24HourFormat": true, + "dateFormat": "hide", + "enableTimezone": true, + "timezoneLocation": { + "name": "Tokyo", + "latitude": 35.6895, + "longitude": 139.69171 + }, + "titleState": "city" + }, + "area": { + "type": "wrapper", + "properties": { + "id": "default" + } + }, + "shape": { + "sm": { + "location": { + "x": 0, + "y": 0 + }, + "size": { + "width": 1, + "height": 1 + } + }, + "md": { + "location": { + "x": 3, + "y": 0 + }, + "size": { + "width": 1, + "height": 1 + } + }, + "lg": { + "location": { + "x": 8, + "y": 1 + }, + "size": { + "width": 2, + "height": 1 + } + } + } + }, + { + "id": "86b1921f-efa7-410f-92dd-79553bf3264d", + "type": "notebook", + "properties": { + "showToolbar": true, + "content": "

Welcome to Homarr 🚀👋

We're glad that you're here! Homarr is a modern and easy to use dashboard that helps you to organize and manage your home network from one place. Control is at your fingertips.

We recommend you to read the getting started guide first. To edit this board you must enter the edit mode - only administrators can do this. Adding an app is the first step you should take. You can do this by clicking the Add tile button at the top right and select App. After you provided an internal URL, external URL and selected an icon you can drag it around when holding down the left mouse button. Make it bigger or smaller using the drag icon at the bottom right. When you're happy with it's position, you must exit edit mode to save your board. Adding widgets works the same way but may require additional configuration - read the documentation for more information.

To remove this widget, you must log in to your administrator account and click on the menu to delete it.

Your TODO list:

  • Read the documentation

  • Add your first app

  • Resize and drag your app to a different position

  • Add the clock widget to your dashboard

  • Create a new user

" + }, + "area": { + "type": "wrapper", + "properties": { + "id": "default" + } + }, + "shape": { + "sm": { + "location": { + "x": 0, + "y": 0 + }, + "size": { + "width": 3, + "height": 2 + } + }, + "md": { + "location": { + "x": 0, + "y": 0 + }, + "size": { + "width": 3, + "height": 4 + } + }, + "lg": { + "location": { + "x": 0, + "y": 1 + }, + "size": { + "width": 6, + "height": 3 + } + } + } + } + ], + "settings": { + "common": { + "searchEngine": { + "type": "google", + "properties": {} + } + }, + "customization": { + "layout": { + "enabledLeftSidebar": false, + "enabledRightSidebar": false, + "enabledDocker": false, + "enabledPing": false, + "enabledSearchbar": true + }, + "pageTitle": "Homarr ⭐️", + "logoImageUrl": "/imgs/logo/logo.png", + "faviconUrl": "/imgs/favicon/favicon-squared.png", + "backgroundImageUrl": "", + "customCss": "", + "colors": { + "primary": "red", + "secondary": "yellow", + "shade": 7 + }, + "appOpacity": 100, + "gridstack": { + "columnCountSmall": 3, + "columnCountMedium": 6, + "columnCountLarge": 10 + } + }, + "access": { + "allowGuests": false + } + } +} \ No newline at end of file diff --git a/scripts/run.sh b/scripts/run.sh index 01e80d0e8..67c5cb508 100644 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -9,4 +9,6 @@ cd ./migrate; yarn db:migrate & PID=$! wait $PID echo "Starting production server..." -node /app/server.js \ No newline at end of file +node /app/server.js & PID=$! + +wait $PID \ No newline at end of file diff --git a/src/components/Onboarding/database-not-writeable.tsx b/src/components/Onboarding/database-not-writeable.tsx new file mode 100644 index 000000000..ac1e92527 --- /dev/null +++ b/src/components/Onboarding/database-not-writeable.tsx @@ -0,0 +1,42 @@ +import { Center, Code, List, Stack, Text, Title } from '@mantine/core'; +import Head from 'next/head'; + +export const DatabaseNotWriteable = ({ error, errorMessage }: { error: any | unknown, errorMessage: string | undefined }) => { + return ( + <> + + Onboard - Error • Homarr + + +
+ + + Critical error while starting Homarr + + + We detected that Homarr is unable to write to the database. Please troubleshoot using + the following steps: + + + + Ensure that you mounted the path /data to a writeable location with + enough disk space. For this, you must add the following mounting point to your docker + compose: {' - /data:/data'} + + + Ensure that you followed the installation instructions at{' '} + + https://homarr.dev/docs/introduction/installation + + + + {error && JSON.stringify(error)} + + {errorMessage && ( + {errorMessage} + )} + +
+ + ); +}; diff --git a/src/middleware.ts b/src/middleware.ts index 63e2b45d0..ee139deca 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -47,11 +47,5 @@ const shouldRedirectToOnboard = async (): Promise => { return cachedUserCount === 0; }; - if (!process.env.DATABASE_URL?.startsWith('file:')) { - return await cacheAndGetUserCount(); - } - - const fileUri = process.env.DATABASE_URL.substring(4); return await cacheAndGetUserCount(); - // TODO: Show an error page if the database file is read-only }; diff --git a/src/pages/onboard.tsx b/src/pages/onboard.tsx index 01c248131..ab7b4b55d 100644 --- a/src/pages/onboard.tsx +++ b/src/pages/onboard.tsx @@ -1,19 +1,28 @@ -import { Box, Button, Center, Image, Stack, Text, Title, useMantineTheme } from '@mantine/core'; +import { Button, Center, Image, Stack, Text, Title, useMantineTheme } from '@mantine/core'; import { useDisclosure } from '@mantine/hooks'; import { IconArrowRight } from '@tabler/icons-react'; +import Consola from 'consola'; import fs from 'fs'; +import fsPromises from 'fs/promises'; import { GetServerSideProps, InferGetServerSidePropsType } from 'next'; import Head from 'next/head'; +import { DatabaseNotWriteable } from '~/components/Onboarding/database-not-writeable'; import { OnboardingSteps } from '~/components/Onboarding/onboarding-steps'; import { ThemeSchemeToggle } from '~/components/ThemeSchemeToggle/ThemeSchemeToggle'; import { FloatingBackground } from '~/components/layout/Background/FloatingBackground'; -import { db } from '~/server/db'; +import { env } from '~/env'; import { getTotalUserCountAsync } from '~/server/db/queries/user'; import { getConfig } from '~/tools/config/getConfig'; import { getServerSideTranslations } from '~/tools/server/getServerSideTranslations'; +const util = require('util'); +const exec = util.promisify(require('child_process').exec); + export default function OnboardPage({ configSchemaVersions, + databaseNotWriteable, + error, + errorMessage }: InferGetServerSidePropsType) { const { fn, colors, colorScheme } = useMantineTheme(); const background = colorScheme === 'dark' ? 'dark.6' : 'gray.1'; @@ -39,29 +48,35 @@ export default function OnboardPage({ - {onboardingSteps ? ( - + {databaseNotWriteable == true ? ( + ) : ( -
- - - Welcome to Homarr! - - - Your favorite dashboard has received a big upgrade. -
- We'll help you update within the next few steps -
+ <> + {onboardingSteps ? ( + + ) : ( +
+ + + Welcome to Homarr! + + + Your favorite dashboard has received a big upgrade. +
+ We'll help you update within the next few steps +
- -
-
+ +
+
+ )} + )} @@ -87,10 +102,65 @@ export const getServerSideProps: GetServerSideProps = async (ctx) => { ctx.res ); + if (env.DATABASE_URL.startsWith('file:')) { + const rawDatabaseUrl = env.DATABASE_URL.substring('file:'.length); + Consola.info( + `Instance is using a database on the file system. Checking if file '${rawDatabaseUrl}' is writable...` + ); + try { + await fsPromises.access(rawDatabaseUrl, fs.constants.W_OK); + } catch (error) { + // this usually occurs when the database path is not mounted in Docker + Consola.error(`Database '${rawDatabaseUrl}' is not writable.`, error); + return { + props: { + ...translations, + configSchemaVersions: configSchemaVersions, + databaseNotWriteable: true, + error: error, + }, + }; + } + Consola.info('Database is writeable'); + + if (process.platform !== 'win32') { + try { + const { stdout, stderr } = await exec("mount | grep '/data'"); + + if (stderr.split('\n').length > 1 || stdout.split('\n').length <= 1) { + Consola.error(`Database at '${rawDatabaseUrl}' has not been mounted: ${stdout.replace('\n', '\\n')} ${stderr.replace('\n', '\\n')}`); + return { + props: { + ...translations, + configSchemaVersions: configSchemaVersions, + databaseNotWriteable: true, + error: `Database at '${rawDatabaseUrl}' is not mounted:\n${stdout}`, + }, + }; + } + } catch (error) { + const errorMessage = `Database at '${rawDatabaseUrl}' has not been mounted: ${error}`; + Consola.error(errorMessage); + return { + props: { + ...translations, + configSchemaVersions: configSchemaVersions, + databaseNotWriteable: true, + error: error, + errorMessage: errorMessage + }, + }; + } + } + + Consola.info(`Database at '${rawDatabaseUrl}' is writeable and mounted`); + } + return { props: { ...translations, configSchemaVersions: configSchemaVersions, + databaseNotWriteable: false }, }; }; diff --git a/src/tools/config/getFallbackConfig.ts b/src/tools/config/getFallbackConfig.ts index cbb760743..dbc74888e 100644 --- a/src/tools/config/getFallbackConfig.ts +++ b/src/tools/config/getFallbackConfig.ts @@ -1,6 +1,6 @@ import { ConfigType } from '~/types/config'; -import defaultConfig from '../../../data/configs/default.json'; +import defaultConfig from '../../../data/default.json'; export const getFallbackConfig = (name?: string) => ({ ...defaultConfig,