diff --git a/resources/sql/autopatches/20141223.daemonloguser.sql b/resources/sql/autopatches/20141223.daemonloguser.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20141223.daemonloguser.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_daemon.daemon_log + ADD runningAsUser VARCHAR(255) COLLATE {$COLLATE_TEXT}; diff --git a/src/applications/config/check/PhabricatorSetupCheckDaemons.php b/src/applications/config/check/PhabricatorSetupCheckDaemons.php --- a/src/applications/config/check/PhabricatorSetupCheckDaemons.php +++ b/src/applications/config/check/PhabricatorSetupCheckDaemons.php @@ -43,12 +43,47 @@ ->addCommand('phabricator/ $ ./bin/phd start'); } + $phd_user = PhabricatorEnv::getEnvConfig('phd.user'); $environment_hash = PhabricatorEnv::calculateEnvironmentHash(); $all_daemons = id(new PhabricatorDaemonLogQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE) ->execute(); foreach ($all_daemons as $daemon) { + + if ($phd_user) { + if ($daemon->getRunningAsUser() != $phd_user) { + $doc_href = PhabricatorEnv::getDocLink( + 'Managing Daemons with phd'); + + $summary = pht( + 'At least one daemon is currently running as a different '. + 'user than configured in the Phabricator phd.user setting'); + + $message = pht( + 'A daemon is running as user %s while the Phabricator config '. + 'specifies phd.user to be %s.'. + "\n\n". + 'Either adjust phd.user to match %s or start '. + 'the daemons as the correct user. '. + "\n\n". + 'phd Daemons will try to '. + 'use sudo to start as the configured user. '. + 'Make sure that the user who starts phd has the correct '. + 'sudo permissions to start phd daemons as %s', + phutil_tag('tt', array(), $daemon->getRunningAsUser()), + phutil_tag('tt', array(), $phd_user), + phutil_tag('tt', array(), $daemon->getRunningAsUser()), + phutil_tag('tt', array(), $phd_user)); + + $this->newIssue('daemons.run-as-different-user') + ->setName(pht('Daemons are running as the wrong user')) + ->setSummary($summary) + ->setMessage($message) + ->addCommand('phabricator/ $ ./bin/phd restart'); + } + } + if ($daemon->getEnvHash() != $environment_hash) { $doc_href = PhabricatorEnv::getDocLink( 'Managing Daemons with phd'); diff --git a/src/applications/daemon/controller/PhabricatorDaemonLogViewController.php b/src/applications/daemon/controller/PhabricatorDaemonLogViewController.php --- a/src/applications/daemon/controller/PhabricatorDaemonLogViewController.php +++ b/src/applications/daemon/controller/PhabricatorDaemonLogViewController.php @@ -163,6 +163,7 @@ $view->addProperty(pht('Daemon Class'), $daemon->getDaemon()); $view->addProperty(pht('Host'), $daemon->getHost()); $view->addProperty(pht('PID'), $daemon->getPID()); + $view->addProperty(pht('Running as'), $daemon->getRunningAsUser()); $view->addProperty(pht('Started'), phabricator_datetime($c_epoch, $viewer)); $view->addProperty( pht('Seen'), diff --git a/src/applications/daemon/event/PhabricatorDaemonEventListener.php b/src/applications/daemon/event/PhabricatorDaemonEventListener.php --- a/src/applications/daemon/event/PhabricatorDaemonEventListener.php +++ b/src/applications/daemon/event/PhabricatorDaemonEventListener.php @@ -34,11 +34,13 @@ private function handleLaunchEvent(PhutilEvent $event) { $id = $event->getValue('id'); + $current_user = posix_getpwuid(posix_geteuid()); $daemon = id(new PhabricatorDaemonLog()) ->setDaemon($event->getValue('daemonClass')) ->setHost(php_uname('n')) ->setPID(getmypid()) + ->setRunningAsUser($current_user['name']) ->setEnvHash(PhabricatorEnv::calculateEnvironmentHash()) ->setStatus(PhabricatorDaemonLog::STATUS_RUNNING) ->setArgv($event->getValue('argv')) diff --git a/src/applications/daemon/management/PhabricatorDaemonManagementDebugWorkflow.php b/src/applications/daemon/management/PhabricatorDaemonManagementDebugWorkflow.php --- a/src/applications/daemon/management/PhabricatorDaemonManagementDebugWorkflow.php +++ b/src/applications/daemon/management/PhabricatorDaemonManagementDebugWorkflow.php @@ -21,11 +21,17 @@ 'name' => 'argv', 'wildcard' => true, ), + array( + 'name' => 'as-current-user', + 'help' => 'Run the daemon as the current user '. + 'instead of the configured phd.user', + ), )); } public function execute(PhutilArgumentParser $args) { $argv = $args->getArg('argv'); + $run_as_current_user = $args->getArg('as-current-user'); if (!$argv) { throw new PhutilArgumentUsageException( @@ -33,7 +39,11 @@ } $daemon_class = array_shift($argv); - return $this->launchDaemon($daemon_class, $argv, $is_debug = true); + return $this->launchDaemon( + $daemon_class, + $argv, + $is_debug = true, + $run_as_current_user); } } diff --git a/src/applications/daemon/management/PhabricatorDaemonManagementWorkflow.php b/src/applications/daemon/management/PhabricatorDaemonManagementWorkflow.php --- a/src/applications/daemon/management/PhabricatorDaemonManagementWorkflow.php +++ b/src/applications/daemon/management/PhabricatorDaemonManagementWorkflow.php @@ -3,6 +3,8 @@ abstract class PhabricatorDaemonManagementWorkflow extends PhabricatorManagementWorkflow { + private $runDaemonsAsUser = null; + protected final function loadAvailableDaemonClasses() { $loader = new PhutilSymbolLoader(); return $loader @@ -103,10 +105,37 @@ return head($match); } - protected final function launchDaemon($class, array $argv, $debug) { + protected final function launchDaemon( + $class, + array $argv, + $debug, + $run_as_current_user = false) { + $daemon = $this->findDaemonClass($class); $console = PhutilConsole::getConsole(); + if (!$run_as_current_user) { + // Check if the script is started as the correct user + $phd_user = PhabricatorEnv::getEnvConfig('phd.user'); + $current_user = posix_getpwuid(posix_geteuid()); + $current_user = $current_user['name']; + if ($phd_user && $phd_user != $current_user) { + if ($debug) { + throw new PhutilArgumentUsageException(pht( + 'You are trying to run a daemon as a nonstandard user, '. + 'and `phd` was not able to `sudo` to the correct user. '."\n". + 'Phabricator is configured to run daemons as "%s", '. + 'but the current user is "%s". '."\n". + 'Use `sudo` to run as a different user, pass `--as-current-user` '. + 'to ignore this warning, or edit `phd.user` '. + 'to change the configuration.', $phd_user, $current_user)); + } else { + $this->runDaemonsAsUser = $phd_user; + $console->writeOut(pht('Starting daemons as %s', $phd_user)."\n"); + } + } + } + if ($debug) { if ($argv) { $console->writeOut( @@ -187,11 +216,39 @@ phutil_passthru('(cd %s && exec %C)', $daemon_script_dir, $command); } else { - $future = new ExecFuture('exec %C', $command); - // Play games to keep 'ps' looking reasonable. - $future->setCWD($daemon_script_dir); - $future->resolvex(); + try { + $this->executeDaemonLaunchCommand( + $command, + $daemon_script_dir, + $this->runDaemonsAsUser); + } catch (CommandException $e) { + // Retry without sudo + $console->writeOut(pht( + "sudo command failed. Starting daemon as current user\n")); + $this->executeDaemonLaunchCommand( + $command, + $daemon_script_dir); + } + } + } + + private function executeDaemonLaunchCommand( + $command, + $daemon_script_dir, + $run_as_user = null) { + + if ($run_as_user) { + // If anything else besides sudo should be + // supported then insert it here (runuser, su, ...) + $command = csprintf( + 'sudo -En -u %s -- %C', + $run_as_user, + $command); } + $future = new ExecFuture('exec %C', $command); + // Play games to keep 'ps' looking reasonable. + $future->setCWD($daemon_script_dir); + $future->resolvex(); } public static function ignoreSignal($signo) { diff --git a/src/applications/daemon/storage/PhabricatorDaemonLog.php b/src/applications/daemon/storage/PhabricatorDaemonLog.php --- a/src/applications/daemon/storage/PhabricatorDaemonLog.php +++ b/src/applications/daemon/storage/PhabricatorDaemonLog.php @@ -13,6 +13,7 @@ protected $daemon; protected $host; protected $pid; + protected $runningAsUser; protected $argv; protected $explicitArgv = array(); protected $envHash; @@ -28,6 +29,7 @@ 'daemon' => 'text255', 'host' => 'text255', 'pid' => 'uint32', + 'runningAsUser' => 'text255?', 'envHash' => 'bytes40', 'status' => 'text8', ),