Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51f136daea | ||
|
|
f351929e5b |
No files matched your search
@@ -12,10 +12,6 @@ AUTH_SECRET="supersecret"
|
|||||||
# or starting the project without any (which will show a randomly generated one).
|
# or starting the project without any (which will show a randomly generated one).
|
||||||
SECRET_ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000
|
SECRET_ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
|
||||||
# Cron job API key is generated every time the container starts as it is required for communication between nextjs-api and tasks-api
|
|
||||||
# export CRON_JOB_API_KEY=$(openssl rand -base64 32)
|
|
||||||
# CRON_JOB_API_KEY="your-generated-api-key"
|
|
||||||
|
|
||||||
LOG_LEVEL='info'
|
LOG_LEVEL='info'
|
||||||
|
|
||||||
# This is how you can use the sqlite driver:
|
# This is how you can use the sqlite driver:
|
||||||
|
|||||||
@@ -43,8 +43,6 @@ RUN mkdir -p /var/cache/nginx && \
|
|||||||
COPY --from=builder /app/apps/nextjs/next.config.ts .
|
COPY --from=builder /app/apps/nextjs/next.config.ts .
|
||||||
COPY --from=builder /app/apps/nextjs/package.json .
|
COPY --from=builder /app/apps/nextjs/package.json .
|
||||||
|
|
||||||
COPY --from=builder /app/apps/tasks/tasks.cjs ./apps/tasks/tasks.cjs
|
|
||||||
COPY --from=builder /app/apps/websocket/wssServer.cjs ./apps/websocket/wssServer.cjs
|
|
||||||
COPY --from=builder /app/node_modules/better-sqlite3/build/Release/better_sqlite3.node /app/build/better_sqlite3.node
|
COPY --from=builder /app/node_modules/better-sqlite3/build/Release/better_sqlite3.node /app/build/better_sqlite3.node
|
||||||
|
|
||||||
COPY --from=builder /app/packages/db/migrations ./db/migrations
|
COPY --from=builder /app/packages/db/migrations ./db/migrations
|
||||||
|
|||||||
@@ -44,9 +44,11 @@
|
|||||||
"@homarr/server-settings": "workspace:^0.1.0",
|
"@homarr/server-settings": "workspace:^0.1.0",
|
||||||
"@homarr/settings": "workspace:^0.1.0",
|
"@homarr/settings": "workspace:^0.1.0",
|
||||||
"@homarr/spotlight": "workspace:^0.1.0",
|
"@homarr/spotlight": "workspace:^0.1.0",
|
||||||
|
"@homarr/tasks": "workspace:^0.1.0",
|
||||||
"@homarr/translation": "workspace:^0.1.0",
|
"@homarr/translation": "workspace:^0.1.0",
|
||||||
"@homarr/ui": "workspace:^0.1.0",
|
"@homarr/ui": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
|
"@homarr/websocket": "workspace:^0.1.0",
|
||||||
"@homarr/widgets": "workspace:^0.1.0",
|
"@homarr/widgets": "workspace:^0.1.0",
|
||||||
"@mantine/colors-generator": "catalog:",
|
"@mantine/colors-generator": "catalog:",
|
||||||
"@mantine/core": "catalog:",
|
"@mantine/core": "catalog:",
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export async function register() {
|
||||||
|
if (process.env.NEXT_RUNTIME === "nodejs") {
|
||||||
|
await import("@homarr/tasks");
|
||||||
|
await import("@homarr/websocket");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,12 +5,11 @@
|
|||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts"
|
".": "./src/main.ts"
|
||||||
},
|
},
|
||||||
"main": "./src/main.ts",
|
"main": "./src/main.ts",
|
||||||
"types": "./src/main.ts",
|
"types": "./src/main.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "esbuild src/main.ts --bundle --platform=node --loader:.scss=text --external:*.node --external:@opentelemetry/api --external:deasync --external:bcrypt --outfile=tasks.cjs",
|
|
||||||
"clean": "rm -rf .turbo node_modules",
|
"clean": "rm -rf .turbo node_modules",
|
||||||
"dev": "pnpm with-env tsx ./src/main.ts",
|
"dev": "pnpm with-env tsx ./src/main.ts",
|
||||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||||
@@ -24,7 +23,6 @@
|
|||||||
"@homarr/auth": "workspace:^0.1.0",
|
"@homarr/auth": "workspace:^0.1.0",
|
||||||
"@homarr/common": "workspace:^0.1.0",
|
"@homarr/common": "workspace:^0.1.0",
|
||||||
"@homarr/core": "workspace:^",
|
"@homarr/core": "workspace:^",
|
||||||
"@homarr/cron-job-api": "workspace:^0.1.0",
|
|
||||||
"@homarr/cron-jobs": "workspace:^0.1.0",
|
"@homarr/cron-jobs": "workspace:^0.1.0",
|
||||||
"@homarr/cron-jobs-core": "workspace:^0.1.0",
|
"@homarr/cron-jobs-core": "workspace:^0.1.0",
|
||||||
"@homarr/db": "workspace:^0.1.0",
|
"@homarr/db": "workspace:^0.1.0",
|
||||||
|
|||||||
@@ -1,52 +1,12 @@
|
|||||||
// This import has to be the first import in the file so that the agent is overridden before any other modules are imported.
|
// This import has to be the first import in the file so that the agent is overridden before any other modules are imported.
|
||||||
import "./overrides";
|
import "./overrides";
|
||||||
|
|
||||||
import type { FastifyTRPCPluginOptions } from "@trpc/server/adapters/fastify";
|
|
||||||
import { fastifyTRPCPlugin } from "@trpc/server/adapters/fastify";
|
|
||||||
import fastify from "fastify";
|
|
||||||
|
|
||||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
|
||||||
import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error";
|
|
||||||
import type { JobRouter } from "@homarr/cron-job-api";
|
|
||||||
import { jobRouter } from "@homarr/cron-job-api";
|
|
||||||
import { CRON_JOB_API_KEY_HEADER, CRON_JOB_API_PATH, CRON_JOB_API_PORT } from "@homarr/cron-job-api/constants";
|
|
||||||
import { jobGroup } from "@homarr/cron-jobs";
|
import { jobGroup } from "@homarr/cron-jobs";
|
||||||
import { db } from "@homarr/db";
|
|
||||||
|
|
||||||
import { JobManager } from "./job-manager";
|
|
||||||
import { onStartAsync } from "./on-start";
|
import { onStartAsync } from "./on-start";
|
||||||
|
|
||||||
const logger = createLogger({ module: "tasksMain" });
|
|
||||||
|
|
||||||
const server = fastify({
|
|
||||||
maxParamLength: 5000,
|
|
||||||
});
|
|
||||||
server.register(fastifyTRPCPlugin, {
|
|
||||||
prefix: CRON_JOB_API_PATH,
|
|
||||||
trpcOptions: {
|
|
||||||
router: jobRouter,
|
|
||||||
createContext: ({ req }) => ({
|
|
||||||
manager: new JobManager(db, jobGroup),
|
|
||||||
apiKey: req.headers[CRON_JOB_API_KEY_HEADER] as string | undefined,
|
|
||||||
}),
|
|
||||||
onError({ path, error }) {
|
|
||||||
logger.error(new ErrorWithMetadata("Error in tasks tRPC handler", { path }, { cause: error }));
|
|
||||||
},
|
|
||||||
} satisfies FastifyTRPCPluginOptions<JobRouter>["trpcOptions"],
|
|
||||||
});
|
|
||||||
|
|
||||||
void (async () => {
|
void (async () => {
|
||||||
await onStartAsync();
|
await onStartAsync();
|
||||||
await jobGroup.initializeAsync();
|
await jobGroup.initializeAsync();
|
||||||
await jobGroup.startAllAsync();
|
await jobGroup.startAllAsync();
|
||||||
|
|
||||||
try {
|
|
||||||
await server.listen({ port: CRON_JOB_API_PORT });
|
|
||||||
logger.info("Tasks web server started successfully", { port: CRON_JOB_API_PORT });
|
|
||||||
} catch (err) {
|
|
||||||
logger.error(
|
|
||||||
new ErrorWithMetadata("Failed to start tasks web server", { port: CRON_JOB_API_PORT }, { cause: err }),
|
|
||||||
);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
})();
|
})();
|
||||||
@@ -4,10 +4,12 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"exports": {
|
||||||
|
".": "./src/main.ts"
|
||||||
|
},
|
||||||
"main": "./src/main.ts",
|
"main": "./src/main.ts",
|
||||||
"types": "./src/main.ts",
|
"types": "./src/main.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "esbuild src/main.ts --bundle --platform=node --outfile=wssServer.cjs --external:bcrypt --external:@opentelemetry/api --external:deasync --external:cpu-features --loader:.html=text --loader:.scss=text --loader:.node=text",
|
|
||||||
"clean": "rm -rf .turbo node_modules",
|
"clean": "rm -rf .turbo node_modules",
|
||||||
"dev": "pnpm with-env tsx ./src/main.ts",
|
"dev": "pnpm with-env tsx ./src/main.ts",
|
||||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@
|
|||||||
"db:migration:sqlite:run": "pnpm -F db migration:sqlite:run",
|
"db:migration:sqlite:run": "pnpm -F db migration:sqlite:run",
|
||||||
"db:push": "pnpm -F db push:sqlite",
|
"db:push": "pnpm -F db push:sqlite",
|
||||||
"db:studio": "pnpm -F db studio",
|
"db:studio": "pnpm -F db studio",
|
||||||
"dev": "turbo dev --parallel",
|
"dev": "pnpm -F nextjs dev",
|
||||||
"docker:dev": "docker compose -f ./development/development.docker-compose.yml up",
|
"docker:dev": "docker compose -f ./development/development.docker-compose.yml up",
|
||||||
"format": "turbo format --continue -- --cache --cache-location node_modules/.cache/.prettiercache",
|
"format": "turbo format --continue -- --cache --cache-location node_modules/.cache/.prettiercache",
|
||||||
"format:fix": "turbo format --continue -- --write --cache --cache-location node_modules/.cache/.prettiercache",
|
"format:fix": "turbo format --continue -- --write --cache --cache-location node_modules/.cache/.prettiercache",
|
||||||
|
|||||||
@@ -25,7 +25,6 @@
|
|||||||
"@homarr/auth": "workspace:^0.1.0",
|
"@homarr/auth": "workspace:^0.1.0",
|
||||||
"@homarr/common": "workspace:^0.1.0",
|
"@homarr/common": "workspace:^0.1.0",
|
||||||
"@homarr/core": "workspace:^0.1.0",
|
"@homarr/core": "workspace:^0.1.0",
|
||||||
"@homarr/cron-job-api": "workspace:^0.1.0",
|
|
||||||
"@homarr/cron-job-status": "workspace:^0.1.0",
|
"@homarr/cron-job-status": "workspace:^0.1.0",
|
||||||
"@homarr/cron-jobs": "workspace:^0.1.0",
|
"@homarr/cron-jobs": "workspace:^0.1.0",
|
||||||
"@homarr/db": "workspace:^0.1.0",
|
"@homarr/db": "workspace:^0.1.0",
|
||||||
|
|||||||
@@ -2,33 +2,36 @@ import { observable } from "@trpc/server/observable";
|
|||||||
import z from "zod/v4";
|
import z from "zod/v4";
|
||||||
|
|
||||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||||
import { cronExpressionSchema, jobGroupKeys, jobNameSchema } from "@homarr/cron-job-api";
|
|
||||||
import { cronJobApi } from "@homarr/cron-job-api/client";
|
|
||||||
import type { TaskStatus } from "@homarr/cron-job-status";
|
import type { TaskStatus } from "@homarr/cron-job-status";
|
||||||
import { createCronJobStatusChannel } from "@homarr/cron-job-status";
|
import { createCronJobStatusChannel } from "@homarr/cron-job-status";
|
||||||
|
import { cronExpressionSchema, jobGroup } from "@homarr/cron-jobs";
|
||||||
|
import { JobManager } from "@homarr/cron-jobs/job-manager";
|
||||||
|
|
||||||
import { createTRPCRouter, permissionRequiredProcedure } from "../trpc";
|
import { createTRPCRouter, permissionRequiredProcedure } from "../trpc";
|
||||||
|
|
||||||
|
const jobGroupKeys = jobGroup.getKeys();
|
||||||
|
const jobNameSchema = z.enum(jobGroup.getKeys());
|
||||||
|
|
||||||
const logger = createLogger({ module: "cronJobsRouter" });
|
const logger = createLogger({ module: "cronJobsRouter" });
|
||||||
|
|
||||||
export const cronJobsRouter = createTRPCRouter({
|
export const cronJobsRouter = createTRPCRouter({
|
||||||
triggerJob: permissionRequiredProcedure
|
triggerJob: permissionRequiredProcedure
|
||||||
.requiresPermission("admin")
|
.requiresPermission("admin")
|
||||||
.input(jobNameSchema)
|
.input(jobNameSchema)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
await cronJobApi.trigger.mutate(input);
|
await new JobManager(ctx.db, jobGroup).triggerAsync(input);
|
||||||
}),
|
}),
|
||||||
startJob: permissionRequiredProcedure
|
startJob: permissionRequiredProcedure
|
||||||
.requiresPermission("admin")
|
.requiresPermission("admin")
|
||||||
.input(jobNameSchema)
|
.input(jobNameSchema)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
await cronJobApi.start.mutate(input);
|
await new JobManager(ctx.db, jobGroup).startAsync(input);
|
||||||
}),
|
}),
|
||||||
stopJob: permissionRequiredProcedure
|
stopJob: permissionRequiredProcedure
|
||||||
.requiresPermission("admin")
|
.requiresPermission("admin")
|
||||||
.input(jobNameSchema)
|
.input(jobNameSchema)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
await cronJobApi.stop.mutate(input);
|
await new JobManager(ctx.db, jobGroup).stopAsync(input);
|
||||||
}),
|
}),
|
||||||
updateJobInterval: permissionRequiredProcedure
|
updateJobInterval: permissionRequiredProcedure
|
||||||
.requiresPermission("admin")
|
.requiresPermission("admin")
|
||||||
@@ -38,23 +41,23 @@ export const cronJobsRouter = createTRPCRouter({
|
|||||||
cron: cronExpressionSchema,
|
cron: cronExpressionSchema,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
await cronJobApi.updateInterval.mutate(input);
|
await new JobManager(ctx.db, jobGroup).updateIntervalAsync(input.name, input.cron);
|
||||||
}),
|
}),
|
||||||
disableJob: permissionRequiredProcedure
|
disableJob: permissionRequiredProcedure
|
||||||
.requiresPermission("admin")
|
.requiresPermission("admin")
|
||||||
.input(jobNameSchema)
|
.input(jobNameSchema)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
await cronJobApi.disable.mutate(input);
|
await new JobManager(ctx.db, jobGroup).disableAsync(input);
|
||||||
}),
|
}),
|
||||||
enableJob: permissionRequiredProcedure
|
enableJob: permissionRequiredProcedure
|
||||||
.requiresPermission("admin")
|
.requiresPermission("admin")
|
||||||
.input(jobNameSchema)
|
.input(jobNameSchema)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
await cronJobApi.enable.mutate(input);
|
await new JobManager(ctx.db, jobGroup).enableAsync(input);
|
||||||
}),
|
}),
|
||||||
getJobs: permissionRequiredProcedure.requiresPermission("admin").query(async () => {
|
getJobs: permissionRequiredProcedure.requiresPermission("admin").query(async ({ ctx }) => {
|
||||||
return await cronJobApi.getAll.query();
|
return await new JobManager(ctx.db, jobGroup).getAllAsync();
|
||||||
}),
|
}),
|
||||||
subscribeToStatusUpdates: permissionRequiredProcedure.requiresPermission("admin").subscription(() => {
|
subscribeToStatusUpdates: permissionRequiredProcedure.requiresPermission("admin").subscription(() => {
|
||||||
return observable<TaskStatus>((emit) => {
|
return observable<TaskStatus>((emit) => {
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
import baseConfig from "@homarr/eslint-config/base";
|
|
||||||
|
|
||||||
/** @type {import('typescript-eslint').Config} */
|
|
||||||
export default [
|
|
||||||
{
|
|
||||||
ignores: [],
|
|
||||||
},
|
|
||||||
...baseConfig,
|
|
||||||
];
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@homarr/cron-job-api",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"private": true,
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"type": "module",
|
|
||||||
"exports": {
|
|
||||||
".": "./src/index.ts",
|
|
||||||
"./env": "./src/env.ts",
|
|
||||||
"./constants": "./src/constants.ts",
|
|
||||||
"./client": "./src/client.ts"
|
|
||||||
},
|
|
||||||
"typesVersions": {
|
|
||||||
"*": {
|
|
||||||
"*": [
|
|
||||||
"src/*"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"clean": "rm -rf .turbo node_modules",
|
|
||||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
|
||||||
"lint": "eslint",
|
|
||||||
"typecheck": "tsc --noEmit"
|
|
||||||
},
|
|
||||||
"prettier": "@homarr/prettier-config",
|
|
||||||
"dependencies": {
|
|
||||||
"@homarr/common": "workspace:^0.1.0",
|
|
||||||
"@homarr/core": "workspace:^0.1.0",
|
|
||||||
"@homarr/cron-jobs": "workspace:^0.1.0",
|
|
||||||
"@tanstack/react-query": "catalog:",
|
|
||||||
"@trpc/client": "catalog:",
|
|
||||||
"@trpc/server": "catalog:",
|
|
||||||
"@trpc/tanstack-react-query": "catalog:",
|
|
||||||
"node-cron": "catalog:",
|
|
||||||
"react": "catalog:",
|
|
||||||
"zod": "catalog:"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
|
||||||
"@types/node-cron": "catalog:",
|
|
||||||
"@types/react": "catalog:",
|
|
||||||
"eslint": "catalog:",
|
|
||||||
"typescript": "catalog:"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import { createTRPCClient, httpLink } from "@trpc/client";
|
|
||||||
|
|
||||||
import type { JobRouter } from ".";
|
|
||||||
import { CRON_JOB_API_KEY_HEADER, CRON_JOB_API_PATH, CRON_JOB_API_PORT } from "./constants";
|
|
||||||
import { env } from "./env";
|
|
||||||
|
|
||||||
export const cronJobApi = createTRPCClient<JobRouter>({
|
|
||||||
links: [
|
|
||||||
httpLink({
|
|
||||||
url: `${getBaseUrl()}${CRON_JOB_API_PATH}`,
|
|
||||||
headers: {
|
|
||||||
[CRON_JOB_API_KEY_HEADER]: env.CRON_JOB_API_KEY,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
function getBaseUrl() {
|
|
||||||
return `http://localhost:${CRON_JOB_API_PORT}`;
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export const CRON_JOB_API_PORT = 3002;
|
|
||||||
export const CRON_JOB_API_PATH = "/trpc";
|
|
||||||
export const CRON_JOB_API_KEY_HEADER = "cron-job-api-key";
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import { z } from "zod/v4";
|
|
||||||
|
|
||||||
import { env as commonEnv } from "@homarr/common/env";
|
|
||||||
import { createEnv } from "@homarr/core/infrastructure/env";
|
|
||||||
|
|
||||||
export const env = createEnv({
|
|
||||||
server: {
|
|
||||||
CRON_JOB_API_KEY: commonEnv.NODE_ENV === "development" ? z.string().default("test") : z.string(),
|
|
||||||
},
|
|
||||||
experimental__runtimeEnv: process.env,
|
|
||||||
});
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
import { initTRPC, TRPCError } from "@trpc/server";
|
|
||||||
import { validate } from "node-cron";
|
|
||||||
import { z } from "zod/v4";
|
|
||||||
|
|
||||||
import type { JobGroupKeys } from "@homarr/cron-jobs";
|
|
||||||
import { jobGroup } from "@homarr/cron-jobs";
|
|
||||||
|
|
||||||
import { env } from "./env";
|
|
||||||
|
|
||||||
export const jobGroupKeys = jobGroup.getKeys();
|
|
||||||
export const jobNameSchema = z.enum(jobGroup.getKeys());
|
|
||||||
|
|
||||||
export interface IJobManager {
|
|
||||||
startAsync(name: JobGroupKeys): Promise<void>;
|
|
||||||
triggerAsync(name: JobGroupKeys): Promise<void>;
|
|
||||||
stopAsync(name: JobGroupKeys): Promise<void>;
|
|
||||||
updateIntervalAsync(name: JobGroupKeys, cron: string): Promise<void>;
|
|
||||||
disableAsync(name: JobGroupKeys): Promise<void>;
|
|
||||||
enableAsync(name: JobGroupKeys): Promise<void>;
|
|
||||||
getAllAsync(): Promise<
|
|
||||||
{
|
|
||||||
name: JobGroupKeys;
|
|
||||||
cron: string;
|
|
||||||
preventManualExecution: boolean;
|
|
||||||
preventCustomInterval: boolean;
|
|
||||||
isEnabled: boolean;
|
|
||||||
}[]
|
|
||||||
>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const t = initTRPC
|
|
||||||
.context<{
|
|
||||||
manager: IJobManager;
|
|
||||||
apiKey?: string;
|
|
||||||
}>()
|
|
||||||
.create();
|
|
||||||
|
|
||||||
const createTrpcRouter = t.router;
|
|
||||||
const apiKeyProcedure = t.procedure.use(({ ctx, next }) => {
|
|
||||||
if (ctx.apiKey !== env.CRON_JOB_API_KEY) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "FORBIDDEN",
|
|
||||||
message: "Missing or invalid API key",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return next({
|
|
||||||
ctx: {
|
|
||||||
...ctx,
|
|
||||||
apiKey: undefined, // Clear the API key after checking
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
export const cronExpressionSchema = z.string().refine((expression) => validate(expression), {
|
|
||||||
error: "Invalid cron expression",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const jobRouter = createTrpcRouter({
|
|
||||||
start: apiKeyProcedure.input(jobNameSchema).mutation(async ({ input, ctx }) => {
|
|
||||||
await ctx.manager.startAsync(input);
|
|
||||||
}),
|
|
||||||
trigger: apiKeyProcedure.input(jobNameSchema).mutation(async ({ input, ctx }) => {
|
|
||||||
await ctx.manager.triggerAsync(input);
|
|
||||||
}),
|
|
||||||
stop: apiKeyProcedure.input(jobNameSchema).mutation(async ({ input, ctx }) => {
|
|
||||||
await ctx.manager.stopAsync(input);
|
|
||||||
}),
|
|
||||||
updateInterval: apiKeyProcedure
|
|
||||||
.input(
|
|
||||||
z.object({
|
|
||||||
name: jobNameSchema,
|
|
||||||
cron: cronExpressionSchema,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
await ctx.manager.updateIntervalAsync(input.name, input.cron);
|
|
||||||
}),
|
|
||||||
disable: apiKeyProcedure.input(jobNameSchema).mutation(async ({ input, ctx }) => {
|
|
||||||
await ctx.manager.disableAsync(input);
|
|
||||||
}),
|
|
||||||
enable: apiKeyProcedure.input(jobNameSchema).mutation(async ({ input, ctx }) => {
|
|
||||||
await ctx.manager.enableAsync(input);
|
|
||||||
}),
|
|
||||||
getAll: apiKeyProcedure.query(({ ctx }) => {
|
|
||||||
return ctx.manager.getAllAsync();
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type JobRouter = typeof jobRouter;
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "@homarr/tsconfig/base.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
|
|
||||||
},
|
|
||||||
"include": ["*.ts", "src"],
|
|
||||||
"exclude": ["node_modules"]
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,8 @@
|
|||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./index.ts"
|
".": "./index.ts",
|
||||||
|
"./job-manager": "./src/job-manager.ts"
|
||||||
},
|
},
|
||||||
"typesVersions": {
|
"typesVersions": {
|
||||||
"*": {
|
"*": {
|
||||||
@@ -38,12 +39,15 @@
|
|||||||
"@homarr/server-settings": "workspace:^0.1.0",
|
"@homarr/server-settings": "workspace:^0.1.0",
|
||||||
"@homarr/translation": "workspace:^0.1.0",
|
"@homarr/translation": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"semver-parser": "catalog:"
|
"node-cron": "catalog:",
|
||||||
|
"semver-parser": "catalog:",
|
||||||
|
"zod": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
|
"@types/node-cron": "catalog:",
|
||||||
"eslint": "catalog:",
|
"eslint": "catalog:",
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ import { updateCheckerJob } from "./jobs/update-checker";
|
|||||||
import { weatherJob } from "./jobs/weather";
|
import { weatherJob } from "./jobs/weather";
|
||||||
import { createCronJobGroup } from "./lib";
|
import { createCronJobGroup } from "./lib";
|
||||||
|
|
||||||
export const jobGroup = createCronJobGroup({
|
const getJobGroup = () => {
|
||||||
|
return createCronJobGroup({
|
||||||
analytics: analyticsJob,
|
analytics: analyticsJob,
|
||||||
iconsUpdater: iconsUpdaterJob,
|
iconsUpdater: iconsUpdaterJob,
|
||||||
ping: pingJob,
|
ping: pingJob,
|
||||||
@@ -55,5 +56,16 @@ export const jobGroup = createCronJobGroup({
|
|||||||
timetable: timetableJob,
|
timetable: timetableJob,
|
||||||
tracearr: tracearrJob,
|
tracearr: tracearrJob,
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add global type augmentation for homarr
|
||||||
|
declare global {
|
||||||
|
var cronJobs: ReturnType<typeof getJobGroup> | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
global.cronJobs ??= getJobGroup();
|
||||||
|
|
||||||
|
export const jobGroup = global.cronJobs;
|
||||||
|
|
||||||
export type JobGroupKeys = ReturnType<(typeof jobGroup)["getKeys"]>[number];
|
export type JobGroupKeys = ReturnType<(typeof jobGroup)["getKeys"]>[number];
|
||||||
|
export * from "./schemas";
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import { schedule, validate as validateCron } from "node-cron";
|
import { schedule, validate as validateCron } from "node-cron";
|
||||||
|
|
||||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||||
import type { IJobManager } from "@homarr/cron-job-api";
|
|
||||||
import type { jobGroup as cronJobGroup, JobGroupKeys } from "@homarr/cron-jobs";
|
import type { jobGroup as cronJobGroup, JobGroupKeys } from "@homarr/cron-jobs";
|
||||||
import type { Database, InferInsertModel } from "@homarr/db";
|
import type { Database, InferInsertModel } from "@homarr/db";
|
||||||
import { eq } from "@homarr/db";
|
import { eq } from "@homarr/db";
|
||||||
@@ -9,7 +8,7 @@ import { cronJobConfigurations } from "@homarr/db/schema";
|
|||||||
|
|
||||||
const logger = createLogger({ module: "jobManager" });
|
const logger = createLogger({ module: "jobManager" });
|
||||||
|
|
||||||
export class JobManager implements IJobManager {
|
export class JobManager {
|
||||||
constructor(
|
constructor(
|
||||||
private db: Database,
|
private db: Database,
|
||||||
private jobGroup: typeof cronJobGroup,
|
private jobGroup: typeof cronJobGroup,
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { validate } from "node-cron";
|
||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
export const cronExpressionSchema = z.string().refine((expression) => validate(expression), {
|
||||||
|
error: "Invalid cron expression",
|
||||||
|
});
|
||||||
Generated
+15
-61
@@ -754,6 +754,9 @@ importers:
|
|||||||
'@homarr/spotlight':
|
'@homarr/spotlight':
|
||||||
specifier: workspace:^0.1.0
|
specifier: workspace:^0.1.0
|
||||||
version: link:../../packages/spotlight
|
version: link:../../packages/spotlight
|
||||||
|
'@homarr/tasks':
|
||||||
|
specifier: workspace:^0.1.0
|
||||||
|
version: link:../tasks
|
||||||
'@homarr/translation':
|
'@homarr/translation':
|
||||||
specifier: workspace:^0.1.0
|
specifier: workspace:^0.1.0
|
||||||
version: link:../../packages/translation
|
version: link:../../packages/translation
|
||||||
@@ -763,6 +766,9 @@ importers:
|
|||||||
'@homarr/validation':
|
'@homarr/validation':
|
||||||
specifier: workspace:^0.1.0
|
specifier: workspace:^0.1.0
|
||||||
version: link:../../packages/validation
|
version: link:../../packages/validation
|
||||||
|
'@homarr/websocket':
|
||||||
|
specifier: workspace:^0.1.0
|
||||||
|
version: link:../websocket
|
||||||
'@homarr/widgets':
|
'@homarr/widgets':
|
||||||
specifier: workspace:^0.1.0
|
specifier: workspace:^0.1.0
|
||||||
version: link:../../packages/widgets
|
version: link:../../packages/widgets
|
||||||
@@ -947,9 +953,6 @@ importers:
|
|||||||
'@homarr/core':
|
'@homarr/core':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
version: link:../../packages/core
|
version: link:../../packages/core
|
||||||
'@homarr/cron-job-api':
|
|
||||||
specifier: workspace:^0.1.0
|
|
||||||
version: link:../../packages/cron-job-api
|
|
||||||
'@homarr/cron-jobs':
|
'@homarr/cron-jobs':
|
||||||
specifier: workspace:^0.1.0
|
specifier: workspace:^0.1.0
|
||||||
version: link:../../packages/cron-jobs
|
version: link:../../packages/cron-jobs
|
||||||
@@ -1148,9 +1151,6 @@ importers:
|
|||||||
'@homarr/core':
|
'@homarr/core':
|
||||||
specifier: workspace:^0.1.0
|
specifier: workspace:^0.1.0
|
||||||
version: link:../core
|
version: link:../core
|
||||||
'@homarr/cron-job-api':
|
|
||||||
specifier: workspace:^0.1.0
|
|
||||||
version: link:../cron-job-api
|
|
||||||
'@homarr/cron-job-status':
|
'@homarr/cron-job-status':
|
||||||
specifier: workspace:^0.1.0
|
specifier: workspace:^0.1.0
|
||||||
version: link:../cron-job-status
|
version: link:../cron-job-status
|
||||||
@@ -1503,61 +1503,6 @@ importers:
|
|||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
|
|
||||||
packages/cron-job-api:
|
|
||||||
dependencies:
|
|
||||||
'@homarr/common':
|
|
||||||
specifier: workspace:^0.1.0
|
|
||||||
version: link:../common
|
|
||||||
'@homarr/core':
|
|
||||||
specifier: workspace:^0.1.0
|
|
||||||
version: link:../core
|
|
||||||
'@homarr/cron-jobs':
|
|
||||||
specifier: workspace:^0.1.0
|
|
||||||
version: link:../cron-jobs
|
|
||||||
'@tanstack/react-query':
|
|
||||||
specifier: 'catalog:'
|
|
||||||
version: 5.100.5(react@19.2.5)
|
|
||||||
'@trpc/client':
|
|
||||||
specifier: 'catalog:'
|
|
||||||
version: 11.16.0(@trpc/server@11.16.0(typescript@5.9.3))(typescript@5.9.3)
|
|
||||||
'@trpc/server':
|
|
||||||
specifier: 'catalog:'
|
|
||||||
version: 11.16.0(typescript@5.9.3)
|
|
||||||
'@trpc/tanstack-react-query':
|
|
||||||
specifier: 'catalog:'
|
|
||||||
version: 11.16.0(@tanstack/react-query@5.100.5(react@19.2.5))(@trpc/client@11.16.0(@trpc/server@11.16.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.16.0(typescript@5.9.3))(react@19.2.5)(typescript@5.9.3)
|
|
||||||
node-cron:
|
|
||||||
specifier: 'catalog:'
|
|
||||||
version: 4.2.1
|
|
||||||
react:
|
|
||||||
specifier: 'catalog:'
|
|
||||||
version: 19.2.5
|
|
||||||
zod:
|
|
||||||
specifier: 'catalog:'
|
|
||||||
version: 4.3.6
|
|
||||||
devDependencies:
|
|
||||||
'@homarr/eslint-config':
|
|
||||||
specifier: workspace:^0.2.0
|
|
||||||
version: link:../../tooling/eslint
|
|
||||||
'@homarr/prettier-config':
|
|
||||||
specifier: workspace:^0.1.0
|
|
||||||
version: link:../../tooling/prettier
|
|
||||||
'@homarr/tsconfig':
|
|
||||||
specifier: workspace:^0.1.0
|
|
||||||
version: link:../../tooling/typescript
|
|
||||||
'@types/node-cron':
|
|
||||||
specifier: 'catalog:'
|
|
||||||
version: 3.0.11
|
|
||||||
'@types/react':
|
|
||||||
specifier: 'catalog:'
|
|
||||||
version: 19.2.14
|
|
||||||
eslint:
|
|
||||||
specifier: 'catalog:'
|
|
||||||
version: 9.39.4
|
|
||||||
typescript:
|
|
||||||
specifier: 'catalog:'
|
|
||||||
version: 5.9.3
|
|
||||||
|
|
||||||
packages/cron-job-status:
|
packages/cron-job-status:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@homarr/redis':
|
'@homarr/redis':
|
||||||
@@ -1630,9 +1575,15 @@ importers:
|
|||||||
'@homarr/validation':
|
'@homarr/validation':
|
||||||
specifier: workspace:^0.1.0
|
specifier: workspace:^0.1.0
|
||||||
version: link:../validation
|
version: link:../validation
|
||||||
|
node-cron:
|
||||||
|
specifier: 'catalog:'
|
||||||
|
version: 4.2.1
|
||||||
semver-parser:
|
semver-parser:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 4.1.8
|
version: 4.1.8
|
||||||
|
zod:
|
||||||
|
specifier: 'catalog:'
|
||||||
|
version: 4.3.6
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@homarr/eslint-config':
|
'@homarr/eslint-config':
|
||||||
specifier: workspace:^0.2.0
|
specifier: workspace:^0.2.0
|
||||||
@@ -1643,6 +1594,9 @@ importers:
|
|||||||
'@homarr/tsconfig':
|
'@homarr/tsconfig':
|
||||||
specifier: workspace:^0.1.0
|
specifier: workspace:^0.1.0
|
||||||
version: link:../../tooling/typescript
|
version: link:../../tooling/typescript
|
||||||
|
'@types/node-cron':
|
||||||
|
specifier: 'catalog:'
|
||||||
|
version: 3.0.11
|
||||||
eslint:
|
eslint:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 9.39.4
|
version: 9.39.4
|
||||||
|
|||||||
+1
-11
@@ -16,8 +16,6 @@ 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
|
# 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)
|
export AUTH_SECRET=$(openssl rand -base64 32)
|
||||||
# Cron job API key is generated every time the container starts as it is required for communication between nextjs-api and tasks-api
|
|
||||||
export CRON_JOB_API_KEY=$(openssl rand -base64 32)
|
|
||||||
|
|
||||||
# Support secret file for encryption key (is not empty and file exists)
|
# Support secret file for encryption key (is not empty and file exists)
|
||||||
if [ -n "$SECRET_ENCRYPTION_KEY_FILE" ] && [ -f "$SECRET_ENCRYPTION_KEY_FILE" ]; then
|
if [ -n "$SECRET_ENCRYPTION_KEY_FILE" ] && [ -f "$SECRET_ENCRYPTION_KEY_FILE" ]; then
|
||||||
@@ -43,21 +41,13 @@ else
|
|||||||
REDIS_PID=$!
|
REDIS_PID=$!
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
node apps/tasks/tasks.cjs &
|
|
||||||
TASKS_PID=$!
|
|
||||||
|
|
||||||
node apps/websocket/wssServer.cjs &
|
|
||||||
WSS_PID=$!
|
|
||||||
|
|
||||||
node apps/nextjs/server.js &
|
node apps/nextjs/server.js &
|
||||||
NEXTJS_PID=$!
|
NEXTJS_PID=$!
|
||||||
|
|
||||||
# Function to handle SIGTERM and shut down services
|
# Function to handle SIGTERM and shut down services
|
||||||
terminate() {
|
terminate() {
|
||||||
echo "Received SIGTERM. Shutting down..."
|
echo "Received SIGTERM. Shutting down..."
|
||||||
kill -TERM $NGINX_PID $TASKS_PID $WSS_PID $NEXTJS_PID 2>/dev/null
|
kill -TERM $NGINX_PID $NEXTJS_PID 2>/dev/null
|
||||||
wait
|
wait
|
||||||
# kill redis-server last because of logging of other services and only if $REDIS_PID is set
|
# kill redis-server last because of logging of other services and only if $REDIS_PID is set
|
||||||
if [ -n "$REDIS_PID" ]; then
|
if [ -n "$REDIS_PID" ]; then
|
||||||
|
|||||||
Reference in new issue
Block a user