Browse Source

chore: fix permission issues with running watcher sudoless

Nicolas Meienberger 1 year ago
parent
commit
ecf1eff86b

+ 1 - 1
package.json

@@ -23,7 +23,7 @@
     "build:next": "next build",
     "start:dev-container": "./.devcontainer/filewatcher.sh && npm run start:dev",
     "start:rc": "docker compose -f docker-compose.rc.yml --env-file .env up --build",
-    "start:dev": "npm run prepare && docker compose -f docker-compose.dev.yml up",
+    "start:dev": "npm run prepare && docker compose -f docker-compose.dev.yml up --build",
     "start:e2e": "./scripts/start-e2e.sh latest",
     "start:pg": "docker run --name test-db -p 5433:5432 -d --rm -e POSTGRES_PASSWORD=postgres postgres:14",
     "version": "echo $npm_package_version",

+ 24 - 23
packages/cli/src/executors/app/app.executors.ts

@@ -46,28 +46,27 @@ export class AppExecutors {
     return { appDataDirPath, appDirPath, configJsonPath, repoPath };
   };
 
-  private ensurePermissions = async (appId: string) => {
-    const { appDataDirPath, configJsonPath } = this.getAppPaths(appId);
-    if (!(await pathExists(appDataDirPath))) {
-      this.logger.info(`Creating app ${appId} data dir`);
-      await fs.promises.mkdir(appDataDirPath, { recursive: true });
-    }
-
-    // Check if app requires special uid and gid
-    if (await pathExists(configJsonPath)) {
-      const config = appInfoSchema.parse(JSON.parse(await fs.promises.readFile(configJsonPath, 'utf-8')));
-      const { uid, gid } = config;
-
-      if (uid && gid) {
-        this.logger.info(`Setting uid and gid to ${uid}:${gid}`);
-        await execAsync(`chown -R' ${uid}:${gid} ${path.join(appDataDirPath, 'data')}`);
-      }
-    }
-
-    // Remove all .gitkeep files from app data dir
-    await execAsync(`find ${appDataDirPath} -name '.gitkeep' -exec rm -f {} \\;`);
-    await execAsync(`chmod -R a+rwx ${appDataDirPath}`);
-  };
+  // private ensurePermissions = async (appId: string) => {
+  //   const { appDataDirPath, configJsonPath } = this.getAppPaths(appId);
+  //   if (!(await pathExists(appDataDirPath))) {
+  //     this.logger.info(`Creating app ${appId} data dir`);
+  //     await fs.promises.mkdir(appDataDirPath, { recursive: true });
+  //   }
+
+  //   // Check if app requires special uid and gid
+  //   if (await pathExists(configJsonPath)) {
+  //     const config = appInfoSchema.parse(JSON.parse(await fs.promises.readFile(configJsonPath, 'utf-8')));
+  //     const { uid, gid } = config;
+
+  //     if (uid && gid) {
+  //       this.logger.info(`Setting uid and gid to ${uid}:${gid}`);
+  //       await execAsync(`chown -R ${uid}:${gid} ${path.join(appDataDirPath, 'data')}`);
+  //     }
+  //   }
+
+  //   // Remove all .gitkeep files from app data dir
+  //   await execAsync(`find ${appDataDirPath} -name '.gitkeep' -exec rm -f {} \\;`);
+  // };
 
   /**
    * Given an app id, ensures that the app folder exists in the apps folder
@@ -133,6 +132,8 @@ export class AppExecutors {
         await copyDataDir(appId);
       }
 
+      // await this.ensurePermissions(appId);
+
       // run docker-compose up
       this.logger.info(`Running docker-compose up for app ${appId}`);
       await compose(appId, 'up -d');
@@ -223,7 +224,7 @@ export class AppExecutors {
       this.logger.info(`Copying folder ${repoPath} to ${appDirPath}`);
       await fs.promises.cp(repoPath, appDirPath, { recursive: true });
 
-      await this.ensurePermissions(appId);
+      // await this.ensurePermissions(appId);
 
       await compose(appId, 'pull');
 

+ 2 - 7
packages/cli/src/executors/repo/repo.executors.ts

@@ -49,15 +49,10 @@ export class RepoExecutors {
 
       this.logger.info(`Cloning repo ${repoUrl} to ${repoPath}`);
 
-      const { stdout, stderr } = await execAsync(`git clone ${repoUrl} ${repoPath}`);
-
-      if (stderr) {
-        this.logger.error(`Error cloning repo ${repoUrl}: ${stderr}`);
-        return { success: false, message: stderr };
-      }
+      await execAsync(`git clone ${repoUrl} ${repoPath}`);
 
       this.logger.info(`Cloned repo ${repoUrl} to ${repoPath}`);
-      return { success: true, message: stdout };
+      return { success: true, message: '' };
     } catch (err) {
       return this.handleRepoError(err);
     }

+ 32 - 0
packages/cli/src/executors/system/system.executors.ts

@@ -33,6 +33,7 @@ export class SystemExecutors {
       fileLogger.error(`An error occurred: ${err.message}`);
       return { success: false, message: err.message };
     }
+    fileLogger.error(`An error occurred: ${err}`);
 
     return { success: false, message: `An error occurred: ${err}` };
   };
@@ -49,6 +50,35 @@ export class SystemExecutors {
     };
   };
 
+  private ensureFilePermissions = async () => {
+    const { rootFolderHost } = getEnv();
+
+    console.log('Tipi is asking for your password to ensure file permissions are correct (performed only in runtipi folder)');
+    const filesAndFolders = [
+      path.join(rootFolderHost, 'apps'),
+      path.join(rootFolderHost, 'logs'),
+      path.join(rootFolderHost, 'media'),
+      path.join(rootFolderHost, 'repos'),
+      path.join(rootFolderHost, 'state'),
+      path.join(rootFolderHost, '.env'),
+      path.join(rootFolderHost, 'docker-compose.yml'),
+    ];
+
+    // 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)) {
+          if (process.getgid && process.getuid) {
+            await execAsync(`sudo chown -R ${process.getuid()}:${process.getgid()} ${fileOrFolder}`);
+          }
+
+          await execAsync(`sudo chmod -R 750 ${fileOrFolder}`);
+        }
+      }),
+    );
+  };
+
   public systemInfo = async () => {
     try {
       const { rootFolderHost } = getEnv();
@@ -105,6 +135,8 @@ export class SystemExecutors {
   public start = async () => {
     const spinner = new TerminalSpinner('Starting Tipi...');
     try {
+      await this.ensureFilePermissions();
+
       spinner.start();
       spinner.setMessage('Copying system files...');
       await copySystemFiles();

+ 12 - 0
packages/cli/src/index.ts

@@ -7,6 +7,18 @@ import { startWorker } from './services/watcher/watcher';
 import { SystemExecutors } from './executors';
 
 const main = async () => {
+  // Ensure the user is running as root
+  if (process.env.NODE_ENV === 'production' && (!process.getuid || process.getuid() !== 0 || !process.getgid || process.getgid() !== 0)) {
+    // console.error(chalk.red('✗'), 'Tipi CLI must be run as root');
+    // process.exit(1);
+  }
+
+  // Ensure the OS is linux
+  if (process.env.NODE_ENV === 'production' && process.platform !== 'linux') {
+    // console.error(chalk.red('✗'), 'Tipi CLI can only be run on Linux');
+    // process.exit(1);
+  }
+
   program.description(description).version(version);
 
   program

File diff suppressed because it is too large
+ 661 - 18
pnpm-lock.yaml


+ 31 - 4
scripts/install.sh

@@ -12,6 +12,22 @@ if [[ "$ARCHITECTURE" == "armv7"* ]] || [[ "$ARCHITECTURE" == "i686" ]] || [[ "$
     exit 1
 fi
 
+### --------------------------------
+### CLI arguments
+### --------------------------------
+UPDATE="false"
+while [ -n "${1-}" ]; do
+    case "$1" in
+    --update) UPDATE="true" ;;
+    --)
+        shift # The double dash makes them parameters
+        break
+        ;;
+    *) echo "Option $1 not recognized" && exit 1 ;;
+    esac
+    shift
+done
+
 
 OS="$(cat /etc/[A-Za-z]*[_-][rv]e[lr]* | grep "^ID=" | cut -d= -f2 | uniq | tr '[:upper:]' '[:lower:]' | tr -d '"')"
 SUB_OS="$(cat /etc/[A-Za-z]*[_-][rv]e[lr]* | grep "^ID_LIKE=" | cut -d= -f2 | uniq | tr '[:upper:]' '[:lower:]' | tr -d '"')"
@@ -145,6 +161,7 @@ function check_dependency_and_install() {
 # Example
 # check_dependency_and_install "openssl"
 
+
 LATEST_VERSION=$(curl -s https://api.github.com/repos/meienberger/runtipi/releases/latest | grep tag_name | cut -d '"' -f4)
 
 LATEST_ASSET="runtipi-cli-linux-x64"
@@ -154,9 +171,19 @@ fi
 
 URL="https://github.com/meienberger/runtipi/releases/download/$LATEST_VERSION/$LATEST_ASSET"
 
-mkdir -p ./runtipi
+if [[ "${UPDATE}" == "false" ]]; then
+    mkdir -p runtipi
+    cd runtipi || exit
+fi
 
-curl --location "$URL" -o ./runtipi/runtipi-cli
-sudo chmod +x ./runtipi/runtipi-cli
+curl --location "$URL" -o ./runtipi-cli
+sudo chmod +x ./runtipi-cli
+
+# Check if user is in docker group
+if ! groups | grep -q docker; then
+  echo "User is not in docker group. Please make sure your user is in the docker group and restart the script."
+  echo "You can add your user to the docker group by running: 'sudo usermod -aG docker $USER' and then exit the shell and restart the script."
+  exit 1
+fi
 
-sudo ./runtipi/runtipi-cli start
+./runtipi-cli start

Some files were not shown because too many files changed in this diff