leinelissen / embedded-postgres

🐘 A Node package that allows you to spawn a Postgresql cluster programatically.
MIT License
53 stars 10 forks source link

Feature request: Detect when running as root and execute postgres as a different user #6

Closed panta82 closed 1 year ago

panta82 commented 1 year ago

The solution involves checking if we are running as root. If so, try to create postgres user and group, pick out their id/pid, adopt the db directory and run the db server as that user.

The reasoning behind this: be able to run embedded-postgres inside a standard docker container (eg. for unit tests in CI).

Like in my other Issue, I ended up implementing this using patch-package:

    constructor(options = {}) {
        // Assign default options to options object
        this.options = Object.assign({}, defaults, options);
        instances.add(this);

        this._spawnOptions = {};
    }

    async initialise() {

        //...

        // Check if we are running as root
        const isRoot = os_1.userInfo().uid === 0;
        if (isRoot) {
          try {
            child_process_1.execSync('groupadd postgres', {stdio: 'pipe'});
            child_process_1.execSync('useradd -g postgres postgres', {stdio: 'pipe'});
            this.options.on_log("Created user and group 'postgres'");
          } catch(err) {
            if (!err.message.includes('already exists')) {
              this.options.on_error('Error while creating user/group:', err);
            }
          }

          // Fetch the uid and gid of the newly created postgres user and group
          let uid, gid;
          try {
            uid = parseInt(child_process_1.execSync('id -u postgres', {stdio: 'pipe'}).toString().trim());
            gid = parseInt(child_process_1.execSync('id -g postgres', {stdio: 'pipe'}).toString().trim());
            this.options.on_log("Postgres uid/gid: " + uid + "/" + gid);
          } catch(err) {
            this.options.on_error('Error while getting uid/gid:', err);
          }

          child_process_1.execSync('chown postgres:postgres ' + this.options.database_dir);
          this.options.on_log("Postgres user is now the owner of " + this.options.database_dir);

          this._spawnOptions = {
            uid: uid,
            gid: gid
          };
        }

       // Initialize the database
        await new Promise((resolve, reject) => {
            const process = (0, child_process_1.spawn)(initdb, [
                `--pgdata=${this.options.database_dir}`,
                `--auth=${this.options.auth_method}`,
                `--username=${this.options.user}`,
                `--pwfile=${passwordFile}`,
            ], this._spawnOptions);

      // ....

            // Spawn a postgres server
            this.process = (0, child_process_1.spawn)(postgres, [
                '-D',
                this.options.database_dir,
                '-p',
                this.options.port.toString(),
            ], this._spawnOptions);

This is very rough, using sync functions, etc. As in my other issue, the problem is I couldn't set up the project to work with real TS code. So I ended up hacking this together, that solves my use case.

leinelissen commented 1 year ago

Implemented in beta.9, see #5.

leinelissen commented 1 year ago

Would love for you to try this out btw, curious to hear what you think.