mirror of
https://github.com/fccview/cronmaster.git
synced 2025-12-23 22:18:20 -05:00
update readme, clean up code, add contributions
This commit is contained in:
17
Dockerfile
17
Dockerfile
@@ -1,6 +1,5 @@
|
||||
FROM node:20-slim AS base
|
||||
|
||||
# Install system utilities for system information and nsenter for host crontab access
|
||||
RUN apt-get update && apt-get install -y \
|
||||
pciutils \
|
||||
curl \
|
||||
@@ -8,11 +7,9 @@ RUN apt-get update && apt-get install -y \
|
||||
util-linux \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install dependencies only when needed
|
||||
FROM base AS deps
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies based on the preferred package manager
|
||||
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
|
||||
RUN \
|
||||
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
|
||||
@@ -21,20 +18,15 @@ RUN \
|
||||
else echo "Lockfile not found." && exit 1; \
|
||||
fi
|
||||
|
||||
# Rebuild the source code only when needed
|
||||
FROM base AS builder
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
# Next.js collects completely anonymous telemetry data about general usage.
|
||||
# Learn more here: https://nextjs.org/telemetry
|
||||
# Uncomment the following line in case you want to disable telemetry during the build.
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
RUN yarn build
|
||||
|
||||
# Production image, copy all the files and run next
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
|
||||
@@ -44,29 +36,20 @@ ENV NEXT_TELEMETRY_DISABLED=1
|
||||
RUN groupadd --system --gid 1001 nodejs
|
||||
RUN useradd --system --uid 1001 nextjs
|
||||
|
||||
# Create directories for mounted volumes with proper permissions
|
||||
RUN mkdir -p /app/scripts /app/data /app/snippets && \
|
||||
chown -R nextjs:nodejs /app/scripts /app/data /app/snippets
|
||||
|
||||
# Copy public directory
|
||||
COPY --from=builder /app/public ./public
|
||||
|
||||
# Copy the entire .next directory
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next
|
||||
|
||||
# Copy app directory for builtin snippets and other app files
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/app ./app
|
||||
|
||||
# Copy package.json and yarn.lock for yarn start
|
||||
COPY --from=builder /app/package.json ./package.json
|
||||
COPY --from=builder /app/yarn.lock ./yarn.lock
|
||||
|
||||
# Copy node_modules for production dependencies
|
||||
COPY --from=deps --chown=nextjs:nodejs /app/node_modules ./node_modules
|
||||
|
||||
# Don't set default user - let docker-compose decide
|
||||
# USER nextjs
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENV PORT=3000
|
||||
|
||||
28
README.md
28
README.md
@@ -49,7 +49,7 @@ If you find my projects helpful and want to fuel my late-night coding sessions w
|
||||
```bash
|
||||
services:
|
||||
cronjob-manager:
|
||||
build: .
|
||||
image: ghcr.io/fccview/cronmaster:main
|
||||
container_name: cronmaster-test
|
||||
user: "root"
|
||||
ports:
|
||||
@@ -212,6 +212,32 @@ The application uses standard cron format: `* * * * *`
|
||||
4. Add tests if applicable
|
||||
5. Submit a pull request
|
||||
|
||||
## Community shouts
|
||||
|
||||
I would like to thank the following members for raising issues and help test/debug them!
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="20%">
|
||||
<a href="https://github.com/hermannx5"><img width="100" height="100" alt="hermannx5" src="https://avatars.githubusercontent.com/u/46320338?v=4&s=100"><br/>hermannx5</a>
|
||||
</td>
|
||||
<td align="center" valign="top" width="20%">
|
||||
<a href="https://github.com/edersong"><img width="100" height="100" src="https://avatars.githubusercontent.com/u/64137913?v=4&s=100"><br />edersong</a>
|
||||
</td>
|
||||
<td align="center" valign="top" width="20%">
|
||||
<a href="https://github.com/corasaniti"><img width="100" height="100" src="https://avatars.githubusercontent.com/u/5001932?u=2e8bc25b74eb11f7675d38c8e312374794a7b6e0&v=4&s=100"><br />corasaniti</a>
|
||||
</td>
|
||||
<td align="center" valign="top" width="20%">
|
||||
<a href="https://github.com/abhisheknair"><img width="100" height="100" src="https://avatars.githubusercontent.com/u/5221047?u=313beaabbb4a8e82fe07a2523076b4dafdc0bfec&v=4&s=100"><br />abhisheknair</a>
|
||||
</td>
|
||||
<td align="center" valign="top" width="20%">
|
||||
<a href="https://github.com/mariushosting"><img width="100" height="100" src="https://avatars.githubusercontent.com/u/37554361?u=9007d0600680ac2b267bde2d8c19b05c06285a34&v=4&s=100"><br />mariushosting</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License.
|
||||
|
||||
@@ -24,7 +24,6 @@ async function readCronFiles(): Promise<string> {
|
||||
}
|
||||
}
|
||||
|
||||
// Use the new host crontab utility for Docker
|
||||
return await readHostCrontab();
|
||||
}
|
||||
|
||||
@@ -41,7 +40,6 @@ async function writeCronFiles(content: string): Promise<boolean> {
|
||||
}
|
||||
}
|
||||
|
||||
// Use the new host crontab utility for Docker
|
||||
return await writeHostCrontab(content);
|
||||
}
|
||||
|
||||
@@ -115,12 +113,10 @@ export async function addCronJob(
|
||||
? `# ${comment}\n${schedule} ${command}`
|
||||
: `${schedule} ${command}`;
|
||||
|
||||
// Handle empty crontab vs existing content properly
|
||||
let newCron;
|
||||
if (cronContent.trim() === "") {
|
||||
newCron = newEntry;
|
||||
} else {
|
||||
// Ensure existing content ends with newline before adding new entry
|
||||
const existingContent = cronContent.endsWith('\n') ? cronContent : cronContent + '\n';
|
||||
newCron = existingContent + newEntry;
|
||||
}
|
||||
|
||||
@@ -3,14 +3,8 @@ import { promisify } from "util";
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
/**
|
||||
* Execute crontab command on the host system using nsenter to host namespaces
|
||||
* This allows the Docker container to manage the host's crontab
|
||||
*/
|
||||
async function execHostCrontab(command: string): Promise<string> {
|
||||
try {
|
||||
// Find the host's init process and use nsenter to execute commands in host context
|
||||
// We use PID 1 which should be the host's init process due to pid: "host" in docker-compose
|
||||
const { stdout } = await execAsync(
|
||||
`nsenter -t 1 -m -u -i -n -p sh -c "${command}"`
|
||||
);
|
||||
@@ -21,24 +15,17 @@ async function execHostCrontab(command: string): Promise<string> {
|
||||
}
|
||||
}
|
||||
|
||||
// Get the target user for crontab operations by detecting the host user dynamically
|
||||
async function getTargetUser(): Promise<string> {
|
||||
try {
|
||||
// If explicitly set via environment variable, use that
|
||||
if (process.env.HOST_CRONTAB_USER) {
|
||||
return process.env.HOST_CRONTAB_USER;
|
||||
}
|
||||
|
||||
// Auto-detect the user by finding the owner of the docker socket
|
||||
// This will typically be the user who started docker compose
|
||||
const { stdout } = await execAsync('stat -c "%U" /var/run/docker.sock');
|
||||
const dockerSocketOwner = stdout.trim();
|
||||
|
||||
// If docker socket is owned by root, try to find the actual user
|
||||
// by looking at process tree or mounted directories
|
||||
if (dockerSocketOwner === 'root') {
|
||||
try {
|
||||
// Try to detect from the mounted project directory ownership
|
||||
const projectDir = process.env.NEXT_PUBLIC_HOST_PROJECT_DIR;
|
||||
if (projectDir) {
|
||||
const dirOwner = await execHostCrontab(`stat -c "%U" "${projectDir}"`);
|
||||
@@ -48,7 +35,6 @@ async function getTargetUser(): Promise<string> {
|
||||
console.warn("Could not detect user from project directory:", error);
|
||||
}
|
||||
|
||||
// Fall back to looking for non-root users with home directories
|
||||
try {
|
||||
const users = await execHostCrontab('getent passwd | grep ":/home/" | head -1 | cut -d: -f1');
|
||||
const firstUser = users.trim();
|
||||
@@ -59,14 +45,13 @@ async function getTargetUser(): Promise<string> {
|
||||
console.warn("Could not detect user from passwd:", error);
|
||||
}
|
||||
|
||||
// Last resort - return root
|
||||
return 'root';
|
||||
}
|
||||
|
||||
return dockerSocketOwner;
|
||||
} catch (error) {
|
||||
console.error("Error detecting target user:", error);
|
||||
return 'root'; // Safe fallback
|
||||
return 'root';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,13 +68,11 @@ export async function readHostCrontab(): Promise<string> {
|
||||
export async function writeHostCrontab(content: string): Promise<boolean> {
|
||||
try {
|
||||
const user = await getTargetUser();
|
||||
// Ensure content ends with a newline (required by crontab)
|
||||
let finalContent = content;
|
||||
if (!finalContent.endsWith('\n')) {
|
||||
finalContent += '\n';
|
||||
}
|
||||
|
||||
// Use base64 encoding to avoid all shell escaping issues
|
||||
const base64Content = Buffer.from(finalContent).toString('base64');
|
||||
await execHostCrontab(`echo '${base64Content}' | base64 -d | crontab -u ${user} -`);
|
||||
return true;
|
||||
|
||||
@@ -43,7 +43,6 @@ export async function GET(request: NextRequest) {
|
||||
}
|
||||
};
|
||||
|
||||
// Calculate memory usage properly - use active memory, not just used
|
||||
const actualUsed = memInfo.active || memInfo.used;
|
||||
const actualFree = memInfo.available || memInfo.free;
|
||||
const memUsage = ((actualUsed / memInfo.total) * 100);
|
||||
@@ -88,7 +87,6 @@ export async function GET(request: NextRequest) {
|
||||
? `${Math.round(((mainInterface.rx_sec || 0) + (mainInterface.tx_sec || 0)) / 1024 / 1024)} Mbps`
|
||||
: "Unknown";
|
||||
|
||||
// Get network latency via ping
|
||||
let latency = 0;
|
||||
try {
|
||||
const { exec } = require('child_process');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
cronjob-manager:
|
||||
build: .
|
||||
image: ghcr.io/fccview/cronmaster:main
|
||||
container_name: cronmaster-test
|
||||
user: "root"
|
||||
ports:
|
||||
@@ -10,8 +10,7 @@ services:
|
||||
- NODE_ENV=production
|
||||
- DOCKER=true
|
||||
- NEXT_PUBLIC_CLOCK_UPDATE_INTERVAL=30000
|
||||
#- NEXT_PUBLIC_HOST_PROJECT_DIR=/path/to/cronmaster/directory
|
||||
- NEXT_PUBLIC_HOST_PROJECT_DIR=/home/fccview/www/projects/cronjob
|
||||
- NEXT_PUBLIC_HOST_PROJECT_DIR=/path/to/cronmaster/directory
|
||||
# If docker struggles to find your crontab user, update this variable with it.
|
||||
# Obviously replace fccview with your user - find it with: ls -asl /var/spool/cron/crontabs/
|
||||
# - HOST_CRONTAB_USER=fccview
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
// Removed standalone output for traditional Next.js deployment
|
||||
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
|
||||
Reference in New Issue
Block a user