feat(cli): create tipi group and assign it to user and folders

This commit is contained in:
Nicolas Meienberger 2023-08-29 20:48:53 +02:00 committed by Nicolas Meienberger
parent 93282a051e
commit 7926c45d88
7 changed files with 60 additions and 16 deletions

View file

@ -5,4 +5,4 @@ APPS_REPO_URL=https://test.com/test
ROOT_FOLDER_HOST=/runtipi
STORAGE_PATH=/runtipi
TIPI_VERSION=1
REDIS_PASSWORD=redis

View file

@ -168,10 +168,14 @@ export class AppExecutors {
await compose(appId, 'down --remove-orphans --volumes --rmi all');
this.logger.info(`Deleting folder ${appDirPath}`);
await fs.promises.rm(appDirPath, { recursive: true, force: true });
await fs.promises.rm(appDirPath, { recursive: true, force: true }).catch((err) => {
this.logger.error(`Error deleting folder ${appDirPath}: ${err.message}`);
});
this.logger.info(`Deleting folder ${appDataDirPath}`);
await fs.promises.rm(appDataDirPath, { recursive: true, force: true });
await fs.promises.rm(appDataDirPath, { recursive: true, force: true }).catch((err) => {
this.logger.error(`Error deleting folder ${appDataDirPath}: ${err.message}`);
});
this.logger.info(`App ${appId} uninstalled`);
return { success: true, message: `App ${appId} uninstalled successfully` };

View file

@ -192,5 +192,7 @@ export const copyDataDir = async (id: string) => {
);
// Remove any .gitkeep files from the app-data folder at any level
await execAsync(`find ${storagePath}/app-data/${id}/data -name .gitkeep -delete`);
if (await pathExists(`${storagePath}/app-data/${id}/data`)) {
await execAsync(`find ${storagePath}/app-data/${id}/data -name .gitkeep -delete`).catch(() => {});
}
};

View file

@ -56,18 +56,43 @@ export class SystemExecutors {
};
private ensureFilePermissions = async (rootFolderHost: string, logSudoRequest = true) => {
const logger = new TerminalSpinner('');
// if we are running as root, we don't need to change permissions
if (process.getuid && process.getuid() === 0) {
return;
}
if (logSudoRequest) {
const logger = new TerminalSpinner('');
logger.log('Tipi needs to change permissions on some files and folders and will ask for your password.');
}
// Create group tipi if it does not exist
try {
await execAsync('getent group tipi');
} catch (e) {
try {
await execAsync('sudo groupadd tipi');
logger.done('Created group tipi');
} catch (e2) {
logger.fail('Failed to create group tipi');
fileLogger.error(e2);
}
}
// Add current user to group tipi
if (!(await execAsync(`groups ${process.env.USER}`)).stdout.includes('tipi')) {
try {
await execAsync(`sudo usermod -aG tipi ${process.env.USER}`);
// Reload permissions
await execAsync('newgrp tipi');
} catch (e) {
logger.fail('Failed to add current user to group tipi');
}
}
const filesAndFolders = [
path.join(rootFolderHost, 'apps'),
// path.join(rootFolderHost, 'app-data'),
path.join(rootFolderHost, 'logs'),
path.join(rootFolderHost, 'media'),
path.join(rootFolderHost, 'repos'),
@ -82,11 +107,12 @@ export class SystemExecutors {
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}`);
await execAsync(`sudo chown -R :tipi ${fileOrFolder}`).catch((e) => {
fileLogger.error(e);
});
await execAsync(`sudo chmod -R 770 ${fileOrFolder}`).catch((e) => {
fileLogger.error(e);
});
}
}),
);
@ -390,6 +416,8 @@ export class SystemExecutors {
process.stderr.write(data);
});
spinner.done(`Tipi ${targetVersion} successfully updated. Please run './runtipi-cli start' to start Tipi again.`);
return { success: true, message: 'Tipi updated' };
} catch (e) {
spinner.fail('Tipi update failed, see logs for more details (logs/error.log)');

View file

@ -33,6 +33,8 @@ type EnvKeys =
| 'REDIS_PASSWORD'
| 'LOCAL_DOMAIN'
| 'DEMO_MODE'
| 'TIPI_GID'
| 'TIPI_UID'
// eslint-disable-next-line @typescript-eslint/ban-types
| (string & {});
@ -177,6 +179,13 @@ export const generateSystemEnvFile = async () => {
envMap.set('LOCAL_DOMAIN', data.localDomain || 'tipi.lan');
envMap.set('NODE_ENV', 'production');
const currentUserGroup = process.getgid ? String(process.getgid()) : '1000';
const currentUserId = process.getuid ? String(process.getuid()) : '1000';
const { stdout: tipiGroupId } = await execAsync('getent group tipi | cut -d: -f3');
envMap.set('TIPI_GID', tipiGroupId.trim() || currentUserGroup);
envMap.set('TIPI_UID', currentUserId);
await fs.promises.writeFile(envFilePath, envMapToString(envMap));
return envMap;

View file

@ -84,7 +84,11 @@ const killOtherWorkers = async () => {
}
console.log(`Killing worker with pid ${pid}`);
process.kill(Number(pid));
try {
process.kill(Number(pid));
} catch (e) {
console.error(`Error killing worker with pid ${pid}: ${e}`);
}
});
};

View file

@ -188,11 +188,8 @@ chmod +x ./runtipi-cli
# Check if user is in docker group
if [ "$(id -u)" -ne 0 ]; then
if ! groups | grep -q docker; then
echo ""
echo "User is not in docker group. Please make sure your user is allowed to run docker commands and restart the script."
echo "See https://docs.docker.com/engine/install/linux-postinstall/ for more information."
echo ""
exit 1
sudo usermod -aG docker "$USER"
newgrp docker
fi
fi