Merge branch 'dev' into fix-truenas-startup-time

This commit is contained in:
Thomas authored and GitHub committed 2026-06-05 00:17:42 +02:00
commit 798635e9a8
1081 files changed
+115844 -30430

No files matched your search

+13
View File
@@ -26,6 +26,19 @@ if [ "${PUID}" != "0" ] || [ "${PGID}" != "0" ]; then
echo "Changing owner to $PUID:$PGID, done."
fi
# support _FILE Suffix for environment variables
for file_var in $(env | cut -d '=' -f 1 | grep "_FILE$"); do
target_var=$(echo "$file_var" | cut -d'=' -f1 | sed 's/_FILE//')
file_path=$(printenv "$file_var")
if [ -f "$file_path" ]; then
export "$target_var"=$(cat "$file_path" | tr -d '\n\r')
echo "Info: Loaded secret for $target_var from $file_path"
else
echo "Warning: Secret file $file_path not found for $target_var"
fi
done
if [ "${PUID}" != "0" ]; then
exec su-exec $PUID:$PGID "$@"
else
+21 -15
View File
@@ -1,14 +1,17 @@
#!/usr/bin/env bash
# Create sub directories in volume
mkdir -p /appdata/db
mkdir -p /appdata/redis
mkdir -p /appdata/trusted-certificates
# Run migrations
if [ $DB_MIGRATIONS_DISABLED = "true" ]; then
if [ "$DB_MIGRATIONS_DISABLED" = "true" ]; then
echo "DB migrations are disabled, skipping"
else
echo "Running DB migrations"
node ./db/migrations/$DB_DIALECT/migrate.cjs ./db/migrations/$DB_DIALECT
# We disable redis logs during migration as the redis client is not yet started
DISABLE_REDIS_LOGS=true node ./db/migrations/$DB_DIALECT/migrate.cjs ./db/migrations/$DB_DIALECT
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
@@ -18,19 +21,20 @@ export AUTH_SECRET=$(openssl rand -base64 32)
# 1. Replace the HOSTNAME in the nginx template file
# 2. Create the nginx configuration file from the template
# 3. Start the nginx server
export HOSTNAME
envsubst '${HOSTNAME}' < /etc/nginx/templates/nginx.conf > /etc/nginx/nginx.conf
# Start services in the background and store their PIDs
nginx -g 'daemon off;' &
NGINX_PID=$!
redis-server /app/redis.conf &
REDIS_PID=$!
node apps/tasks/tasks.cjs &
TASKS_PID=$!
node apps/websocket/wssServer.cjs &
WSS_PID=$!
if [ "$REDIS_IS_EXTERNAL" = "true" ]; then
echo "Using external Redis server at redis://$REDIS_HOST:$REDIS_PORT"
REDIS_PID=""
else
echo "Starting internal Redis server"
redis-server /app/redis.conf &
REDIS_PID=$!
fi
node apps/nextjs/server.js &
NEXTJS_PID=$!
@@ -38,11 +42,13 @@ NEXTJS_PID=$!
# Function to handle SIGTERM and shut down services
terminate() {
echo "Received SIGTERM. Shutting down..."
kill -TERM $NGINX_PID $TASKS_PID $WSS_PID $NEXTJS_PID 2>/dev/null
wait
# kill redis-server last because of logging of other services
kill -TERM $REDIS_PID 2>/dev/null
kill -TERM $NGINX_PID $NEXTJS_PID 2>/dev/null
wait
# kill redis-server last because of logging of other services and only if $REDIS_PID is set
if [ -n "$REDIS_PID" ]; then
kill -TERM $REDIS_PID 2>/dev/null
wait
fi
echo "Shutdown complete."
exit 0
}
@@ -52,4 +58,4 @@ trap terminate TERM INT
# Wait for all processes
wait $NEXTJS_PID
terminate
terminate
+14
View File
@@ -0,0 +1,14 @@
import { readFile, writeFile } from "fs/promises";
const replaceTemplate = "#NEXT_VERSION#";
const fileName = ".github/ISSUE_TEMPLATE/bug_report.yml";
const env = {
NEXT_VERSION: process.env.NEXT_VERSION as string,
};
const content = await readFile(fileName, "utf8");
const updatedContent = content.replace(
replaceTemplate,
`${replaceTemplate}\n - ${env.NEXT_VERSION.replace("v", "")}`,
);
await writeFile(fileName, updatedContent, "utf8");
+62 -8
View File
@@ -1,14 +1,12 @@
import fs from "fs/promises";
const sources = {
crowdin: [
{ projectId: 534422 },
{ projectId: 742587 },
],
crowdin: [{ projectId: 534422 }, { projectId: 742587 }],
github: [
{ slug: "ajnart", repository: "homarr" },
{ slug: "homarr-labs", repository: "homarr" },
],
opencollective: [{ slug: "homarr" }],
};
const env = {
@@ -47,11 +45,55 @@ const fetchCrowdinMembers = async (projectId) => {
};
const response = await fetch(url, options);
const data = await response.json();
const result = await response.json();
return data.data.flatMap((data) => data.data).map(contributor => ({
username: contributor.username,
avatarUrl: contributor.avatarUrl,
return result.data
.flatMap(({ data }) => data)
.map((contributor) => ({
username: contributor.username,
avatarUrl: contributor.avatarUrl,
}));
};
const fetchOpenCollectiveContributors = async (slug) => {
const query = `
query($slug: String!) {
account(slug: $slug) {
... on Organization {
members(role: [BACKER]) {
nodes {
role
account {
id
name
slug
imageUrl
}
}
}
}
}
}
`;
const response = await fetch("https://api.opencollective.com/graphql/v2", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
query,
variables: { slug },
}),
});
const result = await response.json();
const members = result.data?.account?.members?.nodes || [];
return members.map((member) => ({
id: member.account.id,
name: member.account.name,
slug: member.account.slug,
imageUrl: member.account.imageUrl,
}));
};
@@ -61,6 +103,7 @@ const distinctBy = (callback) => (value, index, self) => {
const githubContributors = [];
const crowdinContributors = [];
const opencollectiveContributors = [];
for (const { repository, slug } of sources.github) {
githubContributors.push(...(await fetchGithubContributors(slug, repository)));
@@ -77,3 +120,14 @@ for (const { projectId } of sources.crowdin) {
}
const distinctCrowdinContributors = crowdinContributors.filter(distinctBy((contributor) => contributor.username));
await fs.writeFile("./static-data/translators.json", JSON.stringify(distinctCrowdinContributors));
for (const { slug } of sources.opencollective) {
opencollectiveContributors.push(...(await fetchOpenCollectiveContributors(slug)));
}
const distinctOpenCollectiveContributors = opencollectiveContributors.filter(
distinctBy((contributor) => contributor.slug),
);
await fs.writeFile(
"./static-data/opencollective-contributors.json",
JSON.stringify(distinctOpenCollectiveContributors),
);
+70
View File
@@ -0,0 +1,70 @@
import { readFile, writeFile } from "fs/promises";
import { integrationDefs } from "../packages/definitions/src/integration";
const FILE = "docs/README.md";
const MAX_COLUMNS_PER_ROW = 7;
async function updateIntegrationList() {
// Read current README content
const content = await readFile(FILE, "utf8");
// Define markers
const startMarker = "<!-- AUTO_GENERATE_INTEGRATION_LIST_START -->";
const endMarker = "<!-- AUTO_GENERATE_INTEGRATION_LIST_END -->";
// Find the section to replace
const startIndex = content.indexOf(startMarker);
const endIndex = content.indexOf(endMarker);
if (startIndex === -1 || endIndex === -1) {
throw new Error("Could not find markers in README.md");
}
// Generate the new integration list
const integrations = Object.values(integrationDefs)
.filter((def) => def.name !== "Mock")
.sort((a, b) => a.name.localeCompare(b.name));
const tableRows: string[] = [];
let currentRow: string[] = [];
integrations.forEach((integration) => {
currentRow.push(`<td align="center">
<a href="${integration.documentationUrl}" target="_blank" rel="noreferrer noopener">
<img src="${integration.iconUrl}" alt="${integration.name}" width="90" height="90" />
<br/>
<p align="center">${integration.name.replaceAll(" ", "<br/>")}</p>
</a>
</td>`);
if (currentRow.length === MAX_COLUMNS_PER_ROW) {
tableRows.push(`<tr>${currentRow.join("\n")}</tr>`);
currentRow = [];
}
});
// Add remaining items if any
if (currentRow.length > 0) {
tableRows.push(`<tr>${currentRow.join("\n")}</tr>`);
}
// Create the new content
const newSection = `${startMarker}
<div align="center">
<table>
<tbody>
${tableRows.join("\n")}
</tbody>
</table>
</div>
${endMarker}`;
// Replace the old section with the new one
const newContent = content.slice(0, startIndex) + newSection + content.slice(endIndex + endMarker.length);
// Write the updated content back to the file
await writeFile(FILE, newContent, "utf8");
}
updateIntegrationList().catch(console.error);