update readme, clean up code, add contributions

This commit is contained in:
fccview
2025-08-26 07:59:08 +01:00
parent 888297c56a
commit 08d37154b4
7 changed files with 31 additions and 46 deletions

View File

@@ -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

View File

@@ -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.

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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');

View File

@@ -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

View File

@@ -1,6 +1,6 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
// Removed standalone output for traditional Next.js deployment
}
module.exports = nextConfig