Index: src/applications/config/option/PhabricatorPHDConfigOptions.php =================================================================== --- src/applications/config/option/PhabricatorPHDConfigOptions.php +++ src/applications/config/option/PhabricatorPHDConfigOptions.php @@ -41,6 +41,14 @@ "of output, but can help debug issues. Daemons launched in debug ". "mode with 'phd debug' are always launched in verbose mode. See ". "also 'phd.trace'.")), + $this->newOption('phd.user', 'string', null) + ->setSummary(pht("System user to run daemons as.")) + ->setDescription( + pht( + "Specify a system user to run the daemons as. Primarily, this ". + "user will own the working copies of any repositories that ". + "Phabricator imports or manages. This option is new and ". + "experimental.")), $this->newOption('phd.trace', 'bool', false) ->setBoolOptions( array( Index: src/applications/diffusion/controller/DiffusionServeController.php =================================================================== --- src/applications/diffusion/controller/DiffusionServeController.php +++ src/applications/diffusion/controller/DiffusionServeController.php @@ -326,7 +326,10 @@ $input = PhabricatorStartup::getRawInput(); - list($err, $stdout, $stderr) = id(new ExecFuture('%s', $bin)) + $command = csprintf('%s', $bin); + $command = PhabricatorDaemon::sudoCommandAsDaemonUser($command); + + list($err, $stdout, $stderr) = id(new ExecFuture('%C', $command)) ->setEnv($env, true) ->write($input) ->resolve(); @@ -422,7 +425,10 @@ $input = strlen($input)."\n".$input."0\n"; } - list($err, $stdout, $stderr) = id(new ExecFuture('%s serve --stdio', $bin)) + $command = csprintf('%s serve --stdio', $bin); + $command = PhabricatorDaemon::sudoCommandAsDaemonUser($command); + + list($err, $stdout, $stderr) = id(new ExecFuture('%C', $command)) ->setEnv($env, true) ->setCWD($repository->getLocalPath()) ->write("{$cmd}\n{$args}{$input}") @@ -545,5 +551,6 @@ return $has_pack && $is_hangup; } + } Index: src/applications/diffusion/ssh/DiffusionSSHGitReceivePackWorkflow.php =================================================================== --- src/applications/diffusion/ssh/DiffusionSSHGitReceivePackWorkflow.php +++ src/applications/diffusion/ssh/DiffusionSSHGitReceivePackWorkflow.php @@ -22,9 +22,10 @@ // This is a write, and must have write access. $this->requireWriteAccess(); - $future = new ExecFuture( - 'git-receive-pack %s', - $repository->getLocalPath()); + $command = csprintf('git-receive-pack %s', $repository->getLocalPath()); + $command = PhabricatorDaemon::sudoCommandAsDaemonUser($command); + + $future = new ExecFuture('%C', $command); $err = $this->newPassthruCommand() ->setIOChannel($this->getIOChannel()) Index: src/applications/diffusion/ssh/DiffusionSSHGitUploadPackWorkflow.php =================================================================== --- src/applications/diffusion/ssh/DiffusionSSHGitUploadPackWorkflow.php +++ src/applications/diffusion/ssh/DiffusionSSHGitUploadPackWorkflow.php @@ -19,7 +19,10 @@ $path = head($args->getArg('dir')); $repository = $this->loadRepository($path); - $future = new ExecFuture('git-upload-pack %s', $repository->getLocalPath()); + $command = csprintf('git-upload-pack -- %s', $repository->getLocalPath()); + $command = PhabricatorDaemon::sudoCommandAsDaemonUser($command); + + $future = new ExecFuture('%C', $command); $err = $this->newPassthruCommand() ->setIOChannel($this->getIOChannel()) Index: src/applications/diffusion/ssh/DiffusionSSHMercurialServeWorkflow.php =================================================================== --- src/applications/diffusion/ssh/DiffusionSSHMercurialServeWorkflow.php +++ src/applications/diffusion/ssh/DiffusionSSHMercurialServeWorkflow.php @@ -39,12 +39,12 @@ throw new Exception("Expected `hg ... serve`!"); } - $future = new ExecFuture( - 'hg -R %s serve --stdio', - $repository->getLocalPath()); + $command = csprintf('hg -R %s serve --stdio', $repository->getLocalPath()); + $command = PhabricatorDaemon::sudoCommandAsDaemonUser($command); - $io_channel = $this->getIOChannel(); + $future = new ExecFuture('%C', $command); + $io_channel = $this->getIOChannel(); $protocol_channel = new DiffusionSSHMercurialWireClientProtocolChannel( $io_channel); Index: src/applications/diffusion/ssh/DiffusionSSHSubversionServeWorkflow.php =================================================================== --- src/applications/diffusion/ssh/DiffusionSSHSubversionServeWorkflow.php +++ src/applications/diffusion/ssh/DiffusionSSHSubversionServeWorkflow.php @@ -38,7 +38,10 @@ throw new Exception("Expected `svnserve -t`!"); } - $future = new ExecFuture('svnserve -t'); + $command = csprintf('svnserve -t'); + $command = PhabricatorDaemon::sudoCommandAsDaemonUser($command); + + $future = new ExecFuture('%C', $command); $this->inProtocol = new DiffusionSubversionWireProtocol(); $this->outProtocol = new DiffusionSubversionWireProtocol(); Index: src/applications/diffusion/ssh/DiffusionSSHWorkflow.php =================================================================== --- src/applications/diffusion/ssh/DiffusionSSHWorkflow.php +++ src/applications/diffusion/ssh/DiffusionSSHWorkflow.php @@ -120,5 +120,4 @@ return $this->hasWriteAccess; } - } Index: src/infrastructure/daemon/PhabricatorDaemon.php =================================================================== --- src/infrastructure/daemon/PhabricatorDaemon.php +++ src/infrastructure/daemon/PhabricatorDaemon.php @@ -19,4 +19,35 @@ return PhabricatorUser::getOmnipotentUser(); } + + /** + * Format a command so it executes as the daemon user, if a daemon user is + * defined. This wraps the provided command in `sudo -u ...`, roughly. + * + * @param PhutilCommandString Command to execute. + * @return PhutilCommandString `sudo` version of the command. + */ + public static function sudoCommandAsDaemonUser($command) { + $user = PhabricatorEnv::getEnvConfig('phd.user'); + if (!$user) { + // No daemon user is set, so just run this as ourselves. + return $command; + } + + // Get the absolute path so we're safe against the caller wiping out + // PATH. + $sudo = Filesystem::resolveBinary('sudo'); + if (!$sudo) { + throw new Exception(pht("Unable to find 'sudo'!")); + } + + // Flags here are: + // + // -E: Preserve the environment. + // -n: Non-interactive. Exit with an error instead of prompting. + // -u: Which user to sudo to. + + return csprintf('%s -E -n -u %s -- %C', $sudo, $user, $command); + } + }