diff --git a/apps/BOB/src/net/i2p/BOB/BOB.java b/apps/BOB/src/net/i2p/BOB/BOB.java
index 9f87e4f88f0f3a3ce66100241771f0c90869f09d..1aa432e2ebe4e37a0362f9f5308bea984d7ed3b2 100644
--- a/apps/BOB/src/net/i2p/BOB/BOB.java
+++ b/apps/BOB/src/net/i2p/BOB/BOB.java
@@ -23,6 +23,7 @@
  */
 package net.i2p.BOB;
 
+import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
@@ -34,6 +35,8 @@ import java.net.Socket;
 import java.net.SocketTimeoutException;
 import java.util.Properties;
 import java.util.concurrent.atomic.AtomicBoolean;
+
+import net.i2p.I2PAppContext;
 import net.i2p.client.I2PClient;
 import net.i2p.client.streaming.RetransmissionTimer;
 import net.i2p.util.Log;
@@ -186,16 +189,19 @@ public class BOB {
 		i = Y2.hashCode();
 		try {
 			{
+    				File cfg = new File(configLocation);
+				if (!cfg.isAbsolute())
+					cfg = new File(I2PAppContext.getGlobalContext().getConfigDir(), configLocation);
 				try {
-					FileInputStream fi = new FileInputStream(configLocation);
+					FileInputStream fi = new FileInputStream(cfg);
 					props.load(fi);
 					fi.close();
 				} catch (FileNotFoundException fnfe) {
-					warn("Unable to load up the BOB config file " + configLocation + ", Using defaults.");
+					warn("Unable to load up the BOB config file " + cfg.getAbsolutePath() + ", Using defaults.");
 					warn(fnfe.toString());
 					save = true;
 				} catch (IOException ioe) {
-					warn("IOException on BOB config file " + configLocation + ", using defaults.");
+					warn("IOException on BOB config file " + cfg.getAbsolutePath() + ", using defaults.");
 					warn(ioe.toString());
 				}
 			}
@@ -228,13 +234,16 @@ public class BOB {
 				props.setProperty(PROP_BOB_HOST, "localhost");
 			}
 			if (save) {
+    				File cfg = new File(configLocation);
+				if (!cfg.isAbsolute())
+					cfg = new File(I2PAppContext.getGlobalContext().getConfigDir(), configLocation);
 				try {
-					warn("Writing new defaults file " + configLocation);
-					FileOutputStream fo = new FileOutputStream(configLocation);
-					props.store(fo, configLocation);
+					warn("Writing new defaults file " + cfg.getAbsolutePath());
+					FileOutputStream fo = new FileOutputStream(cfg);
+					props.store(fo, cfg.getAbsolutePath());
 					fo.close();
 				} catch (IOException ioe) {
-					error("IOException on BOB config file " + configLocation + ", " + ioe);
+					error("IOException on BOB config file " + cfg.getAbsolutePath() + ", " + ioe);
 				}
 			}
 
diff --git a/apps/addressbook/java/src/addressbook/AddressBook.java b/apps/addressbook/java/src/addressbook/AddressBook.java
index a46c256c80dd14d128bdefa61e4fa2f8e61f71ed..2694cae781f98288025a12a8ec57ac6c0c3d7ee9 100644
--- a/apps/addressbook/java/src/addressbook/AddressBook.java
+++ b/apps/addressbook/java/src/addressbook/AddressBook.java
@@ -94,20 +94,21 @@ public class AddressBook {
      * @param proxyPort port number of proxy
      */
     public AddressBook(Subscription subscription, String proxyHost, int proxyPort) {
+        File tmp = new File(I2PAppContext.getGlobalContext().getTempDir(), "addressbook.tmp");
         this.location = subscription.getLocation();
         EepGet get = new EepGet(I2PAppContext.getGlobalContext(), true,
-                proxyHost, proxyPort, 0, -1l, MAX_SUB_SIZE, "addressbook.tmp", null,
+                proxyHost, proxyPort, 0, -1l, MAX_SUB_SIZE, tmp.getAbsolutePath(), null,
                 subscription.getLocation(), true, subscription.getEtag(), subscription.getLastModified(), null);
         if (get.fetch()) {
             subscription.setEtag(get.getETag());
             subscription.setLastModified(get.getLastModified());
         }
         try {            
-            this.addresses = ConfigParser.parse(new File("addressbook.tmp"));
+            this.addresses = ConfigParser.parse(tmp);
         } catch (IOException exp) {
             this.addresses = new HashMap();
         }
-        new File("addressbook.tmp").delete();
+        tmp.delete();
     }
 
     /**
diff --git a/apps/addressbook/java/src/addressbook/Daemon.java b/apps/addressbook/java/src/addressbook/Daemon.java
index a1b1ae18a3266ac05b4e755f6906255202c5f351..a8a9a2da6ed54e1ac9b43155857a6c7ba7d178fa 100644
--- a/apps/addressbook/java/src/addressbook/Daemon.java
+++ b/apps/addressbook/java/src/addressbook/Daemon.java
@@ -28,6 +28,8 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 
+import net.i2p.I2PAppContext;
+
 /**
  * Main class of addressbook.  Performs updates, and runs the main loop.
  * 
@@ -125,11 +127,13 @@ public class Daemon {
     
     public void run(String[] args) {
         String settingsLocation = "config.txt";
-        String home;
+        File homeFile;
         if (args.length > 0) {
-            home = args[0];
+            homeFile = new File(args[0]);
+            if (!homeFile.isAbsolute())
+                homeFile = new File(I2PAppContext.getGlobalContext().getRouterDir(), args[0]);
         } else {
-            home = ".";
+            homeFile = new File(System.getProperty("user.dir"));
         }
         
         Map defaultSettings = new HashMap();
@@ -145,7 +149,6 @@ public class Daemon {
         defaultSettings.put("last_modified", "last_modified");
         defaultSettings.put("update_delay", "12");
         
-        File homeFile = new File(home);
         if (!homeFile.exists()) {
             boolean created = homeFile.mkdirs();
             if (created)
@@ -169,7 +172,7 @@ public class Daemon {
                 delay = 1;
             }
             
-            update(settings, home);
+            update(settings, homeFile.getAbsolutePath());
             try {
                 synchronized (this) {
                     wait(delay * 60 * 60 * 1000);
diff --git a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java
index b7e623060ce0a910577098207960a925da8a7f0b..1a43514ded82ec1bc8bf508456388735d67211c8 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java
@@ -73,7 +73,7 @@ public class I2PSnarkUtil {
         // This is used for both announce replies and .torrent file downloads,
         // so it must be available even if not connected to I2CP.
         // so much for multiple instances
-        _tmpDir = new File("tmp", "i2psnark");
+        _tmpDir = new File(ctx.getTempDir(), "i2psnark");
         FileUtil.rmdir(_tmpDir, false);
         _tmpDir.mkdirs();
     }
diff --git a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java
index a45bf5161a76958a28a9834a456e1a068de35dc1..79ea62ebc4ffa0d1000acad3c4517bb160c96380 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java
@@ -31,7 +31,7 @@ public class SnarkManager implements Snark.CompleteListener {
     /** map of (canonical) filename to Snark instance (unsynchronized) */
     private Map _snarks;
     private Object _addSnarkLock;
-    private String _configFile = "i2psnark.config";
+    private File _configFile;
     private Properties _config;
     private I2PAppContext _context;
     private Log _log;
@@ -51,6 +51,7 @@ public class SnarkManager implements Snark.CompleteListener {
     public static final String PROP_META_PREFIX = "i2psnark.zmeta.";
     public static final String PROP_META_BITFIELD_SUFFIX = ".bitfield";
 
+    private static final String CONFIG_FILE = "i2psnark.config";
     public static final String PROP_AUTO_START = "i2snark.autoStart";   // oops
     public static final String DEFAULT_AUTO_START = "false";
     public static final String PROP_LINK_PREFIX = "i2psnark.linkPrefix";
@@ -66,6 +67,9 @@ public class SnarkManager implements Snark.CompleteListener {
         _log = _context.logManager().getLog(SnarkManager.class);
         _messages = new ArrayList(16);
         _util = new I2PSnarkUtil(_context);
+        _configFile = new File(CONFIG_FILE);
+        if (!_configFile.isAbsolute())
+            _configFile = new File(_context.getConfigDir(), CONFIG_FILE);
         loadConfig(null);
     }
 
@@ -112,10 +116,11 @@ public class SnarkManager implements Snark.CompleteListener {
     }
     private int getStartupDelayMinutes() { return 3; }
     public File getDataDir() { 
-        String dir = _config.getProperty(PROP_DIR);
-        if ( (dir == null) || (dir.trim().length() <= 0) )
-            dir = "i2psnark";
-        return new File(dir); 
+        String dir = _config.getProperty(PROP_DIR, "i2psnark");
+        File f = new File(dir);
+        if (!f.isAbsolute())
+            f = new File(_context.getAppDir(), dir);
+        return f; 
     }
     
     /** null to set initial defaults */
@@ -123,8 +128,10 @@ public class SnarkManager implements Snark.CompleteListener {
         if (_config == null)
             _config = new Properties();
         if (filename != null) {
-            _configFile = filename;
             File cfg = new File(filename);
+            if (!cfg.isAbsolute())
+                cfg = new File(_context.getConfigDir(), filename);
+            _configFile = cfg;
             if (cfg.exists()) {
                 try {
                     DataHelper.loadProps(_config, cfg);
@@ -352,10 +359,10 @@ public class SnarkManager implements Snark.CompleteListener {
     public void saveConfig() {
         try {
             synchronized (_configFile) {
-                DataHelper.storeProps(_config, new File(_configFile));
+                DataHelper.storeProps(_config, _configFile);
             }
         } catch (IOException ioe) {
-            addMessage("Unable to save the config to '" + _configFile + "'");
+            addMessage("Unable to save the config to '" + _configFile.getAbsolutePath() + "'");
         }
     }
     
diff --git a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
index 3923484a80901882de9735eff80905314df77516..410e804d981347304f8faab3ec10ca6ca12a0475 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
@@ -173,6 +173,7 @@ public class I2PSnarkServlet extends HttpServlet {
         } else if ("Add torrent".equals(action)) {
             String newFile = req.getParameter("newFile");
             String newURL = req.getParameter("newURL");
+            // NOTE - newFile currently disabled in HTML form - see below
             File f = null;
             if ( (newFile != null) && (newFile.trim().length() > 0) )
                 f = new File(newFile.trim());
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java
index 486bcdead5cb7098ac9725c607f5b9fd70e16b81..e4f8453bc7f5c69114b8443f6844a24a8e9f71c6 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java
@@ -372,6 +372,8 @@ public class I2PTunnel implements Logging, EventDispatcher {
             }
 
             privKeyFile = new File(args[2]);
+            if (!privKeyFile.isAbsolute())
+                privKeyFile = new File(_context.getAppDir(), args[2]);
             if (!privKeyFile.canRead()) {
                 l.log("private key file does not exist");
                 _log.error(getPrefix() + "Private key file does not exist or is not readable: " + args[2]);
@@ -419,6 +421,8 @@ public class I2PTunnel implements Logging, EventDispatcher {
             }
 
             privKeyFile = new File(args[2]);
+            if (!privKeyFile.isAbsolute())
+                privKeyFile = new File(_context.getAppDir(), args[2]);
             if (!privKeyFile.canRead()) {
                 l.log("private key file does not exist");
                 _log.error(getPrefix() + "Private key file does not exist or is not readable: " + args[2]);
@@ -476,6 +480,8 @@ public class I2PTunnel implements Logging, EventDispatcher {
             String spoofedHost = args[2];
             
             privKeyFile = new File(args[3]);
+            if (!privKeyFile.isAbsolute())
+                privKeyFile = new File(_context.getAppDir(), args[3]);
             if (!privKeyFile.canRead()) {
                 l.log("private key file does not exist");
                 _log.error(getPrefix() + "Private key file does not exist or is not readable: " + args[3]);
@@ -870,6 +876,8 @@ public class I2PTunnel implements Logging, EventDispatcher {
             }
 
             File privKeyFile = new File(args[1]);
+            if (!privKeyFile.isAbsolute())
+                privKeyFile = new File(_context.getAppDir(), args[1]);
             if (!privKeyFile.canRead()) {
                 l.log("private key file does not exist");
                 _log.error(getPrefix() + "Private key file does not exist or is not readable: " + args[3]);
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelConnectClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelConnectClient.java
index 59971e8f3d7b1d429928ed52a29ecc982bb12ba7..79270303b00ec6b8090b1d87aa13e981c6550465 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelConnectClient.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelConnectClient.java
@@ -3,6 +3,7 @@
  */
 package net.i2p.i2ptunnel;
 
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -106,6 +107,8 @@ public class I2PTunnelConnectClient extends I2PTunnelClientBase implements Runna
     /** used to assign unique IDs to the threads / clients.  no logic or functionality */
     private static volatile long __clientId = 0;
 
+    private static final File _errorDir = new File(I2PAppContext.getGlobalContext().getBaseDir(), "docs");
+
     /**
      * @throws IllegalArgumentException if the I2PTunnel does not contain
      *                                  valid config to contact the router
@@ -261,9 +264,9 @@ public class I2PTunnelConnectClient extends I2PTunnelClientBase implements Runna
                 String str;
                 byte[] header;
                 if (usingWWWProxy)
-                    str = FileUtil.readTextFile("docs/dnfp-header.ht", 100, true);
+                    str = FileUtil.readTextFile((new File(_errorDir, "dnfp-header.ht")).getAbsolutePath(), 100, true);
                 else
-                    str = FileUtil.readTextFile("docs/dnfh-header.ht", 100, true);
+                    str = FileUtil.readTextFile((new File(_errorDir, "dnfh-header.ht")).getAbsolutePath(), 100, true);
                 if (str != null)
                     header = str.getBytes();
                 else
@@ -357,9 +360,9 @@ public class I2PTunnelConnectClient extends I2PTunnelClientBase implements Runna
             String str;
             byte[] header;
             if (usingWWWProxy)
-                str = FileUtil.readTextFile("docs/dnfp-header.ht", 100, true);
+                str = FileUtil.readTextFile((new File(_errorDir, "dnfp-header.ht")).getAbsolutePath(), 100, true);
             else
-                str = FileUtil.readTextFile("docs/dnf-header.ht", 100, true);
+                str = FileUtil.readTextFile((new File(_errorDir, "dnf-header.ht")).getAbsolutePath(), 100, true);
             if (str != null)
                 header = str.getBytes();
             else
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java
index 7e16114adccbaa7b6e0bb4de15533c86b70beea7..cab438991d926d6112439f5cc266c380961d534a 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java
@@ -4,6 +4,7 @@
 package net.i2p.i2ptunnel;
 
 import java.io.BufferedReader;
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -136,6 +137,9 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
     /** used to assign unique IDs to the threads / clients.  no logic or functionality */
     private static volatile long __clientId = 0;
 
+    private static final File _errorDir = new File(I2PAppContext.getGlobalContext().getBaseDir(), "docs");
+
+
     /**
      * @throws IllegalArgumentException if the I2PTunnel does not contain
      *                                  valid config to contact the router
@@ -372,7 +376,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
                             {
                                 String str;
                                 byte[] header;
-                                str = FileUtil.readTextFile("docs/ahelper-conflict-header.ht", 100, true);
+                                str = FileUtil.readTextFile((new File(_errorDir, "ahelper-conflict-header.ht")).getAbsolutePath(), 100, true);
                                 if (str != null) header = str.getBytes();
                                   else header = ERR_AHELPER_CONFLICT;
 
@@ -558,13 +562,13 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
                 byte[] header;
                 boolean showAddrHelper = false;
                 if (usingWWWProxy)
-                    str = FileUtil.readTextFile("docs/dnfp-header.ht", 100, true);
+                    str = FileUtil.readTextFile((new File(_errorDir, "dnfp-header.ht")).getAbsolutePath(), 100, true);
                 else if(ahelper != 0)
-                    str = FileUtil.readTextFile("docs/dnfb-header.ht", 100, true);
+                    str = FileUtil.readTextFile((new File(_errorDir, "dnfb-header.ht")).getAbsolutePath(), 100, true);
                 else if (destination.length() == 60 && destination.endsWith(".b32.i2p"))
-                    str = FileUtil.readTextFile("docs/dnf-header.ht", 100, true);
+                    str = FileUtil.readTextFile((new File(_errorDir, "dnf-header.ht")).getAbsolutePath(), 100, true);
                 else {
-                    str = FileUtil.readTextFile("docs/dnfh-header.ht", 100, true);
+                    str = FileUtil.readTextFile((new File(_errorDir, "dnfh-header.ht")).getAbsolutePath(), 100, true);
                     showAddrHelper = true;
                 }
                 if (str != null)
@@ -733,9 +737,9 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
                 String str;
                 byte[] header;
                 if (usingWWWProxy)
-                    str = FileUtil.readTextFile("docs/dnfp-header.ht", 100, true);
+                    str = FileUtil.readTextFile((new File(_errorDir, "dnfp-header.ht")).getAbsolutePath(), 100, true);
                 else
-                    str = FileUtil.readTextFile("docs/dnf-header.ht", 100, true);
+                    str = FileUtil.readTextFile((new File(_errorDir, "dnf-header.ht")).getAbsolutePath(), 100, true);
                 if (str != null)
                     header = str.getBytes();
                 else
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java
index 1f67783e9b6578db5d1d803639be99d04834f652..3dbcfea3067cd106ffb6172146ef2eb754aa230d 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java
@@ -72,6 +72,8 @@ public class TunnelController implements Logging {
         }
         
         File keyFile = new File(getPrivKeyFile());
+        if (!keyFile.isAbsolute())
+            keyFile = new File(I2PAppContext.getGlobalContext().getAppDir(), getPrivKeyFile());
         if (keyFile.exists()) {
             //log("Not overwriting existing private keys in " + keyFile.getAbsolutePath());
             return;
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelControllerGroup.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelControllerGroup.java
index 16f418be9ed12b3c0983427ebf796ee700320a54..a0b8ea3f7ffe25c30c9e4e4495c03239724323fe 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelControllerGroup.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelControllerGroup.java
@@ -270,9 +270,11 @@ public class TunnelControllerGroup {
      */
     private Properties loadConfig(String configFile) {
         File cfgFile = new File(configFile);
+        if (!cfgFile.isAbsolute())
+            cfgFile = new File(I2PAppContext.getGlobalContext().getConfigDir(), configFile);
         if (!cfgFile.exists()) {
             if (_log.shouldLog(Log.ERROR))
-                _log.error("Unable to load the controllers from " + configFile);
+                _log.error("Unable to load the controllers from " + cfgFile.getAbsolutePath());
             return null;
         }
         
@@ -282,7 +284,7 @@ public class TunnelControllerGroup {
             return props;
         } catch (IOException ioe) {
             if (_log.shouldLog(Log.ERROR))
-                _log.error("Error reading the controllers from " + configFile, ioe);
+                _log.error("Error reading the controllers from " + cfgFile.getAbsolutePath(), ioe);
             return null;
         }
     }
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java
index 0dc4d1e62bed23908afb0192b477afaa9f433c73..195889fad295a59d565fa55b59de01e8e0120f8d 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java
@@ -41,7 +41,7 @@ public class ConfigServiceHandler extends FormHandler {
         }
         public void run() {
             try {
-                Router.killKeys();
+                ContextHelper.getContext(null).router().killKeys();
                 WrapperManager.signalStopped(_exitCode);
             } catch (Throwable t) {
                 t.printStackTrace();
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ContentHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ContentHelper.java
index ce29250b9fbdad83dca8d8c825fe88baa3435cf8..721655a512085fe5f682db95af760d027965b6c4 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ContentHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ContentHelper.java
@@ -3,7 +3,6 @@ package net.i2p.router.web;
 import java.io.File;
 import java.util.Locale;
 
-import net.i2p.router.RouterContext;
 import net.i2p.util.FileUtil;
 
 public class ContentHelper extends HelperBase {
@@ -14,6 +13,9 @@ public class ContentHelper extends HelperBase {
     
     public ContentHelper() {}
     
+    /**
+     * Caution, use absolute paths only, do not assume files are in CWD
+     */
     public void setPage(String page) { _page = page; }
     public void setStartAtBeginning(String moo) { 
         _startAtBeginning = Boolean.valueOf(""+moo).booleanValue(); 
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/LogsHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/LogsHelper.java
index e1fce8f3ecc2ecc763823fd922ea4e2aca8a982a..a4896cdb80a64e82f55bde6166014bee97a0f0ab 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/LogsHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/LogsHelper.java
@@ -1,66 +1,70 @@
 package net.i2p.router.web;
 
+import java.io.File;
 import java.util.List;
 
-import net.i2p.router.RouterContext;
 import net.i2p.util.FileUtil;
 
 public class LogsHelper extends HelperBase {
     public LogsHelper() {}
     
     public String getLogs() {
-        List msgs = _context.logManager().getBuffer().getMostRecentMessages();
-        StringBuffer buf = new StringBuffer(16*1024); 
-        buf.append("<ul>");
-        buf.append("<code>\n");
-        for (int i = msgs.size(); i > 0; i--) { 
-            String msg = (String)msgs.get(i - 1);
-            msg = msg.replaceAll("<","&lt;");
-            buf.append("<li>");
-            buf.append(msg);
-            buf.append("</li>\n");
-        }
-        buf.append("</code></ul>\n");
-        
-        return buf.toString();
+        return formatMessages(_context.logManager().getBuffer().getMostRecentMessages());
     }
     
     public String getCriticalLogs() {
-        List msgs = _context.logManager().getBuffer().getMostRecentCriticalMessages();
-        StringBuffer buf = new StringBuffer(16*1024); 
-        buf.append("<ul>");
-        buf.append("<code>\n");
-        for (int i = msgs.size(); i > 0; i--) { 
-            String msg = (String)msgs.get(i - 1);
-            msg = msg.replaceAll("<","&lt;");
-            buf.append("<li>");
-            buf.append(msg);
-            buf.append("</li>\n");
-        }
-        buf.append("</code></ul>\n");
-        
-        return buf.toString();
+        return formatMessages(_context.logManager().getBuffer().getMostRecentCriticalMessages());
     }
     
     public String getServiceLogs() {
-        String str = FileUtil.readTextFile("wrapper.log", 250, false);
+        // look in new and old place
+        File f = new File(_context.getLogDir(), "wrapper.log");
+        if (!f.exists())
+            f = new File(_context.getBaseDir(), "wrapper.log");
+        String str = FileUtil.readTextFile(f.getAbsolutePath(), 250, false);
         if (str == null) 
             return "";
         else {
-            str = str.replaceAll("<","&lt;");
+            str = str.replaceAll("<", "&lt;").replaceAll(">", "&gt;");
             return "<pre>" + str + "</pre>";
         }
     }
     
+    /*****  unused
     public String getConnectionLogs() {
-        List msgs = _context.commSystem().getMostRecentErrorMessages();
+        return formatMessages(_context.commSystem().getMostRecentErrorMessages());
+    }
+    ******/
+
+    private String formatMessages(List msgs) {
+        boolean colorize = Boolean.valueOf(_context.getProperty("routerconsole.logs.color")).booleanValue();
         StringBuffer buf = new StringBuffer(16*1024); 
         buf.append("<ul>");
         buf.append("<code>\n");
         for (int i = msgs.size(); i > 0; i--) { 
             String msg = (String)msgs.get(i - 1);
             buf.append("<li>");
-            buf.append(msg);
+            if (colorize) {
+                String color;
+                // Homeland Security Advisory System
+                // http://www.dhs.gov/xinfoshare/programs/Copy_of_press_release_0046.shtm
+                // but pink instead of yellow for WARN
+                if (msg.contains("CRIT"))
+                    color = "#cc0000";
+                else if (msg.contains("ERROR"))
+                    color = "#ff3300";
+                else if (msg.contains("WARN"))
+                    color = "#ff00cc";
+                else if (msg.contains("INFO"))
+                    color = "#000099";
+                else
+                    color = "#006600";
+                buf.append("<font color=\"").append(color).append("\">");
+                buf.append(msg.replaceAll("<", "&lt;").replaceAll(">", "&gt;"));
+                buf.append("</font>");
+            } else {
+                buf.append(msg);
+            }
             buf.append("</li>\n");
         }
         buf.append("</code></ul>\n");
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/NewsFetcher.java b/apps/routerconsole/java/src/net/i2p/router/web/NewsFetcher.java
index 98fac325d191b9b73e9d1261b980c39affbdadb9..fd7662caf42e85961dcbf37524acc6b2300113d4 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/NewsFetcher.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/NewsFetcher.java
@@ -27,6 +27,8 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
     private long _lastUpdated;
     private String _updateVersion;
     private String _lastModified;
+    private File _newsFile;
+    private File _tempFile;
     private static NewsFetcher _instance;
     //public static final synchronized NewsFetcher getInstance() { return _instance; }
     public static final synchronized NewsFetcher getInstance(I2PAppContext ctx) { 
@@ -35,25 +37,26 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
         _instance = new NewsFetcher(ctx);
         return _instance;
     }
-    
+
     private static final String NEWS_FILE = "docs/news.xml";
-    private static final String TEMP_NEWS_FILE = "docs/news.xml.temp";
+    private static final String TEMP_NEWS_FILE = "news.xml.temp";
     
     private NewsFetcher(I2PAppContext ctx) {
         _context = ctx;
         _log = ctx.logManager().getLog(NewsFetcher.class);
         _instance = this;
         _lastFetch = 0;
+        _newsFile = new File(_context.getBaseDir(), NEWS_FILE);
+        _tempFile = new File(_context.getTempDir(), TEMP_NEWS_FILE);
         updateLastFetched();
         _lastUpdated = _lastFetch;
         _updateVersion = "";
     }
     
     private void updateLastFetched() {
-        File news = new File(NEWS_FILE);
-        if (news.exists()) {
+        if (_newsFile.exists()) {
             if (_lastFetch == 0)
-                _lastFetch = news.lastModified();
+                _lastFetch = _newsFile.lastModified();
         } else
             _lastFetch = 0;
     }
@@ -82,7 +85,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
         String policy = _context.getProperty(ConfigUpdateHandler.PROP_UPDATE_POLICY);
         if ("notify".equals(policy))
             return false;
-        File zip = new File(Router.UPDATE_FILE);
+        File zip = new File(_context.getRouterDir(), Router.UPDATE_FILE);
         return !zip.exists();
     }
     
@@ -114,18 +117,17 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
         boolean shouldProxy = Boolean.valueOf(_context.getProperty(ConfigUpdateHandler.PROP_SHOULD_PROXY, ConfigUpdateHandler.DEFAULT_SHOULD_PROXY)).booleanValue();
         String proxyHost = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_HOST, ConfigUpdateHandler.DEFAULT_PROXY_HOST);
         String port = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_PORT, ConfigUpdateHandler.DEFAULT_PROXY_PORT);
-        File tempFile = new File(TEMP_NEWS_FILE);
-        if (tempFile.exists())
-            tempFile.delete();
+        if (_tempFile.exists())
+            _tempFile.delete();
         
         int proxyPort = -1;
         try {
             proxyPort = Integer.parseInt(port);
             EepGet get = null;
             if (shouldProxy)
-                get = new EepGet(_context, true, proxyHost, proxyPort, 2, TEMP_NEWS_FILE, newsURL, true, null, _lastModified);
+                get = new EepGet(_context, true, proxyHost, proxyPort, 2, _tempFile.getAbsolutePath(), newsURL, true, null, _lastModified);
             else
-                get = new EepGet(_context, false, null, 0, 0, TEMP_NEWS_FILE, newsURL, true, null, _lastModified);
+                get = new EepGet(_context, false, null, 0, 0, _tempFile.getAbsolutePath(), newsURL, true, null, _lastModified);
             get.addStatusListener(this);
             if (get.fetch())
                 _lastModified = get.getLastModified();
@@ -138,11 +140,10 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
     private static final String VERSION_PREFIX = "version=\"";
     private void checkForUpdates() {
         _updateAvailable = false;
-        File news = new File(NEWS_FILE);
-        if ( (!news.exists()) || (news.length() <= 0) ) return;
+        if ( (!_newsFile.exists()) || (_newsFile.length() <= 0) ) return;
         FileInputStream in = null;
         try {
-            in = new FileInputStream(news);
+            in = new FileInputStream(_newsFile);
             StringBuffer buf = new StringBuffer(128);
             while (DataHelper.readLine(in, buf)) {
                 int index = buf.indexOf(VERSION_PREFIX);
@@ -220,13 +221,12 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
         if (_log.shouldLog(Log.INFO))
             _log.info("News fetched from " + url + " with " + (alreadyTransferred+bytesTransferred));
         
-        File temp = new File(TEMP_NEWS_FILE);
         long now = _context.clock().now();
-        if (temp.exists()) {
-            boolean copied = FileUtil.copy(TEMP_NEWS_FILE, NEWS_FILE, true);
+        if (_tempFile.exists()) {
+            boolean copied = FileUtil.copy(_tempFile.getAbsolutePath(), _newsFile.getAbsolutePath(), true);
             if (copied) {
                 _lastUpdated = now;
-                temp.delete();
+                _tempFile.delete();
                 checkForUpdates();
             } else {
                 if (_log.shouldLog(Log.ERROR))
@@ -242,8 +242,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
     public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {
         if (_log.shouldLog(Log.WARN))
             _log.warn("Failed to fetch the news from " + url);
-        File temp = new File(TEMP_NEWS_FILE);
-        temp.delete();
+        _tempFile.delete();
     }
     public void headerReceived(String url, int attemptNum, String key, String val) {}
     public void attempting(String url) {}
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ReseedHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ReseedHandler.java
index e701ede9954e8925d1f2018014988859e3ad0fcc..c11c6150c28536ae829f88aff342483679ab8009 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ReseedHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ReseedHandler.java
@@ -256,7 +256,7 @@ public class ReseedHandler {
     
         private void writeSeed(String name, byte data[]) throws Exception {
             String dirName = "netDb"; // _context.getProperty("router.networkDatabase.dbDir", "netDb");
-            File netDbDir = new File(dirName);
+            File netDbDir = new File(_context.getRouterDir(), dirName);
             if (!netDbDir.exists()) {
                 boolean ok = netDbDir.mkdirs();
             }
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java
index 862902b1f811a6eef2eed65cdec5319dcf4a00cb..f58aeb3082c53bd8c16e68769fb13f6fd38789b0 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java
@@ -59,7 +59,7 @@ public class RouterConsoleRunner {
     }
     
     public void startConsole() {
-        File workDir = new File("work");
+        File workDir = new File(I2PAppContext.getGlobalContext().getTempDir(), "jetty-work");
         boolean workDirRemoved = FileUtil.rmdir(workDir, false);
         if (!workDirRemoved)
             System.err.println("ERROR: Unable to remove Jetty temporary work directory");
@@ -95,8 +95,11 @@ public class RouterConsoleRunner {
             }
             _server.setRootWebApp(ROUTERCONSOLE);
             WebApplicationContext wac = _server.addWebApplication("/", _webAppsDir + ROUTERCONSOLE + ".war");
+            File tmpdir = new File(workDir, ROUTERCONSOLE + "-" + _listenPort);
+            tmpdir.mkdir();
+            wac.setTempDirectory(tmpdir);
             initialize(wac);
-            File dir = new File(_webAppsDir);
+            File dir = new File(I2PAppContext.getGlobalContext().getBaseDir(), _webAppsDir);
             String fileNames[] = dir.list(WarFilenameFilter.instance());
             if (fileNames != null) {
                 for (int i = 0; i < fileNames.length; i++) {
@@ -106,6 +109,9 @@ public class RouterConsoleRunner {
                         if (! "false".equals(enabled)) {
                             String path = new File(dir, fileNames[i]).getCanonicalPath();
                             wac = _server.addWebApplication("/"+ appName, path);
+                            tmpdir = new File(workDir, appName + "-" + _listenPort);
+                            tmpdir.mkdir();
+                            wac.setTempDirectory(tmpdir);
                             initialize(wac);
                             if (enabled == null) {
                                 // do this so configclients.jsp knows about all apps from reading the config
@@ -144,10 +150,10 @@ public class RouterConsoleRunner {
         // don't have an installation directory that they can put the flag in yet.
         File noReseedFile = new File(new File(System.getProperty("user.home")), ".i2pnoreseed");
         File noReseedFileAlt1 = new File(new File(System.getProperty("user.home")), "noreseed.i2p");
-        File noReseedFileAlt2 = new File(".i2pnoreseed");
-        File noReseedFileAlt3 = new File("noreseed.i2p");
+        File noReseedFileAlt2 = new File(I2PAppContext.getGlobalContext().getConfigDir(), ".i2pnoreseed");
+        File noReseedFileAlt3 = new File(I2PAppContext.getGlobalContext().getConfigDir(), "noreseed.i2p");
         if (!noReseedFile.exists() && !noReseedFileAlt1.exists() && !noReseedFileAlt2.exists() && !noReseedFileAlt3.exists()) {
-            File netDb = new File("netDb");
+            File netDb = new File(I2PAppContext.getGlobalContext().getRouterDir(), "netDb");
             // sure, some of them could be "my.info" or various leaseSet- files, but chances are, 
             // if someone has those files, they've already been seeded (at least enough to let them
             // get i2p started - they can reseed later in the web console)
@@ -216,7 +222,7 @@ public class RouterConsoleRunner {
         Properties rv = new Properties();
         // String webappConfigFile = ctx.getProperty(PROP_WEBAPP_CONFIG_FILENAME, DEFAULT_WEBAPP_CONFIG_FILENAME);
         String webappConfigFile = DEFAULT_WEBAPP_CONFIG_FILENAME;
-        File cfgFile = new File(webappConfigFile);
+        File cfgFile = new File(I2PAppContext.getGlobalContext().getConfigDir(), webappConfigFile);
         
         try {
             DataHelper.loadProps(rv, cfgFile);
@@ -230,7 +236,7 @@ public class RouterConsoleRunner {
     public static void storeWebAppProperties(Properties props) {
         // String webappConfigFile = ctx.getProperty(PROP_WEBAPP_CONFIG_FILENAME, DEFAULT_WEBAPP_CONFIG_FILENAME);
         String webappConfigFile = DEFAULT_WEBAPP_CONFIG_FILENAME;
-        File cfgFile = new File(webappConfigFile);
+        File cfgFile = new File(I2PAppContext.getGlobalContext().getConfigDir(), webappConfigFile);
         
         try {
             DataHelper.storeProps(props, cfgFile);
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 35edadfb6b6190fd4e500308bc38a1ddaace520c..267fe5a07c74551dd789b7b5ec6fb957e76e139d 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java
@@ -31,6 +31,7 @@ public class UpdateHandler {
     protected RouterContext _context;
     protected Log _log;
     protected DecimalFormat _pct = new DecimalFormat("00.0%");
+    protected String _updateFile;
     
     protected static final String SIGNED_UPDATE_FILE = "i2pupdate.sud";
     protected static final String PROP_UPDATE_IN_PROGRESS = "net.i2p.router.web.UpdateHandler.updateInProgress";
@@ -41,6 +42,7 @@ public class UpdateHandler {
     public UpdateHandler(RouterContext ctx) {
         _context = ctx;
         _log = ctx.logManager().getLog(UpdateHandler.class);
+        _updateFile = (new File(ctx.getRouterDir(), SIGNED_UPDATE_FILE)).getAbsolutePath();
     }
     
     /**
@@ -137,9 +139,9 @@ public class UpdateHandler {
             try {
                 EepGet get = null;
                 if (shouldProxy)
-                    get = new EepGet(_context, proxyHost, proxyPort, 20, SIGNED_UPDATE_FILE, updateURL, false);
+                    get = new EepGet(_context, proxyHost, proxyPort, 20, _updateFile, updateURL, false);
                 else
-                    get = new EepGet(_context, 1, SIGNED_UPDATE_FILE, updateURL, false);
+                    get = new EepGet(_context, 1, _updateFile, updateURL, false);
                 get.addStatusListener(UpdateRunner.this);
                 get.fetch();
             } catch (Throwable t) {
@@ -167,8 +169,9 @@ public class UpdateHandler {
         public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
             _status = "<b>Update downloaded</b>";
             TrustedUpdate up = new TrustedUpdate(_context);
-            String err = up.migrateVerified(RouterVersion.VERSION, SIGNED_UPDATE_FILE, Router.UPDATE_FILE);
-            File f = new File(SIGNED_UPDATE_FILE);
+            File f = new File(_updateFile);
+            File to = new File(_context.getBaseDir(), Router.UPDATE_FILE);
+            String err = up.migrateVerified(RouterVersion.VERSION, f, to);
             f.delete();
             if (err == null) {
                 String policy = _context.getProperty(ConfigUpdateHandler.PROP_UPDATE_POLICY);
diff --git a/apps/routerconsole/jsp/flags.jsp b/apps/routerconsole/jsp/flags.jsp
index 38d068d592bcfc817dcf032a7b04166c38a36fe8..c990ba27a3a87225b2f44c2a3845d35e97906edc 100644
--- a/apps/routerconsole/jsp/flags.jsp
+++ b/apps/routerconsole/jsp/flags.jsp
@@ -15,8 +15,11 @@ if (c != null && c.length() > 0) {
     java.io.OutputStream cout = response.getOutputStream();
     response.setContentType("image/png");
     response.setHeader("Cache-Control", "max-age=86400");  // cache for a day
+    String base = net.i2p.I2PAppContext.getGlobalContext().getBaseDir().getAbsolutePath();
+    String file = "docs" + java.io.File.separatorChar + "icons" + java.io.File.separatorChar +
+                  "flags" + java.io.File.separatorChar + c + ".png";
     try {
-        net.i2p.util.FileUtil.readFile(c + ".png", "docs/icons/flags", cout);
+        net.i2p.util.FileUtil.readFile(file, base, cout);
         rendered = true;
     } catch (java.io.IOException ioe) {}
     if (rendered)
diff --git a/apps/routerconsole/jsp/viewtheme.jsp b/apps/routerconsole/jsp/viewtheme.jsp
index 05ccfecbf91a6380acf27fa651f158eb481ba6e5..5785195c2933e6c9dda645d9ad7f1d90a691004d 100644
--- a/apps/routerconsole/jsp/viewtheme.jsp
+++ b/apps/routerconsole/jsp/viewtheme.jsp
@@ -1,4 +1,4 @@
-<% 
+% 
 /*
  * USE CAUTION WHEN EDITING
  * Trailing whitespace OR NEWLINE on the last line will cause
@@ -16,5 +16,7 @@ if (uri.endsWith(".css")) {
   response.setContentType("image/jpeg");
 }
 
-net.i2p.util.FileUtil.readFile(uri, "./docs", response.getOutputStream());
+String base = net.i2p.I2PAppContext.getGlobalContext().getBaseDir().getAbsolutePath() +
+              java.io.File.separatorChar + "docs";
+net.i2p.util.FileUtil.readFile(uri, base, response.getOutputStream());
 %>
\ No newline at end of file
diff --git a/apps/susidns/src/java/src/i2p/susi/dns/ConfigBean.java b/apps/susidns/src/java/src/i2p/susi/dns/ConfigBean.java
index 15a2204f8ebf5bcfc81d55e95dd167fa038adbdb..8cddafdc42c624e030b16e4916c0eb3d5def1a7d 100644
--- a/apps/susidns/src/java/src/i2p/susi/dns/ConfigBean.java
+++ b/apps/susidns/src/java/src/i2p/susi/dns/ConfigBean.java
@@ -33,12 +33,16 @@ import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.Serializable;
 
+import net.i2p.I2PAppContext;
+
 public class ConfigBean implements Serializable {
 	
 	/*
 	 * as this is not provided as constant in addressbook, we define it here
 	 */
-	public static final String addressbookPrefix = "addressbook/";
+ 	public static final String addressbookPrefix =
+ 		(new File(I2PAppContext.getGlobalContext().getRouterDir(), "addressbook")).getAbsolutePath()
+			+ File.separatorChar;
 	public static final String configFileName = addressbookPrefix + "config.txt";
 	
 	private String action, config;
diff --git a/apps/susimail/build.xml b/apps/susimail/build.xml
index 886f9471f6425982bb3f71a2747118fa02c0ac31..fb437161447213e59f855104977da02a648f7615 100644
--- a/apps/susimail/build.xml
+++ b/apps/susimail/build.xml
@@ -15,6 +15,7 @@
             <classpath>
                 <pathelement location="../jetty/jettylib/javax.servlet.jar" />
                 <pathelement location="../jetty/jettylib/org.mortbay.jetty.jar" />
+                <pathelement location="../../core/java/build/i2p.jar" />
             </classpath>
         </javac>
     </target>
diff --git a/apps/susimail/src/src/i2p/susi/util/Config.java b/apps/susimail/src/src/i2p/susi/util/Config.java
index 219ce71f3420839788b1716e3e377ed59f721183..723c56b992992be84f1a5625967677dc2ae95b86 100644
--- a/apps/susimail/src/src/i2p/susi/util/Config.java
+++ b/apps/susimail/src/src/i2p/susi/util/Config.java
@@ -25,10 +25,13 @@ package i2p.susi.util;
 
 import i2p.susi.debug.Debug;
 
+import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.util.Properties;
 
+import net.i2p.I2PAppContext;
+
 /**
  * @author susi
  */
@@ -81,7 +84,8 @@ public class Config {
 		}
                 FileInputStream fis = null;
 		try {
-			fis = new FileInputStream( "susimail.config" );
+			File cfg = new File(I2PAppContext.getGlobalContext().getConfigDir(), "susimail.config");
+			fis = new FileInputStream(cfg);
 			config.load( fis );
 		} catch (Exception e) {
 			Debug.debug( Debug.DEBUG, "Could not open susimail.config, reason: " + e.getMessage() );
diff --git a/core/java/src/freenet/support/CPUInformation/CPUID.java b/core/java/src/freenet/support/CPUInformation/CPUID.java
index 1d5c848828c4ce670dbe56ff7471d1a8d50b2c5a..58469c6d749bb889737b9c3b9d5992697ea9b597 100644
--- a/core/java/src/freenet/support/CPUInformation/CPUID.java
+++ b/core/java/src/freenet/support/CPUInformation/CPUID.java
@@ -9,6 +9,8 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
 
+import net.i2p.I2PAppContext;
+
 /**
  * @author Iakin
  * A class for retrieveing details about the CPU using the CPUID assembly instruction.
@@ -503,9 +505,10 @@ public class CPUID {
         FileOutputStream fos = null;
         try {
             InputStream libStream = resource.openStream();
-            outFile = new File(libPrefix + "jcpuid" + libSuffix);
+            outFile = new File(I2PAppContext.getGlobalContext().getBaseDir(), libPrefix + "jcpuid" + libSuffix);
             fos = new FileOutputStream(outFile);
-            byte buf[] = new byte[4096*1024];
+            // wtf this was 4096*1024 which is really excessive for a roughly 4KB file
+            byte buf[] = new byte[4096];
             while (true) {
                 int read = libStream.read(buf);
                 if (read < 0) break;
diff --git a/core/java/src/net/i2p/I2PAppContext.java b/core/java/src/net/i2p/I2PAppContext.java
index 3f44146b438c79a76f1f48cc1470cf4c20e315a7..56f774071c26f8a30987a5cc9feffd095765cc36 100644
--- a/core/java/src/net/i2p/I2PAppContext.java
+++ b/core/java/src/net/i2p/I2PAppContext.java
@@ -1,5 +1,6 @@
 package net.i2p;
 
+import java.io.File;
 import java.util.HashSet;
 import java.util.Properties;
 import java.util.Set;
@@ -20,10 +21,12 @@ import net.i2p.crypto.KeyGenerator;
 import net.i2p.crypto.SHA256Generator;
 import net.i2p.crypto.SessionKeyManager;
 import net.i2p.crypto.TransientSessionKeyManager;
+                                            import net.i2p.data.DataHelper;
 import net.i2p.data.RoutingKeyGenerator;
 import net.i2p.stat.StatManager;
 import net.i2p.util.Clock;
 import net.i2p.util.ConcurrentHashSet;
+import net.i2p.util.FileUtil;
 import net.i2p.util.FortunaRandomSource;
 import net.i2p.util.KeyRing;
 import net.i2p.util.LogManager;
@@ -96,6 +99,13 @@ public class I2PAppContext {
     private volatile boolean _keyGeneratorInitialized;
     protected volatile boolean _keyRingInitialized; // used in RouterContext
     private Set<Runnable> _shutdownTasks;
+    private File _baseDir;
+    private File _configDir;
+    private File _routerDir;
+    private File _pidDir;
+    private File _logDir;
+    private File _appDir;
+    private File _tmpDir;
     
     
     /**
@@ -155,8 +165,148 @@ public class I2PAppContext {
         _logManagerInitialized = false;
         _keyRingInitialized = false;
         _shutdownTasks = new ConcurrentHashSet(0);
+        initializeDirs();
     }
     
+   /**
+    *  Directories. These are all set at instantiation and will not be changed by
+    *  subsequent property changes.
+    *  All properties, if set, should be absolute paths.
+    *
+    *  Name	Property 	Method		Files
+    *  -----	-------- 	-----		-----
+    *  Base	i2p.dir.base	getBaseDir()	lib/, webapps/, docs/, geoip/, licenses/, ...
+    *  Temp	i2p.dir.temp	getTempDir()	Temporary files
+    *  Config	i2p.dir.config	getConfigDir()	*.config, hosts.txt, addressbook/, ...
+    *
+    *  (the following all default to the same as Config)
+    *
+    *  Router	i2p.dir.router	getRouterDir()	netDb/, peerProfiles/, router.*, keyBackup/, ...
+    *  Log	i2p.dir.log	getLogDir()	wrapper.log*, logs/
+    *  PID	i2p.dir.pid	getPIDDir()	wrapper *.pid files, router.ping
+    *  App	i2p.dir.app	getAppDir()	eepsite/, ...
+    *
+    *  Note that we can't control where the wrapper puts its files.
+    *
+    *  The app dir is where all data files should be. Apps should always read and write files here,
+    *  using a constructor such as:
+    *
+    *       String path = mypath;
+    *       File f = new File(path);
+    *       if (!f.isAbsolute())
+    *           f = new File(_context.geAppDir(), path);
+    *
+    *  and never attempt to access files in the CWD using
+    *
+    *       File f = new File("foo");
+    *
+    *  An app should assume the CWD is not writable.
+    *
+    *  Here in I2PAppContext, all the dirs default to CWD.
+    *  However these will be different in RouterContext, as Router.java will set
+    *  the properties in the RouterContext constructor.
+    *
+    *  Apps should never need to access the base dir, which is the location of the base I2P install.
+    *  However this is provided for the router's use, and for backward compatibility should an app
+    *  need to look there as well.
+    *
+    *  All dirs except the base are created if they don't exist, but the creation will fail silently.
+    */
+    private void initializeDirs() {
+        String s = getProperty("i2p.dir.base", System.getProperty("user.dir"));
+        _baseDir = new File(s);
+        // config defaults to base
+        s = getProperty("i2p.dir.config");
+        if (s != null) {
+            _configDir = new File(s);
+            if (!_configDir.exists())
+                _configDir.mkdir();
+        } else {
+            _configDir = _baseDir;
+        }
+        _configDir = new File(s);
+        // router defaults to config
+        s = getProperty("i2p.dir.router");
+        if (s != null) {
+            _routerDir = new File(s);
+            if (!_routerDir.exists())
+                _routerDir.mkdir();
+        } else {
+            _routerDir = _configDir;
+        }
+        // these all default to router
+        s = getProperty("i2p.dir.pid");
+        if (s != null) {
+            _pidDir = new File(s);
+            if (!_pidDir.exists())
+                _pidDir.mkdir();
+        } else {
+            _pidDir = _routerDir;
+        }
+        s = getProperty("i2p.dir.log");
+        if (s != null) {
+            _logDir = new File(s);
+            if (!_logDir.exists())
+                _logDir.mkdir();
+        } else {
+            _logDir = _routerDir;
+        }
+        s = getProperty("i2p.dir.app");
+        if (s != null) {
+            _appDir = new File(s);
+            if (!_appDir.exists())
+                _appDir.mkdir();
+        } else {
+            _appDir = _routerDir;
+        }
+        // comment these out later, don't want user names in the wrapper logs
+        System.err.println("Base directory:   " + _baseDir.getAbsolutePath());
+        System.err.println("Config directory: " + _configDir.getAbsolutePath());
+        System.err.println("Router directory: " + _routerDir.getAbsolutePath());
+        System.err.println("App directory:    " + _appDir.getAbsolutePath());
+        System.err.println("Log directory:    " + _logDir.getAbsolutePath());
+        System.err.println("PID directory:    " + _pidDir.getAbsolutePath());
+        System.err.println("Temp directory:   " + getTempDir().getAbsolutePath());
+    }
+
+    public File getBaseDir() { return _baseDir; }
+    public File getConfigDir() { return _configDir; }
+    public File getRouterDir() { return _routerDir; }
+    public File getPIDDir() { return _pidDir; }
+    public File getLogDir() { return _logDir; }
+    public File getAppDir() { return _appDir; }
+    public File getTempDir() {
+        // fixme don't synchronize every time
+        synchronized (this) {
+            if (_tmpDir == null) {
+                String d = getProperty("i2p.dir.temp", System.getProperty("java.io.tmpdir"));
+                // our random() probably isn't warmed up yet
+                String f = "i2p-" + (new java.util.Random()).nextInt() + ".tmp";
+                _tmpDir = new File(d, f);
+                if (_tmpDir.exists()) {
+                    // good or bad ?
+                } else if (_tmpDir.mkdir()) {
+                    _tmpDir.deleteOnExit();
+                } else {
+                    System.err.println("Could not create temp dir " + _tmpDir.getAbsolutePath());
+                    _tmpDir = new File(_routerDir, "tmp");
+                    _tmpDir.mkdir();
+                }
+            }
+        }
+        return _tmpDir;
+    }
+
+    /** don't rely on deleteOnExit() */
+    public void deleteTempDir() {
+        synchronized (this) {
+            if (_tmpDir != null) {
+                FileUtil.rmdir(_tmpDir, false);
+                _tmpDir = null;
+            }
+        }
+    }
+
     /**
      * Access the configuration attributes of this context, using properties 
      * provided during the context construction, or falling back on 
diff --git a/core/java/src/net/i2p/client/naming/HostsTxtNamingService.java b/core/java/src/net/i2p/client/naming/HostsTxtNamingService.java
index 054bd9d8f10c03eff4d0df7bc6f4ecba58a5dc0b..f89d56d09d4153cf05c730b7a844db293200466c 100644
--- a/core/java/src/net/i2p/client/naming/HostsTxtNamingService.java
+++ b/core/java/src/net/i2p/client/naming/HostsTxtNamingService.java
@@ -89,7 +89,7 @@ public class HostsTxtNamingService extends NamingService {
             String hostsfile = (String)filenames.get(i);
             Properties hosts = new Properties();
             try {
-                File f = new File(hostsfile);
+                File f = new File(_context.getRouterDir(), hostsfile);
                 if ( (f.exists()) && (f.canRead()) ) {
                     DataHelper.loadProps(hosts, f, true);
                     
@@ -119,7 +119,7 @@ public class HostsTxtNamingService extends NamingService {
             String hostsfile = (String)filenames.get(i);
             Properties hosts = new Properties();
             try {
-                File f = new File(hostsfile);
+                File f = new File(_context.getRouterDir(), hostsfile);
                 if ( (f.exists()) && (f.canRead()) ) {
                     DataHelper.loadProps(hosts, f, true);
                     Set keyset = hosts.keySet();
@@ -145,7 +145,7 @@ public class HostsTxtNamingService extends NamingService {
             String hostsfile = (String)filenames.get(i);
             Properties hosts = new Properties();
             try {
-                File f = new File(hostsfile);
+                File f = new File(_context.getRouterDir(), hostsfile);
                 if ( (f.exists()) && (f.canRead()) ) {
                     DataHelper.loadProps(hosts, f, true);
                     Set keyset = hosts.keySet();
diff --git a/core/java/src/net/i2p/crypto/TrustedUpdate.java b/core/java/src/net/i2p/crypto/TrustedUpdate.java
index 06e37544b37242e306465ed76bcf9091b92acfd7..6130fa78d59cc50424dbcce460e686376d55c4a4 100644
--- a/core/java/src/net/i2p/crypto/TrustedUpdate.java
+++ b/core/java/src/net/i2p/crypto/TrustedUpdate.java
@@ -1,6 +1,7 @@
 package net.i2p.crypto;
 
 import java.io.ByteArrayInputStream;
+import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -276,7 +277,7 @@ D8usM7Dxp5yrDrCYZ5AIijc=
     }
 
     private static final void showVersionCLI(String signedFile) {
-        String versionString = new TrustedUpdate().getVersionString(signedFile);
+        String versionString = new TrustedUpdate().getVersionString(new File(signedFile));
 
         if (versionString == "")
             System.out.println("No version string found in file '" + signedFile + "'");
@@ -294,7 +295,7 @@ D8usM7Dxp5yrDrCYZ5AIijc=
     }
 
     private static final void verifySigCLI(String signedFile) {
-        boolean isValidSignature = new TrustedUpdate().verify(signedFile);
+        boolean isValidSignature = new TrustedUpdate().verify(new File(signedFile));
 
         if (isValidSignature)
             System.out.println("Signature VALID");
@@ -303,7 +304,7 @@ D8usM7Dxp5yrDrCYZ5AIijc=
     }
 
     private static final void verifyUpdateCLI(String signedFile) {
-        boolean isUpdate = new TrustedUpdate().isUpdatedVersion(CoreVersion.VERSION, signedFile);
+        boolean isUpdate = new TrustedUpdate().isUpdatedVersion(CoreVersion.VERSION, new File(signedFile));
 
         if (isUpdate)
             System.out.println("File version is newer than current version.");
@@ -347,7 +348,7 @@ D8usM7Dxp5yrDrCYZ5AIijc=
      * @return The version string read, or an empty string if no version string
      *         is present.
      */
-    public String getVersionString(String signedFile) {
+    public String getVersionString(File signedFile) {
         FileInputStream fileInputStream = null;
 
         try {
@@ -396,9 +397,9 @@ D8usM7Dxp5yrDrCYZ5AIijc=
      * @return <code>true</code> if the signed update file's version is newer
      *         than the current version, otherwise <code>false</code>.
      */
-    public boolean isUpdatedVersion(String currentVersion, String signedFile) {
+    public boolean isUpdatedVersion(String currentVersion, File signedFile) {
         _newVersion = getVersionString(signedFile);
-        return needsUpdate(currentVersion, getVersionString(signedFile));
+        return needsUpdate(currentVersion, _newVersion);
     }
 
     /**
@@ -413,7 +414,7 @@ D8usM7Dxp5yrDrCYZ5AIijc=
      * @return <code>null</code> if the signature and version were valid and the
      *         data was moved, and an error <code>String</code> otherwise.
      */
-    public String migrateVerified(String currentVersion, String signedFile, String outputFile) {
+    public String migrateVerified(String currentVersion, File signedFile, File outputFile) {
         if (!isUpdatedVersion(currentVersion, signedFile))
             return "Downloaded version is not greater than current version";
 
@@ -606,7 +607,7 @@ D8usM7Dxp5yrDrCYZ5AIijc=
      * @return <code>true</code> if the file has a valid signature, otherwise
      *         <code>false</code>.
      */
-    public boolean verify(String signedFile) {
+    public boolean verify(File signedFile) {
         for (int i = 0; i < _trustedKeys.size(); i++) {
             SigningPublicKey signingPublicKey = new SigningPublicKey();
 
@@ -662,7 +663,7 @@ D8usM7Dxp5yrDrCYZ5AIijc=
                 }
         }
 
-        return verify(signedFile, signingPublicKey);
+        return verify(new File(signedFile), signingPublicKey);
     }
 
     /**
@@ -676,7 +677,7 @@ D8usM7Dxp5yrDrCYZ5AIijc=
      * @return <code>true</code> if the file has a valid signature, otherwise
      *         <code>false</code>.
      */
-    public boolean verify(String signedFile, SigningPublicKey signingPublicKey) {
+    public boolean verify(File signedFile, SigningPublicKey signingPublicKey) {
         FileInputStream fileInputStream = null;
 
         try {
diff --git a/core/java/src/net/i2p/data/PrivateKeyFile.java b/core/java/src/net/i2p/data/PrivateKeyFile.java
index b5d68ee41194f8eb27bb5c7926ad461517281985..497733cc4e348aedbe250da35b4d5118eb8b4549 100644
--- a/core/java/src/net/i2p/data/PrivateKeyFile.java
+++ b/core/java/src/net/i2p/data/PrivateKeyFile.java
@@ -109,6 +109,10 @@ public class PrivateKeyFile {
         this(new File(file), I2PClientFactory.createClient());
     }
 
+    public PrivateKeyFile(File file) {
+        this(file, I2PClientFactory.createClient());
+    }
+
     public PrivateKeyFile(File file, I2PClient client) {
         this.file = file;
         this.client = client;
diff --git a/core/java/src/net/i2p/stat/BufferedStatLog.java b/core/java/src/net/i2p/stat/BufferedStatLog.java
index 7fed2d09042bc40fb321cc9d758e9b5a2f4d457d..ca016622fa59921d846f16e71cfa4c120a8cb69e 100644
--- a/core/java/src/net/i2p/stat/BufferedStatLog.java
+++ b/core/java/src/net/i2p/stat/BufferedStatLog.java
@@ -1,6 +1,7 @@
 package net.i2p.stat;
 
 import java.io.BufferedWriter;
+import java.io.File;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.text.SimpleDateFormat;
@@ -109,6 +110,9 @@ public class BufferedStatLog implements StatLog {
         String filename = _context.getProperty(StatManager.PROP_STAT_FILE);
         if (filename == null)
             filename = StatManager.DEFAULT_STAT_FILE;
+        File foo = new File(filename);
+        if (!foo.isAbsolute())
+            filename = (new File(_context.getRouterDir(), filename)).getAbsolutePath();
         if ( (_outFile != null) && (_outFile.equals(filename)) ) {
             // noop
         } else {
diff --git a/core/java/src/net/i2p/util/FileUtil.java b/core/java/src/net/i2p/util/FileUtil.java
index 677c500866edcf79474a3eb62267d177bb888af8..05346ccfe2a0d8785596b35a15d541560304efc9 100644
--- a/core/java/src/net/i2p/util/FileUtil.java
+++ b/core/java/src/net/i2p/util/FileUtil.java
@@ -18,6 +18,12 @@ import java.util.zip.ZipFile;
 /**
  * General helper methods for messing with files
  *
+ * These are static methods that do NOT convert arguments
+ * to absolute paths for a particular context and directtory.
+ *
+ * Callers should ALWAYS provide absolute paths as arguments,
+ * and should NEVER assume files are in the current working directory.
+ *
  */
 public class FileUtil {
     /**
diff --git a/core/java/src/net/i2p/util/LogManager.java b/core/java/src/net/i2p/util/LogManager.java
index 1f957515ca3a0722772a66c0e320e323c2d89127..c07445d9b5382948773793a1665312daf3db2c17 100644
--- a/core/java/src/net/i2p/util/LogManager.java
+++ b/core/java/src/net/i2p/util/LogManager.java
@@ -67,8 +67,8 @@ public class LogManager {
     /** when was the config file last read (or -1 if never) */
     private long _configLastRead;
 
-    /** filename of the config file */
-    private String _location;
+    /** the config file */
+    private File _locationFile;
     /** Ordered list of LogRecord elements that have not been written out yet */
     private List _records;
     /** List of explicit overrides of log levels (LogLimit objects) */
@@ -115,11 +115,11 @@ public class LogManager {
         _logs = new HashMap(128);
         _defaultLimit = Log.ERROR;
         _configLastRead = 0;
-        _location = context.getProperty(CONFIG_LOCATION_PROP, CONFIG_LOCATION_DEFAULT);
         _context = context;
         _log = getLog(LogManager.class);
+        String location = context.getProperty(CONFIG_LOCATION_PROP, CONFIG_LOCATION_DEFAULT);
+        setConfig(location);
         _consoleBuffer = new LogConsoleBuffer(context);
-        loadConfig();
         _writer = new LogWriter(this);
         Thread t = new I2PThread(_writer);
         t.setName("LogWriter");
@@ -196,8 +196,9 @@ public class LogManager {
     }
 
     public void setConfig(String filename) {
-        _log.debug("Config filename set to " + filename);
-        _location = filename;
+        _locationFile = new File(filename);
+        if (!_locationFile.isAbsolute())
+            _locationFile = new File(_context.getConfigDir(), filename);
         loadConfig();
     }
 
@@ -232,20 +233,12 @@ public class LogManager {
         loadConfig();
     }
 
-    ///
-    ///
-
-    //
-    //
-    //
-
     private void loadConfig() {
-        File cfgFile = new File(_location);
+        File cfgFile = _locationFile;
         if (!cfgFile.exists()) {
             if (!_alreadyNoticedMissingConfig) {
                 if (_log.shouldLog(Log.WARN))
-                    _log.warn("Log file " + _location + " does not exist");
-                //System.err.println("Log file " + _location + " does not exist");
+                    _log.warn("Log file " + _locationFile.getAbsolutePath() + " does not exist");
                 _alreadyNoticedMissingConfig = true;
             }
             parseConfig(new Properties());
@@ -262,9 +255,6 @@ public class LogManager {
             return;
         }
 
-        if (_log.shouldLog(Log.DEBUG))
-            _log.debug("Loading config from " + _location);
-
         Properties p = new Properties();
         FileInputStream fis = null;
         try {
@@ -272,7 +262,7 @@ public class LogManager {
             p.load(fis);
             _configLastRead = _context.clock().now();
         } catch (IOException ioe) {
-            System.err.println("Error loading logger config from " + new File(_location).getAbsolutePath());
+            System.err.println("Error loading logger config from " + cfgFile.getAbsolutePath());
         } finally {
             if (fis != null) try {
                 fis.close();
@@ -540,7 +530,7 @@ public class LogManager {
         String config = createConfig();
         FileOutputStream fos = null;
         try {
-            fos = new FileOutputStream(_location);
+            fos = new FileOutputStream(_locationFile);
             fos.write(config.getBytes());
             return true;
         } catch (IOException ioe) {
diff --git a/core/java/src/net/i2p/util/LogWriter.java b/core/java/src/net/i2p/util/LogWriter.java
index 29fcff7ccd962fc93bff0b10c76609eec337942d..c9f2cb7562ec6f0d405c7423deae786d8bb41063 100644
--- a/core/java/src/net/i2p/util/LogWriter.java
+++ b/core/java/src/net/i2p/util/LogWriter.java
@@ -15,6 +15,8 @@ import java.io.IOException;
 import java.io.OutputStream;
 import java.util.List;
 
+import net.i2p.I2PAppContext;
+
 /**
  * Log writer thread that pulls log records from the LogManager, writes them to
  * the current logfile, and rotates the logs as necessary.  This also periodically
@@ -22,6 +24,7 @@ import java.util.List;
  *
  */
 class LogWriter implements Runnable {
+    /** every 10 seconds? why? Just have the gui force a reread after a change?? */
     private final static long CONFIG_READ_ITERVAL = 10 * 1000;
     private long _lastReadConfig = 0;
     private long _numBytesInCurrentFile = 0;
@@ -38,6 +41,7 @@ class LogWriter implements Runnable {
 
     public LogWriter(LogManager manager) {
         _manager = manager;
+        _lastReadConfig = Clock.getInstance().now();
     }
 
     public void stopWriting() {
@@ -168,15 +172,21 @@ class LogWriter implements Runnable {
      *
      */
     private File getNextFile(String pattern) {
-        File f = null;
+        File f = new File(pattern);
+        File base = null;
+        if (!f.isAbsolute())
+            base = I2PAppContext.getGlobalContext().getLogDir();
 
         if ( (pattern.indexOf('#') < 0) && (pattern.indexOf('@') <= 0) ) {
-            return new File(pattern);
+            if (base != null)
+                return new File(base, pattern);
+            else
+                return f;
         }
         
         int max = _manager.getRotationLimit();
         if (_rotationNum == -1) {
-            return getFirstFile(pattern, max);
+            return getFirstFile(base, pattern, max);
         }
              
         // we're in rotation, just go to the next  
@@ -190,9 +200,13 @@ class LogWriter implements Runnable {
      * Retrieve the first file, updating the rotation number accordingly
      *
      */
-    private File getFirstFile(String pattern, int max) {
+    private File getFirstFile(File base, String pattern, int max) {
         for (int i = 0; i < max; i++) {
-            File f = new File(replace(pattern, i));
+            File f;
+            if (base != null)
+                f = new File(base, replace(pattern, i));
+            else
+                f = new File(replace(pattern, i));
             if (!f.exists()) {
                 _rotationNum = i;
                 return f;
@@ -202,7 +216,11 @@ class LogWriter implements Runnable {
         // all exist, pick the oldest to replace
         File oldest = null;
         for (int i = 0; i < max; i++) {
-            File f = new File(replace(pattern, i));
+            File f;
+            if (base != null)
+                f = new File(base, replace(pattern, i));
+            else
+                f = new File(replace(pattern, i));
             if (oldest == null) {
                 oldest = f;
             } else {
diff --git a/core/java/src/net/i2p/util/NativeBigInteger.java b/core/java/src/net/i2p/util/NativeBigInteger.java
index a6d7e97925e9e4e3151f8a671a393b64821453c5..bafaa5e5c9dae80e96da8ce967cbcec1bf9db7c0 100644
--- a/core/java/src/net/i2p/util/NativeBigInteger.java
+++ b/core/java/src/net/i2p/util/NativeBigInteger.java
@@ -540,9 +540,10 @@ public class NativeBigInteger extends BigInteger {
         FileOutputStream fos = null;
         try {
             InputStream libStream = resource.openStream();
-            outFile = new File(_libPrefix + "jbigi" + _libSuffix);
+            outFile = new File(I2PAppContext.getGlobalContext().getBaseDir(), _libPrefix + "jbigi" + _libSuffix);
             fos = new FileOutputStream(outFile);
-            byte buf[] = new byte[4096*1024];
+            // wtf this was 4096*1024 which is really excessive for a roughly 50KB file
+            byte buf[] = new byte[4096];
             while (true) {
                 int read = libStream.read(buf);
                 if (read < 0) break;
diff --git a/core/java/src/net/i2p/util/RandomSource.java b/core/java/src/net/i2p/util/RandomSource.java
index 550b37b8d2f092df5dc74c6494e63bf7f21245c6..c6ac80a65ca63f6410b178098d2933928e2c3a7a 100644
--- a/core/java/src/net/i2p/util/RandomSource.java
+++ b/core/java/src/net/i2p/util/RandomSource.java
@@ -142,7 +142,7 @@ public class RandomSource extends SecureRandom implements EntropyHarvester {
     private static final String SEEDFILE = "prngseed.rnd";
     
     public static final void writeSeed(byte buf[]) {
-        File f = new File(SEEDFILE);
+        File f = new File(I2PAppContext.getGlobalContext().getConfigDir(), SEEDFILE);
         FileOutputStream fos = null;
         try {
             fos = new FileOutputStream(f);
@@ -164,7 +164,7 @@ public class RandomSource extends SecureRandom implements EntropyHarvester {
     }
     
     private static final boolean seedFromFile(String filename, byte buf[]) {
-        File f = new File(filename);
+        File f = new File(I2PAppContext.getGlobalContext().getConfigDir(), filename);
         if (f.exists()) {
             FileInputStream fis = null;
             try {
diff --git a/core/java/src/net/i2p/util/WorkingDir.java b/core/java/src/net/i2p/util/WorkingDir.java
new file mode 100644
index 0000000000000000000000000000000000000000..4e47f6458c3c6b12ee9d9b07edf420b0c9378706
--- /dev/null
+++ b/core/java/src/net/i2p/util/WorkingDir.java
@@ -0,0 +1,481 @@
+package net.i2p.util;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.Properties;
+
+import net.i2p.data.DataHelper;
+
+/**
+ * Get a working directory for i2p.
+ *
+ * For the location, first try the system property i2p.dir.config
+ * Next try $HOME/.i2p on linux or %APPDATA%\I2P on Windows.
+ *
+ * If the dir exists, return it.
+ * Otherwise, attempt to create it, and copy files from the base directory.
+ * To successfully copy, the base install dir must be the system property i2p.dir.base
+ * or else must be in $CWD.
+ *
+ * If I2P was run from the install directory in the past,
+ * and migrateOldData = true, copy the
+ * necessary data files (except i2psnark/) over to the new working directory.
+ *
+ * Otherwise, just copy over a limited number of files over.
+ *
+ * Do not ever copy or move the old i2psnark/ directory, as if the
+ * old and new locations are on different file systems, this could
+ * be quite slow.
+ *
+ * Modify some files while copying, see methods below.
+ *
+ * After migration, the router will run using the new directory.
+ * The wrapper, however, must be stopped and restarted from the new script - until then,
+ * it will continue to write to wrapper.log* in the old directory.
+ *
+ * @param whether to copy all data over from an existing install
+ */
+public class WorkingDir {
+
+    private final static String PROP_BASE_DIR = "i2p.dir.base";
+    private final static String PROP_WORKING_DIR = "i2p.dir.config";
+    private final static String WORKING_DIR_DEFAULT_WINDOWS = "I2P";
+    private final static String WORKING_DIR_DEFAULT = ".i2p";
+
+    /**
+     * Only call this once on router invocation.
+     * Caller should store the return value for future reference.
+     */
+    public static String getWorkingDir(boolean migrateOldData) {
+        String dir = System.getProperty(PROP_WORKING_DIR);
+        boolean isWindows = System.getProperty("os.name").startsWith("Win");
+        File dirf = null;
+        if (dir != null) {
+            dirf = new File(dir);
+        } else {
+            String home = System.getProperty("user.home");
+            if (isWindows) {
+                String appdata = System.getenv("APPDATA");
+                if (appdata != null)
+                    home = appdata;
+                dirf = new File(home, WORKING_DIR_DEFAULT_WINDOWS);
+            } else {
+                dirf = new File(home, WORKING_DIR_DEFAULT);
+            }
+        }
+        // where we are now
+        String cwd = System.getProperty(PROP_BASE_DIR);
+        if (cwd == null)
+            cwd = System.getProperty("user.dir");
+        // where we want to go
+        String rv = dirf.getAbsolutePath();
+        if (dirf.exists()) {
+            if (dirf.isDirectory())
+                return rv; // all is good, we found the user directory
+            System.err.println("Wanted to use " + rv + " for a working directory but it is not a directory");
+            return cwd;
+        }
+        if (!dirf.mkdir()) {
+            System.err.println("Wanted to use " + rv + " for a working directory but could not create it");
+            return cwd;
+        }
+
+        // Check for a hosts.txt file, if it exists then I2P is there
+        File oldDirf = new File(cwd);
+        File test = new File(oldDirf, "hosts.txt");
+        if (!test.exists()) {
+            System.err.println("ERROR - Cannot find I2P installation in " + cwd);
+            return cwd;
+        }
+
+        // Check for a router.keys file, if it exists it's an old install,
+        // and only migrate the data files if told to do so
+        test = new File(oldDirf, "router.keys");
+        boolean oldInstall = test.exists();
+        migrateOldData &= oldInstall;
+
+        // Do the copying
+        if (migrateOldData)
+            System.err.println("Migrating data files to new user directory " + rv);
+        else
+            System.err.println("Setting up new user directory " + rv);
+        boolean success = migrate(MIGRATE_BASE, oldDirf, dirf);
+        // this one must be after MIGRATE_BASE
+        success &= migrateJettyXml(oldDirf, dirf);
+        success &= migrateWrapperConfig(oldDirf, dirf);
+        if (migrateOldData) {
+            success &= migrate(MIGRATE_DATA, oldDirf, dirf);
+            success &= migrateI2PTunnelKeys(oldDirf, dirf);
+            success &= migrateSnark(oldDirf, dirf);
+            // new installs will have updated scripts left in the install dir
+            // don't bother migrating runplain.sh or i2prouter.bat
+            if (!isWindows)
+                success &= migrateI2prouter(oldDirf, dirf);
+        } else if (!oldInstall) {
+            // copy the default i2psnark.config over
+            success &= migrate("i2psnark.config", oldDirf, dirf);
+        }
+
+        // Report success or failure
+        if (success) {
+            System.err.println("Successfully copied data files to new user directory " + rv);
+            if (migrateOldData) {
+                System.err.println("Libraries and other files remain in the old directory " + cwd + ", do not remove them.");
+                System.err.println("You should manually move any non-standard files, such as additional eepsite directories and key files");
+                System.err.println("After verifying that all is working, you may delete the following data files and directories in " +
+                           cwd + ": " + MIGRATE_DATA.replace(',', ' ') + " i2psnark.config tmp work");
+                if (System.getProperty("wrapper.version") != null)
+                    System.err.println("Note that until you shutdown your router completely and restart, the wrapper will continue" +
+                               " to log to the old wrapper logs in " + cwd);
+                if (!isWindows)
+                    System.err.println("From now on, you should now use the i2prouter" +
+                               " script in the " + rv + " directory to start i2p." +
+                               " You may copy or move this script elsewhere, you need not run it from that directory.");
+            }
+            return rv;
+        } else {
+            System.err.println("FAILED copy of some or all data files to new directory " + rv);
+            System.err.println("Check logs for details");
+            System.err.println("Continung to use data files in old directory " + cwd);
+            return cwd;
+        }
+    }
+
+    /**
+     * files and directories from the base install to copy over
+     * None of these should be included in i2pupdate.zip
+     *
+     * The user should not delete these in the old location, leave them as templates for new users
+     */
+    private static final String MIGRATE_BASE =
+        // base install - dirs
+        // We don't currently have a default addressbook/ in the base distribution,
+        // but distros might put one in
+        "addressbook,eepsite," +
+        // base install - files
+        // We don't currently have a default router.config or logger.config in the base distribution,
+        // but distros might put one in
+        "blocklist.txt,clients.config,hosts.txt,i2ptunnel.config,jetty-i2psnark.xml," +
+        "logger.config,router.config,systray.config";
+
+    /**
+     * files and directories from an old single-directory installation to copy over  - NOT including snark
+     * None of these should be included in i2pupdate.zip
+     *
+     * The user can be advised to delete these from the old location
+     */
+    private static final String MIGRATE_DATA =
+        // post install - dirs
+        // not required to copy - tmp/, work/
+        // addressbook included in MIGRATE_BASE above
+        "keyBackup,logs,netDb,peerProfiles," +
+        // post install - files
+        // not required to copy - prngseed.rnd
+        // logger.config and router.config included in MIGRATE_BASE above
+        "bob.config,privatehosts.txt,router.info,router.keys," +
+        "sam.keys,susimail.config,userhosts.txt,webapps.config," +
+        "wrapper.log,wrapper.log.1,wrapper.log.2";
+
+    private static boolean migrate(String list, File olddir, File todir) {
+        boolean rv = true;
+        String files[] = list.split(",");
+        for (int i = 0; i < files.length; i++) {
+            File from = new File(olddir, files[i]);
+            if (!copy(from, todir)) {
+                System.err.println("Error copying " + from.getAbsolutePath());
+                rv = false;
+            }
+        }
+        return rv;
+    }
+
+    /**
+     *  Copy over the i2psnark.config file with modifications
+     */
+    private static boolean migrateSnark(File olddir, File todir) {
+        boolean rv = true;
+        File oldSnark = new File(olddir, "i2psnark");
+        File newSnark = new File(todir, "i2psnark");
+        File oldSnarkConfig = new File(olddir, "i2psnark.config");
+        File newSnarkConfig = new File(todir, "i2psnark.config");
+        boolean hasData = oldSnark.exists();
+        if (hasData) {
+            File children[] = oldSnark.listFiles();
+            hasData = children != null && children.length > 0;
+        }
+        if (oldSnarkConfig.exists()) {
+            if (hasData) {
+                // edit the snark config file to point to the old location, we aren't moving the data
+                try {
+                    Properties props = new Properties();
+                    DataHelper.loadProps(props, oldSnarkConfig);
+                    String dir = props.getProperty("i2psnark.dir");
+                    if (dir == null)
+                        dir = "i2psnark";
+                    // change relative to absolute path
+                    File f = new File(dir);
+                    props.setProperty("i2psnark.dir", f.getAbsolutePath());
+                    DataHelper.storeProps(props, newSnarkConfig);
+                    System.err.println("Copied i2psnark.config with modifications");
+                } catch (IOException ioe) {
+                    System.err.println("FAILED copy i2psnark.config");
+                    rv = false;
+                }
+            } else {
+                // copy the i2psnark config file over
+                copy(newSnarkConfig, todir);
+                System.err.println("Copied i2psnark.config");
+            }
+        } else {
+            if (hasData) {
+                // data but no previous config file (unlikely) - make new config file
+                try {
+                    Properties props = new Properties();
+                    File f = new File("i2psnark");
+                    props.setProperty("i2psnark.dir", f.getAbsolutePath());
+                    DataHelper.storeProps(props, newSnarkConfig);
+                } catch (IOException ioe) {
+                    // ignore
+                }
+            } // else no config and no data
+        }
+        if (hasData) {
+          /*************
+            // crude attempt to detect same filesystem
+            if ((oldSnark.getAbsolutePath().startsWith("/home/") && newSnark.getAbsolutePath().startsWith("/home/")) ||
+                (System.getProperty("os.name").toLowerCase.indexOf("windows") >= 0 &&
+                 oldSnark.getAbsolutePath().substring(0,1).equals(newSnark.getAbsolutePath().substring(0,1) &&
+                 oldSnark.getAbsolutePath().substring(1,2).equals(":\\") &&
+                 newSnark.getAbsolutePath().substring(1,2).equals(":\\"))) {
+                 // OK, apparently in same file system
+                 // move everything
+            }
+          **************/
+            System.err.println("NOT moving the i2psnark data directory " + oldSnark.getAbsolutePath() +
+                   " to the new directory " + newSnark.getAbsolutePath() +
+                   ". You may move the directory contents manually WHILE I2P IS NOT RUNNING," +
+                   " and edit the file " + newSnarkConfig.getAbsolutePath() +
+                   " to configure i2psnark to use a different location by editing the i2psnark.dir configuration to be" +
+                   " i2psnark.dir=" + oldSnark.getAbsolutePath() +
+                   " and restart, or you may leave the i2psnark directory in its old location.");
+        }
+        return true;
+    }
+
+    /**
+     *  Copy over the i2prouter file with modifications
+     *  The resulting script can be run from any location.
+     */
+    private static boolean migrateI2prouter(File olddir, File todir) {
+        File oldFile = new File(olddir, "i2prouter");
+        File newFile = new File(todir, "i2prouter");
+        FileInputStream in = null;
+        PrintWriter out = null;
+        try {
+            in = new FileInputStream(oldFile);
+            out = new PrintWriter(new BufferedWriter(new FileWriter(newFile)));
+            boolean firstTime = true;
+            String s = null;
+            while ((s = DataHelper.readLine(in)) != null) {
+                if (s.equals("WRAPPER_CMD=\"./i2psvc\"")) {
+                    // i2psvc in the old location
+                    File f = new File("i2psvc");
+                    s = "WRAPPER_CMD=\"" + f.getAbsolutePath() + "\"";
+                } else if(s.equals("WRAPPER_CONF=\"wrapper.config\"")) {
+                    // wrapper.config the new location
+                    File f = new File(todir, "wrapper.config");
+                    s = "WRAPPER_CONF=\"" + f.getAbsolutePath() + "\"";
+                } else if(s.equals("PIDDIR=\".\"")) {
+                    // i2p.pid in the new location
+                    s = "PIDDIR=\"" + todir.getAbsolutePath() + "\"";
+                }
+                out.println(s);
+                if (firstTime) {
+                    // first line was #!/bin/sh, so had to wait until now
+                    out.println("# Modified by I2P User dir migration script");
+                    firstTime = false;
+                }            
+            }            
+            System.err.println("Copied i2prouter with modifications");
+            return true;
+        } catch (IOException ioe) {
+            if (in != null) {
+                System.err.println("FAILED copy i2prouter");
+                return false;
+            }
+            return true;
+        } finally {
+            if (in != null) try { in.close(); } catch (IOException ioe) {}
+            if (out != null) out.close();
+        }
+    }
+
+    /**
+     *  Copy over the wrapper.config file with modifications
+     */
+    private static boolean migrateWrapperConfig(File olddir, File todir) {
+        File oldFile = new File(olddir, "wrapper.config");
+        File newFile = new File(todir, "wrapper.config");
+        FileInputStream in = null;
+        PrintWriter out = null;
+        try {
+            in = new FileInputStream(oldFile);
+            out = new PrintWriter(new BufferedWriter(new FileWriter(newFile)));
+            out.println("# Modified by I2P User dir migration script");
+            String s = null;
+            // Don't use replaceFirst because backslashes in the replacement string leads to havoc
+            // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4689750
+            // "Note that backslashes and dollar signs in the replacement string may cause the results
+            // to be different than if it were being treated as a literal replacement string.
+            // Dollar signs may be treated as references to captured subsequences as described above,
+            // and backslashes are used to escape literal characters in the replacement string."
+            while ((s = DataHelper.readLine(in)) != null) {
+                if (s.startsWith("wrapper.java.classpath.")) {
+                    // libraries in the old location
+                    s = s.replace("=lib/", '=' + olddir.getAbsolutePath() + File.separatorChar + "lib" + File.separatorChar);
+                } else if (s.startsWith("wrapper.java.library.path.")) {
+                    // libraries in the old location
+                    if (s.contains("=."))
+                        s = s.replace("=.", '=' + olddir.getAbsolutePath());
+                    else if (s.contains("=lib"))
+                        s = s.replace("=lib", '=' + olddir.getAbsolutePath() + File.separatorChar + "lib");
+                } else if (s.startsWith("wrapper.logfile=wrapper.log")) {
+                    // wrapper logs in the new location
+                    s = s.replace("=", '=' + todir.getAbsolutePath() + File.separatorChar);
+                } else if (s.startsWith("wrapper.pidfile=i2p.pid")) {
+                    // i2p.pid in the new location
+                    s = s.replace("=", '=' + todir.getAbsolutePath() + File.separatorChar);
+                }
+                out.println(s);
+            }
+            System.err.println("Copied wrapper.config with modifications");
+            return true;
+        } catch (IOException ioe) {
+            if (in != null) {
+                System.err.println("FAILED copy wrapper.config");
+                return false;
+            }
+            return false;
+        } finally {
+            if (in != null) try { in.close(); } catch (IOException ioe) {}
+            if (out != null) out.close();
+        }
+    }
+
+    /**
+     *  Copy over the jetty.xml file with modifications
+     *  It was already copied over once in migrate(), throw that out and
+     *  do it again with modifications.
+     */
+    private static boolean migrateJettyXml(File olddir, File todir) {
+        File eepsite1 = new File(olddir, "eepsite");
+        File oldFile = new File(eepsite1, "jetty.xml");
+        File eepsite2 = new File(todir, "eepsite");
+        File newFile = new File(eepsite2, "jetty.xml");
+        FileInputStream in = null;
+        PrintWriter out = null;
+        try {
+            in = new FileInputStream(oldFile);
+            out = new PrintWriter(new BufferedWriter(new FileWriter(newFile)));
+            String s = null;
+            while ((s = DataHelper.readLine(in)) != null) {
+                if (s.indexOf("./eepsite/") >= 0) {
+                    s = s.replace("./eepsite/", todir.getAbsolutePath() + File.separatorChar + "eepsite" + File.separatorChar);
+                }
+                out.println(s);
+            }
+            out.println("<!-- Modified by I2P User dir migration script -->");
+            System.err.println("Copied jetty.xml with modifications");
+            return true;
+        } catch (IOException ioe) {
+            if (in != null) {
+                System.err.println("FAILED copy jetty.xml");
+                return false;
+            }
+            return false;
+        } finally {
+            if (in != null) try { in.close(); } catch (IOException ioe) {}
+            if (out != null) out.close();
+        }
+    }
+
+    /**
+     * Relatively recent default i2ptunnel key file name
+     */
+    private static boolean migrateI2PTunnelKeys(File olddir, File todir) {
+        for (int i = 0; i < 100; i++) {
+            copy(new File(olddir, "i2ptunnel" + i + "-privKeys.dat"), todir);
+        }
+        return true;
+    }
+
+    /**
+     * Recursive copy a file or dir to a dir
+     * 
+     * @param src file or directory, need not exist
+     * @param target the directory to copy to, will be created if it doesn't exist
+     * @return true for success OR if src does not exist
+     */
+    public static final boolean copy(File src, File targetDir) {
+        if (!src.exists())
+            return true;
+        if (!targetDir.exists()) {
+            if (!targetDir.mkdir()) {
+                System.err.println("FAILED copy " + src.getPath());
+                return false;
+            }
+        }
+        File targetFile = new File(targetDir, src.getName());
+        if (!src.isDirectory())
+            return copyFile(src, targetFile);
+        File children[] = src.listFiles();
+        if (children == null) {
+            System.err.println("FAILED copy " + src.getPath());
+            return false;
+        }
+        boolean rv = true;
+        for (int i = 0; i < children.length; i++) {
+            rv &= copy(children[i], targetFile);
+        }
+        return rv;
+    }
+    
+    /**
+     * @param src not a directory, must exist
+     * @param dest not a directory, will be overwritten if existing
+     * @@reurn true if it was copied successfully
+     */
+    public static boolean copyFile(File src, File dst) {
+        if (!src.exists()) return false;
+        boolean rv = true;
+
+        byte buf[] = new byte[4096];
+        FileInputStream in = null;
+        FileOutputStream out = null;
+        try {
+            in = new FileInputStream(src);
+            out = new FileOutputStream(dst);
+            
+            int read = 0;
+            while ( (read = in.read(buf)) != -1)
+                out.write(buf, 0, read);
+            
+            System.err.println("Copied " + src.getPath());
+        } catch (IOException ioe) {
+            System.err.println("FAILED copy " + src.getPath());
+            rv = false;
+        } finally {
+            if (in != null) try { in.close(); } catch (IOException ioe) {}
+            if (out != null) try { out.close(); } catch (IOException ioe) {}
+        }
+        if (rv)
+            dst.setLastModified(src.lastModified());
+        return rv;
+    }
+}
diff --git a/router/java/src/net/i2p/router/Blocklist.java b/router/java/src/net/i2p/router/Blocklist.java
index 7546ac4a713494b9b61de6da9efeda60996bb70a..6195d6a76914dbccea1f601afb8a289de1b1e939 100644
--- a/router/java/src/net/i2p/router/Blocklist.java
+++ b/router/java/src/net/i2p/router/Blocklist.java
@@ -166,6 +166,8 @@ public class Blocklist {
     */
     private void readBlocklistFile(String file) {
         File BLFile = new File(file);
+        if (!BLFile.isAbsolute())
+            BLFile = new File(_context.getConfigDir(), file);
         if (BLFile == null || (!BLFile.exists()) || BLFile.length() <= 0) {
             if (_log.shouldLog(Log.WARN))
                 _log.warn("Blocklist file not found: " + file);
@@ -701,6 +703,8 @@ public class Blocklist {
     private synchronized void shitlistForever(Hash peer) {
         String file = _context.getProperty(PROP_BLOCKLIST_FILE, BLOCKLIST_FILE_DEFAULT);
         File BLFile = new File(file);
+        if (!BLFile.isAbsolute())
+            BLFile = new File(_context.getConfigDir(), file);
         if (BLFile == null || (!BLFile.exists()) || BLFile.length() <= 0) {
             if (_log.shouldLog(Log.ERROR))
                 _log.error("Blocklist file not found: " + file);
diff --git a/router/java/src/net/i2p/router/KeyManager.java b/router/java/src/net/i2p/router/KeyManager.java
index 9fc62a70f29a0213f450a8109faa26d13cd80e3d..711759a408deab886f51d59426efc6a93352729a 100644
--- a/router/java/src/net/i2p/router/KeyManager.java
+++ b/router/java/src/net/i2p/router/KeyManager.java
@@ -147,10 +147,8 @@ public class KeyManager {
             super(KeyManager.this._context);
         }
         public void runJob() {
-            String keyDir = getContext().getProperty(PROP_KEYDIR);
-            if (keyDir == null) 
-                keyDir = DEFAULT_KEYDIR;
-            File dir = new File(keyDir);
+            String keyDir = getContext().getProperty(PROP_KEYDIR, DEFAULT_KEYDIR);
+            File dir = new File(getContext().getRouterDir(), keyDir);
             if (!dir.exists())
                 dir.mkdirs();
             if (dir.exists() && dir.isDirectory() && dir.canRead() && dir.canWrite()) {
diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java
index 1e8e905528ced8738ee0c6cc3a5c11afd0ffc916..35518feda31186bee210c5bfd426b15cda6c7b9b 100644
--- a/router/java/src/net/i2p/router/Router.java
+++ b/router/java/src/net/i2p/router/Router.java
@@ -44,6 +44,7 @@ import net.i2p.util.I2PThread;
 import net.i2p.util.Log;
 import net.i2p.util.SimpleScheduler;
 import net.i2p.util.SimpleTimer;
+import net.i2p.util.WorkingDir;
 
 /**
  * Main driver for the router.
@@ -53,6 +54,7 @@ public class Router {
     private Log _log;
     private RouterContext _context;
     private final Properties _config;
+    /** full path */
     private String _configFilename;
     private RouterInfo _routerInfo;
     private long _started;
@@ -104,14 +106,6 @@ public class Router {
     public Router(Properties envProps) { this(null, envProps); }
     public Router(String configFilename) { this(configFilename, null); }
     public Router(String configFilename, Properties envProps) {
-        if (!beginMarkingLiveliness(envProps)) {
-            System.err.println("ERROR: There appears to be another router already running!");
-            System.err.println("       Please make sure to shut down old instances before starting up");
-            System.err.println("       a new one.  If you are positive that no other instance is running,");
-            System.err.println("       please delete the file " + getPingFile(envProps));
-            System.exit(-1);
-        }
-
         _gracefulExitCode = -1;
         _config = new Properties();
 
@@ -125,7 +119,35 @@ public class Router {
             _configFilename = configFilename;
         }
                     
+        // we need the user directory figured out by now, so figure it out here rather than
+        // in the RouterContext() constructor.
+        //
+        // Fixme read config file before migration? or before? or both?
+        //
+        // Then add it to envProps (but not _config, we don't want it in the router.config file)
+        // where it will then be available to all via _context.dir()
+        //
+        // This call also migrates all files to the new working directory,
+        // including router.config
+        //
+
+        // Do we copy all the data files to the new directory? default false
+        String migrate = System.getProperty("i2p.dir.migrate");
+        boolean migrateFiles = Boolean.valueOf(migrate).booleanValue();
+        String userDir = WorkingDir.getWorkingDir(migrateFiles);
+
+        // Use the router.config file specified in the router.configLocation property
+        // (default "router.config"),
+        // if it is an abolute path, otherwise look in the userDir returned by getWorkingDir
+        // replace relative path with absolute
+        File cf = new File(_configFilename);
+        if (!cf.isAbsolute()) {
+            cf = new File(userDir, _configFilename);
+            _configFilename = cf.getAbsolutePath();
+        }
+
         readConfig();
+
         if (envProps == null) {
             envProps = _config;
         } else {
@@ -135,11 +157,42 @@ public class Router {
                 envProps.setProperty(k, v);
             }
         }
+
         // This doesn't work, guess it has to be in the static block above?
         // if (Boolean.valueOf(envProps.getProperty("router.disableIPv6")).booleanValue())
         //    System.setProperty("java.net.preferIPv4Stack", "true");
 
+        if (envProps.getProperty("i2p.dir.config") == null)
+            envProps.setProperty("i2p.dir.config", userDir);
+
+        // The important thing that happens here is the directory paths are set and created
+        // i2p.dir.router defaults to i2p.dir.config
+        // i2p.dir.app defaults to i2p.dir.router
+        // i2p.dir.log defaults to i2p.dir.router
+        // i2p.dir.pid defaults to i2p.dir.router
+        // i2p.dir.base defaults to user.dir == $CWD
         _context = new RouterContext(this, envProps);
+
+        // This is here so that we can get the directory location from the context
+        // for the ping file
+        if (!beginMarkingLiveliness()) {
+            System.err.println("ERROR: There appears to be another router already running!");
+            System.err.println("       Please make sure to shut down old instances before starting up");
+            System.err.println("       a new one.  If you are positive that no other instance is running,");
+            System.err.println("       please delete the file " + getPingFile().getAbsolutePath());
+            System.exit(-1);
+        }
+
+        // This is here so that we can get the directory location from the context
+        // for the zip file and the base location to unzip to.
+        // If it does an update, it never returns.
+        // I guess it's better to have the other-router check above this, we don't want to
+        // overwrite an existing running router's jar files. Other than ours.
+        installUpdates();
+
+        // NOW we start all the activity
+        _context.initAll();
+
         _routerInfo = null;
         _higherVersionSeen = false;
         _log = _context.logManager().getLog(Router.class);
@@ -245,6 +298,7 @@ public class Router {
         
         _context.keyManager().startup();
         
+        // why are we reading this again, it's read in the constructor
         readConfig();
         
         setupHandlers();
@@ -285,6 +339,7 @@ public class Router {
         }
     }
     
+    /** this does not use ctx.getConfigDir(), must provide a full path in filename */
     private static Properties getConfig(RouterContext ctx, String filename) {
         Log log = null;
         if (ctx != null) {
@@ -456,11 +511,11 @@ public class Router {
                                                                };
 
     static final String IDENTLOG = "identlog.txt";
-    public static void killKeys() {
+    public void killKeys() {
         new Exception("Clearing identity files").printStackTrace();
         int remCount = 0;
         for (int i = 0; i < _rebuildFiles.length; i++) {
-            File f = new File(_rebuildFiles[i]);
+            File f = new File(_context.getRouterDir(),_rebuildFiles[i]);
             if (f.exists()) {
                 boolean removed = f.delete();
                 if (removed) {
@@ -474,7 +529,7 @@ public class Router {
         if (remCount > 0) {
             FileOutputStream log = null;
             try {
-                log = new FileOutputStream(IDENTLOG, true);
+                log = new FileOutputStream(new File(_context.getRouterDir(), IDENTLOG), true);
                 log.write((new Date() + ": Old router identity keys cleared\n").getBytes());
             } catch (IOException ioe) {
                 // ignore
@@ -814,8 +869,9 @@ public class Router {
         try { _context.messageValidator().shutdown(); } catch (Throwable t) { _log.log(Log.CRIT, "Error shutting down the message validator", t); }
         try { _context.inNetMessagePool().shutdown(); } catch (Throwable t) { _log.log(Log.CRIT, "Error shutting down the inbound net pool", t); }
         //try { _sessionKeyPersistenceHelper.shutdown(); } catch (Throwable t) { _log.log(Log.CRIT, "Error shutting down the session key manager", t); }
+        _context.deleteTempDir();
         RouterContext.listContexts().remove(_context);
-        dumpStats();
+        //dumpStats();
         finalShutdown(exitCode);
     }
 
@@ -833,7 +889,7 @@ public class Router {
                 killKeys();
         }
 
-        File f = new File(getPingFile());
+        File f = getPingFile();
         f.delete();
         if (_killVMOnEnd) {
             try { Thread.sleep(1000); } catch (InterruptedException ie) {}
@@ -1003,8 +1059,9 @@ public class Router {
     
     public static void main(String args[]) {
         System.out.println("Starting I2P " + RouterVersion.FULL_VERSION);
-        installUpdates();
-        verifyWrapperConfig();
+        // installUpdates() moved to constructor so we can get file locations from the context
+        // installUpdates();
+        //verifyWrapperConfig();
         Router r = new Router();
         if ( (args != null) && (args.length == 1) && ("rebuild".equals(args[0])) ) {
             r.rebuildNewIdentity();
@@ -1015,11 +1072,30 @@ public class Router {
     
     public static final String UPDATE_FILE = "i2pupdate.zip";
     
-    private static void installUpdates() {
-        File updateFile = new File(UPDATE_FILE);
-        if (updateFile.exists()) {
+    /**
+     * Unzip update file found in the router dir OR base dir, to the base dir
+     *
+     * If we can't write to the base dir, complain.
+     */
+    private void installUpdates() {
+        File updateFile = new File(_context.getRouterDir(), UPDATE_FILE);
+        boolean exists = updateFile.exists();
+        if (!exists) {
+            updateFile = new File(_context.getBaseDir(), UPDATE_FILE);
+            exists = updateFile.exists();
+        }
+        if (exists) {
+            File test = new File(_context.getBaseDir(), "history.txt");
+            if ((!test.canWrite()) || (!_context.getBaseDir().canWrite())) {
+                String msg = "ERROR: No write permissions on " + _context.getBaseDir() +
+                             " to extract software update file";
+                System.out.println(msg);
+                _log.log(Log.CRIT, msg);
+                // carry on
+                return;
+            }
             System.out.println("INFO: Update file exists [" + UPDATE_FILE + "] - installing");
-            boolean ok = FileUtil.extractZip(updateFile, new File("."));
+            boolean ok = FileUtil.extractZip(updateFile, _context.getBaseDir());
             if (ok)
                 System.out.println("INFO: Update installed");
             else
@@ -1037,6 +1113,7 @@ public class Router {
         }
     }
     
+/*******
     private static void verifyWrapperConfig() {
         File cfgUpdated = new File("wrapper.config.updated");
         if (cfgUpdated.exists()) {
@@ -1046,15 +1123,22 @@ public class Router {
             System.exit(EXIT_HARD);
         }
     }
+*******/
     
+/*
     private static String getPingFile(Properties envProps) {
         if (envProps != null) 
             return envProps.getProperty("router.pingFile", "router.ping");
         else
             return "router.ping";
     }
-    private String getPingFile() {
-        return _context.getProperty("router.pingFile", "router.ping");
+*/
+    private File getPingFile() {
+        String s = _context.getProperty("router.pingFile", "router.ping");
+        File f = new File(s);
+        if (!f.isAbsolute())
+            f = new File(_context.getPIDDir(), s);
+        return f;
     }
     
     static final long LIVELINESS_DELAY = 60*1000;
@@ -1066,9 +1150,8 @@ public class Router {
      * 
      * @return true if the router is the only one running 
      */
-    private boolean beginMarkingLiveliness(Properties envProps) {
-        String filename = getPingFile(envProps);
-        File f = new File(filename);
+    private boolean beginMarkingLiveliness() {
+        File f = getPingFile();
         if (f.exists()) {
             long lastWritten = f.lastModified();
             if (System.currentTimeMillis()-lastWritten > LIVELINESS_DELAY) {
@@ -1376,15 +1459,14 @@ private static class PersistRouterInfoJob extends JobImpl {
         if (_log.shouldLog(Log.DEBUG))
             _log.debug("Persisting updated router info");
 
-        String infoFilename = getContext().getProperty(Router.PROP_INFO_FILENAME);
-        if (infoFilename == null)
-            infoFilename = Router.PROP_INFO_FILENAME_DEFAULT;
+        String infoFilename = getContext().getProperty(PROP_INFO_FILENAME, PROP_INFO_FILENAME_DEFAULT);
+        File infoFile = new File(getContext().getRouterDir(), infoFilename);
 
         RouterInfo info = getContext().router().getRouterInfo();
 
         FileOutputStream fos = null;
         try {
-            fos = new FileOutputStream(infoFilename);
+            fos = new FileOutputStream(infoFile);
             info.writeBytes(fos);
         } catch (DataFormatException dfe) {
             _log.error("Error rebuilding the router information", dfe);
diff --git a/router/java/src/net/i2p/router/RouterContext.java b/router/java/src/net/i2p/router/RouterContext.java
index 719224058b501f5b5eadcccd75b4ab31134617e5..656b1299bd5f06372dcb8309c1e4b7b9f1dbf2ea 100644
--- a/router/java/src/net/i2p/router/RouterContext.java
+++ b/router/java/src/net/i2p/router/RouterContext.java
@@ -70,7 +70,11 @@ public class RouterContext extends I2PAppContext {
     public RouterContext(Router router, Properties envProps) { 
         super(filterProps(envProps));
         _router = router;
-        initAll();
+        // Disabled here so that the router can get a context and get the
+        // directory locations from it, to do an update, without having
+        // to init everything. Caller MUST call initAll() afterwards.
+        // Sorry, this breaks some main() unit tests out there.
+        //initAll();
         _contexts.add(this);
     }
     /**
@@ -86,7 +90,7 @@ public class RouterContext extends I2PAppContext {
             envProps.setProperty("time.disabled", "false");
         return envProps;
     }
-    private void initAll() {
+    public void initAll() {
         //_adminManager = new AdminManager(this);
         if ("false".equals(getProperty("i2p.dummyClientFacade", "false")))
             _clientManagerFacade = new ClientManagerFacadeImpl(this);
diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java b/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java
index 282b67e0cdc0973045144633a4cac6a29ac17914..92124d69010545391858f37bcd2617239cad060c 100644
--- a/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java
+++ b/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java
@@ -410,7 +410,7 @@ class PersistentDataStore extends TransientDataStore {
     
     
     private File getDbDir() throws IOException {
-        File f = new File(_dbDir);
+        File f = new File(_context.getRouterDir(), _dbDir);
         if (!f.exists()) {
             boolean created = f.mkdirs();
             if (!created)
diff --git a/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java b/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java
index 4f5d23611622b5213b2cfc2442308b0f99ea449d..e1f265d8f1499c03f30df5f3b2704a36b077019a 100644
--- a/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java
+++ b/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java
@@ -297,18 +297,8 @@ class ProfilePersistenceHelper {
     
     private File getProfileDir() {
         if (_profileDir == null) {
-            String dir = null;
-            if (_context.router() == null) {
-                dir = _context.getProperty(PROP_PEER_PROFILE_DIR, DEFAULT_PEER_PROFILE_DIR);
-            } else {
-                dir = _context.router().getConfigSetting(PROP_PEER_PROFILE_DIR);
-                if (dir == null) {
-                    _log.info("No peer profile dir specified [" + PROP_PEER_PROFILE_DIR 
-                              + "], using [" + DEFAULT_PEER_PROFILE_DIR + "]");
-                    dir = DEFAULT_PEER_PROFILE_DIR;
-                }
-            }
-            _profileDir = new File(dir);
+            String dir = _context.getProperty(PROP_PEER_PROFILE_DIR, DEFAULT_PEER_PROFILE_DIR);
+            _profileDir = new File(_context.getRouterDir(), dir);
         }
         return _profileDir;
     }
diff --git a/router/java/src/net/i2p/router/startup/ClientAppConfig.java b/router/java/src/net/i2p/router/startup/ClientAppConfig.java
index 316c0b5006760708546cd0705408b47fbb607f5d..f5d48cb1f2dd109612a26463c6e1cfcaaa97096b 100644
--- a/router/java/src/net/i2p/router/startup/ClientAppConfig.java
+++ b/router/java/src/net/i2p/router/startup/ClientAppConfig.java
@@ -43,6 +43,8 @@ public class ClientAppConfig {
         Properties rv = new Properties();
         String clientConfigFile = ctx.getProperty(PROP_CLIENT_CONFIG_FILENAME, DEFAULT_CLIENT_CONFIG_FILENAME);
         File cfgFile = new File(clientConfigFile);
+        if (!cfgFile.isAbsolute())
+            cfgFile = new File(ctx.getConfigDir(), clientConfigFile);
         
         // fall back to use router.config's clientApp.* lines
         if (!cfgFile.exists()) 
@@ -91,9 +93,12 @@ public class ClientAppConfig {
 
     public static void writeClientAppConfig(RouterContext ctx, List apps) {
         String clientConfigFile = ctx.getProperty(PROP_CLIENT_CONFIG_FILENAME, DEFAULT_CLIENT_CONFIG_FILENAME);
+        File cfgFile = new File(clientConfigFile);
+        if (!cfgFile.isAbsolute())
+            cfgFile = new File(ctx.getConfigDir(), clientConfigFile);
         FileOutputStream fos = null;
         try {
-            fos = new FileOutputStream(clientConfigFile);
+            fos = new FileOutputStream(cfgFile);
             StringBuffer buf = new StringBuffer(2048);
             for(int i = 0; i < apps.size(); i++) {
                 ClientAppConfig app = (ClientAppConfig) apps.get(i);
diff --git a/router/java/src/net/i2p/router/startup/CreateRouterInfoJob.java b/router/java/src/net/i2p/router/startup/CreateRouterInfoJob.java
index 92b176c30fd50d66a309ca02c93df308e5afe973..979cefa78f6d470d595581c5013e7cc52b34bd7a 100644
--- a/router/java/src/net/i2p/router/startup/CreateRouterInfoJob.java
+++ b/router/java/src/net/i2p/router/startup/CreateRouterInfoJob.java
@@ -8,6 +8,7 @@ package net.i2p.router.startup;
  *
  */
 
+import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.HashSet;
@@ -76,16 +77,14 @@ public class CreateRouterInfoJob extends JobImpl {
             
             info.sign(signingPrivKey);
             
-            String infoFilename = getContext().router().getConfigSetting(Router.PROP_INFO_FILENAME);
-            if (infoFilename == null)
-                infoFilename = Router.PROP_INFO_FILENAME_DEFAULT;
-            fos1 = new FileOutputStream(infoFilename);
+            String infoFilename = getContext().getProperty(Router.PROP_INFO_FILENAME, Router.PROP_INFO_FILENAME_DEFAULT);
+            File ifile = new File(getContext().getRouterDir(), infoFilename);
+            fos1 = new FileOutputStream(ifile);
             info.writeBytes(fos1);
             
-            String keyFilename = getContext().router().getConfigSetting(Router.PROP_KEYS_FILENAME);
-            if (keyFilename == null)
-                keyFilename = Router.PROP_KEYS_FILENAME_DEFAULT;
-            fos2 = new FileOutputStream(keyFilename);
+            String keyFilename = getContext().getProperty(Router.PROP_KEYS_FILENAME, Router.PROP_KEYS_FILENAME_DEFAULT);
+            File kfile = new File(getContext().getRouterDir(), keyFilename);
+            fos2 = new FileOutputStream(kfile);
             privkey.writeBytes(fos2);
             signingPrivKey.writeBytes(fos2);
             pubkey.writeBytes(fos2);
@@ -96,7 +95,7 @@ public class CreateRouterInfoJob extends JobImpl {
             getContext().keyManager().setPrivateKey(privkey);
             getContext().keyManager().setPublicKey(pubkey);
             
-            _log.info("Router info created and stored at " + infoFilename + " with private keys stored at " + keyFilename + " [" + info + "]");
+            _log.info("Router info created and stored at " + ifile.getAbsolutePath() + " with private keys stored at " + kfile.getAbsolutePath() + " [" + info + "]");
         } catch (DataFormatException dfe) {
             _log.error("Error building the new router information", dfe);
         } catch (IOException ioe) {
diff --git a/router/java/src/net/i2p/router/startup/LoadRouterInfoJob.java b/router/java/src/net/i2p/router/startup/LoadRouterInfoJob.java
index 0374922afdbb8bfcff5222d0335f65e6b3aeae7b..f3428c4474886f08dc6a8b9edca93cf5fcf6f086 100644
--- a/router/java/src/net/i2p/router/startup/LoadRouterInfoJob.java
+++ b/router/java/src/net/i2p/router/startup/LoadRouterInfoJob.java
@@ -51,21 +51,17 @@ public class LoadRouterInfoJob extends JobImpl {
     }
     
     private void loadRouterInfo() {
-        String routerInfoFile = getContext().router().getConfigSetting(Router.PROP_INFO_FILENAME);
-        if (routerInfoFile == null)
-            routerInfoFile = Router.PROP_INFO_FILENAME_DEFAULT;
+        String routerInfoFile = getContext().getProperty(Router.PROP_INFO_FILENAME, Router.PROP_INFO_FILENAME_DEFAULT);
         RouterInfo info = null;
         boolean failedRead = false;
         
         
-        String keyFilename = getContext().router().getConfigSetting(Router.PROP_KEYS_FILENAME);
-        if (keyFilename == null)
-            keyFilename = Router.PROP_KEYS_FILENAME_DEFAULT;
+        String keyFilename = getContext().getProperty(Router.PROP_KEYS_FILENAME, Router.PROP_KEYS_FILENAME_DEFAULT);
         
-        File rif = new File(routerInfoFile);
+        File rif = new File(getContext().getRouterDir(), routerInfoFile);
         if (rif.exists())
             _infoExists = true;
-        File rkf = new File(keyFilename);
+        File rkf = new File(getContext().getRouterDir(), keyFilename);
         if (rkf.exists())
             _keysExist = true;
         
@@ -98,14 +94,14 @@ public class LoadRouterInfoJob extends JobImpl {
             
             _us = info;
         } catch (IOException ioe) {
-            _log.error("Error reading the router info from " + routerInfoFile + " and the keys from " + keyFilename, ioe);
+            _log.error("Error reading the router info from " + rif.getAbsolutePath() + " and the keys from " + rkf.getAbsolutePath(), ioe);
             _us = null;
             rif.delete();
             rkf.delete();
             _infoExists = false;
             _keysExist = false;
         } catch (DataFormatException dfe) {
-            _log.error("Corrupt router info or keys at " + routerInfoFile + " / " + keyFilename, dfe);
+            _log.error("Corrupt router info or keys at " + rif.getAbsolutePath() + " / " + rkf.getAbsolutePath(), dfe);
             _us = null;
             rif.delete();
             rkf.delete();
diff --git a/router/java/src/net/i2p/router/startup/RebuildRouterInfoJob.java b/router/java/src/net/i2p/router/startup/RebuildRouterInfoJob.java
index 967bc7a797bc7e175261b4f6539e5af88e722d87..fda82de8e595e3a84fb3a6999406de0c7995f27a 100644
--- a/router/java/src/net/i2p/router/startup/RebuildRouterInfoJob.java
+++ b/router/java/src/net/i2p/router/startup/RebuildRouterInfoJob.java
@@ -57,18 +57,11 @@ public class RebuildRouterInfoJob extends JobImpl {
     
     public void runJob() {
         _log.debug("Testing to rebuild router info");
-        String infoFile = getContext().router().getConfigSetting(Router.PROP_INFO_FILENAME);
-        if (infoFile == null) {
-            _log.debug("Info filename not configured, defaulting to " + Router.PROP_INFO_FILENAME_DEFAULT);
-            infoFile = Router.PROP_INFO_FILENAME_DEFAULT;
-        }
-        
-        String keyFilename = getContext().router().getConfigSetting(Router.PROP_KEYS_FILENAME);
-        if (keyFilename == null)
-            keyFilename = Router.PROP_KEYS_FILENAME_DEFAULT;
-        File keyFile = new File(keyFilename);
+        String infoFile = getContext().getProperty(Router.PROP_INFO_FILENAME, Router.PROP_INFO_FILENAME_DEFAULT);
+        File info = new File(getContext().getRouterDir(), infoFile);
+        String keyFilename = getContext().getProperty(Router.PROP_KEYS_FILENAME, Router.PROP_KEYS_FILENAME_DEFAULT);
+        File keyFile = new File(getContext().getRouterDir(), keyFilename);
         
-        File info = new File(infoFile);
         if (!info.exists() || !keyFile.exists()) {
             _log.info("Router info file [" + info.getAbsolutePath() + "] or private key file [" + keyFile.getAbsolutePath() + "] deleted, rebuilding");
             rebuildRouterInfo();
@@ -86,14 +79,10 @@ public class RebuildRouterInfoJob extends JobImpl {
         _log.debug("Rebuilding the new router info");
         boolean fullRebuild = false;
         RouterInfo info = null;
-        String infoFilename = getContext().router().getConfigSetting(Router.PROP_INFO_FILENAME);
-        if (infoFilename == null)
-            infoFilename = Router.PROP_INFO_FILENAME_DEFAULT;
-        
-        String keyFilename = getContext().router().getConfigSetting(Router.PROP_KEYS_FILENAME);
-        if (keyFilename == null)
-            keyFilename = Router.PROP_KEYS_FILENAME_DEFAULT;
-        File keyFile = new File(keyFilename);
+        String infoFilename = getContext().getProperty(Router.PROP_INFO_FILENAME, Router.PROP_INFO_FILENAME_DEFAULT);
+        File infoFile = new File(getContext().getRouterDir(), infoFilename);
+        String keyFilename = getContext().getProperty(Router.PROP_KEYS_FILENAME, Router.PROP_KEYS_FILENAME_DEFAULT);
+        File keyFile = new File(getContext().getRouterDir(), keyFilename);
         
         if (keyFile.exists()) {
             // ok, no need to rebuild a brand new identity, just update what we can
@@ -146,7 +135,7 @@ public class RebuildRouterInfoJob extends JobImpl {
             
             FileOutputStream fos = null;
             try {
-                fos = new FileOutputStream(infoFilename);
+                fos = new FileOutputStream(infoFile);
                 info.writeBytes(fos);
             } catch (DataFormatException dfe) {
                 _log.error("Error rebuilding the router information", dfe);
diff --git a/router/java/src/net/i2p/router/transport/GeoIP.java b/router/java/src/net/i2p/router/transport/GeoIP.java
index a7da9fad8617366f3de79763be416abac5efb737..fb4984978d99b1f7f55aa9740e3342cd6e25628d 100644
--- a/router/java/src/net/i2p/router/transport/GeoIP.java
+++ b/router/java/src/net/i2p/router/transport/GeoIP.java
@@ -130,7 +130,8 @@ public class GeoIP {
     *
     */
     private void readCountryFile() {
-        File GeoFile = new File(GEOIP_DIR_DEFAULT, COUNTRY_FILE_DEFAULT);
+        File GeoFile = new File(_context.getBaseDir(), GEOIP_DIR_DEFAULT);
+        GeoFile = new File(GeoFile, COUNTRY_FILE_DEFAULT);
         if (GeoFile == null || (!GeoFile.exists())) {
             if (_log.shouldLog(Log.WARN))
                 _log.warn("Country file not found: " + GeoFile.getAbsolutePath());
@@ -188,7 +189,8 @@ public class GeoIP {
     *
     */
     private String[] readGeoIPFile(Long[] search) {
-        File GeoFile = new File(GEOIP_DIR_DEFAULT, GEOIP_FILE_DEFAULT);
+        File GeoFile = new File(_context.getBaseDir(), GEOIP_DIR_DEFAULT);
+        GeoFile = new File(GeoFile, GEOIP_FILE_DEFAULT);
         if (GeoFile == null || (!GeoFile.exists())) {
             if (_log.shouldLog(Log.WARN))
                 _log.warn("GeoIP file not found: " + GeoFile.getAbsolutePath());