Merge branch 'dev' into fix-truenas-startup-time
This commit is contained in:
1081 files changed
+115844
-30430
No files matched your search
@@ -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
@@ -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
|
||||
@@ -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");
|
||||
@@ -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),
|
||||
);
|
||||
@@ -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);
|
||||
Reference in new issue
Block a user