🐳 Fix issues with dockerfile (#1611)

* 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 <meierschlumpf@gmail.com>
Co-authored-by: Manuel <30572287+manuel-rw@users.noreply.github.com>
Co-authored-by: Manuel <manuel.ruwe@bluewin.ch>
This commit is contained in:
Thomas Camlong
2023-11-13 20:04:44 +01:00
committed by GitHub
parent a3ca74ab46
commit 811d940f2b
7 changed files with 657 additions and 42 deletions

View File

@@ -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"]
VOLUME [ "/app/data/configs" ]
VOLUME [ "/data" ]
ENTRYPOINT ["sh", "./scripts/run.sh"]

513
data/default.json Normal file
View File

@@ -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": "<h2><strong>Welcome to Homarr 🚀👋</strong></h2><p>We're glad that you're here! Homarr is a <em>modern </em>and <em>easy to use</em> dashboard that helps you to <strong>organize and manage</strong> your home network from one place. Control is <strong>at your fingertips</strong>.</p><p>We recommend you to read the <a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://homarr.dev/docs/introduction/after-the-installation\">getting started guide</a> 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 <code>Add tile</code> button at the top right and select <code>App</code>. 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 <strong>must exit edit mode to save your board</strong>. Adding widgets works the same way but may require additional configuration - read the documentation for more information.</p><p>To remove this widget, you must log in to your administrator account and click on the menu to delete it.</p><p><strong><u>Your TODO list:</u></strong></p><ul data-type=\"taskList\"><li data-checked=\"false\" data-type=\"taskItem\"><label><input type=\"checkbox\"><span></span></label><div><p>Read the <a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://homarr.dev\">documentation</a></p></div></li><li data-checked=\"false\" data-type=\"taskItem\"><label><input type=\"checkbox\"><span></span></label><div><p>Add your <em>first app</em></p></div></li><li data-checked=\"false\" data-type=\"taskItem\"><label><input type=\"checkbox\"><span></span></label><div><p><em>Resize </em>and <em>drag</em> your app to a different position</p></div></li><li data-checked=\"false\" data-type=\"taskItem\"><label><input type=\"checkbox\"><span></span></label><div><p>Add the <em>clock widget</em> to your dashboard</p></div></li><li data-checked=\"false\" data-type=\"taskItem\"><label><input type=\"checkbox\"><span></span></label><div><p>Create a <em>new user</em></p></div></li></ul>"
},
"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
}
}
}

View File

@@ -9,4 +9,6 @@ cd ./migrate; yarn db:migrate & PID=$!
wait $PID
echo "Starting production server..."
node /app/server.js
node /app/server.js & PID=$!
wait $PID

View File

@@ -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 (
<>
<Head>
<title>Onboard - Error Homarr</title>
</Head>
<Center h="100%">
<Stack align="center" p="lg">
<Title order={1} weight={800} size="3rem" opacity={0.8}>
Critical error while starting Homarr
</Title>
<Text size="lg" mb={40}>
We detected that Homarr is unable to write to the database. Please troubleshoot using
the following steps:
</Text>
<List>
<List.Item>
Ensure that you mounted the path <code>/data</code> to a writeable location with
enough disk space. For this, you must add the following mounting point to your docker
compose: <Code block>{' - <your-path>/data:/data'}</Code>
</List.Item>
<List.Item>
Ensure that you followed the installation instructions at{' '}
<a href="https://homarr.dev/docs/introduction/installation">
https://homarr.dev/docs/introduction/installation
</a>
</List.Item>
</List>
<Code block>{error && JSON.stringify(error)}</Code>
{errorMessage && (
<Code block>{errorMessage}</Code>
)}
</Stack>
</Center>
</>
);
};

View File

@@ -47,11 +47,5 @@ const shouldRedirectToOnboard = async (): Promise<boolean> => {
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
};

View File

@@ -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<typeof getServerSideProps>) {
const { fn, colors, colorScheme } = useMantineTheme();
const background = colorScheme === 'dark' ? 'dark.6' : 'gray.1';
@@ -39,29 +48,35 @@ export default function OnboardPage({
</Center>
</Center>
{onboardingSteps ? (
<OnboardingSteps isUpdate={isUpgradeFromSchemaOne} />
{databaseNotWriteable == true ? (
<DatabaseNotWriteable error={error} errorMessage={errorMessage} />
) : (
<Center h="100%">
<Stack align="center" p="lg">
<Title order={1} weight={800} size="3rem" opacity={0.8}>
Welcome to Homarr!
</Title>
<Text size="lg" mb={40}>
Your favorite dashboard has received a big upgrade.
<br />
We'll help you update within the next few steps
</Text>
<>
{onboardingSteps ? (
<OnboardingSteps isUpdate={isUpgradeFromSchemaOne} />
) : (
<Center h="100%">
<Stack align="center" p="lg">
<Title order={1} weight={800} size="3rem" opacity={0.8}>
Welcome to Homarr!
</Title>
<Text size="lg" mb={40}>
Your favorite dashboard has received a big upgrade.
<br />
We'll help you update within the next few steps
</Text>
<Button
onClick={showOnboardingSteps}
rightIcon={<IconArrowRight size="1rem" />}
variant="default"
>
Start update process
</Button>
</Stack>
</Center>
<Button
onClick={showOnboardingSteps}
rightIcon={<IconArrowRight size="1rem" />}
variant="default"
>
Start update process
</Button>
</Stack>
</Center>
)}
</>
)}
</Stack>
</>
@@ -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
},
};
};

View File

@@ -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,