     if entry of opts
       console.warn "[Warn] `#{entry}` configuration is deprecated."
 
-  # Creates the web server according to the configuration.
+  return opts
+
+#---------------------------------------------------------------------
+
+$readFile = $Promise.promisify $fs.readFile
+
+$httpListenSuccess = ->
+  $debug "Web server listening on #{@niceAddress()}"
+  return
+
+$httpListenFailure = (error) ->
+  console.warn "[WARN] Web server could not listen on #{@niceAddress()}"
+  switch error.code
+    when 'EACCES'
+      console.warn '       Access denied.'
+      console.warn '       Ports < 1024 are often reserved to privileges users.'
+    when 'EADDRINUSE'
+      console.warn '       Address already in use.'
+  return
+
+# Returns a promise to a new HTTP server plus when it is listening on
+# all port.
+createWebServer = (opts) ->
   webServer = new $WebServer()
-  $wait $Promise.map opts.http.listen, (options) ->
-    # Reads certificate and key if necessary.
-    if options.certificate? and options.key?
-      options.certificate = $wait $readFile options.certificate
-      options.key = $wait $readFile options.key
-
-    # Starts listening
-    return webServer.listen(options).then $httpListenSuccess, $httpListenFailure
+
+  return $Promise.map(opts, (opts) ->
+    return $Promise.try(() ->
+      # Reads certificate and key if necessary.
+      if opts.certificate and opts.key
+        return $Promise.join(
+          $readFile(opts.certificate), $readFile(opts.key),
+          (certificate, key) ->
+            opts.certificate = certificate
+            opts.key = key
+            return
+        )
+      return
+    ).then(() ->
+      return webServer.listen(opts).then($httpListenSuccess, $httpListenFailure)
+    )
+  ).return(webServer)
+
+#---------------------------------------------------------------------
+
+setUpStaticFiles = (connect, opts) ->
+  for urlPath, filePaths of opts
+    filePaths = [filePaths] unless $isArray filePaths
+    for filePath in filePaths
+      $debug 'Setting up %s → %s', urlPath, filePath
+      connect.use urlPath, $serveStatic filePath
+
+  return
+
+#---------------------------------------------------------------------
+
+setUpApi = (webServer, xo) ->
+  api = new $API(xo)
+
+  webSocketServer = new $WSServer({
+    server: webServer,
+    path: '/api/',
+  })
+
+  webSocketServer.on('connection', (connection) ->
+    $debug '+ WebSocket connection'
+
+    # Forward declaration.
+    xoConnection = null
+
+    # Create the JSON-RPC server for this connection.
+    jsonRpc = $jsonRpc.createServer((message) ->
+      if message.type is 'request'
+        return api.exec(xoConnection, message)
+
+      return
+    )
+
+    # Create the abstract XO object for this connection.
+    xoConnection = xo.createUserConnection({
+      close: $bind(connection.close, connection),
+      notify: $bind(jsonRpc.notify, jsonRpc),
+    })
+
+    # Close the XO connection with this WebSocket.
+    connection.once('close', () ->
+      $debug '- WebSocket connection'
+      xoConnection.close()
+
+      return
+    )
+
+    # Connect the WebSocket to the JSON-RPC server.
+    connection.on('message', (message) ->
+      jsonRpc.write(message)
+      return
+    )
+    webSocketSendError = (error) ->
+      if error
+        console.error('[WARN] WebSocket send', error)
+        connection.close()
+      return
+    jsonRpc.on('data', (data) ->
+      connection.send(JSON.stringify(data), webSocketSendError)
+      return
+    )
+
+    return
+  )
+
+  return
+
+#---------------------------------------------------------------------
+
+getVmConsoleUrl = (xo, id) ->
+  vm = xo.getObject(id, ['VM', 'VM-controller'])
+  return unless vm?.power_state is 'Running'
+
+  {sessionId} = xo.getXAPI(vm)
+
+  for console in vm.consoles
+    if console.protocol is 'rfb'
+      return "#{console.location}&session_id=#{sessionId}"
+
+  return
+
+CONSOLE_PROXY_PATH_RE = /^\/consoles\/(.*)$/
+setUpConsoleProxy = (webServer, xo) ->
+  webSocketServer = new $WSServer({
+    noServer: true,
+  })
+
+  webServer.on('upgrade', (req, res, head) ->
+    matches = CONSOLE_PROXY_PATH_RE.exec(req.url)
+    return unless matches
+
+    url = getVmConsoleUrl(xo, matches[1])
+    return unless url
+
+    webSocketServer.handleUpgrade(req, res, head, (connection) ->
+      $wsProxy(connection, url)
+      return
+    )
+    return
+  )
+
+  return
+
+#=====================================================================
+
+# Main.
+exports = module.exports = $coroutine (args) ->
+  return exports.help() unless (
+    (args.indexOf '--help') is -1 and
+    (args.indexOf '-h') is -1
+  )
+
+  opts = $wait loadConfiguration()
+
+  webServer = $wait createWebServer(opts.http.listen)
 
   # Now the web server is listening, drop privileges.
   try

  }

  # Loads config files.
  opts = $wait $appConf.load 'xo-server',
    defaults: opts
    ignoreUnknownFormats: true

  $debug 'Configuration loaded.'



#=====================================================================

loadConfiguration = () ->
  defaults = {
    http: {
      listen: [
        port: 80
