diff --git a/apps/addressbook/build.xml b/apps/addressbook/build.xml
index 2fe77475cb9d1dab7707b0e547e25a1ccefa2e3f..ad27a009059cb83d09cdaebac3cab0e145e93160 100644
--- a/apps/addressbook/build.xml
+++ b/apps/addressbook/build.xml
@@ -55,6 +55,7 @@
 		<property name="workspace.changes" value="" />
 			<manifest>
 				<attribute name="Main-Class" value="addressbook.Daemon"/>
+				<attribute name="Implementation-Version" value="${full.version}" />
 		                <attribute name="Build-Date" value="${build.timestamp}" />
 		                <attribute name="Base-Revision" value="${workspace.version}" />
 		                <attribute name="Workspace-Changes" value="${workspace.changes}" />
@@ -73,6 +74,7 @@
 		<property name="workspace.changes.tr" value="" />
 		<war basedir="${dist}/tmp" webxml="web.xml" destfile="${dist}/${war}">
 			<manifest>
+				<attribute name="Implementation-Version" value="${full.version}" />
 		                <attribute name="Build-Date" value="${build.timestamp}" />
 		                <attribute name="Base-Revision" value="${workspace.version}" />
 		                <attribute name="Workspace-Changes" value="${workspace.changes.tr}" />
diff --git a/apps/i2psnark/java/build.xml b/apps/i2psnark/java/build.xml
index edc92ed5b102c5ac9e48b3e3921261efaf83da8a..c5a3b106fd7fd5e3d44efcb0184818a04652c005 100644
--- a/apps/i2psnark/java/build.xml
+++ b/apps/i2psnark/java/build.xml
@@ -60,6 +60,7 @@
             <manifest>
                 <attribute name="Main-Class" value="org.klomp.snark.Snark" />
                 <attribute name="Class-Path" value="i2p.jar mstreaming.jar streaming.jar" />
+                <attribute name="Implementation-Version" value="${full.version}" />
                 <attribute name="Build-Date" value="${build.timestamp}" />
                 <attribute name="Base-Revision" value="${workspace.version}" />
                 <attribute name="Workspace-Changes" value="${workspace.changes.tr}" />
@@ -98,6 +99,7 @@
           <!-- include only the web stuff, as of 0.7.12 the router will add i2psnark.jar to the classpath for the war -->
           <classes dir="./build/obj" includes="**/web/*.class" />
             <manifest>
+                <attribute name="Implementation-Version" value="${full.version}" />
                 <attribute name="Build-Date" value="${build.timestamp}" />
                 <attribute name="Base-Revision" value="${workspace.version}" />
                 <attribute name="Workspace-Changes" value="${workspace.changes.tr}" />
diff --git a/apps/i2ptunnel/java/build.xml b/apps/i2ptunnel/java/build.xml
index 1778ffb8722dc2102d5d996994c05c11acbfd74f..1b62279145de25669f48b9c6356ee25069488ba1 100644
--- a/apps/i2ptunnel/java/build.xml
+++ b/apps/i2ptunnel/java/build.xml
@@ -62,6 +62,7 @@
             <manifest>
                 <attribute name="Main-Class" value="net.i2p.i2ptunnel.I2PTunnel" />
                 <attribute name="Class-Path" value="i2p.jar mstreaming.jar" />
+                <attribute name="Implementation-Version" value="${full.version}" />
                 <attribute name="Build-Date" value="${build.timestamp}" />
                 <attribute name="Base-Revision" value="${workspace.version}" />
                 <attribute name="Workspace-Changes" value="${workspace.changes.j.tr}" />
@@ -134,6 +135,7 @@
         <war destfile="build/i2ptunnel.war" webxml="../jsp/web-out.xml"
              basedir="../jsp/" excludes="web.xml, web-fragment.xml, web-out.xml, **/*.java, *.jsp">
             <manifest>
+                <attribute name="Implementation-Version" value="${full.version}" />
                 <attribute name="Build-Date" value="${build.timestamp}" />
                 <attribute name="Base-Revision" value="${workspace.version}" />
                 <attribute name="Workspace-Changes" value="${workspace.changes.w.tr}" />
diff --git a/apps/ministreaming/java/build.xml b/apps/ministreaming/java/build.xml
index 8904bafb3b1ddb7f01c3e3356c881611b50aafb8..235088fc312852a00a4acd178198fc3b7267932d 100644
--- a/apps/ministreaming/java/build.xml
+++ b/apps/ministreaming/java/build.xml
@@ -50,6 +50,7 @@
         <property name="workspace.changes.tr" value="" />
         <jar destfile="./build/mstreaming.jar" basedir="./build/obj" includes="**/*.class" >
             <manifest>
+                <attribute name="Implementation-Version" value="${full.version}" />
                 <attribute name="Build-Date" value="${build.timestamp}" />
                 <attribute name="Base-Revision" value="${workspace.version}" />
                 <attribute name="Workspace-Changes" value="${workspace.changes.tr}" />
diff --git a/apps/routerconsole/java/build.xml b/apps/routerconsole/java/build.xml
index f49153486c6587179aa8355893eb6c5c587b884c..3dba335781c6f87708177aa15ce91c80d5017dba 100644
--- a/apps/routerconsole/java/build.xml
+++ b/apps/routerconsole/java/build.xml
@@ -35,6 +35,7 @@
         <dependset>
              <srcfilelist dir="." files="../../../router/java/build/obj/net/i2p/router/RouterVersion.class" />
              <targetfilelist dir="." files="build/obj/net/i2p/router/web/NewsFetcher.class" />
+             <targetfilelist dir="." files="build/obj/net/i2p/router/web/PluginStarter.class" />
              <targetfilelist dir="." files="build/obj/net/i2p/router/web/SummaryHelper.class" />
              <targetfilelist dir="." files="build/obj/net/i2p/router/web/UpdateHandler.class" />
         </dependset>
@@ -90,6 +91,7 @@
                 <!-- top level installer will rename to jrobin.jar -->
                 <!-- DTG added in 0.8.4, not in the classpath for very old installs, before we changed wrapper.config to specify * -->
                 <attribute name="Class-Path" value="i2p.jar router.jar jrobin.jar desktopgui.jar" />
+                <attribute name="Implementation-Version" value="${full.version}" />
                 <attribute name="Build-Date" value="${build.timestamp}" />
                 <attribute name="Base-Revision" value="${workspace.version}" />
                 <attribute name="Workspace-Changes" value="${workspace.changes.j.tr}" />
@@ -169,6 +171,7 @@
         <war destfile="build/routerconsole.war" webxml="../jsp/web-out.xml"
              basedir="../jsp/" excludes="web.xml, *.css, **/*.java, *.jsp, *.jsi, web-fragment.xml, web-out.xml">
             <manifest>
+                <attribute name="Implementation-Version" value="${full.version}" />
                 <attribute name="Build-Date" value="${build.timestamp}" />
                 <attribute name="Base-Revision" value="${workspace.version}" />
                 <attribute name="Workspace-Changes" value="${workspace.changes.w.tr}" />
@@ -292,6 +295,8 @@
 
     <uptodate property="precompilejsp.uptodate" targetfile="../jsp/web-out.xml">
         <srcfiles dir= "../jsp" includes="**/*.jsp, *.jsi, **/*.html, *.css, susimail/susimail, web.xml"/>
+        <!-- so the version is right on logs.jsp -->
+        <srcfiles dir= "../../../router/java/src/net/i2p/router" includes="RouterVersion.java"/>
     </uptodate>
 
     <target name="javadoc">
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHandler.java
index b65d58fb6b78eb5d2a9ee4b866cdb14047b97536..7c911156fa327f5c5d3f1036a2359e49a2e6f514 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHandler.java
@@ -54,6 +54,10 @@ public class ConfigClientsHandler extends FormHandler {
             installPlugin();
             return;
         }
+        if (_action.equals(_("Update All Installed Plugins"))) {
+            updateAllPlugins();
+            return;
+        }
         // value
         if (_action.startsWith("Start ")) {
             String app = _action.substring(6);
@@ -322,6 +326,16 @@ public class ConfigClientsHandler extends FormHandler {
         installPlugin(url);
     }
 
+    /** @since 0.8.13 */
+    private void updateAllPlugins() {
+        addFormNotice(_("Updating all plugins"));
+        PluginStarter.updateAll(_context);
+        // So that update() will post a status to the summary bar before we reload
+        try {
+           Thread.sleep(1000);
+        } catch (InterruptedException ie) {}
+    }
+
     private void installPlugin(String url) {
         if ("true".equals(System.getProperty(UpdateHandler.PROP_UPDATE_IN_PROGRESS))) {
             addFormError(_("Plugin or update download already in progress."));
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/FileDumpHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/FileDumpHelper.java
index 6c95734f7ba80924fdc4b3b50d4c935c1aafa718..eea306aa209f174fdb77cf725fbfced0306f8085 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/FileDumpHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/FileDumpHelper.java
@@ -113,17 +113,18 @@ public class FileDumpHelper extends HelperBase {
         if (att == null)
             att = new Attributes();
         buf.append("<td align=\"center\">");
+        String iv = getAtt(att, "Implementation-Version");
+        if (iv != null)
+            buf.append("<b>").append(iv).append("</b>");
         String s = getAtt(att, "Base-Revision");
         if (s != null && s.length() > 20) {
+            if (iv != null)
+                buf.append("<br>");
             buf.append("<a href=\"http://stats.i2p/cgi-bin/viewmtn/revision/info/").append(s)
                .append("\">" +
                        "<tt>").append(s.substring(0, 20)).append("</tt>" +
                        "<br>" +
                        "<tt>").append(s.substring(20)).append("</tt></a>");
-        } else {
-            s = getAtt(att, "Implementation-Version");
-            if (s != null)
-                buf.append("<b>").append(s).append("</b>");
         }
         buf.append("</td><td>");
         s = getAtt(att, "Created-By");
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java b/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java
index 6be4b1cd7ee034d23a7e953f0e7eb7431e630a98..c4c567b3f9d7124a1a559dec2a34d0d1476544b3 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java
@@ -22,10 +22,12 @@ import net.i2p.I2PAppContext;
 import net.i2p.data.DataHelper;
 import net.i2p.router.Job;
 import net.i2p.router.RouterContext;
+import net.i2p.router.RouterVersion;
 import net.i2p.router.startup.ClientAppConfig;
 import net.i2p.router.startup.LoadClientAppsJob;
 import net.i2p.util.ConcurrentHashSet;
 import net.i2p.util.FileUtil;
+import net.i2p.util.I2PAppThread;
 import net.i2p.util.Log;
 import net.i2p.util.Translate;
 import net.i2p.util.VersionComparator;
@@ -63,9 +65,96 @@ public class PluginStarter implements Runnable {
     }
 
     public void run() {
+        if (_context.getBooleanPropertyDefaultTrue("plugins.autoUpdate") &&
+            (!Boolean.valueOf(System.getProperty(UpdateHandler.PROP_UPDATE_IN_PROGRESS)).booleanValue()) &&
+            (!RouterVersion.VERSION.equals(_context.getProperty("router.previousVersion"))))
+            updateAll(_context, true);
         startPlugins(_context);
     }
 
+    /**
+     *  threaded
+     *  @since 0.8.13
+     */
+    static void updateAll(RouterContext ctx) {
+        Thread t = new I2PAppThread(new PluginUpdater(ctx), "PluginUpdater", true);
+        t.start();
+    }
+
+    /**
+     *  thread
+     *  @since 0.8.13
+     */
+    private static class PluginUpdater implements Runnable {
+        private final RouterContext _ctx;
+
+        public PluginUpdater(RouterContext ctx) {
+            _ctx = ctx;
+        }
+
+        public void run() {
+            updateAll(_ctx, false);
+        }
+    }
+
+    /**
+     *  inline
+     *  @since 0.8.13
+     */
+    private static void updateAll(RouterContext ctx, boolean delay) {
+        List<String> plugins = getPlugins();
+        Map<String, String> toUpdate = new HashMap();
+        for (String appName : plugins) {
+            Properties props = pluginProperties(ctx, appName);
+            String url = props.getProperty("updateURL");
+            if (url != null)
+                toUpdate.put(appName, url);
+        }
+        if (toUpdate.isEmpty())
+            return;
+        PluginUpdateChecker puc = PluginUpdateChecker.getInstance(ctx);
+        if (puc.isRunning())
+            return;
+
+        if (delay) {
+            // wait for proxy
+            System.setProperty(UpdateHandler.PROP_UPDATE_IN_PROGRESS, "true");
+            puc.setAppStatus(Messages.getString("Checking for plugin updates", ctx));
+            try {
+                Thread.sleep(3*60*1000);
+            } catch (InterruptedException ie) {}
+            System.setProperty(UpdateHandler.PROP_UPDATE_IN_PROGRESS, "false");
+        }
+
+        Log log = ctx.logManager().getLog(PluginStarter.class);
+        for (Map.Entry<String, String> entry : toUpdate.entrySet()) {
+            String appName = entry.getKey();
+            if (log.shouldLog(Log.WARN))
+                log.warn("Checking for update plugin: " + appName);
+            puc.update(appName);
+            do {
+                try {
+                    Thread.sleep(5*1000);
+                } catch (InterruptedException ie) {}
+            } while (puc.isRunning());
+            if (!puc.isNewerAvailable()) {
+                if (log.shouldLog(Log.WARN))
+                    log.warn("No update available for plugin: " + appName);
+                continue;
+            }
+            PluginUpdateHandler puh = PluginUpdateHandler.getInstance(ctx);
+            String url = entry.getValue();
+            if (log.shouldLog(Log.WARN))
+                log.warn("Updating plugin: " + appName);
+            puh.update(url);
+            do {
+                try {
+                    Thread.sleep(5*1000);
+                } catch (InterruptedException ie) {}
+            } while (puh.isRunning());
+        }
+    }
+
     /** this shouldn't throw anything */
     static void startPlugins(RouterContext ctx) {
         Log log = ctx.logManager().getLog(PluginStarter.class);
@@ -75,6 +164,9 @@ public class PluginStarter implements Runnable {
             if (name.startsWith(PREFIX) && name.endsWith(ENABLED)) {
                 if (Boolean.valueOf(props.getProperty(name)).booleanValue()) {
                     String app = name.substring(PREFIX.length(), name.lastIndexOf(ENABLED));
+                    // plugins could have been started after update
+                    if (isPluginRunning(app, ctx))
+                        continue;
                     try {
                         if (!startPlugin(ctx, app))
                             log.error("Failed to start plugin: " + app);
@@ -95,6 +187,7 @@ public class PluginStarter implements Runnable {
         File pluginDir = new File(ctx.getConfigDir(), PluginUpdateHandler.PLUGIN_DIR + '/' + appName);
         if ((!pluginDir.exists()) || (!pluginDir.isDirectory())) {
             log.error("Cannot start nonexistent plugin: " + appName);
+            disablePlugin(appName);
             return false;
         }
 
@@ -104,6 +197,7 @@ public class PluginStarter implements Runnable {
             (new VersionComparator()).compare(CoreVersion.VERSION, minVersion) < 0) {
             String foo = "Plugin " + appName + " requires I2P version " + minVersion + " or higher";
             log.error(foo);
+            disablePlugin(appName);
             throw new Exception(foo);
         }
 
@@ -112,6 +206,7 @@ public class PluginStarter implements Runnable {
             (new VersionComparator()).compare(System.getProperty("java.version"), minVersion) < 0) {
             String foo = "Plugin " + appName + " requires Java version " + minVersion + " or higher";
             log.error(foo);
+            disablePlugin(appName);
             throw new Exception(foo);
         }
 
@@ -121,6 +216,7 @@ public class PluginStarter implements Runnable {
             (new VersionComparator()).compare(minVersion, jVersion) > 0) {
             String foo = "Plugin " + appName + " requires Jetty version " + minVersion + " or higher";
             log.error(foo);
+            disablePlugin(appName);
             throw new Exception(foo);
         }
 
@@ -129,6 +225,7 @@ public class PluginStarter implements Runnable {
             (new VersionComparator()).compare(maxVersion, jVersion) < 0) {
             String foo = "Plugin " + appName + " requires Jetty version " + maxVersion + " or lower";
             log.error(foo);
+            disablePlugin(appName);
             throw new Exception(foo);
         }
 
@@ -334,7 +431,7 @@ public class PluginStarter implements Runnable {
         Properties props = pluginProperties();
         for (Iterator iter = props.keySet().iterator(); iter.hasNext(); ) {
             String name = (String)iter.next();
-            if (name.startsWith(PREFIX + appName))
+            if (name.startsWith(PREFIX + appName + '.'))
                 iter.remove();
         }
         storePluginProperties(props);
@@ -373,6 +470,32 @@ public class PluginStarter implements Runnable {
         return rv;
     }
 
+    /**
+     *  Is the plugin enabled in plugins.config?
+     *  Default true
+     *
+     *  @since 0.8.13
+     */
+    public static boolean isPluginEnabled(String appName) {
+        Properties props = pluginProperties();
+        String prop = PREFIX + appName + ENABLED;
+        return Boolean.valueOf(props.getProperty(prop, "true")).booleanValue();
+    }
+
+    /**
+     *  Disable in plugins.config
+     *
+     *  @since 0.8.13
+     */
+    public static void disablePlugin(String appName) {
+        Properties props = pluginProperties();
+        String prop = PREFIX + appName + ENABLED;
+        if (Boolean.valueOf(props.getProperty(prop, "true")).booleanValue()) {
+            props.setProperty(prop, "false");
+            storePluginProperties(props);
+        }
+    }
+
     /**
      *  all installed plugins whether enabled or not
      */
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/PluginStopper.java b/apps/routerconsole/java/src/net/i2p/router/web/PluginStopper.java
index 2f29eddf273e66f67b0266fc7ae8d7e360667292..ca81463d04223486f9fbb861083f7d1d37d22636 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/PluginStopper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/PluginStopper.java
@@ -4,7 +4,7 @@ import net.i2p.router.RouterContext;
 import net.i2p.util.Log;
 
 /**
- *  Stop all plugins that are installed
+ *  Stop all plugins that are installed and running
  *
  *  @since 0.7.13
  *  @author zzz
@@ -21,19 +21,20 @@ public class PluginStopper extends PluginStarter {
     }
 
     /**
-     *  Stop all plugins
-     *  (whether or not they were ever started)
+     *  Stop all running plugins
      *
      *  this shouldn't throw anything
      */
-    static void stopPlugins(RouterContext ctx) {
+    private static void stopPlugins(RouterContext ctx) {
         Log log = ctx.logManager().getLog(PluginStopper.class);
         for (String app : getPlugins()) {
-            try {
-               stopPlugin(ctx, app);
-            } catch (Throwable e) {
-               if (log.shouldLog(Log.WARN))
-                   log.warn("Failed to stop plugin: " + app, e);
+            if (isPluginRunning(app, ctx)) {
+                try {
+                   stopPlugin(ctx, app);
+                } catch (Throwable e) {
+                   if (log.shouldLog(Log.WARN))
+                       log.warn("Failed to stop plugin: " + app, e);
+                }
             }
         }
     }
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/PluginUpdateChecker.java b/apps/routerconsole/java/src/net/i2p/router/web/PluginUpdateChecker.java
index cf3f75f3ebba6e6263af039f461e7717e8200396..73b6850a04dc544e949e0c4539a63a05e03c880c 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/PluginUpdateChecker.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/PluginUpdateChecker.java
@@ -36,6 +36,7 @@ public class PluginUpdateChecker extends UpdateHandler {
     private String _appName;
     private String _oldVersion;
     private String _xpi2pURL;
+    private volatile boolean _isNewerAvailable;
 
     private static PluginUpdateChecker _instance;
     public static final synchronized PluginUpdateChecker getInstance(RouterContext ctx) { 
@@ -49,12 +50,19 @@ public class PluginUpdateChecker extends UpdateHandler {
         super(ctx);
     }
     
-    /** check all plugins */
+    /**
+     *  check all plugins
+     *  @deprecated not finished
+     */
     public void update() {
         Thread t = new I2PAppThread(new AllCheckerRunner(), "AllAppChecker", true);
         t.start();
     }
 
+    /**
+     *  check all plugins
+     *  @deprecated not finished
+     */
     public class AllCheckerRunner implements Runnable {
         public void run() {
             List<String> plugins = PluginStarter.getPlugins();
@@ -85,12 +93,18 @@ public class PluginUpdateChecker extends UpdateHandler {
             _xpi2pURL = xpi2pURL;
             _appName = appName;
             _oldVersion = oldVersion;
+            _isNewerAvailable = false;
             System.setProperty(PROP_UPDATE_IN_PROGRESS, "true");
             I2PAppThread update = new I2PAppThread(_pluginUpdateCheckerRunner, "AppChecker", true);
             update.start();
         }
     }
     
+    /** @since 0.8.13 */
+    public void setAppStatus(String status) {
+        updateStatus(status);
+    }
+
     public boolean isRunning() {
         return _pluginUpdateCheckerRunner != null && _pluginUpdateCheckerRunner.isRunning();
     }
@@ -101,12 +115,17 @@ public class PluginUpdateChecker extends UpdateHandler {
         return false;
     }
     
+    /** @since 0.8.13 */
+    public boolean isNewerAvailable() {
+        return _isNewerAvailable;
+    }
+
     private void scheduleStatusClean(String msg) {
-        SimpleScheduler.getInstance().addEvent(new Cleaner(msg), 60*60*1000);
+        SimpleScheduler.getInstance().addEvent(new Cleaner(msg), 20*60*1000);
     }
 
     private class Cleaner implements SimpleTimer.TimedEvent {
-        private String _msg;
+        private final String _msg;
         public Cleaner(String msg) {
             _msg = msg;
         }
@@ -126,6 +145,7 @@ public class PluginUpdateChecker extends UpdateHandler {
 
         @Override
         protected void update() {
+            _isNewerAvailable = false;
             updateStatus("<b>" + _("Checking for update of plugin {0}", _appName) + "</b>");
             // use the same settings as for updater
             // always proxy, or else FIXME
@@ -142,6 +162,10 @@ public class PluginUpdateChecker extends UpdateHandler {
             }
         }
         
+        public boolean isNewerAvailable() {
+            return _isNewerAvailable;
+        }
+
         @Override
         public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {
         }
@@ -151,10 +175,12 @@ public class PluginUpdateChecker extends UpdateHandler {
             String newVersion = TrustedUpdate.getVersionString(new ByteArrayInputStream(_baos.toByteArray()));
             boolean newer = (new VersionComparator()).compare(newVersion, _oldVersion) > 0;
             String msg;
-            if (newer)
+            if (newer) {
                 msg = "<b>" + _("New plugin version {0} is available", newVersion) + "</b>";
-            else
+                _isNewerAvailable = true;
+            } else {
                 msg = "<b>" + _("No new version is available for plugin {0}", _appName) + "</b>";
+            }
             updateStatus(msg);
             scheduleStatusClean(msg);
         }
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/PluginUpdateHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/PluginUpdateHandler.java
index 511bfb0cd075de25f00352148c67a4a16c89a9d2..ecef4bb19e9aea665b40b8e32c18845283b8ad28 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/PluginUpdateHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/PluginUpdateHandler.java
@@ -93,11 +93,11 @@ public class PluginUpdateHandler extends UpdateHandler {
     }
     
     private void scheduleStatusClean(String msg) {
-        SimpleScheduler.getInstance().addEvent(new Cleaner(msg), 60*60*1000);
+        SimpleScheduler.getInstance().addEvent(new Cleaner(msg), 20*60*1000);
     }
 
     private class Cleaner implements SimpleTimer.TimedEvent {
-        private String _msg;
+        private final String _msg;
         public Cleaner(String msg) {
             _msg = msg;
         }
@@ -286,6 +286,7 @@ public class PluginUpdateHandler extends UpdateHandler {
                 return;
             }
 
+            boolean wasRunning = false;
             File destDir = new SecureDirectory(appDir, appName);
             if (destDir.exists()) {
                 if (Boolean.valueOf(props.getProperty("install-only")).booleanValue()) {
@@ -350,14 +351,16 @@ public class PluginUpdateHandler extends UpdateHandler {
                     return;
                 }
 
-                // check if it is running first?
-                try {
-                    if (!PluginStarter.stopPlugin(_context, appName)) {
-                        // failed, ignore
+                if (PluginStarter.isPluginRunning(appName, _context)) {
+                    wasRunning = true;
+                    try {
+                        if (!PluginStarter.stopPlugin(_context, appName)) {
+                            // failed, ignore
+                        }
+                    } catch (Throwable e) {
+                        // no updateStatus() for this one
+                        _log.error("Error stopping plugin " + appName, e);
                     }
-                } catch (Throwable e) {
-                    // no updateStatus() for this one
-                    _log.error("Error stopping plugin " + appName, e);
                 }
 
             } else {
@@ -390,8 +393,8 @@ public class PluginUpdateHandler extends UpdateHandler {
                     pluginProps.setProperty(PluginStarter.PREFIX + appName + PluginStarter.ENABLED, "false");
                     PluginStarter.storePluginProperties(pluginProps);
                 }
-            } else {
-                // start everything
+            } else if (wasRunning || PluginStarter.isPluginEnabled(appName)) {
+                // start everything unless it was disabled and not running before
                 try {
                     if (PluginStarter.startPlugin(_context, appName)) {
                         String linkName = ConfigClientsHelper.stripHTML(props, "consoleLinkName_" + Messages.getLanguage(_context));
@@ -411,6 +414,8 @@ public class PluginUpdateHandler extends UpdateHandler {
                     statusDone("<b>" + _("Plugin {0} installed but failed to start", appName) + ": " + e + "</b>");
                     _log.error("Error starting plugin " + appName, e);
                 }
+            } else {
+                statusDone("<b>" + _("Plugin {0} installed", appName) + "</b>");
             }
         }
 
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java
index 2f2d3db74f2575ffb283548ca5695e0f45cce372..5b7eaec03c2bfe90288ee8be0fa4c2c83160171d 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java
@@ -127,7 +127,7 @@ public class UpdateHandler {
     }
     
     public class UpdateRunner implements Runnable, EepGet.StatusListener {
-        protected boolean _isRunning;
+        protected volatile boolean _isRunning;
         protected boolean done;
         protected EepGet _get;
         protected final DecimalFormat _pct = new DecimalFormat("0.0%");
diff --git a/apps/routerconsole/jsp/configclients.jsp b/apps/routerconsole/jsp/configclients.jsp
index 4c70beafed1b3f7d984408b7d6bce64c843d5ce1..25df053f46a55e5f0450dc3d5d42d3f02ac4c325 100644
--- a/apps/routerconsole/jsp/configclients.jsp
+++ b/apps/routerconsole/jsp/configclients.jsp
@@ -103,6 +103,7 @@ button span.hide{
  <jsp:getProperty name="clientshelper" property="form2" />
  <p><i><%=intl._("All changes require restart to take effect.")%></i>
  </p><hr><div class="formaction">
+ <input type="submit" class="cancel" name="foo" value="<%=intl._("Cancel")%>" />
  <input type="submit" name="action" class="accept" value="<%=intl._("Save WebApp Configuration")%>" />
 </div></form></div>
 
@@ -114,6 +115,7 @@ button span.hide{
 <input type="hidden" name="nonce" value="<%=pageNonce%>" >
  <jsp:getProperty name="clientshelper" property="form3" />
 <hr><div class="formaction">
+ <input type="submit" class="cancel" name="foo" value="<%=intl._("Cancel")%>" />
  <input type="submit" name="action" class="accept" value="<%=intl._("Save Plugin Configuration")%>" />
 </div></form></div>
 
@@ -125,7 +127,9 @@ button span.hide{
 <p>
  <input type="text" size="60" name="pluginURL" >
  </p><hr><div class="formaction">
+ <input type="submit" class="cancel" name="foo" value="<%=intl._("Cancel")%>" />
  <input type="submit" name="action" class="download" value="<%=intl._("Install Plugin")%>" />
+ <input type="submit" name="action" class="reload" value="<%=intl._("Update All Installed Plugins")%>" />
  </div></form></div>
 <% } %>
 </div></div></body></html>
diff --git a/apps/sam/java/build.xml b/apps/sam/java/build.xml
index d55ed0d61c3bca555838c1067b553d83caf21986..4433c057543ae511bb5b7a83a20ee29169507fbd 100644
--- a/apps/sam/java/build.xml
+++ b/apps/sam/java/build.xml
@@ -66,6 +66,7 @@
             <manifest>
                 <attribute name="Main-Class" value="net.i2p.sam.SAMBridge" />
                 <attribute name="Class-Path" value="i2p.jar mstreaming.jar streaming.jar" />
+                <attribute name="Implementation-Version" value="${full.version}" />
                 <attribute name="Build-Date" value="${build.timestamp}" />
                 <attribute name="Base-Revision" value="${workspace.version}" />
                 <attribute name="Workspace-Changes" value="${workspace.changes.tr}" />
diff --git a/apps/streaming/java/build.xml b/apps/streaming/java/build.xml
index b5c842f5b94641dd273d53dd46104893e1b84470..5f278f307c67183780e29bb28cb3294c0721d6df 100644
--- a/apps/streaming/java/build.xml
+++ b/apps/streaming/java/build.xml
@@ -63,6 +63,7 @@
         <property name="workspace.changes.tr" value="" />
         <jar destfile="./build/streaming.jar" basedir="./build/obj" includes="**/*.class" >
             <manifest>
+                <attribute name="Implementation-Version" value="${full.version}" />
                 <attribute name="Build-Date" value="${build.timestamp}" />
                 <attribute name="Base-Revision" value="${workspace.version}" />
                 <attribute name="Workspace-Changes" value="${workspace.changes.tr}" />
diff --git a/apps/susidns/src/build.xml b/apps/susidns/src/build.xml
index d287e5a4d013d576563de775a45cfb83ade59f8d..3fff55a34c9fce7f4e652c65027842ddd79d6a28 100644
--- a/apps/susidns/src/build.xml
+++ b/apps/susidns/src/build.xml
@@ -97,6 +97,7 @@
         		<include name="WEB-INF/classes/${project}.properties"/>
         	</fileset>
             <manifest>
+                <attribute name="Implementation-Version" value="${full.version}" />
                 <attribute name="Build-Date" value="${build.timestamp}" />
                 <attribute name="Base-Revision" value="${workspace.version}" />
                 <attribute name="Workspace-Changes" value="${workspace.changes.tr}" />
diff --git a/apps/susimail/build.xml b/apps/susimail/build.xml
index 8a8d33c4ad00b9fdb7b3c395014174dbdfe8bb9f..3368283d115e8b65a72f790f99e4c982d7f4b75d 100644
--- a/apps/susimail/build.xml
+++ b/apps/susimail/build.xml
@@ -45,6 +45,7 @@
         <war destfile="susimail.war" webxml="src/WEB-INF/web.xml"
              basedir="src/" excludes="WEB-INF/web.xml LICENSE src/**/*">
             <manifest>
+                <attribute name="Implementation-Version" value="${full.version}" />
                 <attribute name="Build-Date" value="${build.timestamp}" />
                 <attribute name="Base-Revision" value="${workspace.version}" />
                 <attribute name="Workspace-Changes" value="${workspace.changes.tr}" />
diff --git a/apps/systray/java/build.xml b/apps/systray/java/build.xml
index 4dd50f0c98914c25456a0f2b25b70d1e74dac31e..5baf4ab6224a5b7183af97e6a43053b9b3e78180 100644
--- a/apps/systray/java/build.xml
+++ b/apps/systray/java/build.xml
@@ -44,6 +44,7 @@
             <manifest>
                 <attribute name="Main-Class" value="net.i2p.apps.systray.SysTray" />
                 <attribute name="Class-Path" value="systray4j.jar" />
+                <attribute name="Implementation-Version" value="${full.version}" />
                 <attribute name="Build-Date" value="${build.timestamp}" />
                 <attribute name="Base-Revision" value="${workspace.version}" />
                 <attribute name="Workspace-Changes" value="${workspace.changes.tr}" />
diff --git a/build.xml b/build.xml
index dd49fd8cb511e75e7c602d7ede0d4393c1cb06f9..6365da50b01513768683c8e7d7fe3343dc1ca8fc 100644
--- a/build.xml
+++ b/build.xml
@@ -182,12 +182,14 @@
         </exec>
     </target>
 
-    <target name="buildProperties" depends="getMtnRev" >
+    <target name="buildProperties" depends="getMtnRev, getReleaseNumber, getBuildNumber" >
         <!-- default if not set above -->
         <property name="workspace.version" value="unknown" />
         <tstamp>
             <format property="build.timestamp" pattern="yyyy-MM-dd HH:mm:ss z" timezone="UTC" locale="en" />
         </tstamp>
+        <property name="full.version" value="${release.number}-${build.number}${build.extra}" />
+        <echo message="Building version ${full.version} (mtn rev ${workspace.version})" />
     </target>
 
     <!-- end of sub-build.xml targets -->
diff --git a/core/java/build.xml b/core/java/build.xml
index d6275085945258c6f4fad34e43b62fdf50395ce6..c3eaf5c0780dd3d9f59692af034f100916ccf2c5 100644
--- a/core/java/build.xml
+++ b/core/java/build.xml
@@ -58,6 +58,7 @@
         <property name="workspace.changes.tr" value="" />
         <jar destfile="./build/i2p.jar" basedir="./build/obj" includes="**/*.class" >
             <manifest>
+                <attribute name="Implementation-Version" value="${full.version}" />
                 <attribute name="Build-Date" value="${build.timestamp}" />
                 <attribute name="Base-Revision" value="${workspace.version}" />
                 <attribute name="Workspace-Changes" value="${workspace.changes.tr}" />
diff --git a/history.txt b/history.txt
index 0ca887885c4e88e7ad6c6d2bc4b5ee2504344432..da999adc4b6373491e4a025f21d0233bf121849d 100644
--- a/history.txt
+++ b/history.txt
@@ -1,3 +1,19 @@
+2012-01-16 zzz
+  * Build: Put Implementation-Version in manifests
+  * NetDB: Hopefully fix rare NPE (ticket #589)
+  * Plugins:
+    - Only stop a plugin before update if it was running
+    - Only stop a plugin at shutdown if it was running
+    - Don't start a plugin after update if it was disabled
+    - Disable plugin if it fails version checks at startup
+    - Auto-update plugins after a router update
+    - Add update-all button and more cancel buttons
+  * Router:
+    - Add synchronized change-and-save-config methods to avoid races
+    - Save previous version in config so we know when we updated
+  * Transport: Revert change from -2, put addresses back in RouterInfo
+               when hidden, broke inbound tunnel building
+
 2012-01-14 zzz
   * i2ptunnel: Partial fix for dest formatting (ticket #581)
   * jars.jsp: New debug page
diff --git a/router/java/build.xml b/router/java/build.xml
index ea9f7b6135f6505c65267e0d761b85c4f48cd79f..e933086cf805521f2df9b380d6623be6d1c5a74d 100644
--- a/router/java/build.xml
+++ b/router/java/build.xml
@@ -72,6 +72,7 @@
         <property name="workspace.changes.tr" value="" />
         <jar destfile="./build/router.jar" basedir="./build/obj" includes="**/*.class" >
             <manifest>
+                <attribute name="Implementation-Version" value="${full.version}" />
                 <attribute name="Build-Date" value="${build.timestamp}" />
                 <attribute name="Base-Revision" value="${workspace.version}" />
                 <attribute name="Workspace-Changes" value="${workspace.changes.tr}" />
diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java
index 084731b7bbf96518c1b023a4ed8c0793d75fefae..f97422d02e720848627d16af11ddfa1397f44570 100644
--- a/router/java/src/net/i2p/router/Router.java
+++ b/router/java/src/net/i2p/router/Router.java
@@ -15,6 +15,7 @@ import java.io.FileOutputStream;
 import java.io.InputStreamReader;
 import java.io.IOException;
 import java.io.Writer;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
 import java.util.HashSet;
@@ -241,6 +242,8 @@ public class Router implements RouterClock.ClockShiftListener {
             String now = Long.toString(System.currentTimeMillis());
             _config.put("router.firstInstalled", now);
             _config.put("router.updateLastInstalled", now);
+            // First added in 0.8.13
+            _config.put("router.previousVersion", RouterVersion.VERSION);
             saveConfig();
         }
         // *********  Start no threads before here ********* //
@@ -324,9 +327,23 @@ public class Router implements RouterClock.ClockShiftListener {
     public String getConfigSetting(String name) { 
             return _config.get(name); 
     }
+
+    /**
+     *  Warning, race between here and saveConfig(),
+     *  saveConfig(String name, String value) or saveConfig(Map toAdd, Set toRemove) is recommended.
+     *
+     *  @since 0.8.13
+     */
     public void setConfigSetting(String name, String value) { 
             _config.put(name, value); 
     }
+
+    /**
+     *  Warning, race between here and saveConfig(),
+     *  saveConfig(String name, String value) or saveConfig(Map toAdd, Set toRemove) is recommended.
+     *
+     *  @since 0.8.13
+     */
     public void removeConfigSetting(String name) { 
             _config.remove(name); 
     }
@@ -1041,6 +1058,13 @@ public class Router implements RouterClock.ClockShiftListener {
                 _log.log(Log.CRIT, "Error running shutdown task", t);
             }
         }
+
+        // Set the last version to the current version, since 0.8.13
+        if (!RouterVersion.VERSION.equals(_config.get("router.previousVersion"))) {
+            _config.put("router.previousVersion", RouterVersion.VERSION);
+            saveConfig();
+        }
+
         _context.removeShutdownTasks();
         try { _context.clientManager().shutdown(); } catch (Throwable t) { _log.error("Error shutting down the client manager", t); }
         try { _context.namingService().shutdown(); } catch (Throwable t) { _log.error("Error shutting down the naming service", t); }
@@ -1244,6 +1268,45 @@ public class Router implements RouterClock.ClockShiftListener {
         return true;
     }
     
+    /**
+     * Updates the current config and then saves it.
+     * Prevents a race in the interval between setConfigSetting() / removeConfigSetting() and saveConfig(),
+     * Synchronized with getConfig() / saveConfig()
+     *
+     * @param name setting to add/change/remove before saving
+     * @param value if non-null, updated value; if null, setting will be removed
+     * @return success
+     * @since 0.8.13
+     */
+    public synchronized boolean saveConfig(String name, String value) {
+        if (value != null)
+            _config.put(name, value);
+        else
+            _config.remove(name);
+        return saveConfig();
+    }
+
+    /**
+     * Updates the current config and then saves it.
+     * Prevents a race in the interval between setConfigSetting() / removeConfigSetting() and saveConfig(),
+     * Synchronized with getConfig() / saveConfig()
+     *
+     * @param toAdd settings to add/change before saving, may be null or empty
+     * @param toRemove settings to remove before saving, may be null or empty
+     * @return success
+     * @since 0.8.13
+     */
+    public synchronized boolean saveConfig(Map toAdd, Collection<String> toRemove) {
+        if (toAdd != null)
+            _config.putAll(toAdd);
+        if (toRemove != null) {
+            for (String s : toRemove) {
+                _config.remove(toRemove);
+            }
+        }
+        return saveConfig();
+    }
+
     /**
      *  The clock shift listener.
      *  Restart the router if we should.
@@ -1345,6 +1408,8 @@ public class Router implements RouterClock.ClockShiftListener {
                 // This may be useful someday. First added in 0.8.2
                 // Moved above the extract so we don't NCDFE
                 _config.put("router.updateLastInstalled", "" + System.currentTimeMillis());
+                // Set the last version to the current version, since 0.8.13
+                _config.put("router.previousVersion", RouterVersion.VERSION);
                 saveConfig();
                 ok = FileUtil.extractZip(updateFile, _context.getBaseDir());
             }
diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java
index 0725033fa600cfb5180ac82283f5194ee35b34b0..f6e6df3a5c0d10fbf1efc2f7069c342c2fe16f4e 100644
--- a/router/java/src/net/i2p/router/RouterVersion.java
+++ b/router/java/src/net/i2p/router/RouterVersion.java
@@ -18,7 +18,7 @@ public class RouterVersion {
     /** deprecated */
     public final static String ID = "Monotone";
     public final static String VERSION = CoreVersion.VERSION;
-    public final static long BUILD = 3;
+    public final static long BUILD = 4;
 
     /** for example "-test" */
     public final static String EXTRA = "";
diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/KBucketSet.java b/router/java/src/net/i2p/router/networkdb/kademlia/KBucketSet.java
index f27be069a496ae0863938bff25ae9e445783c5a6..09560458056307b19defcf4f2cefcfa545c2c7c6 100644
--- a/router/java/src/net/i2p/router/networkdb/kademlia/KBucketSet.java
+++ b/router/java/src/net/i2p/router/networkdb/kademlia/KBucketSet.java
@@ -25,10 +25,10 @@ import net.i2p.util.Log;
  *
  */
 class KBucketSet {
-    private Log _log;
-    private I2PAppContext _context;
-    private LocalHash _us;
-    private KBucket _buckets[];
+    private final Log _log;
+    private final I2PAppContext _context;
+    private final LocalHash _us;
+    private final KBucket _buckets[];
     private volatile int _size;
     
     public final static int BASE = 8; // must go into KEYSIZE_BITS evenly
@@ -41,7 +41,7 @@ class KBucketSet {
         _us = new LocalHash(us);
         _context = context;
         _log = context.logManager().getLog(KBucketSet.class);
-        createBuckets();
+        _buckets = createBuckets();
         context.statManager().createRateStat("netDb.KBSGetAllTime", "Time to add all Hashes to the Collector", "NetworkDatabase", new long[] { 60*60*1000 });
     }
     
@@ -132,12 +132,13 @@ class KBucketSet {
     
     public KBucket getBucket(int bucket) { return _buckets[bucket]; }
     
-    protected void createBuckets() {
-        _buckets = new KBucket[NUM_BUCKETS];
+    protected KBucket[] createBuckets() {
+        KBucket[] buckets = new KBucket[NUM_BUCKETS];
         for (int i = 0; i < NUM_BUCKETS-1; i++) {
-            _buckets[i] = createBucket(i*BASE, (i+1)*BASE);
+            buckets[i] = createBucket(i*BASE, (i+1)*BASE);
         }
-        _buckets[NUM_BUCKETS-1] = createBucket(BASE*(NUM_BUCKETS-1), BASE*(NUM_BUCKETS) + 1);
+        buckets[NUM_BUCKETS-1] = createBucket(BASE*(NUM_BUCKETS-1), BASE*(NUM_BUCKETS) + 1);
+        return buckets;
     }
     
     protected KBucket createBucket(int start, int end) {
diff --git a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java
index c1302b9aa5f4abe93edd4f504478da34a4f87615..73fb5cb93016c5e3e00dc8fb74ba03d1b8534013 100644
--- a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java
+++ b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java
@@ -179,8 +179,9 @@ public class CommSystemFacadeImpl extends CommSystemFacade {
     /** @return non-null, possibly empty */
     @Override
     public Set<RouterAddress> createAddresses() {
-        if (_context.router().isHidden())
-            return Collections.EMPTY_SET;
+        // No, don't do this, it makes it almost impossible to build inbound tunnels
+        //if (_context.router().isHidden())
+        //    return Collections.EMPTY_SET;
         Map<String, RouterAddress> addresses = null;
         boolean newCreated = false;