refactor: run watch with sudo by default

This commit is contained in:
Nicolas Meienberger 2023-08-30 14:00:51 +02:00
parent a0f5d07df1
commit 00f6ab1e64
7 changed files with 69 additions and 18 deletions

View file

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

View file

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

View file

@ -158,7 +158,10 @@ export class AppExecutors {
this.logger.info(`App ${appId} started`); 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` }; return { success: true, message: `App ${appId} started successfully` };
} catch (err) { } catch (err) {

View file

@ -19,6 +19,7 @@ import { pathExists } from '@/utils/fs-helpers';
import { getEnv } from '@/utils/environment/environment'; import { getEnv } from '@/utils/environment/environment';
import { fileLogger } from '@/utils/logger/file-logger'; import { fileLogger } from '@/utils/logger/file-logger';
import { runPostgresMigrations } from '@/utils/migrations/run-migration'; import { runPostgresMigrations } from '@/utils/migrations/run-migration';
import { getUserIds } from '@/utils/environment/user';
const execAsync = promisify(exec); const execAsync = promisify(exec);
@ -103,16 +104,15 @@ export class SystemExecutors {
path.join(rootFolderHost, 'VERSION'), path.join(rootFolderHost, 'VERSION'),
]; ];
const { uid, gid } = getUserIds();
// Give permission to read and write to all files and folders for the current user // Give permission to read and write to all files and folders for the current user
await Promise.all( await Promise.all(
filesAndFolders.map(async (fileOrFolder) => { filesAndFolders.map(async (fileOrFolder) => {
if (await pathExists(fileOrFolder)) { if (await pathExists(fileOrFolder)) {
await execAsync(`sudo chown -R :tipi ${fileOrFolder}`).catch((e) => { await execAsync(`sudo chown -R ${uid}:${gid} ${fileOrFolder}`);
fileLogger.error(e);
}); await execAsync(`sudo chmod -R 750 ${fileOrFolder}`);
await execAsync(`sudo chmod -R 770 ${fileOrFolder}`).catch((e) => {
fileLogger.error(e);
});
} }
}), }),
); );
@ -181,11 +181,22 @@ export class SystemExecutors {
* This method will start Tipi. * This method will start Tipi.
* It will copy the system files, generate the system env file, pull the images and start the containers. * 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...'); const spinner = new TerminalSpinner('Starting Tipi...');
try { try {
if (permissions) { if (sudo) {
await this.ensureFilePermissions(this.rootFolder); 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(); spinner.start();
@ -194,7 +205,7 @@ export class SystemExecutors {
spinner.done('System files copied'); spinner.done('System files copied');
if (permissions) { if (sudo) {
await this.ensureFilePermissions(this.rootFolder, false); await this.ensureFilePermissions(this.rootFolder, false);
} }
@ -238,8 +249,16 @@ export class SystemExecutors {
const out = fs.openSync('./logs/watcher.log', 'a'); const out = fs.openSync('./logs/watcher.log', 'a');
const err = 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] }); if (sudo) {
subprocess.unref(); // 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'); spinner.done('Watcher started');
@ -318,7 +337,7 @@ export class SystemExecutors {
* runtipi-cli binary with the new one. * runtipi-cli binary with the new one.
* @param {string} target * @param {string} target
*/ */
public update = async (target: string) => { public update = async (target: string, sudo = true) => {
const spinner = new TerminalSpinner('Evaluating target version...'); const spinner = new TerminalSpinner('Evaluating target version...');
try { try {
spinner.start(); spinner.start();
@ -408,7 +427,15 @@ export class SystemExecutors {
// eslint-disable-next-line no-promise-executor-return // eslint-disable-next-line no-promise-executor-return
await new Promise((resolve) => setTimeout(resolve, 3000)); 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) => { childProcess.stdout.on('data', (data) => {
process.stdout.write(data); process.stdout.write(data);

View file

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

View file

@ -4,10 +4,14 @@ import { exec } from 'child_process';
import { promisify } from 'util'; import { promisify } from 'util';
import { AppExecutors, RepoExecutors, SystemExecutors } from '@/executors'; import { AppExecutors, RepoExecutors, SystemExecutors } from '@/executors';
import { getEnv } from '@/utils/environment/environment'; import { getEnv } from '@/utils/environment/environment';
import { getUserIds } from '@/utils/environment/user';
const execAsync = promisify(exec); const execAsync = promisify(exec);
const runCommand = async (jobData: unknown) => { 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 { installApp, startApp, stopApp, uninstallApp, updateApp, regenerateAppEnv } = new AppExecutors();
const { cloneRepo, pullRepo } = new RepoExecutors(); const { cloneRepo, pullRepo } = new RepoExecutors();
const { systemInfo, restart, update } = new SystemExecutors(); const { systemInfo, restart, update } = new SystemExecutors();
@ -65,7 +69,11 @@ const runCommand = async (jobData: unknown) => {
} }
if (data.command === 'update') { 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));
}
} }
} }

View file

@ -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 };
};