diff --git a/README.md b/README.md
index df6ef245753e0fbb986b008adcaf684816a4edfc..82a91127921c63310072122520cf29cb664f3bdf 100644
--- a/README.md
+++ b/README.md
@@ -6,5 +6,6 @@ Getting started:
 1. install node.js (>= 0.4.5): http://nodejs.org/
 2. install the Connect framework (>= 1.3.0): http://senchalabs.github.com/connect/
 3. install xml2js
-4. run the top level `run.js` script
-5. visit the demo application ('rp') in your web browser (url output on the console at runtime)␁
+4. install sqlite (npm install sqlite will get it for you, this is the flavor: https://github.com/orlandov/node-sqlite)
+5. run the top level `run.js` script
+6. visit the demo application ('rp') in your web browser (url output on the console at runtime)␁
diff --git a/authority/.gitignore b/authority/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..ae9f5955b3f2463d8aa275899dfa93fe2d05c42b
--- /dev/null
+++ b/authority/.gitignore
@@ -0,0 +1 @@
+/authdb.sqlite
diff --git a/authority/server/db.js b/authority/server/db.js
index 6f13cadf8bb88cbe2b7f4c253480c1140ba3e9df..726d5c9dc2fef1ec11e57f02bdfd35065b4c1ba3 100644
--- a/authority/server/db.js
+++ b/authority/server/db.js
@@ -1,7 +1,27 @@
-// Registered users.  This is a horribly inefficient data structure
-// which only exists for prototype purposes.
-var g_users = [
-];
+const sqlite = require('sqlite'),
+        path = require('path');
+
+var db = new sqlite.Database();
+
+db.open(path.join(path.dirname(__dirname), "authdb.sqlite"), function (error) {
+  if (error) {
+    console.log("Couldn't open database: " + error);
+    throw error;
+  }
+
+  function createTable(name, sql) {
+    db.execute(sql, function (error, rows) {
+      if (error) {
+        console.log("Couldn't create " + name + " table: " + error);
+        throw error;
+      }
+    });
+  }
+
+  createTable('users',  "CREATE TABLE IF NOT EXISTS users  ( id INTEGER PRIMARY KEY, password TEXT )");
+  createTable('emails', "CREATE TABLE IF NOT EXISTS emails ( id INTEGER PRIMARY KEY, user INTEGER, address TEXT UNIQUE )");
+  createTable('keys',   "CREATE TABLE IF NOT EXISTS keys   ( id INTEGER PRIMARY KEY, email INTEGER, key TEXT, expires INTEGER )");
+});
 
 // half created user accounts (pending email verification)
 // OR
@@ -9,6 +29,46 @@ var g_users = [
 var g_staged = {
 };
 
+// an email to secret map for efficient fulfillment of isStaged queries
+var g_stagedEmails = {
+};
+
+function executeTransaction(statements, cb) {
+  function executeTransaction2(statements, cb) {
+    if (statements.length == 0) cb();
+    else {
+      var s = statements.shift();
+      db.execute(s[0], s[1], function(err, rows) {
+        if (err) cb(err);
+        else executeTransaction2(statements, cb);
+      });
+    }
+  }
+
+  db.execute('BEGIN', function(err, rows) {
+    executeTransaction2(statements, function(err) {
+      if (err) cb(err);
+      else db.execute('COMMIT', function(err, rows) {
+        cb(err);
+      });
+    });
+  });
+}
+
+function emailToUserID(email, cb) {
+  db.execute(
+    'SELECT users.id FROM emails, users WHERE emails.address = ? AND users.id == emails.user',
+    [ email ],
+    function (err, rows) {
+      if (rows && rows.length == 1) {
+        cb(rows[0].id);
+      } else {
+        if (err) console.log("database error: " + err);
+        cb(undefined);
+      }
+    });
+}
+
 exports.findByEmail = function(email) {
   for (var i = 0; i < g_users.length; i++) {
     for (var j = 0; j < g_users[i].emails.length; j++) {
@@ -18,12 +78,17 @@ exports.findByEmail = function(email) {
   return undefined;
 };
 
+exports.emailKnown = function(email, cb) {
+  db.execute(
+    "SELECT id FROM emails WHERE address = ?",
+    [ email ],
+    function(error, rows) {
+      cb(rows.length > 0);
+    });
+};
+
 exports.isStaged = function(email) {
-  // XXX: not efficient
-  for (var k in g_staged) {
-    if (g_staged[k].email === email) return true;
-  }
-  return false;
+  return g_stagedEmails.hasOwnProperty(email);
 };
 
 function generateSecret() {
@@ -35,12 +100,48 @@ function generateSecret() {
   return str;
 }
 
-exports.addEmailToAccount = function(existing_email, email, pubkey) {
-  var acct = exports.findByEmail(existing_email);
-  if (acct === undefined) throw "no such email: " + existing_email;
-  if (acct.emails.indexOf(email) == -1) acct.emails.push(email);
-  acct.keys.push(email);
-  return;
+exports.addEmailToAccount = function(existing_email, email, pubkey, cb) {
+  emailToUserID(existing_email, function(userID) {
+    if (userID == undefined) {
+      cb("no such email: " + existing_email, undefined);
+    } else {
+        executeTransaction([
+          [ "INSERT INTO emails (user, address) VALUES(?,?)", [ userID, email ] ],
+          [ "INSERT INTO keys (email, key, expires) VALUES(last_insert_rowid(),?,?)",
+            [ pubkey, ((new Date()).getTime() + (14 * 24 * 60 * 60 * 1000)) ]
+          ]
+        ], function (error) {
+          if (error) cb(error);
+          else cb();
+        });
+    }
+  });
+}
+
+exports.addKeyToEmail = function(existing_email, email, pubkey, cb) {
+  emailToUserID(existing_email, function(userID) {
+    if (userID == undefined) {
+      cb("no such email: " + existing_email, undefined);
+      return;
+    }
+
+    db.execute("SELECT emails.id FROM emails,users WHERE users.id = ? AND emails.address = ? AND emails.user = users.id",
+               [ userID, email ],
+               function(err, rows) {
+                 if (err || rows.length != 1) {
+                   cb(err);
+                   return;
+                 }
+                 executeTransaction([
+                   [ "INSERT INTO keys (email, key, expires) VALUES(?,?,?)",
+                     [ rows[0].id, pubkey, ((new Date()).getTime() + (14 * 24 * 60 * 60 * 1000)) ]
+                   ]
+                 ], function (error) {
+                   if (error) cb(error);
+                   else cb();
+                 });
+               });
+  });
 }
 
 /* takes an argument object including email, pass, and pubkey. */
@@ -53,6 +154,7 @@ exports.stageUser = function(obj) {
     pubkey: obj.pubkey,
     pass: obj.pass
   };
+  g_stagedEmails[obj.email] = secret;
   return secret;
 };
 
@@ -66,39 +168,48 @@ exports.stageEmail = function(existing_email, new_email, pubkey) {
     email: new_email,
     pubkey: pubkey
   };
+  g_stagedEmails[new_email] = secret;
   return secret;
 };
 
 /* invoked when a user clicks on a verification URL in their email */ 
-exports.gotVerificationSecret = function(secret) {
-  if (!g_staged.hasOwnProperty(secret)) return false;
+exports.gotVerificationSecret = function(secret, cb) {
+  if (!g_staged.hasOwnProperty(secret)) cb("unknown secret");
 
   // simply move from staged over to the emails "database"
   var o = g_staged[secret];
   delete g_staged[secret];
+  delete g_stagedEmails[o.email];
   if (o.type === 'add_account') {
-    if (undefined != exports.findByEmail(o.email)) {
-      throw "email already exists!";
-    }
-    g_users.push({
-      emails: [ o.email ],
-      keys: [ o.pubkey ],
-      pass: o.pass
+    exports.emailKnown(o.email, function(known) {
+      if (known) cb("email already exists!");
+      else {
+        executeTransaction([
+          [ "INSERT INTO users (password) VALUES(?)", [ o.pass ] ] ,
+          [ "INSERT INTO emails (user, address) VALUES(last_insert_rowid(),?)", [ o.email ] ],
+          [ "INSERT INTO keys (email, key, expires) VALUES(last_insert_rowid(),?,?)",
+            [ o.pubkey, ((new Date()).getTime() + (14 * 24 * 60 * 60 * 1000)) ]
+          ]
+        ], function (error) {
+          if (error) cb(error);
+          else cb();
+        });
+      }
     });
   } else if (o.type === 'add_email') {
-    exports.addEmailToAccount(o.existing_email, o.email, o.pubkey);
+    exports.addEmailToAccount(o.existing_email, o.email, o.pubkey, cb);
   } else {
-    return false;
+    cb("internal error");
   }
-
-  return true;
 };
 
 /* takes an argument object including email, pass, and pubkey. */
-exports.checkAuth = function(email, pass) {
-  var acct = exports.findByEmail(email);
-  if (acct === undefined) return false;
-  return pass === acct.pass;
+exports.checkAuth = function(email, pass, cb) {
+  db.execute("SELECT users.id FROM emails, users WHERE users.id = emails.user AND emails.address = ? AND users.password = ?",
+             [ email, pass ],
+             function (error, rows) {
+               cb(rows.length === 1);
+             });
 };
 
 /* a high level operation that attempts to sync a client's view with that of the
@@ -112,28 +223,57 @@ exports.checkAuth = function(email, pass) {
  * NOTE: it's not neccesary to differentiate between #2 and #3, as the client action
  *       is the same (regen keypair and tell us about it).
  */
-exports.getSyncResponse = function(email, identities) {
+exports.getSyncResponse = function(email, identities, cb) {
   var respBody = {
     unknown_emails: [ ],
     key_refresh: [ ]
   };
 
-  // fetch user acct
-  var acct = exports.findByEmail(email);
+  // get the user id associated with this account 
+  emailToUserID(email, function(userID) {
+    if (userID === undefined) {
+      cb("no such email: " + email);
+      return;
+    }
+    db.execute(
+      'SELECT address FROM emails WHERE ? = user',
+      [ userID ],
+      function (err, rows) {
+        if (err) cb(err);
+        else {
+          var emails = [ ];
+          for (var i = 0; i < rows.length; i++) emails.push(rows[i].address);
 
-  // #1
-  for (var e in identities) {
-    if (acct.emails.indexOf(e) == -1) respBody.unknown_emails.push(e);
-  }
+          // #1
+          for (var e in identities) {
+            if (emails.indexOf(e) == -1) respBody.unknown_emails.push(e);
+          }
 
-  // #2
-  for (var e in acct.emails) {
-    e = acct.emails[e];
-    if (!identities.hasOwnProperty(e)) respBody.key_refresh.push(e);
-  }
+          // #2
+          for (var e in emails) {
+            e = emails[e];
+            if (!identities.hasOwnProperty(e)) respBody.key_refresh.push(e);
+          }
+
+          // #3
+          // XXX todo
+
+          cb(undefined, respBody); 
+        }
+      });
+  });
+};
 
-  // #3
-  // XXX todo
 
-  return respBody;
+exports.pubkeysForEmail = function(identity, cb) {
+  db.execute('SELECT keys.key FROM keys, emails WHERE emails.address = ? AND keys.email = emails.id',
+             [ identity ],
+             function(err, rows) {
+               var keys = undefined;
+               if (!err && rows && rows.length) {
+                 keys = [ ];
+                 for (var i = 0; i < rows.length; i++) keys.push(rows[i].key);
+               }
+               cb(keys);
+             });
 };
\ No newline at end of file
diff --git a/authority/server/email.js b/authority/server/email.js
index edd8c02c1aff6829818b754dab9e023d7407c364..2777ab6cae4deca9b47678052cf80e554f6185f9 100644
--- a/authority/server/email.js
+++ b/authority/server/email.js
@@ -6,6 +6,10 @@ exports.sendVerificationEmail = function(email, secret) {
   // we'll just wait 5 seconds and manually feed the secret back into the
   // system, as if a user had clicked a link
   setTimeout(function() {
-    db.gotVerificationSecret(secret);
+    db.gotVerificationSecret(secret, function(e) {
+      if (e) {
+        console.log("error completing the verification: " + e);
+      }
+    });
   }, 5000);
 };
\ No newline at end of file
diff --git a/authority/server/httputils.js b/authority/server/httputils.js
index 31a77d6c950eaea6ed25d79a5125d1c62bcd7083..669cc049449a9958eee9958726404a01ea5fd0f8 100644
--- a/authority/server/httputils.js
+++ b/authority/server/httputils.js
@@ -35,6 +35,13 @@ exports.jsonResponse = function(resp, obj)
   resp.end();
 };
 
+exports.xmlResponse = function(resp, doc)
+{
+  resp.writeHead(200, {"Content-Type": "text/xml"});
+  if (doc !== undefined) resp.write(doc);
+  resp.end();
+};
+
 exports.checkGetArgs = function(req, args) {
     [ "email", "pass", "pubkey" ].forEach(function(k) {
       if (!urlobj.hasOwnProperty(k) || typeof urlobj[k] !== 'string') {
diff --git a/authority/server/run.js b/authority/server/run.js
index c36d32f929950780db38b08f38ca27fc92735d78..a44146ae3c124d360d118287b9828cc4d6221344 100644
--- a/authority/server/run.js
+++ b/authority/server/run.js
@@ -2,8 +2,8 @@ const path = require('path'),
        url = require('url'),
      wsapi = require('./wsapi.js'),
  httputils = require('./httputils.js'),
-   connect = require('connect');
-
+   connect = require('connect'),
+ webfinger = require('./webfinger.js');
 
 const STATIC_DIR = path.join(path.dirname(__dirname), "static");
 
@@ -22,6 +22,16 @@ exports.handler = function(request, response, serveFile) {
       console.log(errMsg);
       httputils.fourOhFour(response, errMsg);
     }
+  } else if (/^\/users\/[^\/]+.xml$/.test(urlpath)) {
+    var identity = path.basename(urlpath).replace(/.xml$/, '');
+
+    webfinger.renderUserPage(identity, function (resultDocument) {
+      if (resultDocument === undefined) {
+        httputils.fourOhFour(response, "I don't know anything about: " + identity);
+      } else {
+        httputils.xmlResponse(response, resultDocument);
+      }
+    });
   } else {
     // node.js takes care of sanitizing the request path
     serveFile(path.join(STATIC_DIR, urlpath), response);
diff --git a/authority/server/webfinger.js b/authority/server/webfinger.js
new file mode 100644
index 0000000000000000000000000000000000000000..ecabf1ffc64e9f91abfbad9f9299f8c1d7873dda
--- /dev/null
+++ b/authority/server/webfinger.js
@@ -0,0 +1,21 @@
+const db = require('./db.js');
+
+const HEADER = "<?xml version='1.0' encoding='UTF-8'?>\n<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'\n     xmlns:hm='http://host-meta.net/xrd/1.0'>\n";
+
+const FOOTER ="</XRD>\n";
+const KEYHEADER = "  <Link rel=\"public-key\" value=\"";
+const KEYFOOTER = "\"/>\n";
+
+exports.renderUserPage = function(identity, cb) {
+  db.pubkeysForEmail(identity, function(keys) {
+    var respDoc = undefined;
+    if (keys && keys.length) {
+      respDoc = HEADER;
+      for (var i = 0; i < keys.length; i++) {
+        respDoc += (KEYHEADER + keys[i] + KEYFOOTER) ;
+      }
+      respDoc += FOOTER;
+    }
+    cb(respDoc);
+  });
+};
\ No newline at end of file
diff --git a/authority/server/wsapi.js b/authority/server/wsapi.js
index 103a5962cbb779d795d10128828aa8504ad16922..1dc316de155b6049e64d15df1618db98f18ed75d 100644
--- a/authority/server/wsapi.js
+++ b/authority/server/wsapi.js
@@ -38,7 +38,9 @@ exports.have_email = function(req, resp) {
   // get inputs from get data!
   var email = url.parse(req.url, true).query['email'];
   logRequest("have_email", {email: email});
-  httputils.jsonResponse(resp, undefined != db.findByEmail(email));
+  db.emailKnown(email, function(known) { 
+    httputils.jsonResponse(resp, known);
+  });
 };
 
 /* First half of account creation.  Stages a user account for creation.
@@ -74,15 +76,17 @@ exports.registration_status = function(req, resp) {
   logRequest("registration_status", req.session);
 
   var email = req.session.pendingRegistration;
-  if (undefined != db.findByEmail(email)) {
-    delete req.session.pendingRegistration;
-    req.session.authenticatedUser = email;
-    httputils.jsonResponse(resp, "complete");
-  } else if (db.isStaged(email)) {
-    httputils.jsonResponse(resp, "pending");
-  } else {
-    httputils.jsonResponse(resp, "noRegistration");
-  }
+  db.emailKnown(email, function(known) {
+    if (known) {
+      delete req.session.pendingRegistration;
+      req.session.authenticatedUser = email;
+      httputils.jsonResponse(resp, "complete");
+    } else if (db.isStaged(email)) {
+      httputils.jsonResponse(resp, "pending");
+    } else {
+      httputils.jsonResponse(resp, "noRegistration");
+    }
+  });
 };
 
 exports.authenticate_user = function(req, resp) {
@@ -91,12 +95,10 @@ exports.authenticate_user = function(req, resp) {
 
   if (!checkParams(getArgs, resp, [ "email", "pass" ])) return;
 
-  if (db.checkAuth(getArgs.email, getArgs.pass)) {
-    req.session.authenticatedUser = getArgs.email;
-    httputils.jsonResponse(resp, true);      
-  } else {
-    httputils.jsonResponse(resp, false);      
-  }
+  db.checkAuth(getArgs.email, getArgs.pass, function(rv) {
+    if (rv) req.session.authenticatedUser = getArgs.email;
+    httputils.jsonResponse(resp, rv);
+  });
 };
 
 exports.add_email = function (req, resp) {
@@ -132,8 +134,9 @@ exports.set_key = function (req, resp) {
   if (!checkParams(getArgs, resp, [ "email", "pubkey" ])) return;
   if (!isAuthed(req, resp)) return;
   logRequest("set_key", getArgs);
-  db.addEmailToAccount(req.session.authenticatedUser, getArgs.email, getArgs.pubkey);
-  httputils.jsonResponse(resp, true);
+  db.addKeyToEmail(req.session.authenticatedUser, getArgs.email, getArgs.pubkey, function (rv) {
+    httputils.jsonResponse(resp, rv);
+  });
 };
 
 exports.am_authed = function(req,resp) {
@@ -152,10 +155,12 @@ exports.sync_emails = function(req,resp) {
     logRequest("sync_emails", requestBody);
     try {
       var emails = JSON.parse(requestBody);
-      var syncResponse = db.getSyncResponse(req.session.authenticatedUser, emails);
-      httputils.jsonResponse(resp, syncResponse);
     } catch(e) {
       httputils.badRequest(resp, "malformed payload: " + e);
     }
+    db.getSyncResponse(req.session.authenticatedUser, emails, function(err, syncResponse) {
+      if (err) httputils.serverError(resp, err);
+      else httputils.jsonResponse(resp, syncResponse);
+    });
   });
 };
diff --git a/authority/static/.well-known/host-meta b/authority/static/.well-known/host-meta
new file mode 100644
index 0000000000000000000000000000000000000000..f96a548972c015172efab6b25ddec3f5c5ec8d4f
--- /dev/null
+++ b/authority/static/.well-known/host-meta
@@ -0,0 +1,11 @@
+<?xml version='1.0' encoding='UTF-8'?>
+
+<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0' 
+     xmlns:hm='http://host-meta.net/xrd/1.0'>
+
+  <hm:Host xmlns='http://host-meta.net/xrd/1.0'>authority.mozilla.org</hm:Host>
+
+  <Link rel='lrdd' template='http://authority.mozilla.org/users/{uri}.xml'></Link>
+
+  <Link rel='other' value='something-different'></Link>
+</XRD>
diff --git a/rp/server/httputils.js b/rp/server/httputils.js
new file mode 100644
index 0000000000000000000000000000000000000000..669cc049449a9958eee9958726404a01ea5fd0f8
--- /dev/null
+++ b/rp/server/httputils.js
@@ -0,0 +1,52 @@
+// various little utilities to make crafting boilerplate responses
+// simple
+
+exports.fourOhFour = function(resp, reason)
+{
+  resp.writeHead(404, {"Content-Type": "text/plain"});
+  resp.write("Not Found");
+  if (reason) {
+    resp.write(": " + reason);
+  }
+  resp.end();
+};
+
+exports.serverError = function(resp, reason)
+{
+  resp.writeHead(500, {"Content-Type": "text/plain"});
+  if (reason) resp.write(reason);
+  resp.end();
+};
+
+exports.badRequest = function(resp, reason)
+{
+  resp.writeHead(400, {"Content-Type": "text/plain"});
+  resp.write("Bad Request");
+  if (reason) {
+    resp.write(": " + reason);
+  }
+  resp.end();
+};
+
+exports.jsonResponse = function(resp, obj)
+{
+  resp.writeHead(200, {"Content-Type": "application/json"});
+  if (obj !== undefined) resp.write(JSON.stringify(obj));
+  resp.end();
+};
+
+exports.xmlResponse = function(resp, doc)
+{
+  resp.writeHead(200, {"Content-Type": "text/xml"});
+  if (doc !== undefined) resp.write(doc);
+  resp.end();
+};
+
+exports.checkGetArgs = function(req, args) {
+    [ "email", "pass", "pubkey" ].forEach(function(k) {
+      if (!urlobj.hasOwnProperty(k) || typeof urlobj[k] !== 'string') {
+        throw k;
+      }
+    });
+
+};
diff --git a/rp/server/run.js b/rp/server/run.js
new file mode 100644
index 0000000000000000000000000000000000000000..291f1e1cc25ee53b4182dcbad51aa7a222694d42
--- /dev/null
+++ b/rp/server/run.js
@@ -0,0 +1,45 @@
+const path = require('path'),
+       url = require('url'),
+     wsapi = require('./wsapi.js'),
+ httputils = require('./httputils.js'),
+   connect = require('connect'),
+        fs = require('fs');
+
+const STATIC_DIR = path.join(path.dirname(__dirname), "static");
+
+exports.handler = function(request, response, serveFile) {
+  // dispatch!
+  var urlpath = url.parse(request.url).pathname;
+
+  if (urlpath === '/authenticate') {
+    // XXX: do something
+    httputils.serverError(response, "notImplemented");
+  } else {
+    // node.js takes care of sanitizing the request path
+    // automatically serve index.html if this is a directory
+    var filename = path.join(STATIC_DIR, urlpath)
+    fs.stat(filename, function(err, s) {
+      if (err === null && s.isDirectory()) {
+        serveFile(path.join(filename, "index.html"), response);
+      } else {
+        serveFile(filename, response);
+      }
+    });
+  }
+};
+
+exports.setup = function(server) {
+  var week = (7 * 24 * 60 * 60 * 1000);
+
+  server
+    .use(connect.cookieParser())
+    .use(connect.session({
+      secret: "rhodesian ridgeback",
+      cookie: {
+        path: '/',
+        httpOnly: true,
+        expires:  new Date(Date.now() + week),// a week XXX: think about session security, etc
+        maxAge: week
+      }
+    }));
+}
diff --git a/rp/server/wsapi.js b/rp/server/wsapi.js
new file mode 100644
index 0000000000000000000000000000000000000000..e802e619dec3a7e094bab6e8b2398e92b1914b98
--- /dev/null
+++ b/rp/server/wsapi.js
@@ -0,0 +1,37 @@
+// a module which implements the authorities web server api.
+// every export is a function which is a WSAPI method handler
+
+const    url = require('url'),
+   httputils = require('./httputils.js')
+
+function checkParams(getArgs, resp, params) {
+  try {
+    params.forEach(function(k) {
+      if (!getArgs.hasOwnProperty(k) || typeof getArgs[k] !== 'string') {
+        throw k;
+      }
+    });
+  } catch(e) {
+    httputils.badRequest(resp, "missing '" + e + "' argument");
+    return false;
+  }
+  return true;
+}
+
+function isAuthed(req, resp) {
+  if (typeof req.session.authenticatedUser !== 'string') {
+    httputils.badRequest(resp, "requires authentication");
+    return false;
+  }
+  return true;
+}
+
+exports.all_words = function(req,resp) {
+  if (!isAuthed(req,resp)) return;
+  httputils.serverError(resp, "notImplemented");
+};
+
+exports.add_word = function(req,resp) {
+  if (!isAuthed(req,resp)) return;
+  httputils.serverError(resp, "notImplemented");
+};
diff --git a/rp/index.html b/rp/static/index.html
similarity index 100%
rename from rp/index.html
rename to rp/static/index.html
diff --git a/rp/jquery-min.js b/rp/static/jquery-min.js
similarity index 100%
rename from rp/jquery-min.js
rename to rp/static/jquery-min.js
diff --git a/run.js b/run.js
index c797b49404036bf4bc9b4d1cc7eb0c76741ede44..42da0e40f86b80f70fc1a3f46391c45e6cb5e3e4 100644
--- a/run.js
+++ b/run.js
@@ -66,7 +66,7 @@ function serveFile(filename, response) {
         var a = o.server.address();
         var from = o.name + ".mozilla.org";
         var to = a.address + ":" + a.port;
-        data = data.replace(from, to);
+        data = data.replace(new RegExp(from, 'g'), to);
       }
 
       response.writeHead(200, {"Content-Type": mimeType});