Compare commits

..
Author SHA1 Message Date
Thomas Camlong e85c407453 fix: use dedicated internal port to avoid collision with PORT env
If a platform injects PORT=3000 (e.g. Railway), nginx and Next.js
would both try to bind port 3000. Use a dedicated internal port (7901)
for the nginx→Next.js proxy to prevent this.
2026-06-08 16:53:43 +02:00
Thomas Camlong e004afa806 feat: respect PORT env variable for Coolify preview deployments
Nginx now listens on ${PORT} (default 7575) instead of hardcoded 7575,
so Coolify can inject a unique port per preview container. The internal
Next.js server is pinned to port 3000 to avoid conflicting with nginx.
2026-06-08 16:44:16 +02:00
homarr-renovate[bot] a447a9f615 chore(deps): update dependency axios@>=1.0.0 <1.8.2 to >=1.17.0 (#5897)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-08 14:15:50 +00:00
homarr-renovate[bot] 87d3f6a0a1 chore(deps): update dependency @gfazioli/mantine-onboarding-tour to ^4.0.4 (#5896)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-08 09:30:40 +00:00
12 changed files with 40 additions and 82 deletions

No files matched your search

+3
View File
@@ -65,5 +65,8 @@ ENV AUTH_PROVIDERS='credentials'
ENV REDIS_IS_EXTERNAL='false'
ENV NODE_ENV='production'
ARG PORT=7575
EXPOSE ${PORT}
ENTRYPOINT [ "/app/entrypoint.sh" ]
CMD ["sh", "run.sh"]
+1 -1
View File
@@ -9,7 +9,7 @@
"dev": "pnpm with-env next dev",
"format": "oxfmt .",
"lint": "oxlint",
"start": "pnpm with-env next start",
"start": "pnpm with-env next start -p ${PORT:-3000}",
"typecheck": "tsc --noEmit",
"with-env": "dotenv -e ../../.env --"
},
@@ -1,7 +1,6 @@
import { useMemo } from "react";
import type { GridItemHTMLElement } from "@homarr/gridstack";
import { WidgetSizeProvider } from "@homarr/widgets";
import type { DynamicSectionItem, SectionItem } from "~/app/[locale]/boards/_types";
import { BoardItemContent } from "../items/item-content";
@@ -19,7 +18,7 @@ export const SectionContent = () => {
* @see https://github.com/homarr-labs/homarr/pull/1770
*/
const sortedItems = useMemo(() => {
return [...items, ...innerSections].toSorted((itemA, itemB) => {
return [...items, ...innerSections].sort((itemA, itemB) => {
if (itemA.yOffset === itemB.yOffset) {
return itemA.xOffset - itemB.xOffset;
}
@@ -60,13 +59,7 @@ const Item = ({ item, innerRef }: ItemProps) => {
minWidth={minWidth}
minHeight={minHeight}
>
{item.type === "item" ? (
<WidgetSizeProvider gridWidth={item.width} gridHeight={item.height}>
<BoardItemContent item={item} />
</WidgetSizeProvider>
) : (
<BoardDynamicSection section={item} />
)}
{item.type === "item" ? <BoardItemContent item={item} /> : <BoardDynamicSection section={item} />}
</GridStackItem>
);
};
+1 -1
View File
@@ -1,2 +1,2 @@
:: Please do not run this command in production. It is only for local testing.
docker run -p 7575:7575 -e SECRET_ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000 homarr:latest
docker run -p 7575:7575 -e PORT=7575 -e SECRET_ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000 homarr:latest
+3 -5
View File
@@ -4,10 +4,9 @@ events {
http {
server {
listen 7575;
listen [::]:7575;
listen ${PORT};
listen [::]:${PORT};
# Route websockets traffic to port 3001
location /websockets {
proxy_pass http://${HOSTNAME}:3001;
proxy_http_version 1.1;
@@ -16,9 +15,8 @@ http {
proxy_set_header Host $http_host;
}
# Route all other traffic to port 3000
location / {
proxy_pass http://${HOSTNAME}:3000;
proxy_pass http://${HOSTNAME}:${INTERNAL_NEXTJS_PORT};
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+1 -1
View File
@@ -78,7 +78,7 @@
"overrides": {
"@babel/helpers@<7.26.10": ">=7.29.7",
"@babel/runtime@<7.26.10": ">=7.29.7",
"axios@>=1.0.0 <1.8.2": ">=1.16.1",
"axios@>=1.0.0 <1.8.2": ">=1.17.0",
"brace-expansion@>=2.0.0 <=2.0.1": ">=5.0.6",
"brace-expansion@>=1.0.0 <=1.1.11": ">=5.0.6",
"esbuild@<=0.24.2": ">=0.28.0",
-1
View File
@@ -53,7 +53,6 @@ import * as weather from "./weather";
export type { WidgetDefinition, WidgetOptionsSettings } from "./definition";
export type { WidgetComponentProps };
export { useWidgetSize, WidgetSizeProvider } from "./widget-size";
export const widgetImports = {
clock,
+16 -25
View File
@@ -11,7 +11,6 @@ import { metricToImperial } from "@homarr/common";
import { useScopedI18n } from "@homarr/translation/client";
import type { WidgetComponentProps } from "../definition";
import { useWidgetSize } from "../widget-size";
import { WeatherDescription, WeatherIcon } from "./icon";
export default function WeatherWidget({ isEditMode, options }: WidgetComponentProps<"weather">) {
@@ -30,23 +29,19 @@ export default function WeatherWidget({ isEditMode, options }: WidgetComponentPr
onData: (data) => utils.widget.weather.atLocation.setData(input, data),
});
const { gridWidth, gridHeight } = useWidgetSize();
const isCompact = gridWidth <= 1 || gridHeight <= 1;
const showForecast = options.hasForecast && !isCompact;
return (
<Stack
align="center"
gap={isCompact ? "xs" : "sm"}
gap="sm"
justify="center"
w="100%"
h="100%"
style={{ pointerEvents: isEditMode ? "none" : undefined }}
>
{showForecast ? (
{options.hasForecast ? (
<WeeklyForecast weather={weather} options={options} />
) : (
<DailyWeather weather={weather} options={options} compact={isCompact} />
<DailyWeather weather={weather} options={options} />
)}
</Stack>
);
@@ -56,28 +51,24 @@ interface WeatherProps extends Pick<WidgetComponentProps<"weather">, "options">
weather: RouterOutputs["widget"]["weather"]["atLocation"];
}
const DailyWeather = ({ options, weather, compact }: WeatherProps & { compact?: boolean }) => {
const DailyWeather = ({ options, weather }: WeatherProps) => {
const t = useScopedI18n("widget.weather");
const tCommon = useScopedI18n("common");
const iconSize = compact ? 20 : 30;
const tempSize = compact ? 20 : 30;
const detailSize = compact ? 13 : 16;
return (
<>
<Group className="weather-day-group" gap="sm">
<HoverCard>
<HoverCard.Target>
<Box>
<WeatherIcon size={iconSize} code={weather.current.weathercode} />
<WeatherIcon size={30} code={weather.current.weathercode} />
</Box>
</HoverCard.Target>
<HoverCard.Dropdown>
<WeatherDescription weatherOnly weatherCode={weather.current.weathercode} />
</HoverCard.Dropdown>
</HoverCard>
<Text fz={tempSize}>
<Text fz={30}>
{getPreferredUnit(
weather.current.temperature,
options.isFormatFahrenheit,
@@ -86,10 +77,10 @@ const DailyWeather = ({ options, weather, compact }: WeatherProps & { compact?:
</Text>
</Group>
<Stack gap="xs" align="center">
{options.showCurrentWindSpeed && !compact && (
{options.showCurrentWindSpeed && (
<Group className="weather-current-wind-speed-group" wrap="nowrap" gap="xs">
<IconWind size={detailSize} />
<Text fz={detailSize}>
<IconWind size={16} />
<Text fz={16}>
{t("currentWindSpeed", {
currentWindSpeed: (options.useImperialSpeed
? metricToImperial(weather.current.windspeed)
@@ -104,8 +95,8 @@ const DailyWeather = ({ options, weather, compact }: WeatherProps & { compact?:
)}
<Group className="weather-max-min-temp-group" wrap="nowrap" gap="sm">
<Group gap="xs" wrap="nowrap">
<IconArrowUpRight size={detailSize} />
<Text fz={detailSize}>
<IconArrowUpRight size={16} />
<Text fz={16}>
{getPreferredUnit(
weather.daily[0]?.maxTemp,
options.isFormatFahrenheit,
@@ -114,8 +105,8 @@ const DailyWeather = ({ options, weather, compact }: WeatherProps & { compact?:
</Text>
</Group>
<Group gap="xs" wrap="nowrap">
<IconArrowDownRight size={detailSize} />
<Text fz={detailSize}>
<IconArrowDownRight size={16} />
<Text fz={16}>
{getPreferredUnit(
weather.daily[0]?.minTemp,
options.isFormatFahrenheit,
@@ -125,11 +116,11 @@ const DailyWeather = ({ options, weather, compact }: WeatherProps & { compact?:
</Group>
</Group>
</Stack>
{options.showCity && !compact && (
{options.showCity && (
<>
<Group className="weather-city-group" wrap="nowrap" gap="xs">
<IconMapPin size={detailSize} />
<Text fz={detailSize} style={{ whiteSpace: "nowrap" }}>
<IconMapPin size={16} />
<Text fz={16} style={{ whiteSpace: "nowrap" }}>
{options.location.name}
</Text>
</Group>
-25
View File
@@ -1,25 +0,0 @@
"use client";
import type { ReactNode } from "react";
import { createContext, useContext } from "react";
export interface WidgetSize {
gridWidth: number;
gridHeight: number;
}
const WidgetSizeContext = createContext<WidgetSize | null>(null);
export const useWidgetSize = () => {
const ctx = useContext(WidgetSizeContext);
if (!ctx) throw new Error("useWidgetSize must be used within WidgetSizeProvider");
return ctx;
};
interface WidgetSizeProviderProps extends WidgetSize {
children: ReactNode;
}
export const WidgetSizeProvider = ({ gridWidth, gridHeight, children }: WidgetSizeProviderProps) => (
<WidgetSizeContext.Provider value={{ gridWidth, gridHeight }}>{children}</WidgetSizeContext.Provider>
);
+7 -7
View File
@@ -40,8 +40,8 @@ catalogs:
specifier: 7.2.1
version: 7.2.1
'@gfazioli/mantine-onboarding-tour':
specifier: ^4.0.3
version: 4.0.3
specifier: ^4.0.4
version: 4.0.4
'@gitbeaker/rest':
specifier: ^43.8.0
version: 43.8.0
@@ -532,7 +532,7 @@ catalogs:
overrides:
'@babel/helpers@<7.26.10': '>=7.29.7'
'@babel/runtime@<7.26.10': '>=7.29.7'
axios@>=1.0.0 <1.8.2: '>=1.16.1'
axios@>=1.0.0 <1.8.2: '>=1.17.0'
brace-expansion@>=2.0.0 <=2.0.1: '>=5.0.6'
brace-expansion@>=1.0.0 <=1.1.11: '>=5.0.6'
esbuild@<=0.24.2: '>=0.28.0'
@@ -651,7 +651,7 @@ importers:
version: 3.2.2(react@19.2.7)
'@gfazioli/mantine-onboarding-tour':
specifier: 'catalog:'
version: 4.0.3(@mantine/core@9.2.2(@mantine/hooks@9.2.2(react@19.2.7))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.2.2(react@19.2.7))(@tabler/icons-react@3.44.0(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
version: 4.0.4(@mantine/core@9.2.2(@mantine/hooks@9.2.2(react@19.2.7))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.2.2(react@19.2.7))(@tabler/icons-react@3.44.0(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
'@homarr/analytics':
specifier: workspace:^0.1.0
version: link:../../packages/analytics
@@ -3316,8 +3316,8 @@ packages:
'@formatjs/intl-localematcher@0.8.1':
resolution: {integrity: sha512-xwEuwQFdtSq1UKtQnyTZWC+eHdv7Uygoa+H2k/9uzBVQjDyp9r20LNDNKedWXll7FssT3GRHvqsdJGYSUWqYFA==}
'@gfazioli/mantine-onboarding-tour@4.0.3':
resolution: {integrity: sha512-krYmZoGSV9qpvtztK+QChhfwv1l6LsP/n0u+ih2/A0mV0Osn5avmlj+UBAKoIvwzA4xS50NSoPHj3B53QLzs/Q==}
'@gfazioli/mantine-onboarding-tour@4.0.4':
resolution: {integrity: sha512-8O70elJVFSJJCB/Ie4TDxAKuDgE1BRU8o7O7ArFWzPYGx4Sa4jWW8OC0QyjxZul5aFaFJFl55lG263oh7IMkHg==}
peerDependencies:
'@mantine/core': '>=9.0.0'
'@mantine/hooks': '>=9.0.0'
@@ -10525,7 +10525,7 @@ snapshots:
'@formatjs/fast-memoize': 3.1.0
tslib: 2.8.1
'@gfazioli/mantine-onboarding-tour@4.0.3(@mantine/core@9.2.2(@mantine/hooks@9.2.2(react@19.2.7))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.2.2(react@19.2.7))(@tabler/icons-react@3.44.0(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
'@gfazioli/mantine-onboarding-tour@4.0.4(@mantine/core@9.2.2(@mantine/hooks@9.2.2(react@19.2.7))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.2.2(react@19.2.7))(@tabler/icons-react@3.44.0(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
dependencies:
'@mantine/core': 9.2.2(@mantine/hooks@9.2.2(react@19.2.7))(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
'@mantine/hooks': 9.2.2(react@19.2.7)
+1 -1
View File
@@ -14,7 +14,7 @@ catalog:
"@dnd-kit/utilities": ^3.2.2
"@drizzle-team/brocli": ^0.12.0
"@extractus/feed-extractor": 7.2.1
'@gfazioli/mantine-onboarding-tour': ^4.0.3
'@gfazioli/mantine-onboarding-tour': ^4.0.4
"@gitbeaker/rest": ^43.8.0
"@homarr/gridstack": ^1.12.0
"@homarr/node-unifi": ^2.6.0
+5 -6
View File
@@ -17,12 +17,11 @@ fi
# Auth secret is generated every time the container starts as it is required, but not used because we don't need JWTs or Mail hashing
export AUTH_SECRET=$(openssl rand -base64 32)
# Start nginx proxy
# 1. Replace the HOSTNAME in the nginx template file
# 2. Create the nginx configuration file from the template
# 3. Start the nginx server
export PORT=${PORT:-7575}
export INTERNAL_NEXTJS_PORT=7901
export HOSTNAME
envsubst '${HOSTNAME}' < /etc/nginx/templates/nginx.conf > /etc/nginx/nginx.conf
envsubst '${HOSTNAME} ${PORT} ${INTERNAL_NEXTJS_PORT}' < /etc/nginx/templates/nginx.conf > /etc/nginx/nginx.conf
# Start services in the background and store their PIDs
nginx -g 'daemon off;' &
NGINX_PID=$!
@@ -36,7 +35,7 @@ else
REDIS_PID=$!
fi
node apps/nextjs/server.js &
PORT=$INTERNAL_NEXTJS_PORT node apps/nextjs/server.js &
NEXTJS_PID=$!
# Function to handle SIGTERM and shut down services