فهرست منبع

refactor: run watch with sudo by default

Nicolas Meienberger 1 سال پیش
والد
کامیت
00f6ab1e64

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "runtipi",
-  "version": "1.6.1",
+  "version": "2.0.0",
   "description": "A homeserver for everyone",
   "scripts": {
     "knip": "knip",

+ 1 - 1
packages/cli/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@runtipi/cli",
-  "version": "1.6.0",
+  "version": "2.0.0",
   "description": "",
   "main": "index.js",
   "bin": "dist/index.js",

+ 4 - 1
packages/cli/src/executors/app/app.executors.ts

@@ -158,7 +158,10 @@ export class AppExecutors {
 
       this.logger.info(`App ${appId} started`);
 
-      await execAsync(`chmod -R a+rwx ${path.join(appDataDirPath)}`).catch(() => {});
+      this.logger.info(`Setting permissions for app ${appId}`);
+      await execAsync(`chmod -R a+rwx ${path.join(appDataDirPath)}`).catch(() => {
+        this.logger.error(`Error setting permissions for app ${appId}`);
+      });
 
       return { success: true, message: `App ${appId} started successfully` };
     } catch (err) {

+ 40 - 13
packages/cli/src/executors/system/system.executors.ts

@@ -19,6 +19,7 @@ import { pathExists } from '@/utils/fs-helpers';
 import { getEnv } from '@/utils/environment/environment';
 import { fileLogger } from '@/utils/logger/file-logger';
 import { runPostgresMigrations } from '@/utils/migrations/run-migration';
+import { getUserIds } from '@/utils/environment/user';
 
 const execAsync = promisify(exec);
 
@@ -103,16 +104,15 @@ export class SystemExecutors {
       path.join(rootFolderHost, 'VERSION'),
     ];
 
+    const { uid, gid } = getUserIds();
+
     // Give permission to read and write to all files and folders for the current user
     await Promise.all(
       filesAndFolders.map(async (fileOrFolder) => {
         if (await pathExists(fileOrFolder)) {
-          await execAsync(`sudo chown -R :tipi ${fileOrFolder}`).catch((e) => {
-            fileLogger.error(e);
-          });
-          await execAsync(`sudo chmod -R 770 ${fileOrFolder}`).catch((e) => {
-            fileLogger.error(e);
-          });
+          await execAsync(`sudo chown -R ${uid}:${gid} ${fileOrFolder}`);
+
+          await execAsync(`sudo chmod -R 750 ${fileOrFolder}`);
         }
       }),
     );
@@ -181,11 +181,22 @@ export class SystemExecutors {
    * This method will start Tipi.
    * It will copy the system files, generate the system env file, pull the images and start the containers.
    */
-  public start = async (permissions = true) => {
+  public start = async (sudo = true) => {
     const spinner = new TerminalSpinner('Starting Tipi...');
     try {
-      if (permissions) {
+      if (sudo) {
         await this.ensureFilePermissions(this.rootFolder);
+      } else {
+        console.log(
+          boxen("You are running in sudoless mode. While tipi should work as expected, you'll probably face folder permission issues with the apps you install and you'll need to manually fix them.", {
+            title: '⛔️ Sudoless mode',
+            titleAlignment: 'center',
+            padding: 1,
+            borderStyle: 'double',
+            borderColor: 'red',
+            margin: { top: 1 },
+          }),
+        );
       }
 
       spinner.start();
@@ -194,7 +205,7 @@ export class SystemExecutors {
 
       spinner.done('System files copied');
 
-      if (permissions) {
+      if (sudo) {
         await this.ensureFilePermissions(this.rootFolder, false);
       }
 
@@ -238,8 +249,16 @@ export class SystemExecutors {
       const out = fs.openSync('./logs/watcher.log', 'a');
       const err = fs.openSync('./logs/watcher.log', 'a');
 
-      const subprocess = spawn('./runtipi-cli', [process.argv[1] as string, 'watch'], { cwd: this.rootFolder, detached: true, stdio: ['ignore', out, err] });
-      subprocess.unref();
+      if (sudo) {
+        // Dummy sudo call to ask for password
+        await execAsync('sudo echo "Dummy sudo call"');
+        const subprocess = spawn('sudo', ['./runtipi-cli', 'watch'], { cwd: this.rootFolder, stdio: ['inherit', out, err] });
+
+        subprocess.unref();
+      } else {
+        const subprocess = spawn('./runtipi-cli', [process.argv[1] as string, 'watch'], { cwd: this.rootFolder, detached: true, stdio: ['ignore', out, err] });
+        subprocess.unref();
+      }
 
       spinner.done('Watcher started');
 
@@ -318,7 +337,7 @@ export class SystemExecutors {
    * runtipi-cli binary with the new one.
    * @param {string} target
    */
-  public update = async (target: string) => {
+  public update = async (target: string, sudo = true) => {
     const spinner = new TerminalSpinner('Evaluating target version...');
     try {
       spinner.start();
@@ -408,7 +427,15 @@ export class SystemExecutors {
       // eslint-disable-next-line no-promise-executor-return
       await new Promise((resolve) => setTimeout(resolve, 3000));
 
-      const childProcess = spawn('./runtipi-cli', [process.argv[1] as string, 'start', '--no-permissions']);
+      const args = [process.argv[1] as string, 'start'];
+
+      const { isSudo } = getUserIds();
+
+      if (!sudo && !isSudo) {
+        args.push('--no-sudo');
+      }
+
+      const childProcess = spawn('./runtipi-cli', args);
 
       childProcess.stdout.on('data', (data) => {
         process.stdout.write(data);

+ 2 - 1
packages/cli/src/index.ts

@@ -21,9 +21,10 @@ const main = async () => {
     .command('start')
     .description('Start tipi')
     .option('--no-permissions', 'Skip permissions check')
+    .option('--no-sudo', 'Skip sudo usage')
     .action(async (options) => {
       const systemExecutors = new SystemExecutors();
-      await systemExecutors.start(options.permissions);
+      await systemExecutors.start(options.sudo);
     });
 
   program

+ 9 - 1
packages/cli/src/services/watcher/watcher.ts

@@ -4,10 +4,14 @@ import { exec } from 'child_process';
 import { promisify } from 'util';
 import { AppExecutors, RepoExecutors, SystemExecutors } from '@/executors';
 import { getEnv } from '@/utils/environment/environment';
+import { getUserIds } from '@/utils/environment/user';
 
 const execAsync = promisify(exec);
 
 const runCommand = async (jobData: unknown) => {
+  const { gid, uid, isSudo } = getUserIds();
+  console.log(`Running command with uid ${uid} and gid ${gid}`);
+
   const { installApp, startApp, stopApp, uninstallApp, updateApp, regenerateAppEnv } = new AppExecutors();
   const { cloneRepo, pullRepo } = new RepoExecutors();
   const { systemInfo, restart, update } = new SystemExecutors();
@@ -65,7 +69,11 @@ const runCommand = async (jobData: unknown) => {
     }
 
     if (data.command === 'update') {
-      ({ success, message } = await update(data.version));
+      if (!isSudo) {
+        ({ success, message } = await update(data.version, false));
+      } else {
+        ({ success, message } = await update(data.version, true));
+      }
     }
   }
 

+ 12 - 0
packages/cli/src/utils/environment/user.ts

@@ -0,0 +1,12 @@
+/**
+ * Returns the user id and group id of the current user
+ */
+export const getUserIds = () => {
+  if (process.getgid && process.getuid) {
+    const isSudo = process.getgid() === 0 && process.getuid() === 0;
+
+    return { uid: process.getuid(), gid: process.getgid(), isSudo };
+  }
+
+  return { uid: 1000, gid: 1000, isSudo: false };
+};