8 Commits

Author SHA1 Message Date
fccview
e2e95968ef Merge pull request #43 from Navino16/feature/extra-groups-ids
Very happy with these changes, thank you for the help!
2025-10-08 16:04:56 +01:00
fccview
a4ae5ec148 update readme 2025-10-08 16:03:02 +01:00
fccview
8e8069ee92 remove docker exec user and update readme with a note for docker compose as root 2025-10-08 15:44:31 +01:00
fccview
f96c37b55c remove debugging logs 2025-10-08 15:01:18 +01:00
fccview
cda9685e6d Add Navino to readme table 2025-10-08 14:53:56 +01:00
fccview
8329c0d030 make it so root works via a user instead, also update permissions for scripts on creation so they are executable right off the bat 2025-10-08 14:34:34 +01:00
Nathan JAUNET
6e34474993 Preserve user permission for command execution 2025-10-02 17:23:44 +02:00
fccview
65ac81d97c Merge pull request #40 from fccview/bugfix/fix-scripts-issues
Bugfix/fix scripts issues
2025-09-20 21:14:07 +01:00
5 changed files with 48 additions and 31 deletions

1
.gitignore vendored
View File

@@ -11,5 +11,6 @@ node_modules
.vscode
.DS_Store
.cursorignore
.idea
tsconfig.tsbuildinfo
docker-compose.test.yml

View File

@@ -2,18 +2,6 @@
<img src="public/heading.png" width="400px">
</p>
# ATTENTION BREAKING UPDATE!!
> The latest `main` branch has completely changed the way this app used to run.
> The main reason being trying to address some security concerns and make the whole application work
> across multiple platform without too much trouble.
>
> If you came here due to this change trying to figure out why your app stopped working you have two options:
>
> 1 - Update your `docker-compose.yml` with the new one provided within this readme (or just copy [docker-compose.yml](docker-compose.yml))
>
> 2 - Keep your `docker-compose.yml` file as it is and use the legacy tag in the image `image: ghcr.io/fccview/cronmaster:legacy`. However bear in mind this will not be supported going forward, any issue regarding the legacy tag will be ignored and I will only support the main branch. Feel free to fork that specific branch in case you want to work on it yourself :)
## Features
- **Modern UI**: Beautiful, responsive interface with dark/light mode.
@@ -23,6 +11,23 @@
- **Docker Support**: Runs entirely from a Docker container.
- **Easy Setup**: Quick presets for common cron schedules.
<br />
---
<p align="center">
<a href="http://discord.gg/invite/mMuk2WzVZu">
<img width="40" src="public/repo-images/discord_icon.webp">
</a>
<br />
<i>Join the discord server for more info</i>
<br />
</p>
---
<br />
## Before we start
Hey there! 👋 Just a friendly heads-up: I'm a big believer in open source and love sharing my work with the community. Everything you find in my GitHub repos is and always will be 100% free. If someone tries to sell you a "premium" version of any of my projects while claiming to be me, please know that this is not legitimate. 🚫
@@ -166,6 +171,8 @@ HOST_PROJECT_DIR=/home/<your_user_here>/homelab/cronmaster
- `HOST_PROJECT_DIR` is required in order for the scripts created within the app to run properly
- The `DOCKER=true` environment variable enables direct file access mode for crontab operations. This is REQUIRED when running the application in docker mode.
**Important Note on Root Commands**: When running commands as `root` within Cronmaster, ensure that these commands also function correctly as `root` on your host machine. If a command works as `root` on your host but fails within Cronmaster, please open an issue with detailed information.
## Usage
### Viewing System Information
@@ -257,6 +264,9 @@ I would like to thank the following members for raising issues and help test/deb
<td align="center" valign="top" width="20%">
<a href="https://github.com/cerede2000"><img width="100" height="100" src="https://avatars.githubusercontent.com/u/38144752?v=4&size=100"><br />cerede2000</a>
</td>
<td align="center" valign="top" width="20%">
<a href="https://github.com/Navino16"><img width="100" height="100" src="https://avatars.githubusercontent.com/u/22234867?v=4&size=100"><br />Navino16</a>
</td>
</tr>
</tbody>
</table>

View File

@@ -265,12 +265,10 @@ export const runCronJob = async (
if (isDocker) {
const userInfo = await getUserInfo(job.user);
const executionUser = userInfo ? userInfo.username : "root";
const escapedCommand = job.command.replace(/'/g, "'\\''");
if (userInfo && userInfo.username !== "root") {
command = `nsenter -t 1 -m -u -i -n -p --setuid=${userInfo.uid} --setgid=${userInfo.gid} sh -c "${job.command}"`;
} else {
command = `nsenter -t 1 -m -u -i -n -p sh -c "${job.command}"`;
}
command = `nsenter -t 1 -m -u -i -n -p su - ${executionUser} -c '${escapedCommand}'`;
}
const { stdout, stderr } = await execAsync(command, {

View File

@@ -21,7 +21,7 @@ const sanitizeScriptName = (name: string): string => {
.replace(/-+/g, "-")
.replace(/^-|-$/g, "")
.substring(0, 50);
}
};
const generateUniqueFilename = async (baseName: string): Promise<string> => {
const scripts = await loadAllScripts();
@@ -34,14 +34,14 @@ const generateUniqueFilename = async (baseName: string): Promise<string> => {
}
return filename;
}
};
const ensureScriptsDirectory = async () => {
const scriptsDir = await SCRIPTS_DIR();
if (!existsSync(scriptsDir)) {
await mkdir(scriptsDir, { recursive: true });
}
}
};
const ensureHostScriptsDirectory = async () => {
const hostProjectDir = process.env.HOST_PROJECT_DIR || process.cwd();
@@ -50,7 +50,7 @@ const ensureHostScriptsDirectory = async () => {
if (!existsSync(hostScriptsDir)) {
await mkdir(hostScriptsDir, { recursive: true });
}
}
};
const saveScriptFile = async (filename: string, content: string) => {
const isDocker = process.env.DOCKER === "true";
@@ -59,7 +59,13 @@ const saveScriptFile = async (filename: string, content: string) => {
const scriptPath = join(scriptsDir, filename);
await writeFile(scriptPath, content, "utf8");
}
try {
await execAsync(`chmod +x "${scriptPath}"`);
} catch (error) {
console.error(`Failed to set execute permissions on ${scriptPath}:`, error);
}
};
const deleteScriptFile = async (filename: string) => {
const isDocker = process.env.DOCKER === "true";
@@ -68,11 +74,11 @@ const deleteScriptFile = async (filename: string) => {
if (existsSync(scriptPath)) {
await unlink(scriptPath);
}
}
};
export const fetchScripts = async (): Promise<Script[]> => {
return await loadAllScripts();
}
};
export const createScript = async (
formData: FormData
@@ -120,7 +126,7 @@ export const createScript = async (
console.error("Error creating script:", error);
return { success: false, message: "Error creating script" };
}
}
};
export const updateScript = async (
formData: FormData
@@ -159,7 +165,7 @@ export const updateScript = async (
console.error("Error updating script:", error);
return { success: false, message: "Error updating script" };
}
}
};
export const deleteScript = async (
id: string
@@ -180,7 +186,7 @@ export const deleteScript = async (
console.error("Error deleting script:", error);
return { success: false, message: "Error deleting script" };
}
}
};
export const cloneScript = async (
id: string,
@@ -230,7 +236,7 @@ export const cloneScript = async (
console.error("Error cloning script:", error);
return { success: false, message: "Error cloning script" };
}
}
};
export const getScriptContent = async (filename: string): Promise<string> => {
try {
@@ -263,9 +269,11 @@ export const getScriptContent = async (filename: string): Promise<string> => {
console.error("Error reading script content:", error);
return "";
}
}
};
export const executeScript = async (filename: string): Promise<{
export const executeScript = async (
filename: string
): Promise<{
success: boolean;
output: string;
error: string;
@@ -301,4 +309,4 @@ export const executeScript = async (filename: string): Promise<{
error: error.message || "Unknown error",
};
}
}
};

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB