4 Commits

Author SHA1 Message Date
fccview
d21bed64fe edit readme to push to pr 2025-10-08 14:44:15 +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 47 additions and 18 deletions

1
.gitignore vendored
View File

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

View File

@@ -72,6 +72,12 @@ services:
# replace root with your user - find it with: ls -asl /var/spool/cron/crontabs/ # replace root with your user - find it with: ls -asl /var/spool/cron/crontabs/
# For multiple users, use comma-separated values: HOST_CRONTAB_USER=root,user1,user2 # For multiple users, use comma-separated values: HOST_CRONTAB_USER=root,user1,user2
- HOST_CRONTAB_USER=root - HOST_CRONTAB_USER=root
# --- !! IMPORTANT !!DOCKER EXEC USER
# If you do not specify this user to be a valid user on your system,
# any cronjob containing a docker command will fail. IDEALLY you should not be running
# docker commands as root, so this is only a fallback. ONLY ONE USER IS ALLOWED.
- DOCKER_EXEC_USER=fccview
volumes: volumes:
# --- MOUNT DOCKER SOCKET # --- MOUNT DOCKER SOCKET
# Mount Docker socket to execute commands on host # Mount Docker socket to execute commands on host
@@ -147,6 +153,7 @@ The following environment variables can be configured:
| `DOCKER` | `false` | ONLY set this to true if you are runnign the app via docker, in the docker-compose.yml file | | `DOCKER` | `false` | ONLY set this to true if you are runnign the app via docker, in the docker-compose.yml file |
| `HOST_CRONTAB_USER` | `root` | Comma separated list of users that run cronjobs on your host machine | | `HOST_CRONTAB_USER` | `root` | Comma separated list of users that run cronjobs on your host machine |
| `AUTH_PASSWORD` | `N/A` | If you set a password the application will be password protected with basic next-auth | | `AUTH_PASSWORD` | `N/A` | If you set a password the application will be password protected with basic next-auth |
| `DOCKER_EXEC_USER` | `N/A` | If you don't set this user you won't be able to run docker commands as root |
**Example**: To change the clock update interval to 60 seconds: **Example**: To change the clock update interval to 60 seconds:

View File

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

View File

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

View File

@@ -23,6 +23,12 @@ services:
# replace root with your user - find it with: ls -asl /var/spool/cron/crontabs/ # replace root with your user - find it with: ls -asl /var/spool/cron/crontabs/
# For multiple users, use comma-separated values: HOST_CRONTAB_USER=root,user1,user2 # For multiple users, use comma-separated values: HOST_CRONTAB_USER=root,user1,user2
- HOST_CRONTAB_USER=root - HOST_CRONTAB_USER=root
# --- !! IMPORTANT !!DOCKER EXEC USER
# If you do not specify this user to be a valid user on your system,
# any cronjob containing a docker command will fail. IDEALLY you should not be running
# docker commands as root, so this is only a fallback. ONLY ONE USER IS ALLOWED.
- DOCKER_EXEC_USER=fccview
volumes: volumes:
# --- MOUNT DOCKER SOCKET # --- MOUNT DOCKER SOCKET
# Mount Docker socket to execute commands on host # Mount Docker socket to execute commands on host