6 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
fccview
968fbae13c remove bugfix from runs 2025-09-20 21:06:00 +01:00
fccview
c739d29141 quick fix for script extra character and delete issues 2025-09-20 21:05:10 +01:00
8 changed files with 63 additions and 25 deletions

View File

@@ -2,7 +2,7 @@ name: Docker
on:
push:
branches: ["main", "legacy", "feature/*", "bugfix/*"]
branches: ["main", "legacy", "feature/*"]
tags: ["*"]
pull_request:
branches: ["main"]

1
.gitignore vendored
View File

@@ -11,5 +11,6 @@ node_modules
.vscode
.DS_Store
.cursorignore
.idea
tsconfig.tsbuildinfo
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/
# For multiple users, use comma-separated values: HOST_CRONTAB_USER=root,user1,user2
- 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:
# --- MOUNT DOCKER SOCKET
# 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 |
| `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 |
| `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:

View File

@@ -265,12 +265,19 @@ export const runCronJob = async (
if (isDocker) {
const userInfo = await getUserInfo(job.user);
const dockerExecUser = process.env.DOCKER_EXEC_USER;
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}"`;
let executionUser = userInfo ? userInfo.username : "root";
if (dockerExecUser && executionUser === "root") {
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, {

View File

@@ -6,7 +6,7 @@ import { join } from "path";
import { existsSync } from "fs";
import { exec } from "child_process";
import { promisify } from "util";
import { SCRIPTS_DIR } from "@/app/_utils/scripts";
import { SCRIPTS_DIR, normalizeLineEndings } from "@/app/_utils/scripts";
import { loadAllScripts, type Script } from "@/app/_utils/scriptScanner";
const execAsync = promisify(exec);
@@ -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,18 +59,26 @@ 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 scriptPath = join(await SCRIPTS_DIR(), filename);
const isDocker = process.env.DOCKER === "true";
const scriptsDir = isDocker ? "/app/scripts" : await SCRIPTS_DIR();
const scriptPath = join(scriptsDir, filename);
if (existsSync(scriptPath)) {
await unlink(scriptPath);
}
}
};
export const fetchScripts = async (): Promise<Script[]> => {
return await loadAllScripts();
}
};
export const createScript = async (
formData: FormData
@@ -95,7 +103,8 @@ export const createScript = async (
`;
const fullContent = metadataHeader + content;
const normalizedContent = normalizeLineEndings(content);
const fullContent = metadataHeader + normalizedContent;
await saveScriptFile(filename, fullContent);
revalidatePath("/");
@@ -117,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
@@ -145,7 +154,8 @@ export const updateScript = async (
`;
const fullContent = metadataHeader + content;
const normalizedContent = normalizeLineEndings(content);
const fullContent = metadataHeader + normalizedContent;
await saveScriptFile(existingScript.filename, fullContent);
revalidatePath("/");
@@ -155,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
@@ -176,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,
@@ -203,7 +213,8 @@ export const cloneScript = async (
`;
const fullContent = metadataHeader + originalContent;
const normalizedContent = normalizeLineEndings(originalContent);
const fullContent = metadataHeader + normalizedContent;
await saveScriptFile(filename, fullContent);
revalidatePath("/");
@@ -225,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 {
@@ -258,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;
@@ -296,4 +309,4 @@ export const executeScript = async (filename: string): Promise<{
error: error.message || "Unknown error",
};
}
}
};

View File

@@ -21,4 +21,8 @@ export const getHostScriptPath = async (filename: string): Promise<string> => {
return `bash ${join(hostScriptsDir, filename)}`;
}
export const normalizeLineEndings = (content: string): string => {
return content.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
};
export { SCRIPTS_DIR };

View File

@@ -23,6 +23,12 @@ services:
# 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
- 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:
# --- MOUNT DOCKER SOCKET
# Mount Docker socket to execute commands on host

View File

@@ -2,5 +2,5 @@
# @title: Hi, this is a demo script
# @description: This script logs a "hello world" to teach you how scripts work.
#!/bin/bash
#!/bin/bash
echo 'Hello World' > hello.txt