/**
  * Build a new Conduit client in order to make a service call to this
  * repository.
  *
  * If the repository is hosted locally, this method may return `null`. The
  * caller should use `ConduitCall` or other local logic to complete the
  * request.
  *
  * By default, we will return a @{class:ConduitClient} for any repository with
  * a service, even if that service is on the current device.
  *
  * We do this because this configuration does not make very much sense in a
  * production context, but is very common in a test/development context
  * (where the developer's machine is both the web host and the repository
  * service). By proxying in development, we get more consistent behavior
  * between development and production, and don't have a major untested
  * codepath.
  *
  * The `$never_proxy` parameter can be used to prevent this local proxying.
  * If the flag is passed:
  *
  *   - The method will return `null` (implying a local service call)
  *     if the repository service is hosted on the current device.
  *   - The method will throw if it would need to return a client.
  *
  * This is used to prevent loops in Conduit: the first request will proxy,
  * even in development, but the second request will be identified as a
  * cluster request and forced not to proxy.
  *
  * For lower-level service resolution, see @{method:getAlmanacServiceURI}.
  *
  * @param PhabricatorUser Viewing user.
  * @param bool `true` to throw if a client would be returned.
  * @return ConduitClient|null Client, or `null` for local repositories.
  */
 public function newConduitClient(PhabricatorUser $viewer, $never_proxy = false)
 {
     $uri = $this->getAlmanacServiceURI($viewer, $never_proxy, array('http', 'https'));
     if ($uri === null) {
         return null;
     }
     $domain = id(new PhutilURI(PhabricatorEnv::getURI('/')))->getDomain();
     $client = id(new ConduitClient($uri))->setHost($domain);
     if ($viewer->isOmnipotent()) {
         // If the caller is the omnipotent user (normally, a daemon), we will
         // sign the request with this host's asymmetric keypair.
         $public_path = AlmanacKeys::getKeyPath('device.pub');
         try {
             $public_key = Filesystem::readFile($public_path);
         } catch (Exception $ex) {
             throw new PhutilAggregateException(pht('Unable to read device public key while attempting to make ' . 'authenticated method call within the Phabricator cluster. ' . 'Use `%s` to register keys for this device. Exception: %s', 'bin/almanac register', $ex->getMessage()), array($ex));
         }
         $private_path = AlmanacKeys::getKeyPath('device.key');
         try {
             $private_key = Filesystem::readFile($private_path);
             $private_key = new PhutilOpaqueEnvelope($private_key);
         } catch (Exception $ex) {
             throw new PhutilAggregateException(pht('Unable to read device private key while attempting to make ' . 'authenticated method call within the Phabricator cluster. ' . 'Use `%s` to register keys for this device. Exception: %s', 'bin/almanac register', $ex->getMessage()), array($ex));
         }
         $client->setSigningKeys($public_key, $private_key);
     } else {
         // If the caller is a normal user, we generate or retrieve a cluster
         // API token.
         $token = PhabricatorConduitToken::loadClusterTokenForUser($viewer);
         if ($token) {
             $client->setConduitToken($token->getToken());
         }
     }
     return $client;
 }