diff --git a/ChangeLog b/ChangeLog
index 6dd3d19e1c3114b1e3bc726c093759c2ca1856c0..63313b66470814d106127312a39006ebbce6acce 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,4 +1,22 @@
-train-2011.01.25 (in progress):
+train-2011.02.08 (in progress):
+
+train-2011.02.02:
+  * i18n support, now BrowserID speaks your language: #926, #936, #977, #1013, #1031
+  * improved error screens on slow server responses: #913, #915
+  * better cache headers on all html resources (which Vary by Accept-Languages): #226, #620, #920, #938
+  * cosmetic fixes: #918, #947, #966, #981, #1020, #987
+  * preliminary work to improve messaging when cookies are disabled: #835
+  * remove dead code: #925
+  * fix include.orig.js: #921, #911
+  * load testing compatibility and minified resources are no longer mutually exclusive: #939
+  * improve usability via default button focus (just hit enter in more places): #946, #960
+  * scripts to deploy to an amazon EC2 instance.
+  * improve configuration mechanism: #582, #1006
+  * limit post bodies to verifier: #878
+  * cancel from forgot password doesn't cause your email to be, uh, forgotten: #1001
+  * remember the users email as they move from screen to screen in the dialog: #984, #1001, #1002, #1003, #1004
+  * secondary "cancel" style buttons have a smaller font: #1020
+  * build fixes: #1021, #1024
 
 train-2011.01.18:
   * support for 3rd party primary identity providers: #761, #904, #865
@@ -12,6 +30,16 @@ train-2011.01.18:
   * language/rendering refinements: #850, #439, #622, #818, #901, #630, #888, #345, #815
   * front end performance improvements: #899, #910
   * better UX for network timeouts: #905
+  * (hotfix 2012.01.23) Remove unwanted scrollbar in dialog: issue #947
+  * (hotfix 2012.01.23) Fix black backgrounds on IE8: issue #929
+  * (hotfix 2012.01.23) fix broken transition to "check your email": #933, #934, #935
+  * (hotfix 2012.01.24) Fix "slow script" error on IE8 during keygen on behalf of primary: #956
+  * (hotfix 2012.01.24) Publish javascript API to provide a native-support compatible for primaries' auth pages: #909
+  * (hotfix 2012.01.24) Allow load testing hooks to be enabled with minified resources: #939
+  * (hotfix 2012.01.24) IE8 fixes for primary flow: #962, #961, #958, #955
+  * (hotfix 2012.01.24) print correct url for where the user will be directed: #964
+  * (hotfix 2012.01.31) fix silent assertions: #972
+  * (hotfix 2012.02.01) fix verification of email on a browser other than the initiator: #973, #1026 (and maybe others)
 
 train-2011.01.05:
   * client entropy pool mixes in randomness from server for better browser RNG: #298, #800
diff --git a/example/primary/sign_in.html b/example/primary/sign_in.html
index 0e89ba67bb5343504e29942180f10bfb85b2afa4..51a0eaccae58c47b4ffa31f88220cb0a9b2aec60 100644
--- a/example/primary/sign_in.html
+++ b/example/primary/sign_in.html
@@ -16,6 +16,8 @@ body { margin: auto; font: 13px/1.5 Helvetica, Arial, 'Liberation Sans', FreeSan
 .intro { font-size: 1.2em; width: 600px; margin: auto; }
 .main { text-align: center; margin-top: 2em; font-size: 1.2em; width: 500px; margin: auto; }
 #who { font-weight: bold; }
+#cancel { font-size: small; }
+button { line-height: 20px; }
 
 </style>
 </head>
@@ -27,6 +29,7 @@ body { margin: auto; font: 13px/1.5 Helvetica, Arial, 'Liberation Sans', FreeSan
 <div class="main" id="logged_out">
   Sign in as <span id="who">...</span>
   <button>doit</button>
+  <a href="#" id="cancel">cancel</a>
 </div>
 
 <script type="text/javascript" src="jquery.js"></script>
@@ -57,6 +60,11 @@ $(document).ready(function() {
         window.location = getParameterByName('return_to');
       });
   });
+
+  $("#cancel").click(function(e) {
+    e.preventDefault();
+    window.location = getParameterByName('return_to');
+  });
 });
 </script>
 </body>
diff --git a/lib/browserid/views.js b/lib/browserid/views.js
index 819d8b271828819c0b27aee460765567070892a7..d87269bd1bb467ce5ee0f20174bf28cd17569286 100644
--- a/lib/browserid/views.js
+++ b/lib/browserid/views.js
@@ -52,10 +52,9 @@ exports.setup = function(app) {
   });
 
   app.get('/include.js', function(req, res, next) {
-    var env = config.get('env');
-
     req.url = "/include_js/include.js";
-    if (env === 'production') {
+
+    if (config.get('use_minified_resources') === true) {
       req.url = "/production/include.js"
     }
 
diff --git a/resources/static/dialog/controllers/actions.js b/resources/static/dialog/controllers/actions.js
index 3a2aa0eea5f2e68e6a6cc9a9e1d025cba78060cd..9f90bc380fb24ae5848238673c8452d41680ed9f 100644
--- a/resources/static/dialog/controllers/actions.js
+++ b/resources/static/dialog/controllers/actions.js
@@ -147,6 +147,10 @@ BrowserID.Modules.Actions = (function() {
       startService("verify_primary_user", info);
     },
 
+    doCannotVerifyRequiredPrimary: function(info) {
+      this.renderError("cannot_verify_required_email", info);
+    },
+
     doPrimaryUserProvisioned: function(info) {
       startService("primary_user_provisioned", info);
     },
diff --git a/resources/static/dialog/controllers/dialog.js b/resources/static/dialog/controllers/dialog.js
index d7acf4f3ca37aa4338797c5254e4152e31c94794..7192887df535bad47060c338a62ee6acefc9c327 100644
--- a/resources/static/dialog/controllers/dialog.js
+++ b/resources/static/dialog/controllers/dialog.js
@@ -128,19 +128,27 @@ BrowserID.Modules.Dialog = (function() {
 
         // XXX Perhaps put this into the state machine.
         self.bind(win, "unload", onWindowUnload);
+
         if(hash.indexOf("#CREATE_EMAIL=") === 0) {
           var email = hash.replace(/#CREATE_EMAIL=/, "");
-          self.renderDialog("primary_user_verified", { email: email });
-          self.close("primary_user", { email: email, add: false });
+          params.type = "primary";
+          params.email = email;
+          params.add = false;
         }
         else if(hash.indexOf("#ADD_EMAIL=") === 0) {
           var email = hash.replace(/#ADD_EMAIL=/, "");
-          self.renderDialog("primary_user_verified", { email: email });
-          self.close("primary_user", { email: email, add: true });
+          params.type = "primary";
+          params.email = email;
+          params.add = true;
         }
-        else {
-          self.publish("start", params);
+
+        /*
+        if(hash.indexOf("REQUIRED=true") > -1) {
+          params.requiredEmail = params.email;
         }
+        */
+
+        self.publish("start", params);
       }
     }
 
diff --git a/resources/static/dialog/resources/state.js b/resources/static/dialog/resources/state.js
index d29f7ffa4b5ab3d9bfb7b82a8063730e01e82158..22a3098e78db35eba06155414b2251cf0a8f3f13 100644
--- a/resources/static/dialog/resources/state.js
+++ b/resources/static/dialog/resources/state.js
@@ -10,10 +10,12 @@ BrowserID.State = (function() {
       publish = mediator.publish.bind(mediator),
       user = bid.User,
       moduleManager = bid.module,
+      complete = bid.Helpers.complete,
       controller,
       addPrimaryUser = false,
       email,
-      requiredEmail;
+      requiredEmail,
+      primaryVerificationInfo;
 
   function startStateMachine() {
     var self = this,
@@ -27,7 +29,7 @@ BrowserID.State = (function() {
 
           var func = controller[msg].bind(controller);
           self.gotoState(save, func, options);
-        }
+        },
         cancelState = self.popState.bind(self);
 
     subscribe("offline", function(msg, info) {
@@ -41,11 +43,14 @@ BrowserID.State = (function() {
       self.allowPersistent = !!info.allowPersistent;
       requiredEmail = info.requiredEmail;
 
-      if ((typeof(requiredEmail) !== "undefined")
-       && (!bid.verifyEmail(requiredEmail))) {
+      if ((typeof(requiredEmail) !== "undefined") && (!bid.verifyEmail(requiredEmail))) {
         // Invalid format
         startState("doError", "invalid_required_email", {email: requiredEmail});
       }
+      else if(info.email && info.type === "primary") {
+        primaryVerificationInfo = info;
+        publish("primary_user", info);
+      }
       else {
         startState("doCheckAuth");
       }
@@ -95,8 +100,6 @@ BrowserID.State = (function() {
       addPrimaryUser = !!info.add;
       email = info.email;
 
-      //updateCurrentStateInfo(info);
-
       var idInfo = storage.getEmail(email);
       if(idInfo && idInfo.cert) {
         publish("primary_user_ready", info);
@@ -120,7 +123,24 @@ BrowserID.State = (function() {
       info.add = !!addPrimaryUser;
       info.email = email;
       info.requiredEmail = !!requiredEmail;
-      startState("doVerifyPrimaryUser", info);
+      if(primaryVerificationInfo) {
+        primaryVerificationInfo = null;
+        if(requiredEmail) {
+          startState("doCannotVerifyRequiredPrimary", info);
+        }
+        else if(info.add) {
+          // Add the pick_email in case the user cancels the add_email screen.
+          // The user needs something to go "back" to.
+          publish("pick_email", info);
+          publish("add_email", info);
+        }
+        else {
+          publish("authenticate", info);
+        }
+      }
+      else {
+        startState("doVerifyPrimaryUser", info);
+      }
     });
 
     subscribe("primary_user_authenticating", function(msg, info) {
@@ -142,11 +162,11 @@ BrowserID.State = (function() {
     });
 
     subscribe("email_chosen", function(msg, info) {
-      var email = info.email
+      var email = info.email,
           idInfo = storage.getEmail(email);
 
-      function complete() {
-        info.complete && info.complete();
+      function oncomplete() {
+        complete(info.complete);
       }
 
       if(idInfo) {
@@ -176,8 +196,8 @@ BrowserID.State = (function() {
             else {
               startState("doEmailChosen", info);
             }
-            complete();
-          }, complete);
+            oncomplete();
+          }, oncomplete);
         }
       }
       else {
@@ -219,7 +239,7 @@ BrowserID.State = (function() {
     });
 
     subscribe("add_email", function(msg, info) {
-      startState("doAddEmail");
+      startState("doAddEmail", info);
     });
 
     subscribe("email_staged", function(msg, info) {
diff --git a/resources/static/dialog/views/cannot_verify_required_email.ejs b/resources/static/dialog/views/cannot_verify_required_email.ejs
new file mode 100644
index 0000000000000000000000000000000000000000..3bd7f68169ab8aee0d0a5a2f0107dd3d933b006c
--- /dev/null
+++ b/resources/static/dialog/views/cannot_verify_required_email.ejs
@@ -0,0 +1,12 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+  <h2 id="cannotVerifyRequiredEmail"><%= format(gettext('Cannot verify %s'), [email]) %></h2>
+
+  <p>
+    <%= format(gettext('%s is a required address, but we cannot verify that you own this address.'), [email]) %>
+  </p>
+
+
diff --git a/resources/static/dialog/views/test_template_no_input.ejs b/resources/static/dialog/views/test_template_no_input.ejs
index 825dc5a89ae4ae28507c68f1625b0c327a2c5a5c..1fc8e84c44d2cc16a95e1ff59749c88519263196 100644
--- a/resources/static/dialog/views/test_template_no_input.ejs
+++ b/resources/static/dialog/views/test_template_no_input.ejs
@@ -3,7 +3,5 @@
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 
-<%= gettext("translated text") %>
-
 <button id="focusButton">Button!</button>
 
diff --git a/resources/static/dialog/views/test_template_with_input.ejs b/resources/static/dialog/views/test_template_with_input.ejs
index 60e33f244b2597b7a2acdf0e235ef904cf1f8a73..86d4ffa2a8d0dfcc8d44e6b930926dd6a4f887e1 100644
--- a/resources/static/dialog/views/test_template_with_input.ejs
+++ b/resources/static/dialog/views/test_template_with_input.ejs
@@ -4,5 +4,3 @@
 
 <input id="templateInput" type="text" value="" />
 
-<%= gettext("translated text") %>
-
diff --git a/resources/static/dialog/views/verify_primary_user.ejs b/resources/static/dialog/views/verify_primary_user.ejs
index 105cb872e2b771bb171849ffd32f92a9723318f0..ea43a338118f5409606a1296c3e32299830d74dc 100644
--- a/resources/static/dialog/views/verify_primary_user.ejs
+++ b/resources/static/dialog/views/verify_primary_user.ejs
@@ -17,7 +17,6 @@
 
           <li>
               <%= gettext("You must sign in with your email provider to verify ownership of this address. This window will be redirected to") %>
-
               <p>
                   <strong><%= auth_url %></strong>.
               </p>
@@ -34,7 +33,6 @@
 
   <div class="cf form_section">
     <%= gettext("You must sign in with your email provider to verify ownership of this address. This window will be redirected to") %>
-
     <p>
       <strong><%= auth_url %></strong>.
     </p>
diff --git a/resources/static/shared/wait-messages.js b/resources/static/shared/wait-messages.js
index 20b11a4b50e2d817073f9599e41f0104b3d13327..00a64aa65609bbe0036a9bda3033220cced2ca87 100644
--- a/resources/static/shared/wait-messages.js
+++ b/resources/static/shared/wait-messages.js
@@ -17,7 +17,7 @@ BrowserID.Wait = (function(){
 
     slowXHR: {
       title:  gettext("We are sorry, this request is taking a LOOONG time."),
-      message:  gettext("This message will go away when the request completes (hopefully soon).  If you wait too long, close this window and try again."),
+      message:  gettext("This message will go away when the request completes (hopefully soon). If you wait too long, close this window and try again."),
       id: "slowXHR"
     }
 
diff --git a/resources/static/test/cases/controllers/actions.js b/resources/static/test/cases/controllers/actions.js
index 74ef5b3a55aec42bb78eb8e76ea7b2ee4a36c910..20593ecac0c0a8920a89d67386f861beaa5cafa0 100644
--- a/resources/static/test/cases/controllers/actions.js
+++ b/resources/static/test/cases/controllers/actions.js
@@ -97,6 +97,18 @@
     });
   });
 
+  asyncTest("doCannotVerifyRequiredPrimary - show the error screen", function() {
+    createController({
+      ready: function() {
+        controller.doCannotVerifyRequiredPrimary({ email: "testuser@testuser.com"});
+
+        testHelpers.testErrorVisible();
+        start();
+      }
+    });
+
+  });
+
   asyncTest("doPrimaryUserProvisioned - start the primary_user_verified service", function() {
     createController({
       ready: function() {
diff --git a/resources/static/test/cases/controllers/dialog.js b/resources/static/test/cases/controllers/dialog.js
index 643309043d13baaf436bc3ca8ce25626adc7c5fd..ee944e7d4e4b471795188f44523add65d394d051 100644
--- a/resources/static/test/cases/controllers/dialog.js
+++ b/resources/static/test/cases/controllers/dialog.js
@@ -131,12 +131,13 @@
     });
   });
 
-  asyncTest("initialization with #CREATE_EMAIL=testuser@testuser.com", function() {
+  asyncTest("initialization with #CREATE_EMAIL=testuser@testuser.com - trigger start with correct params", function() {
     winMock.location.hash = "#CREATE_EMAIL=testuser@testuser.com";
 
     createController({
       ready: function() {
-        mediator.subscribe("primary_user", function(msg, info) {
+        mediator.subscribe("start", function(msg, info) {
+          equal(info.type, "primary", "correct type");
           equal(info.email, "testuser@testuser.com", "email_chosen with correct email");
           equal(info.add, false, "add is not specified with CREATE_EMAIL option");
           start();
@@ -153,12 +154,13 @@
     });
   });
 
-  asyncTest("initialization with #ADD_EMAIL=testuser@testuser.com", function() {
+  asyncTest("initialization with #ADD_EMAIL=testuser@testuser.com - trigger start with correct params", function() {
     winMock.location.hash = "#ADD_EMAIL=testuser@testuser.com";
 
     createController({
       ready: function() {
-        mediator.subscribe("primary_user", function(msg, info) {
+        mediator.subscribe("start", function(msg, info) {
+          equal(info.type, "primary", "correct type");
           equal(info.email, "testuser@testuser.com", "email_chosen with correct email");
           equal(info.add, true, "add is specified with ADD_EMAIL option");
           start();
diff --git a/resources/static/test/cases/resources/state.js b/resources/static/test/cases/resources/state.js
index 84f7961a57efadd5b86798f9992d884aa4270f3d..a9e95a48b27d6f9bcdddb780e85731a8b2c1059c 100644
--- a/resources/static/test/cases/resources/state.js
+++ b/resources/static/test/cases/resources/state.js
@@ -9,6 +9,7 @@
   var bid = BrowserID,
       mediator = bid.Mediator,
       State = bid.State,
+      user = bid.User,
       machine,
       actions,
       storage = bid.Storage,
@@ -106,27 +107,47 @@
     equal(actions.info.doConfirmEmail.required, true, "doConfirmEmail called with required flag");
   });
 
-  test("primary_user with already provisioned primary user calls doEmailChosen", function() {
+  test("primary_user with already provisioned primary user - call doEmailChosen", function() {
     storage.addEmail("testuser@testuser.com", { type: "primary", cert: "cert" });
     mediator.publish("primary_user", { email: "testuser@testuser.com" });
     ok(actions.called.doEmailChosen, "doEmailChosen called");
   });
 
-  test("primary_user with unprovisioned primary user doProvisionPrimaryUser", function() {
+  test("primary_user with unprovisioned primary user - call doProvisionPrimaryUser", function() {
     mediator.publish("primary_user", { email: "testuser@testuser.com" });
     ok(actions.called.doProvisionPrimaryUser, "doPrimaryUserProvisioned called");
   });
 
-  test("primary_user_provisioned calls doEmailChosen", function() {
+  test("primary_user_provisioned - call doEmailChosen", function() {
     mediator.publish("primary_user_provisioned", { email: "testuser@testuser.com" });
     ok(actions.called.doPrimaryUserProvisioned, "doPrimaryUserProvisioned called");
   });
 
-  test("primary_user_unauthenticated calls doVerifyPrimaryUser", function() {
+  test("primary_user_unauthenticated before verification - call doVerifyPrimaryUser", function() {
+    mediator.publish("start");
     mediator.publish("primary_user_unauthenticated");
     ok(actions.called.doVerifyPrimaryUser, "doVerifyPrimaryUser called");
   });
 
+  test("primary_user_unauthenticated after required email - call doCannotVerifyRequiredPrimary", function() {
+    mediator.publish("start", { requiredEmail: "testuser@testuser.com", type: "primary", add: false, email: "testuser@testuser.com" });
+    mediator.publish("primary_user_unauthenticated");
+    ok(actions.called.doCannotVerifyRequiredPrimary, "doCannotVerifyRequiredPrimary called");
+  });
+
+  test("primary_user_unauthenticated after verification of new user - call doAuthenticate", function() {
+    mediator.publish("start", { email: "testuser@testuser.com", type: "primary", add: false });
+    mediator.publish("primary_user_unauthenticated");
+    ok(actions.called.doAuthenticate, "doAuthenticate called");
+  });
+
+  test("primary_user_unauthenticated after verification of additional email to current user - call doPickEmail and doAddEmail", function() {
+    mediator.publish("start", { email: "testuser@testuser.com", type: "primary", add: true });
+    mediator.publish("primary_user_unauthenticated");
+    ok(actions.called.doPickEmail, "doPickEmail called");
+    ok(actions.called.doAddEmail, "doAddEmail called");
+  });
+
   test("primary_user_authenticating stops all modules", function() {
     try {
       mediator.publish("primary_user_authenticating");
@@ -137,13 +158,13 @@
     }
   });
 
-  test("primary_user calls doProvisionPrimaryUser", function() {
+  test("primary_user - call doProvisionPrimaryUser", function() {
     mediator.publish("primary_user", { email: "testuser@testuser.com", assertion: "assertion" });
 
     ok(actions.called.doProvisionPrimaryUser, "doProvisionPrimaryUser called");
   });
 
-  test("primary_user_ready calls doEmailChosen", function() {
+  test("primary_user_ready - call doEmailChosen", function() {
     mediator.publish("primary_user_ready", { email: "testuser@testuser.com", assertion: "assertion" });
 
     ok(actions.called.doEmailChosen, "doEmailChosen called");
@@ -200,11 +221,11 @@
     equal(actions.info.doAssertionGenerated, "assertion", "assertion generated with good assertion");
   });
 
-  test("add_email", function() {
-    // XXX rename add_email to request_add_email
-    mediator.publish("add_email");
+  test("add_email - call doAddEmail", function() {
+    mediator.publish("add_email", { email: "testuser@testuser.com" });
 
     ok(actions.called.doAddEmail, "user wants to add an email");
+    ok(actions.info.doAddEmail.email, "testuser@testuser.com", "correct email passed");
   });
 
   test("email_confirmed", function() {
@@ -237,13 +258,13 @@
     equal(actions.info.doAuthenticate.email, "testuser@testuser.com", "authenticate with testuser@testuser.com");
   });
 
-  test("start with no required email address should go straight to checking auth", function() {
+  test("start with no special parameters - go straight to checking auth", function() {
     mediator.publish("start");
 
     equal(actions.called.doCheckAuth, true, "checking auth on start");
   });
 
-  test("start with invalid requiredEmail prints error screen", function() {
+  test("start with invalid requiredEmail - print error screen", function() {
     mediator.publish("start", {
       requiredEmail: "bademail"
     });
@@ -251,7 +272,7 @@
     equal(actions.called.doError, true, "error screen is shown");
   });
 
-  test("start with empty requiredEmail prints error screen", function() {
+  test("start with empty requiredEmail - prints error screen", function() {
     mediator.publish("start", {
       requiredEmail: ""
     });
@@ -259,14 +280,22 @@
     equal(actions.called.doError, true, "error screen is shown");
   });
 
-  test("start with valid requiredEmail goes to auth", function() {
-    mediator.publish("start", {
-      requiredEmail: "testuser@testuser.com"
-    });
+  test("start with valid requiredEmail - go to doCheckAuth", function() {
+    mediator.publish("start", { requiredEmail: "testuser@testuser.com" });
 
     equal(actions.called.doCheckAuth, true, "checking auth on start");
   });
 
+  asyncTest("start to complete successful primary email verification - goto 'primary_user'", function() {
+    mediator.subscribe("primary_user", function(msg, info) {
+      equal(info.email, "testuser@testuser.com", "correct email given");
+      equal(info.add, true, "correct add flag");
+      start();
+    });
+
+    mediator.publish("start", { email: "testuser@testuser.com", type: "primary", add: true });
+  });
+
   test("cancel", function() {
     mediator.publish("cancel");
 
diff --git a/resources/views/dialog_layout.ejs b/resources/views/dialog_layout.ejs
index 3d779ae93a12675cc253596a09df76ae2e3022e1..7a5e9b63989afabc87bb253356d4166713eaa68c 100644
--- a/resources/views/dialog_layout.ejs
+++ b/resources/views/dialog_layout.ejs
@@ -20,7 +20,7 @@
       <link href="/dialog/css/m.css" rel="stylesheet" type="text/css">
   <% } %>
   <link href="https://fonts.googleapis.com/css?family=Droid+Serif:400,400italic,700,700italic" rel="stylesheet" type="text/css">
-  <title><%= gettext('Browser ID') %></title>
+  <title><%= gettext('BrowserID') %></title>
 </head>
   <body class="waiting">
       <div id="wrapper">
diff --git a/resources/views/verify_email_address.ejs b/resources/views/verify_email_address.ejs
index e8ead6f6ffad837d1c43ecd3c9222d7940cbc97d..4df7506ed2be239db5aeeff28049931b1820ede6 100644
--- a/resources/views/verify_email_address.ejs
+++ b/resources/views/verify_email_address.ejs
@@ -5,7 +5,7 @@
 <div id="vAlign" class="display_always">
     <div id="signUpFormWrap">
         <ul class="notifications">
-            <li class="notification error" id="cannotconfirm"><%= gettext('There was a problem with your signup link.  Has this address already been registered?') %></li>
+            <li class="notification error" id="cannotconfirm"><%= gettext('There was a problem with your signup link. Has this address already been registered?') %></li>
             <li class="notification error" id="cannotcommunicate"><%= gettext('Error comunicating with server.') %></li>
             <li class="notification error" id="cannotcomplete"><%= gettext('Error encountered trying to complete registration.') %></li>
         </ul>
diff --git a/scripts/browserid.spec b/scripts/browserid.spec
index 0f7f1a708c0c8be4b73dc7ef4e0a888335470865..6e98d59e4c32965287b071fed0ed3b00bd46a01c 100644
--- a/scripts/browserid.spec
+++ b/scripts/browserid.spec
@@ -1,7 +1,7 @@
 %define _rootdir /opt/browserid
 
 Name:          browserid-server
-Version:       0.2012.01.25
+Version:       0.2012.02.08
 Release:       1%{?dist}
 Summary:       BrowserID server
 Packager:      Pete Fritchman <petef@mozilla.com>
diff --git a/scripts/deploy.js b/scripts/deploy.js
index b5df5f74d24527b72f3c70481ea3482ecedb9a32..aebb6c72ff31fdd28b0bc0a31e197f44d53f68b2 100755
--- a/scripts/deploy.js
+++ b/scripts/deploy.js
@@ -40,25 +40,32 @@ verbs['destroy'] = function(args) {
   }
   var name = args[0];
   validateName(name);
+  var hostname =  name + ".hacksign.in";
 
-  process.stdout.write("trying to destroy VM for " + name + ".hacksign.in: "); 
-  vm.destroy(name, function(err) {
+  process.stdout.write("trying to destroy VM for " + hostname + ": ");
+  vm.destroy(name, function(err, deets) {
     console.log(err ? ("failed: " + err) : "done");
-    process.stdout.write("trying to remove DNS for " + name + ".hacksign.in: "); 
-    dns.deleteRecord(name, function(err) {
+    process.stdout.write("trying to remove DNS for " + hostname + ": ");
+    dns.deleteRecord(hostname, function(err) {
       console.log(err ? "failed: " + err : "done");
+      if (deets && deets.ipAddress) {
+        process.stdout.write("trying to remove git remote: ");
+        git.removeRemote(name, deets.ipAddress, function(err) {
+          console.log(err ? "failed: " + err : "done");
+        });
+      }
     });
   });
 }
 
 verbs['test'] = function() {
   // let's see if we can contact aws and zerigo
-  process.stdout.write("Checking DNS management access: "); 
+  process.stdout.write("Checking DNS management access: ");
   dns.inUse("somerandomname", function(err) {
     console.log(err ? "NOT ok: " + err : "good");
-    process.stdout.write("Checking AWS access: "); 
+    process.stdout.write("Checking AWS access: ");
     vm.list(function(err) {
-      console.log(err ? "NOT ok: " + err : "good");      
+      console.log(err ? "NOT ok: " + err : "good");
     });
   });
 }
@@ -69,10 +76,12 @@ verbs['deploy'] = function(args) {
   }
   var name = args[0];
   validateName(name);
+  var hostname =  name + ".hacksign.in";
+  var longName = 'browserid deployment (' + name + ')';
 
   console.log("attempting to set up " + name + ".hacksign.in");
 
-  dns.inUse(name, function(err, r) {
+  dns.inUse(hostname, function(err, r) {
     checkErr(err);
     if (r) checkErr("sorry!  that name '" + name + "' is already being used.  so sad");
 
@@ -83,11 +92,11 @@ verbs['deploy'] = function(args) {
       vm.waitForInstance(r.instanceId, function(err, deets) {
         checkErr(err);
         console.log("   ... Instance ready, setting up DNS");
-        dns.updateRecord(name, deets.ipAddress, function(err) {
+        dns.updateRecord(name, "hacksign.in", deets.ipAddress, function(err) {
           checkErr(err);
           console.log("   ... DNS set up, setting human readable name in aws");
 
-          vm.setName(r.instanceId, args[0], function(err) {
+          vm.setName(r.instanceId, longName, function(err) {
             checkErr(err);
             console.log("   ... name set, waiting for ssh access and configuring");
             var config = { public_url: "https://" + name + ".hacksign.in"};
@@ -99,7 +108,7 @@ verbs['deploy'] = function(args) {
                 if (err && /already exists/.test(err)) {
                   console.log("OOPS! you already have a git remote named 'test'!");
                   console.log("to create a new one: git remote add <name> " +
-                              "app@" + deets.ipAddress + ":git");  
+                              "app@" + deets.ipAddress + ":git");
                 } else {
                   checkErr(err);
                 }
diff --git a/scripts/deploy/dns.js b/scripts/deploy/dns.js
index 522dc538bb9e90d20298fe96d28de9c473de8898..effcd7d49f51814080340718981134de446ffcac 100644
--- a/scripts/deploy/dns.js
+++ b/scripts/deploy/dns.js
@@ -39,11 +39,11 @@ function doRequest(method, path, body, cb) {
   req.end();
 };
 
-exports.updateRecord = function (hostname, ip, cb) {
+exports.updateRecord = function (hostname, zone, ip, cb) {
   doRequest('GET', '/api/1.1/zones.xml', null, function(err, r) {
     if (err) return cb(err);
     var m = jsel.match('object:has(:root > .domain:val(?)) > .id .$t',
-                       [ 'hacksign.in' ], r);
+                       [ zone ], r);
     if (m.length != 1) return cb("couldn't extract domain id from zerigo"); 
     var path = '/api/1.1/hosts.xml?zone_id=' + m[0];
     var body = '<host><data>' + ip + '</data><host-type>A</host-type>';
@@ -56,7 +56,7 @@ exports.updateRecord = function (hostname, ip, cb) {
 };
 
 exports.deleteRecord = function (hostname, cb) {
-  doRequest('GET', '/api/1.1/hosts.xml?fqdn=' + hostname + ".hacksign.in", null, function(err, r) {
+  doRequest('GET', '/api/1.1/hosts.xml?fqdn=' + hostname, null, function(err, r) {
     if (err) return cb(err);
     var m = jsel.match('.host .id > .$t', r);
     if (!m.length) return cb("no such DNS record");
@@ -73,7 +73,7 @@ exports.deleteRecord = function (hostname, cb) {
 };
 
 exports.inUse = function (hostname, cb) {
-  doRequest('GET', '/api/1.1/hosts.xml?fqdn=' + hostname + ".hacksign.in", null, function(err, r) {
+  doRequest('GET', '/api/1.1/hosts.xml?fqdn=' + hostname, null, function(err, r) {
     if (err) return cb(err);
     var m = jsel.match('.hosts object:.host', r);
     // we shouldn't have multiple!  oops!  let's return the first one
diff --git a/scripts/deploy/git.js b/scripts/deploy/git.js
index 34b88c26be8b74f78d8b546da26ea7e99759de5b..757e6678ff1046abc464df1f5a38197f01c1590a 100644
--- a/scripts/deploy/git.js
+++ b/scripts/deploy/git.js
@@ -1,7 +1,70 @@
 const
 child_process = require('child_process');
+spawn = child_process.spawn;
 
 exports.addRemote = function(name, host, cb) {
   var cmd = 'git remote add ' + name  + ' app@'+ host + ':git';
   child_process.exec(cmd, cb);
 };
+
+// remove a remote, but only if it is pointed to a specific
+// host.  This will keep deploy from killing manuall remotes
+// that you've set up
+exports.removeRemote = function(name, host, cb) {
+  var desired = 'app@'+ host + ':git';
+  var cmd = 'git remote -v show | grep push';
+  child_process.exec(cmd, function(err, r) {
+    try {
+      var remotes = {};
+      r.split('\n').forEach(function(line) {
+        if (!line.length) return;
+        var line = line.split('\t');
+        if (!line.length == 2) return;
+        remotes[line[0]] = line[1].split(" ")[0];
+      });
+      if (remotes[name] && remotes[name] === desired) {
+        child_process.exec('git remote rm ' + name, cb);
+      } else {
+        throw "no such remote";
+      }
+    } catch(e) {
+      cb(e);
+    }
+  });
+};
+
+exports.currentSHA = function(dir, cb) {
+  if (typeof dir === 'function' && cb === undefined) {
+    cb = dir;
+    dir = path.join(__dirname, '..', '..');
+  }
+
+  var p = spawn('git', [ 'log', '--pretty=%h', '-1' ], { cwd: dir });
+  var buf = "";
+  p.stdout.on('data', function(d) {
+    buf += d;
+  });
+  p.on('exit', function(code, signal) {
+    var gitsha = buf.toString().trim();
+    if (gitsha && gitsha.length === 7) {
+      return cb(null, gitsha);
+    }
+    cb("can't extract git sha from " + dir);
+  });
+};
+
+exports.push = function(dir, host, pr, cb) {
+  if (typeof host === 'function' && cb === undefined) {
+    cb = pr;
+    pr = host;
+    host = dir;
+    dir = path.join(__dirname, '..', '..');
+  }
+
+  var p = spawn('git', [ 'push', 'app@' + host + ":git", 'dev:master' ], { cwd: dir });
+  p.stdout.on('data', pr);
+  p.stderr.on('data', pr);
+  p.on('exit', function(code, signal) {
+    return cb(code = 0);
+  });
+};
\ No newline at end of file
diff --git a/scripts/deploy/ssh.js b/scripts/deploy/ssh.js
index 9f76461c1520dc2ef8548c49513522a9bb7cbbc0..29c504ab24be86c8e8123d978c7ab8c7ab2dcf25 100644
--- a/scripts/deploy/ssh.js
+++ b/scripts/deploy/ssh.js
@@ -24,3 +24,15 @@ exports.copyUpConfig = function(host, config, cb) {
     oneTry();
   });
 };
+
+exports.copySSL = function(host, pub, priv, cb) {
+  var cmd = 'scp -o "StrictHostKeyChecking no" ' + pub + ' ec2-user@' + host + ":/etc/ssl/certs/hacksign.in.crt";
+  child_process.exec(cmd, function(err, r) {
+    if (err) return cb(err);
+    var cmd = 'scp -o "StrictHostKeyChecking no" ' + priv + ' ec2-user@' + host + ":/etc/ssl/certs/hacksign.in.key";
+    child_process.exec(cmd, function(err, r) {
+      var cmd = 'ssh -o "StrictHostKeyChecking no" ec2-user@' + host + " 'sudo /etc/init.d/nginx restart'";
+      child_process.exec(cmd, cb);
+    });
+  });
+};
diff --git a/scripts/deploy/vm.js b/scripts/deploy/vm.js
index f6efc45e6e606873fab39ed77dce584c8a3e65cd..ae94b2a11691e3daaab585be16b0429a4f9dbee3 100644
--- a/scripts/deploy/vm.js
+++ b/scripts/deploy/vm.js
@@ -4,7 +4,7 @@ jsel = require('JSONSelect'),
 key = require('./key.js'),
 sec = require('./sec.js');
 
-const BROWSERID_TEMPLATE_IMAGE_ID = 'ami-1553827c';
+const BROWSERID_TEMPLATE_IMAGE_ID = 'ami-6978a900';
 
 function extractInstanceDeets(horribleBlob) {
   var instance = {};
@@ -44,7 +44,7 @@ exports.destroy = function(name, cb) {
       InstanceId: r[name].instanceId
     }, function(result) {
       try { return cb(result.Errors.Error.Message); } catch(e) {};
-      cb(null);
+      cb(null, r[name]);
     });
   });
 };
@@ -105,8 +105,6 @@ exports.waitForInstance = function(id, cb) {
 };
 
 exports.setName = function(id, name, cb) {
-  name = 'browserid deployment (' + name + ')';
-
   aws.call('CreateTags', {
     "ResourceId.0": id,
     "Tag.0.Key": 'Name',
diff --git a/scripts/deploy_dev.js b/scripts/deploy_dev.js
new file mode 100755
index 0000000000000000000000000000000000000000..f615705719ea99e2a8f7c75fc5fbdcd9a34dbb46
--- /dev/null
+++ b/scripts/deploy_dev.js
@@ -0,0 +1,114 @@
+#!/usr/bin/env node
+
+/*
+ * Deploy dev.diresworb.org, for fun and profit.
+ */
+
+const
+aws = require('./deploy/aws.js');
+path = require('path');
+vm = require('./deploy/vm.js'),
+key = require('./deploy/key.js'),
+ssh = require('./deploy/ssh.js'),
+git = require('./deploy/git.js'),
+dns = require('./deploy/dns.js'),
+util = require('util'),
+events = require('events'),
+fs = require('fs');
+
+// verify we have files we need
+
+// a class capable of deploying and emmitting events along the way
+function DevDeployer() {
+  events.EventEmitter.call(this);
+
+  this.sslpub = process.env['DEV_SSL_PUB'];
+  this.sslpriv = process.env['DEV_SSL_PRIV'];
+
+  if (!this.sslpub || !this.sslpriv) {
+    throw("you must provide ssl cert paths via DEV_SSL_PUB & DEV_SSL_PRIV");
+  }
+
+  if (!fs.statSync(this.sslpub).isFile() || !fs.statSync(this.sslpriv).isFile()) {
+    throw("DEV_SSL_PUB & DEV_SSL_PRIV must be paths to actual files.  duh");
+  }
+}
+
+util.inherits(DevDeployer, events.EventEmitter);
+
+DevDeployer.prototype.setup = function(cb) {
+  var self = this;
+  git.currentSHA(function(err, r) {
+    if (err) return cb(err);
+    self.sha = r;
+    vm.startImage(function(err, r) {
+      if (err) return cb(err);
+      self.emit('progress', "starting new image");
+      vm.waitForInstance(r.instanceId, function(err, d) {
+        if (err) return cb(err);
+        self.deets = d;
+        self.emit('progress', "image started");
+        vm.setName(r.instanceId, "dev.diresworb.org (" + self.sha + ")", function(err, r) {
+          if (err) return cb(err);
+          self.emit('progress', "name set");
+          cb(null);
+        });
+      });
+    });
+  });
+}
+
+DevDeployer.prototype.configure = function(cb) {
+  var self = this;
+  var config = { public_url: "https://dev.diresworb.org" };
+  ssh.copyUpConfig(self.deets.ipAddress, config, function (err) {
+    if (err) return cb(err);
+    ssh.copySSL(self.deets.ipAddress, self.sslpub, self.sslpriv, cb);
+  });
+}
+
+DevDeployer.prototype.pushCode = function(cb) {
+  var self = this;
+  git.push(this.deets.ipAddress, function(d) { self.emit('build_output', d); }, cb);
+}
+
+DevDeployer.prototype.updateDNS = function(cb) {
+  var self = this;
+  dns.deleteRecord('dev.diresworb.org', function() {
+    dns.updateRecord('', 'dev.diresworb.org', self.deets.ipAddress, cb);
+  });
+}
+
+var deployer = new DevDeployer();
+
+deployer.on('progress', function(d) {
+  console.log("PR: " + d);
+});
+
+deployer.on('build_output', function(d) {
+  console.log("BO: " + d);
+});
+
+function checkerr(err) {
+  if (err) {
+    process.stderr.write("fatal error: " + err + "\n");
+    process.exit(1);
+  }
+}
+
+var startTime = new Date();
+deployer.setup(function(err) {
+  checkerr(err);
+  deployer.configure(function(err) {
+    checkerr(err);
+    deployer.updateDNS(function(err) {
+      checkerr(err);
+      deployer.pushCode(function(err) {
+        checkerr(err);
+        console.log("dev.diresworb.org (" + deployer.sha + ") deployed to " +
+                    deployer.deets.ipAddress + " in " +
+                    ((new Date() - startTime) / 1000.0).toFixed(2) + "s");
+      });
+    });
+  });
+});
diff --git a/scripts/production_locales b/scripts/production_locales
index 1f8fb02ec8c924917c7092d1481231987f73a1a4..7245ecbfea8ce0ae0f4ba1698ccf3c75734783d3 100755
--- a/scripts/production_locales
+++ b/scripts/production_locales
@@ -1,11 +1,13 @@
 #!/usr/bin/env node
 /* This is a helper script for compress.sh */
 
+var path = require('path');
+
 // configuration will create directories under VAR_PATH
 process.env['VAR_PATH'] = '/tmp/browserid';
 
 // Pick up production languages
-process.env['NODE_ENV'] = 'production';
+process.env['CONFIG_FILES'] = process.env['CONFIG_FILES'] || path.join(__dirname, '..', 'config', 'local.json');
 
 var path = require('path'),
     format = require('util').format,
@@ -13,4 +15,4 @@ var path = require('path'),
     i18n = require(path.join(__dirname, '../lib/i18n.js'));
 
 var langs = config.get('supported_languages');
-process.stdout.write(format("%s\n", langs.map(i18n.localeFrom).join(' ')));
\ No newline at end of file
+process.stdout.write(format("%s\n", langs.map(i18n.localeFrom).join(' ')));
diff --git a/scripts/show_config.js b/scripts/show_config.js
index db4073ad7a39882f5e8c7c45ac704835a0df874d..857e511ddbf29967157a348eab0fbd5ee1aa7778 100755
--- a/scripts/show_config.js
+++ b/scripts/show_config.js
@@ -1,3 +1,9 @@
 #!/usr/bin/env node
 
+var path = require('path');
+
+// use the 'local' configuration if one isn't explicitly specified in the environment
+process.env['CONFIG_FILES'] = process.env['CONFIG_FILES'] ||
+  path.join(__dirname, '..', 'config', 'local.json');
+
 console.log(require("../lib/configuration.js").toString());