diff --git a/config.js b/config.js
new file mode 100644
index 0000000000000000000000000000000000000000..aa05fbec83bdeea8478d583367fb53a16cd73932
--- /dev/null
+++ b/config.js
@@ -0,0 +1,22 @@
+/*
+ * Samizdat config
+ * 
+ * This is the config for Samizdat as deployed on the https://samizdat.is/ site
+ * 
+ * When deploying Samizdat on your website you will need to create your own config,
+ * using this one as a template.
+ */
+
+// plugins config
+self.SamizdatConfig.plugins["gateway-ipns"] = {
+    // the pubkey of the preconfigured IPNS node
+    ipnsPubkey: 'QmYGVgGGfD5N4Xcc78CcMJ99dKcH6K6myhd4Uenv5yJwiJ'
+}
+self.SamizdatConfig.plugins["gun+ipfs"] = {
+    // the pubkey of the preconfigured Gun user
+    gunPubkey: 'WUK5ylwqqgUorceQRa84qfbBFhk7eNRDUoPbGK05SyE.-yohFhTzWPpDT-UDMuKGgemOUrw_cMMYWpy6plILqrg'
+}
+self.SamizdatConfig.plugins["ipns+ipfs"] = {
+    // the pubkey of the preconfigured Gun user
+    ipnsPubkey: 'QmYGVgGGfD5N4Xcc78CcMJ99dKcH6K6myhd4Uenv5yJwiJ'
+}
diff --git a/plugins/cache.js b/plugins/cache.js
index dc70dc0c83c41cc75de87f55f3804e3c18dd63f4..9975dce11cf0543e07c9edb42ee00f37c56d12ef 100644
--- a/plugins/cache.js
+++ b/plugins/cache.js
@@ -2,129 +2,139 @@
 |* === Stashing plugin using the Cache API                               === *|
 \* ========================================================================= */
 
-/**
- * getting content from cache
- */
-let getContentFromCache = (url) => {
-    console.log('Samizdat: getting from cache!')
-    return caches.open('v1')
-        .then((cache) => {
-            return cache.match(url)
-        })
-        .then((response) => {
-            if (typeof response === 'undefined') {
-                throw new Error('Resource not found in cache');
-            } else {
-                response.headers.forEach(function(v, k){
-                    console.log('+-- Retrieved cached header: ', k, ' :: ', v)
-                });
-                // return the response
-                return response
-            }
-        })
-}
+// no polluting of the global namespace please
+(function () {
 
-/**
- * add resources to cache
- * 
- * implements the stash() Samizdat plugin method
- * 
- * accepts either a Response
- * or a string containing a URL
- * or an Array of string URLs
- */
-let cacheContent = (resource, key) => {
-    return caches.open('v1')
-        .then((cache) => {
-            if (typeof resource === 'string') {
-                // assume URL
-                console.log("(COMMIT_UNKNOWN) caching an URL")
-                return cache.add(resource)
-            } else if (Array.isArray(resource)) {
-                // assume array of URLs
-                console.log("(COMMIT_UNKNOWN) caching an Array of URLs")
-                return cache.addAll(resource)
-            } else {
-                // assume a Response
-                // which means we either have a Request in key, a string URL in key,
-                // or we can use the URL in resource.url
-                if ( (typeof key !== 'object') && ( (typeof key !== 'string') || (key === '') ) ) {
-                    if (typeof resource.url !== 'string' || resource.url === '') {
-                        throw new Error('No URL to work with!')
+    /*
+     * this plugin has no config settings
+     */
+    
+    /**
+     * getting content from cache
+     */
+    let getContentFromCache = (url) => {
+        console.log('Samizdat: getting from cache!')
+        return caches.open('v1')
+            .then((cache) => {
+                return cache.match(url)
+            })
+            .then((response) => {
+                if (typeof response === 'undefined') {
+                    throw new Error('Resource not found in cache');
+                } else {
+                    response.headers.forEach(function(v, k){
+                        console.log('+-- Retrieved cached header: ', k, ' :: ', v)
+                    });
+                    // return the response
+                    return response
+                }
+            })
+    }
+
+    /**
+     * add resources to cache
+     * 
+     * implements the stash() Samizdat plugin method
+     * 
+     * accepts either a Response
+     * or a string containing a URL
+     * or an Array of string URLs
+     */
+    let cacheContent = (resource, key) => {
+        return caches.open('v1')
+            .then((cache) => {
+                if (typeof resource === 'string') {
+                    // assume URL
+                    console.log("(COMMIT_UNKNOWN) caching an URL")
+                    return cache.add(resource)
+                } else if (Array.isArray(resource)) {
+                    // assume array of URLs
+                    console.log("(COMMIT_UNKNOWN) caching an Array of URLs")
+                    return cache.addAll(resource)
+                } else {
+                    // assume a Response
+                    // which means we either have a Request in key, a string URL in key,
+                    // or we can use the URL in resource.url
+                    if ( (typeof key !== 'object') && ( (typeof key !== 'string') || (key === '') ) ) {
+                        if (typeof resource.url !== 'string' || resource.url === '') {
+                            throw new Error('No URL to work with!')
+                        }
+                        key = resource.url
                     }
-                    key = resource.url
+                
+                    // we need to create a new Response object
+                    // with all the headers added explicitly
+                    // otherwise the x-samizdat-* headers get ignored
+                    var init = {
+                        status:     resource.status,
+                        statusText: resource.statusText,
+                        headers: {}
+                    };
+                    resource.headers.forEach(function(val, header){
+                        init.headers[header] = val;
+                    });
+                    return resource
+                            .blob()
+                            .then((blob) => {
+                                console.log("(COMMIT_UNKNOWN) caching a Response to: " + key)
+                                return cache.put(key, new Response(
+                                    blob,
+                                    init
+                                ))
+                            })
                 }
-            
-                // we need to create a new Response object
-                // with all the headers added explicitly
-                // otherwise the x-samizdat-* headers get ignored
-                var init = {
-                    status:     resource.status,
-                    statusText: resource.statusText,
-                    headers: {}
-                };
-                resource.headers.forEach(function(val, header){
-                    init.headers[header] = val;
-                });
-                return resource
-                        .blob()
-                        .then((blob) => {
-                            console.log("(COMMIT_UNKNOWN) caching a Response to: " + key)
-                            return cache.put(key, new Response(
-                                blob,
-                                init
-                            ))
+            })
+    }
+
+    /**
+     * remove resources from cache
+     * 
+     * implements the unstash() Samizdat plugin method 
+     * 
+     * accepts either a Response
+     * or a string containing a URL
+     * or an Array of string URLs
+     */
+    let clearCachedContent = (resource) => {
+        return caches.open('v1')
+            .then((cache) => {
+                if (typeof resource === 'string') {
+                    // assume URL
+                    console.log("(COMMIT_UNKNOWN) deleting a cached URL")
+                    return cache.delete(resource)
+                } else if (Array.isArray(resource)) {
+                    // assume array of URLs
+                    console.log("(COMMIT_UNKNOWN) deleting an Array of cached URLs")
+                    return Promise.all(
+                        resource.map((res)=>{
+                            return cache.delete(res)
                         })
-            }
-        })
-}
+                    )
+                } else {
+                    // assume a Response
+                    // which means we have an URL in resource.url
+                    console.log("(COMMIT_UNKNOWN) removing a Response from cache: " + resource.url)
+                    return cache.delete(resource.url)
+                }
+            })
+    }
 
-/**
- * remove resources from cache
- * 
- * implements the unstash() Samizdat plugin method 
- * 
- * accepts either a Response
- * or a string containing a URL
- * or an Array of string URLs
- */
-let clearCachedContent = (resource) => {
-    return caches.open('v1')
-        .then((cache) => {
-            if (typeof resource === 'string') {
-                // assume URL
-                console.log("(COMMIT_UNKNOWN) deleting a cached URL")
-                return cache.delete(resource)
-            } else if (Array.isArray(resource)) {
-                // assume array of URLs
-                console.log("(COMMIT_UNKNOWN) deleting an Array of cached URLs")
-                return Promise.all(
-                    resource.map((res)=>{
-                        return cache.delete(res)
-                    })
-                )
-            } else {
-                // assume a Response
-                // which means we have an URL in resource.url
-                console.log("(COMMIT_UNKNOWN) removing a Response from cache: " + resource.url)
-                return cache.delete(resource.url)
-            }
-        })
-}
+    
+    // initialize the SamizdatPlugins array
+    if (!Array.isArray(self.SamizdatPlugins)) {
+        self.SamizdatPlugins = new Array()
+    }
 
- 
-// initialize the SamizdatPlugins array
-if (!Array.isArray(self.SamizdatPlugins)) {
-    self.SamizdatPlugins = new Array()
-}
+    // and add ourselves to it
+    // with some additional metadata
+    self.SamizdatPlugins.push({
+        name: 'cache',
+        description: 'Locally cached responses, using the Cache API.',
+        version: 'COMMIT_UNKNOWN',
+        fetch: getContentFromCache,
+        stash: cacheContent,
+        unstash: clearCachedContent
+    })
 
-// and add ourselves to it
-// with some additional metadata
-self.SamizdatPlugins.push({
-    name: 'cache',
-    description: 'Locally cached responses, using the Cache API.',
-    version: 'COMMIT_UNKNOWN',
-    fetch: getContentFromCache,
-    stash: cacheContent,
-    unstash: clearCachedContent
-})
+// done with not poluting the global namespace
+})()
diff --git a/plugins/fetch.js b/plugins/fetch.js
index 79212157ad8394ddf53271db16767895c7ce80e2..ac4c1946eda5aef2b05900b9573d6ae0735da312 100644
--- a/plugins/fetch.js
+++ b/plugins/fetch.js
@@ -6,59 +6,69 @@
  * this plugin does not implement any push method
  */
 
-/**
- * getting content using regular HTTP(S) fetch()
- */
-let fetchContent = (url) => {
-    console.log('Samizdat: regular fetch!')
-    return fetch(url, {cache: "reload"})
-        .then((response) => {
-            // 4xx? 5xx? that's a paddlin'
-            if (response.status >= 400) {
-                // throw an Error to fall back to Samizdat:
-                throw new Error('HTTP Error: ' + response.status + ' ' + response.statusText);
-            }
-            // all good, it seems
-            console.log("(COMMIT_UNKNOWN) Fetched:", response.url);
-            
-            // we need to create a new Response object
-            // with all the headers added explicitly,
-            // since response.headers is immutable
-            var init = {
-                status:     response.status,
-                statusText: response.statusText,
-                headers: {}
-            };
-            response.headers.forEach(function(val, header){
-                init.headers[header] = val;
-            });
-            
-            // add the X-Samizdat-* headers to the mix
-            init.headers['X-Samizdat-Method'] = 'fetch'
-            init.headers['X-Samizdat-ETag'] = response.headers.get('ETag')
-            
-            // return the new response, using the Blob from the original one
-            return response
-                    .blob()
-                    .then((blob) => {
-                        return new Response(
-                            blob,
-                            init
-                        )
-                    })
-        })
-}
- 
-// initialize the SamizdatPlugins array
-if (!Array.isArray(self.SamizdatPlugins)) {
-    self.SamizdatPlugins = new Array()
-}
+// no polluting of the global namespace please
+(function () {
+    
+    /*
+     * this plugin has no config settings
+     */
+
+    /**
+     * getting content using regular HTTP(S) fetch()
+     */
+    let fetchContent = (url) => {
+        console.log('Samizdat: regular fetch!')
+        return fetch(url, {cache: "reload"})
+            .then((response) => {
+                // 4xx? 5xx? that's a paddlin'
+                if (response.status >= 400) {
+                    // throw an Error to fall back to Samizdat:
+                    throw new Error('HTTP Error: ' + response.status + ' ' + response.statusText);
+                }
+                // all good, it seems
+                console.log("(COMMIT_UNKNOWN) Fetched:", response.url);
+                
+                // we need to create a new Response object
+                // with all the headers added explicitly,
+                // since response.headers is immutable
+                var init = {
+                    status:     response.status,
+                    statusText: response.statusText,
+                    headers: {}
+                };
+                response.headers.forEach(function(val, header){
+                    init.headers[header] = val;
+                });
+                
+                // add the X-Samizdat-* headers to the mix
+                init.headers['X-Samizdat-Method'] = 'fetch'
+                init.headers['X-Samizdat-ETag'] = response.headers.get('ETag')
+                
+                // return the new response, using the Blob from the original one
+                return response
+                        .blob()
+                        .then((blob) => {
+                            return new Response(
+                                blob,
+                                init
+                            )
+                        })
+            })
+    }
+    
+    // initialize the SamizdatPlugins array
+    if (!Array.isArray(self.SamizdatPlugins)) {
+        self.SamizdatPlugins = new Array()
+    }
+
+    // and add ourselves to it
+    // with some additional metadata
+    self.SamizdatPlugins.push({
+        name: 'fetch',
+        description: 'Just a regular HTTP(S) fetch()',
+        version: 'COMMIT_UNKNOWN',
+        fetch: fetchContent
+    })
 
-// and add ourselves to it
-// with some additional metadata
-self.SamizdatPlugins.push({
-    name: 'fetch',
-    description: 'Just a regular HTTP(S) fetch()',
-    version: 'COMMIT_UNKNOWN',
-    fetch: fetchContent
-})
+// done with not poluting the global namespace
+})()
diff --git a/plugins/gateway-ipns.js b/plugins/gateway-ipns.js
index 9241a7d9199a4052dd2ed63119acee8128df22e4..01e2fb2ece25c742ca018ed4c2b366f1662a2877 100644
--- a/plugins/gateway-ipns.js
+++ b/plugins/gateway-ipns.js
@@ -6,129 +6,148 @@
  * this plugin does not implement any push method
  */
 
-// the pubkey of the preconfigured IPNS node
-const ipnsPubKey = 'QmYGVgGGfD5N4Xcc78CcMJ99dKcH6K6myhd4Uenv5yJwiJ'
+// no polluting of the global namespace please
+(function () {
 
-// this will become useful later
-//const gatewaysJSONUrl = "https://ipfs.github.io/public-gateway-checker/gateways.json";
-// important:
-// we cannot use gateways that use hash directly in the (sub)domain:
-// https://github.com/node-fetch/node-fetch/issues/260
-const gateways = [
-    'https://ninetailed.ninja/ipns/',       // Russia
-    'https://10.via0.com/ipns/',            // USA
-    'https://ipfs.sloppyta.co/ipns/',       // UK
-    'https://gateway.temporal.cloud/ipns/', // Germany
-    'https://ipfs.best-practice.se/ipns/'   // Sweden
-]
+    /*
+     * plugin config settings
+     */
+    
+    // sane defaults
+    let defaultConfig = {
+        // the pubkey of the preconfigured IPNS node; always needs to be set in config.js
+        ipnsPubkey: null,
+        // some default IPFS gateways to use
+        // 
+        // important:
+        // we cannot use gateways that use hash directly in the (sub)domain:
+        // https://github.com/node-fetch/node-fetch/issues/260
+        ipfsGateways: [
+            'https://ninetailed.ninja/ipns/',       // Russia
+            'https://10.via0.com/ipns/',            // USA
+            'https://ipfs.sloppyta.co/ipns/',       // UK
+            'https://gateway.temporal.cloud/ipns/', // Germany
+            'https://ipfs.best-practice.se/ipns/'   // Sweden
+        ]
+    }
 
+    // merge the defaults with settings from SamizdatConfig
+    let config = {...defaultConfig, ...self.SamizdatConfig.plugins["gateway-ipns"]}
 
-/*
- * to do this right we need a Promise.any() polyfill
- * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/any
- */
-Promise.any = async (promises) => {
-    // Promise.all() is the polar opposite of Promise.any()
-    // in that it returns as soon as there is a first rejection
-    // but without it, it returns an array of resolved results
-    return Promise.all(
-        promises.map(p => {
-            return new Promise((resolve, reject) =>
-                // swap reject and resolve, so that we can use Promise.all()
-                // and get the result we need
-                Promise.resolve(p).then(reject, resolve)
-            );
-        })
-    // now, swap errors and values back
-    ).then(
-        err => Promise.reject(err),
-        val => Promise.resolve(val)
-    );
-};
+    // reality check: Gun pubkey needs to be set to a non-empty string
+    if (typeof(config.ipnsPubkey) !== "string" || config.ipnsPubkey === "") {
+        let err = new Error("ipnsPubkey not confgured")
+        console.error(err)
+        throw err
+    }
 
+    /*
+     * to do this right we need a Promise.any() polyfill
+     * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/any
+     */
+    Promise.any = async (promises) => {
+        // Promise.all() is the polar opposite of Promise.any()
+        // in that it returns as soon as there is a first rejection
+        // but without it, it returns an array of resolved results
+        return Promise.all(
+            promises.map(p => {
+                return new Promise((resolve, reject) =>
+                    // swap reject and resolve, so that we can use Promise.all()
+                    // and get the result we need
+                    Promise.resolve(p).then(reject, resolve)
+                );
+            })
+        // now, swap errors and values back
+        ).then(
+            err => Promise.reject(err),
+            val => Promise.resolve(val)
+        );
+    };
 
-/**
- * getting content using regular HTTP(S) fetch()
- */
-let fetchContentFromGatewayIPNS = (url) => {
-    
-    console.log(`Samizdat: pre-configured gateways:\n  ${gateways.join('\n  ')}`)
+
+    /**
+     * getting content using regular HTTP(S) fetch()
+     */
+    let fetchContentFromGatewayIPNS = (url) => {
         
-    // we're going to try a random gateway and building an URL of the form:
-    // https://<gateway_address>/<ipnsPubkey>/<rest_of_URL>
-    var ipnsUrl = ipnsPubKey + url.replace(/https?:\/\/[^/]+/, '')
-    
-    // pick 3 gateways, at random
-    var sourceGateways = [...gateways]
-    // if we have fewer than 3 gateways configured, use all of them
-    if (sourceGateways.length <= 3) {
-        var useGateways = sourceGateways
-    // otherwise get 3 at random
-    } else {
-        var useGateways = new Array()
-        while (useGateways.length < 3) {
-            // put in the address while we're at it
-            useGateways.push(
-                sourceGateways
-                    .splice(Math.floor(Math.random() * sourceGateways.length), 1)[0] + ipnsUrl
-            )
+        // we're going to try a random gateway and building an URL of the form:
+        // https://<gateway_address>/<pubkey>/<rest_of_URL>
+        var ipnsUrl = config.ipnsPubkey + url.replace(/https?:\/\/[^/]+/, '')
+        
+        // pick 3 gateways, at random
+        var sourceGateways = [...config.ipfsGateways]
+        // if we have fewer than 3 gateways configured, use all of them
+        if (sourceGateways.length <= 3) {
+            var useGateways = sourceGateways
+        // otherwise get 3 at random
+        } else {
+            var useGateways = new Array()
+            while (useGateways.length < 3) {
+                // put in the address while we're at it
+                useGateways.push(
+                    sourceGateways
+                        .splice(Math.floor(Math.random() * sourceGateways.length), 1)[0] + ipnsUrl
+                )
+            }
         }
+        
+        // debug log
+        console.log(`Samizdat: gateway IPNS fetching:\n  ${useGateways.join('\n  ')}`)
+        
+        return Promise.any(
+            useGateways.map(
+                u=>fetch(u, {cache: "reload"})
+            ))
+            .then((response) => {
+                // 4xx? 5xx? that's a paddlin'
+                if (response.status >= 400) {
+                    // throw an Error to fall back to other plugins:
+                    throw new Error('HTTP Error: ' + response.status + ' ' + response.statusText);
+                }
+                // all good, it seems
+                console.log("(COMMIT_UNKNOWN) Fetched:", response.url);
+                
+                // we need to create a new Response object
+                // with all the headers added explicitly,
+                // since response.headers is immutable
+                var init = {
+                    status:     response.status,
+                    statusText: response.statusText,
+                    headers: {}
+                };
+                response.headers.forEach(function(val, header){
+                    init.headers[header] = val;
+                });
+                
+                // add the X-Samizdat-* headers to the mix
+                init.headers['X-Samizdat-Method'] = 'gateway-ipns'
+                init.headers['X-Samizdat-ETag'] = response.headers.get('ETag')
+                
+                // return the new response, using the Blob from the original one
+                return response
+                        .blob()
+                        .then((blob) => {
+                            return new Response(
+                                blob,
+                                init
+                            )
+                        })
+            })
     }
     
-    // debug log
-    console.log(`Samizdat: gateway IPNS fetching:\n  ${useGateways.join('\n  ')}`)
-    
-    return Promise.any(
-        useGateways.map(
-            u=>fetch(u, {cache: "reload"})
-        ))
-        .then((response) => {
-            // 4xx? 5xx? that's a paddlin'
-            if (response.status >= 400) {
-                // throw an Error to fall back to other plugins:
-                throw new Error('HTTP Error: ' + response.status + ' ' + response.statusText);
-            }
-            // all good, it seems
-            console.log("(COMMIT_UNKNOWN) Fetched:", response.url);
-            
-            // we need to create a new Response object
-            // with all the headers added explicitly,
-            // since response.headers is immutable
-            var init = {
-                status:     response.status,
-                statusText: response.statusText,
-                headers: {}
-            };
-            response.headers.forEach(function(val, header){
-                init.headers[header] = val;
-            });
-            
-            // add the X-Samizdat-* headers to the mix
-            init.headers['X-Samizdat-Method'] = 'gateway-ipns'
-            init.headers['X-Samizdat-ETag'] = response.headers.get('ETag')
-            
-            // return the new response, using the Blob from the original one
-            return response
-                    .blob()
-                    .then((blob) => {
-                        return new Response(
-                            blob,
-                            init
-                        )
-                    })
-        })
-}
- 
-// initialize the SamizdatPlugins array
-if (!Array.isArray(self.SamizdatPlugins)) {
-    self.SamizdatPlugins = new Array()
-}
+    // initialize the SamizdatPlugins array
+    if (!Array.isArray(self.SamizdatPlugins)) {
+        self.SamizdatPlugins = new Array()
+    }
 
-// and add ourselves to it
-// with some additional metadata
-self.SamizdatPlugins.push({
-    name: 'gateway-ipns',
-    description: 'HTTP(S) fetch() from IPNS via known public gateways',
-    version: 'COMMIT_UNKNOWN',
-    fetch: fetchContentFromGatewayIPNS
-})
+    // and add ourselves to it
+    // with some additional metadata
+    self.SamizdatPlugins.push({
+        name: 'gateway-ipns',
+        description: 'HTTP(S) fetch() from IPNS via known public gateways',
+        version: 'COMMIT_UNKNOWN',
+        fetch: fetchContentFromGatewayIPNS
+    })
+    
+// done with not poluting the global namespace
+})()
diff --git a/plugins/gun-ipfs.js b/plugins/gun-ipfs.js
index 90d915055d4414c894273f3aaa0944598f1d5f86..8fab65b8db256b4d388bc4b8098d8064cc0096ed 100644
--- a/plugins/gun-ipfs.js
+++ b/plugins/gun-ipfs.js
@@ -11,431 +11,450 @@
 |* === General stuff and setup                                           === *|
 \* ========================================================================= */
 
-var ipfs;
-var gun;
-var gunUser;
-
-// the pubkey of the preconfigured Gun user
-const gunPubKey = 'WUK5ylwqqgUorceQRa84qfbBFhk7eNRDUoPbGK05SyE.-yohFhTzWPpDT-UDMuKGgemOUrw_cMMYWpy6plILqrg'
+// no polluting of the global namespace please
+(function () {
+
+    var ipfs;
+    var gun;
+    var gunUser;
+
+    // sane defaults
+    let defaultConfig = {
+        // the pubkey of the preconfigured Gun user; always needs to be set in config.js
+        gunPubkey: null,
+        // the IPFS gateway we're using for verification when publishing; default is usually ok
+        ipfsGateway: 'https://gateway.ipfs.io'
+    }
 
-// the IPFS gateway we're using for verification
-const IPFSGateway = 'https://gateway.ipfs.io'
+    // merge the defaults with settings from SamizdatConfig
+    let config = {...defaultConfig, ...self.SamizdatConfig.plugins["gun+ipfs"]}
 
-/**
- * this is apparently needed by Gun
- * and `window` does not exist in ServiceWorker context
- */
-if (typeof window === 'undefined') {
-    console.log('(COMMIT_UNKNOWN) redefining window...')
-    var window = self;
-}
+    // reality check: Gun pubkey needs to be set to a non-empty string
+    if (typeof(config.gunPubkey) !== "string" || config.gunPubkey === "") {
+        let err = new Error("gunPubkey not confgured")
+        console.error(err)
+        throw err
+    }
 
-/**
- * importing stuff works differently between a browser window context
- * and a ServiceWorker context, because things just can't be easy and sane
- */
-function doImport() {
-    var args = Array.prototype.slice.call(arguments);
-    if (typeof self.importScripts !== 'undefined') {
-        self.importScripts.apply(self, args)
-    } else {
-        console.log('(COMMIT_UNKNOWN) assuming these scripts are already included:')
-        args.forEach(function(src){
-            console.log('+--', src)
-        })
+    /**
+    * this is apparently needed by Gun
+    * and `window` does not exist in ServiceWorker context
+    */
+    if (typeof window === 'undefined') {
+        console.log('(COMMIT_UNKNOWN) redefining window...')
+        var window = self;
     }
-}
-
-async function setup_ipfs() {
-    if (ipfs === undefined) {
-        ipfs = false // we don't want to start a few times over
-        console.log('(COMMIT_UNKNOWN) Importing IPFS-related libraries...');
-        doImport(
-            "./lib/ipfs.js");
-        console.log('(COMMIT_UNKNOWN) Setting up IPFS...')
-        ipfs = await self.Ipfs.create();
-        console.log('+-- IPFS loaded       :: ipfs is   : ' + typeof ipfs)
+
+    /**
+     * importing stuff works differently between a browser window context
+     * and a ServiceWorker context, because things just can't be easy and sane
+     */
+    function doImport() {
+        var args = Array.prototype.slice.call(arguments);
+        if (typeof self.importScripts !== 'undefined') {
+            self.importScripts.apply(self, args)
+        } else {
+            console.log('(COMMIT_UNKNOWN) assuming these scripts are already included:')
+            args.forEach(function(src){
+                console.log('+--', src)
+            })
+        }
     }
-}
-
-async function setup_gun() {
-    if (gun === undefined) {
-        gun = false // we don't want to start a few times over
-        console.log('(COMMIT_UNKNOWN) Importing Gun-related libraries...');
-        doImport(
-            "./lib/gun.js",
-            "./lib/sea.js",
-            "./lib/webrtc.js");
-        console.log('(COMMIT_UNKNOWN) Setting up Gun...')
-        gun = Gun(['https://gunjs.herokuapp.com/gun', 'https://samizdat.is/gun']);
-        console.log('+-- Gun loaded        :: gun is    : ' + typeof gun);
+
+    async function setup_ipfs() {
+        if (ipfs === undefined) {
+            ipfs = false // we don't want to start a few times over
+            console.log('(COMMIT_UNKNOWN) Importing IPFS-related libraries...');
+            doImport(
+                "./lib/ipfs.js");
+            console.log('(COMMIT_UNKNOWN) Setting up IPFS...')
+            ipfs = await self.Ipfs.create();
+            console.log('+-- IPFS loaded       :: ipfs is   : ' + typeof ipfs)
+        }
     }
-    if ( (gun !== false) && (gun !== undefined) && (gunUser === undefined) ) {
-        gunUser = false // we don't want to start a few times over
-        console.log('(COMMIT_UNKNOWN) Setting up gunUser...')
-        gunUser = gun.user(gunPubKey)
-        console.log('+-- Gun init complete :: gunUser is: ' + typeof gunUser);
+
+    async function setup_gun() {
+        if (gun === undefined) {
+            gun = false // we don't want to start a few times over
+            console.log('(COMMIT_UNKNOWN) Importing Gun-related libraries...');
+            doImport(
+                "./lib/gun.js",
+                "./lib/sea.js",
+                "./lib/webrtc.js");
+            console.log('(COMMIT_UNKNOWN) Setting up Gun...')
+            gun = Gun(['https://gunjs.herokuapp.com/gun', 'https://samizdat.is/gun']);
+            console.log('+-- Gun loaded        :: gun is    : ' + typeof gun);
+        }
+        if ( (gun !== false) && (gun !== undefined) && (gunUser === undefined) ) {
+            gunUser = false // we don't want to start a few times over
+            console.log('(COMMIT_UNKNOWN) Setting up gunUser...')
+            gunUser = gun.user(config.gunPubkey)
+            console.log('+-- Gun init complete :: gunUser is: ' + typeof gunUser);
+        }
     }
-}
-
-async function setup_gun_ipfs() {
-    if (ipfs === undefined || gun === undefined) {
-        console.log('(COMMIT_UNKNOWN) Setting up Samizdat...')
-        setup_ipfs();
-        setup_gun();
-    } else {
-        console.log('(COMMIT_UNKNOWN) Samizdat setup already underway (ipfs: ' + ( (ipfs) ? 'done' : 'loading' ) + ', gun: ' + ( (gun) ? 'done' : 'loading' ) + ')')
+
+    async function setup_gun_ipfs() {
+        if (ipfs === undefined || gun === undefined) {
+            console.log('(COMMIT_UNKNOWN) Setting up Samizdat...')
+            setup_ipfs();
+            setup_gun();
+        } else {
+            console.log('(COMMIT_UNKNOWN) Samizdat setup already underway (ipfs: ' + ( (ipfs) ? 'done' : 'loading' ) + ', gun: ' + ( (gun) ? 'done' : 'loading' ) + ')')
+        }
     }
-}
 
 
-/* ========================================================================= *\
-|* === Main functionality                                                === *|
-\* ========================================================================= */
+    /* ========================================================================= *\
+    |* === Main functionality                                                === *|
+    \* ========================================================================= */
+
+    let getGunData = (gunaddr) => {
+        return new Promise(
+            (resolve, reject) => {
+                console.log('(COMMIT_UNKNOWN) getGunData()');
+                console.log('(COMMIT_UNKNOWN) getGunData() :: +-- gunUser   : ' + typeof gunUser);
+                console.log('(COMMIT_UNKNOWN) getGunData() :: +-- gunaddr[] : ' + gunaddr);
+
+                // get the data
+                gunUser
+                    .get(gunaddr[0])
+                    .get(gunaddr[1])
+                    .once(function(addr){
+                        if (typeof addr !== 'undefined') {
+                            console.log("2.1 IPFS address: '" + addr + "'");
+                            resolve(addr);
+                        } else {
+                            // looks like we didn't get anything
+                            reject(new Error('IPFS address is undefined for: ' + gunaddr[1]))
+                        }
+                    // ToDo: what happens when we hit the timeout here?
+                    }, {wait: 5000});
+            }
+        );
+    };
 
-let getGunData = (gunaddr) => {
-    return new Promise(
-        (resolve, reject) => {
-            console.log('(COMMIT_UNKNOWN) getGunData()');
-            console.log('(COMMIT_UNKNOWN) getGunData() :: +-- gunUser   : ' + typeof gunUser);
-            console.log('(COMMIT_UNKNOWN) getGunData() :: +-- gunaddr[] : ' + gunaddr);
-
-            // get the data
-            gunUser
-                .get(gunaddr[0])
-                .get(gunaddr[1])
-                .once(function(addr){
-                    if (typeof addr !== 'undefined') {
-                        console.log("2.1 IPFS address: '" + addr + "'");
-                        resolve(addr);
-                    } else {
-                        // looks like we didn't get anything
-                        reject(new Error('IPFS address is undefined for: ' + gunaddr[1]))
-                    }
-                // ToDo: what happens when we hit the timeout here?
-                }, {wait: 5000});
+    /**
+     * the workhorse of this plugin
+     */
+    async function getContentFromGunAndIPFS(url) {
+        var urlArray = url.replace(/https?:\/\//, '').split('/')
+        var gunaddr = [urlArray[0], '/' + urlArray.slice(1).join('/')]
+
+        /*
+        * if the gunaddr[1] ends in '/', append 'index.html' to it
+        */
+        if (gunaddr[1].charAt(gunaddr[1].length - 1) === '/') {
+            console.log("NOTICE: address ends in '/', assuming '/index.html' should be appended.");    
+            gunaddr[1] += 'index.html';
         }
-    );
-};
 
-/**
- * the workhorse of this plugin
- */
-async function getContentFromGunAndIPFS(url) {
-    var urlArray = url.replace(/https?:\/\//, '').split('/')
-    var gunaddr = [urlArray[0], '/' + urlArray.slice(1).join('/')]
+        console.log("2. Starting Gun lookup of: '" + gunaddr.join(', ') + "'");
+        console.log("   +-- gun     : " + typeof gun);
+        console.log("   +-- gunUser : " + typeof gunUser);
 
-    /*
-     * if the gunaddr[1] ends in '/', append 'index.html' to it
-     */
-    if (gunaddr[1].charAt(gunaddr[1].length - 1) === '/') {
-        console.log("NOTICE: address ends in '/', assuming '/index.html' should be appended.");    
-        gunaddr[1] += 'index.html';
+        /*
+         * naïvely assume content type based on file extension
+         * TODO: this needs a fix
+         */
+        var contentType = '';
+        switch (gunaddr.slice(-1)[0].split('.', -1)[1].toLowerCase()) {
+            case 'html':
+            case 'htm':
+                contentType = 'text/html';
+                break;
+            case 'css':
+                contentType = 'text/css';
+                break;
+            case 'js':
+                contentType = 'text/javascript';
+                break;
+            case 'svg':
+                contentType = 'image/svg+xml';
+                break;
+            case 'ico':
+                contentType = 'image/x-icon';
+                break;
+        }
+        console.log("   +-- guessed contentType : " + contentType);
+
+        return getGunData(gunaddr).then(ipfsaddr => {
+            console.log("3. Starting IPFS lookup of: '" + ipfsaddr + "'");
+            return ipfs.get(ipfsaddr).next();
+        }).then(file => {
+            // we only need one
+            if (file.value.content) {
+                async function getContent(source) {
+                    var content = new Uint8Array()
+                    var data = await source.next()
+                    while (! data.done) {
+                        var newContent = new Uint8Array(content.length + data.value.length);
+                        newContent.set(content)
+                        newContent.set(data.value, content.length)
+                        content = newContent
+                        data = await source.next()
+                    }
+                    return content
+                }
+                return getContent(file.value.content).then((content)=>{
+                    console.log('4. Got a Gun-addressed IPFS-stored file: ' + file.value.path + '; content is: ' + typeof content);
+                    // creating and populating the blob
+                    var blob = new Blob(
+                        [content],
+                        {'type': contentType}
+                    );
+
+                    return new Response(
+                        blob,
+                        {
+                            'status': 200,
+                            'statusText': 'OK',
+                            'headers': {
+                                'Content-Type': contentType,
+                                'ETag': file.value.path,
+                                'X-Samizdat-Method': 'gun+ipfs',
+                                'X-Samizdat-ETag': file.value.path
+                            }
+                        }
+                    );
+                })
+            };
+        });
     }
 
-    console.log("2. Starting Gun lookup of: '" + gunaddr.join(', ') + "'");
-    console.log("   +-- gun     : " + typeof gun);
-    console.log("   +-- gunUser : " + typeof gunUser);
 
+    /* ========================================================================= *\
+    |* === Publishing stuff                                                  === *|
+    \* ========================================================================= */
+    
     /*
-     * naïvely assume content type based on file extension
-     * TODO: this needs a fix
+     * these are used for adding content to IPFS and Gun
      */
-    var contentType = '';
-    switch (gunaddr.slice(-1)[0].split('.', -1)[1].toLowerCase()) {
-        case 'html':
-        case 'htm':
-            contentType = 'text/html';
-            break;
-        case 'css':
-            contentType = 'text/css';
-            break;
-        case 'js':
-            contentType = 'text/javascript';
-            break;
-        case 'svg':
-            contentType = 'image/svg+xml';
-            break;
-        case 'ico':
-            contentType = 'image/x-icon';
-            break;
-    }
-    console.log("   +-- guessed contentType : " + contentType);
-
-    return getGunData(gunaddr).then(ipfsaddr => {
-        console.log("3. Starting IPFS lookup of: '" + ipfsaddr + "'");
-        return ipfs.get(ipfsaddr).next();
-    }).then(file => {
-        // we only need one
-        if (file.value.content) {
-            async function getContent(source) {
-                var content = new Uint8Array()
-                var data = await source.next()
-                while (! data.done) {
-                    var newContent = new Uint8Array(content.length + data.value.length);
-                    newContent.set(content)
-                    newContent.set(data.value, content.length)
-                    content = newContent
-                    data = await source.next()
-                }
-                return content
-            }
-            return getContent(file.value.content).then((content)=>{
-                console.log('4. Got a Gun-addressed IPFS-stored file: ' + file.value.path + '; content is: ' + typeof content);
-                // creating and populating the blob
-                var blob = new Blob(
-                    [content],
-                    {'type': contentType}
-                );
-
-                return new Response(
-                    blob,
-                    {
-                        'status': 200,
-                        'statusText': 'OK',
-                        'headers': {
-                            'Content-Type': contentType,
-                            'ETag': file.value.path,
-                            'X-Samizdat-Method': 'gun+ipfs',
-                            'X-Samizdat-ETag': file.value.path
-                        }
-                    }
-                );
-            })
-        };
-    });
-}
 
 
-/* ========================================================================= *\
-|* === Publishing stuff                                                  === *|
-\* ========================================================================= */
-/*
- * these are used for adding content to IPFS and Gun
- */
-
-
-/**
- * adding stuff to IPFS
- * accepts an array of URLs
- * 
- * returns a Promise that resolves to an object mapping URLs to IPFS hashes
- */
-let addToIPFS = (resources) => {
-    return new Promise((resolve, reject) => {
-        
-        console.log("Adding to IPFS...")
-        console.log("+-- number of resources:", resources.length)
-        var ipfs_addresses = {};
-
-        resources.forEach(function(res){
-            console.log("    +-- handling internal resource:", res)
+    /**
+     * adding stuff to IPFS
+     * accepts an array of URLs
+     * 
+     * returns a Promise that resolves to an object mapping URLs to IPFS hashes
+     */
+    let addToIPFS = (resources) => {
+        return new Promise((resolve, reject) => {
             
-            ipfs.add(Ipfs.urlSource(res))
-                .then((result) => {
-                    // add to the list -- this is needed to add stuff to Gun
-                    // result.path is just the filename stored in IPFS, not the actual path!
-                    // res holds the full URL
-                    // what we need in ipfs_addresses is in fact the absolute path (no domain, no scheme)
-                    var abs_path = res.replace(window.location.origin, '')
-                    ipfs_addresses[abs_path] = '/ipfs/' + result.cid.string
-                    console.log("Added to IPFS: " + abs_path + ' as ' + ipfs_addresses[abs_path])
-                    // if we seem to have all we need, resolve!
-                    if (Object.keys(ipfs_addresses).length === resources.length) resolve(ipfs_addresses);
-                })
-        
-        });
-    })
-}
+            console.log("Adding to IPFS...")
+            console.log("+-- number of resources:", resources.length)
+            var ipfs_addresses = {};
+
+            resources.forEach(function(res){
+                console.log("    +-- handling internal resource:", res)
+                
+                ipfs.add(Ipfs.urlSource(res))
+                    .then((result) => {
+                        // add to the list -- this is needed to add stuff to Gun
+                        // result.path is just the filename stored in IPFS, not the actual path!
+                        // res holds the full URL
+                        // what we need in ipfs_addresses is in fact the absolute path (no domain, no scheme)
+                        var abs_path = res.replace(window.location.origin, '')
+                        ipfs_addresses[abs_path] = '/ipfs/' + result.cid.string
+                        console.log("Added to IPFS: " + abs_path + ' as ' + ipfs_addresses[abs_path])
+                        // if we seem to have all we need, resolve!
+                        if (Object.keys(ipfs_addresses).length === resources.length) resolve(ipfs_addresses);
+                    })
+            
+            });
+        })
+    }
 
-/**
- * verification that content pushed to IPFS
- * is, in fact, available in IPFS
- * 
- * a nice side-effect is that this will pre-load the content on
- * a gateway, which tends to be a large (and fast) IPFS node
- * 
- * this is prety naïve, in that it pulls the content from an ipfs gateway
- * and assumes all is well if it get a HTTP 200 and any content
- * 
- * that is, it does *not* check that the content matches what was pushed
- * we trust IPFS here, I guess
- * 
- * finally, we're using a regular fetch() instead of just going through our
- * ipfs object because our IPFS object might have things cached and we want
- * to test a completey independent route
- * 
- * takes a object mapping paths to IPFS addresses
- * and returns a Promise that resolves to true 
- */
-let verifyInIPFS = (ipfs_addresses) => {
-    return new Promise((resolve, reject) => {
-        console.log('Checking IPFS content against a gateway...')
-        console.log('+-- gateway in use: ' + IPFSGateway)
-        // get the list of IPFS addresses
-        var updatedPaths = Object.values(ipfs_addresses)
-        for (path in ipfs_addresses) {
-            // start the fetch
-            fetch(IPFSGateway + ipfs_addresses[path])
-                .then((response) => {
-                    ipfsaddr = response.url.replace(IPFSGateway, '')
-                    if (response.ok) {
-                        console.log('+-- verified: ' + ipfsaddr)
-                        var pathIndex = updatedPaths.indexOf(ipfsaddr)
-                        if (pathIndex > -1) {
-                            updatedPaths.splice(pathIndex, 1)
-                        }
-                        if (updatedPaths.length === 0) {
-                            console.log('All updates confirmed successful!')
-                            resolve(ipfs_addresses);
+    /**
+     * verification that content pushed to IPFS
+     * is, in fact, available in IPFS
+     * 
+     * a nice side-effect is that this will pre-load the content on
+     * a gateway, which tends to be a large (and fast) IPFS node
+     * 
+     * this is prety naïve, in that it pulls the content from an ipfs gateway
+     * and assumes all is well if it get a HTTP 200 and any content
+     * 
+     * that is, it does *not* check that the content matches what was pushed
+     * we trust IPFS here, I guess
+     * 
+     * finally, we're using a regular fetch() instead of just going through our
+     * ipfs object because our IPFS object might have things cached and we want
+     * to test a completey independent route
+     * 
+     * takes a object mapping paths to IPFS addresses
+     * and returns a Promise that resolves to true 
+     */
+    let verifyInIPFS = (ipfs_addresses) => {
+        return new Promise((resolve, reject) => {
+            console.log('Checking IPFS content against a gateway...')
+            console.log('+-- gateway in use: ' + config.ipfsGateway)
+            // get the list of IPFS addresses
+            var updatedPaths = Object.values(ipfs_addresses)
+            for (path in ipfs_addresses) {
+                // start the fetch
+                fetch(config.ipfsGateway + ipfs_addresses[path])
+                    .then((response) => {
+                        ipfsaddr = response.url.replace(config.ipfsGateway, '')
+                        if (response.ok) {
+                            console.log('+-- verified: ' + ipfsaddr)
+                            var pathIndex = updatedPaths.indexOf(ipfsaddr)
+                            if (pathIndex > -1) {
+                                updatedPaths.splice(pathIndex, 1)
+                            }
+                            if (updatedPaths.length === 0) {
+                                console.log('All updates confirmed successful!')
+                                resolve(ipfs_addresses);
+                            }
+                        } else {
+                            reject(new Error('HTTP error (' + response.status + ' ' + response.statusText + ' for: ' + ipfsaddr))
                         }
-                    } else {
-                        reject(new Error('HTTP error (' + response.status + ' ' + response.statusText + ' for: ' + ipfsaddr))
-                    }
-                })
-                .catch((err) => {
-                    // it would be nice to have the failed path here somehow
-                    // alternatively, updating updatedPaths with info on failed
-                    // requests might work
-                    reject(err)
-                })
-        }
-    })
-}
-
-/**
- * auth a Gun admin user
- * (and verify it's the correct one with regards to the configured gunPubKey)
- */
-let authGunAdmin = (user, pass) => {
-    return new Promise((resolve, reject) => {
-        // we need a separate Gun instance, otherwise gu will get merged with gunUser
-        // and we want these to be separate
-        var g = Gun(['https://gunjs.herokuapp.com/gun', 'https://samizdat.is/gun'])
-        var gu = g.user()
-        gu.auth(user, pass, (userReference) => {
-            if (userReference.err) {
-                reject(new Error(userReference.err))
-            // reality check -- does it match our preconfigured pubkey?
-            } else if (gu._.soul.slice(1) === gunPubKey) {
-                console.log('Gun Admin user authenticated using password.');
-                // we need to keep the reference to g, otherwise gu becomes unusable
-                var gApi = {
-                    user: gu,
-                    gun: g
-                } 
-                resolve(gApi)
-            } else {
-                reject(new Error('Password-authenticated user does not match preconfigured pubkey!'))
+                    })
+                    .catch((err) => {
+                        // it would be nice to have the failed path here somehow
+                        // alternatively, updating updatedPaths with info on failed
+                        // requests might work
+                        reject(err)
+                    })
             }
         })
-    })
-}
+    }
 
-/**
- * add IPFS addresses to Gun
- */
-let addToGun = (user, pass, ipfs_addresses) => {
-    // we need an authenticated Gun user
-    return authGunAdmin(user, pass)
-        .then((gunAPI) => {
-            console.log('+-- adding new IPFS addresses to Gun...')
-            gunAPI.user.get(window.location.host).put(ipfs_addresses /*, function(ack) {...}*/);
-            return gunAPI;
+    /**
+     * auth a Gun admin user
+     * (and verify it's the correct one with regards to the configured config.gunPubkey)
+     */
+    let authGunAdmin = (user, pass) => {
+        return new Promise((resolve, reject) => {
+            // we need a separate Gun instance, otherwise gu will get merged with gunUser
+            // and we want these to be separate
+            var g = Gun(['https://gunjs.herokuapp.com/gun', 'https://samizdat.is/gun'])
+            var gu = g.user()
+            gu.auth(user, pass, (userReference) => {
+                if (userReference.err) {
+                    reject(new Error(userReference.err))
+                // reality check -- does it match our preconfigured pubkey?
+                } else if (gu._.soul.slice(1) === config.gunPubkey) {
+                    console.log('Gun Admin user authenticated using password.');
+                    // we need to keep the reference to g, otherwise gu becomes unusable
+                    var gApi = {
+                        user: gu,
+                        gun: g
+                    } 
+                    resolve(gApi)
+                } else {
+                    reject(new Error('Password-authenticated user does not match preconfigured pubkey!'))
+                }
+            })
         })
-        /**
-         * regular confirmations don't seem to work
-         * 
-         * so instead we're using the regular read-only Gun user
-         * to .get() the data that we've .put() just a minute ago
-         * 
-         * we then subscribe to the .on() events and once we notice the correct
-         * addresseswe consider our job done and quit.
-         */
-        .then((gunAPI) => {
-            // get the paths
-            console.log('+-- starting verification of updated Gun data...')
-            var updatedPaths = Object.keys(ipfs_addresses)
-            for (path in ipfs_addresses) {
-                console.log('    +-- watching: ' + path)
-                //debuglog('watching path for updates:', path)
-                // using the global gunUser to check if updates propagated
-                gunUser.get(window.location.host).get(path).on(function(updaddr, updpath){
-                    /*debuglog('+--', updpath)
-                    debuglog('    updated  :', ipfs_addresses[updpath])
-                    debuglog('    received :', updaddr)*/
-                    if (ipfs_addresses[updpath] == updaddr) {
-                        // update worked!
-                        gunUser.get(window.location.host).get(updpath).off()
-                        console.log('+-- update confirmed for:', updpath, '[' + updaddr + ']')
-                        var pathIndex = updatedPaths.indexOf(updpath)
-                        if (pathIndex > -1) {
-                            updatedPaths.splice(pathIndex, 1)
-                        }
-                        if (updatedPaths.length === 0) {
-                            console.log('All updates confirmed successful!')
-                            return true;
+    }
+
+    /**
+     * add IPFS addresses to Gun
+     */
+    let addToGun = (user, pass, ipfs_addresses) => {
+        // we need an authenticated Gun user
+        return authGunAdmin(user, pass)
+            .then((gunAPI) => {
+                console.log('+-- adding new IPFS addresses to Gun...')
+                gunAPI.user.get(window.location.host).put(ipfs_addresses /*, function(ack) {...}*/);
+                return gunAPI;
+            })
+            /**
+             * regular confirmations don't seem to work
+             * 
+             * so instead we're using the regular read-only Gun user
+             * to .get() the data that we've .put() just a minute ago
+             * 
+             * we then subscribe to the .on() events and once we notice the correct
+             * addresseswe consider our job done and quit.
+             */
+            .then((gunAPI) => {
+                // get the paths
+                console.log('+-- starting verification of updated Gun data...')
+                var updatedPaths = Object.keys(ipfs_addresses)
+                for (path in ipfs_addresses) {
+                    console.log('    +-- watching: ' + path)
+                    //debuglog('watching path for updates:', path)
+                    // using the global gunUser to check if updates propagated
+                    gunUser.get(window.location.host).get(path).on(function(updaddr, updpath){
+                        /*debuglog('+--', updpath)
+                        debuglog('    updated  :', ipfs_addresses[updpath])
+                        debuglog('    received :', updaddr)*/
+                        if (ipfs_addresses[updpath] == updaddr) {
+                            // update worked!
+                            gunUser.get(window.location.host).get(updpath).off()
+                            console.log('+-- update confirmed for:', updpath, '[' + updaddr + ']')
+                            var pathIndex = updatedPaths.indexOf(updpath)
+                            if (pathIndex > -1) {
+                                updatedPaths.splice(pathIndex, 1)
+                            }
+                            if (updatedPaths.length === 0) {
+                                console.log('All updates confirmed successful!')
+                                return true;
+                            }
                         }
-                    }
-                })
-            }
-        })
-}
+                    })
+                }
+            })
+    }
 
-/**
- * example code for of adding content to IPFS, verifying it was successfully added,
- * and adding the new addresses to Gun (and verifying changes propagated) 
- * 
- * TODO: this should accept a URL, a Response, or a list of URLs,
- *       and handle stuff appropriately
- */
-let publishContent = (resource, user, password) => {
-    
-    if (typeof resource === 'string') {
-        // we need this as an array of strings
-        resource = [resource]
-    } else if (typeof resource === 'object') {
-        if (!Array.isArray(resource)) {
-            // TODO: this needs to be implemented such that the Response is used directly
-            //       but that would require all called functions to also accept a Response
-            //       and act accordingly; #ThisIsComplicated
-            throw new Error("Handling a Response: not implemented yet")
+    /**
+     * example code for of adding content to IPFS, verifying it was successfully added,
+     * and adding the new addresses to Gun (and verifying changes propagated) 
+     * 
+     * TODO: this should accept a URL, a Response, or a list of URLs,
+     *       and handle stuff appropriately
+     */
+    let publishContent = (resource, user, password) => {
+        
+        if (typeof resource === 'string') {
+            // we need this as an array of strings
+            resource = [resource]
+        } else if (typeof resource === 'object') {
+            if (!Array.isArray(resource)) {
+                // TODO: this needs to be implemented such that the Response is used directly
+                //       but that would require all called functions to also accept a Response
+                //       and act accordingly; #ThisIsComplicated
+                throw new Error("Handling a Response: not implemented yet")
+            }
+        } else {
+            // everything else -- that's a paddlin'!
+            throw new TypeError("Only accepts: string, Array of string, Response.")
         }
-    } else {
-        // everything else -- that's a paddlin'!
-        throw new TypeError("Only accepts: string, Array of string, Response.")
+            
+        // add to IPFS
+        var ipfsPromise = addToIPFS(resource)
+        return Promise.all([
+            // verify stuff ended up in IPFS
+            ipfsPromise.then(verifyInIPFS),
+            // add to Gun and verify Gun updates propagation
+            ipfsPromise.then((hashes) => {
+                addToGun(user, password, hashes)
+            })
+        ])
     }
-        
-    // add to IPFS
-    var ipfsPromise = addToIPFS(resource)
-    return Promise.all([
-        // verify stuff ended up in IPFS
-        ipfsPromise.then(verifyInIPFS),
-        // add to Gun and verify Gun updates propagation
-        ipfsPromise.then((hashes) => {
-            addToGun(user, password, hashes)
-        })
-    ])
-}
 
-/* ========================================================================= *\
-|* === Initialization                                                    === *|
-\* ========================================================================= */
+    /* ========================================================================= *\
+    |* === Initialization                                                    === *|
+    \* ========================================================================= */
 
-// we probably need to handle this better
-setup_gun_ipfs();
-
-// initialize the SamizdatPlugins array
-if (!Array.isArray(self.SamizdatPlugins)) {
-    self.SamizdatPlugins = new Array()
-}
-
-// and add ourselves to it
-// with some additional metadata
-self.SamizdatPlugins.push({
-    name: 'gun+ipfs',
-    description: 'Decentralized resource fetching using Gun for address resolution and IPFS for content delivery.',
-    version: 'COMMIT_UNKNOWN',
-    fetch: getContentFromGunAndIPFS,
-    publish: publishContent
-})
+    // we probably need to handle this better
+    setup_gun_ipfs();
+
+    // initialize the SamizdatPlugins array
+    if (!Array.isArray(self.SamizdatPlugins)) {
+        self.SamizdatPlugins = new Array()
+    }
+
+    // and add ourselves to it
+    // with some additional metadata
+    self.SamizdatPlugins.push({
+        name: 'gun+ipfs',
+        description: 'Decentralized resource fetching using Gun for address resolution and IPFS for content delivery.',
+        version: 'COMMIT_UNKNOWN',
+        fetch: getContentFromGunAndIPFS,
+        publish: publishContent
+    })
+    
+// done with not poluting the global namespace
+})()
diff --git a/plugins/ipns-ipfs.js b/plugins/ipns-ipfs.js
index 5c549930fedf5dd33d92c0ac97f1ea0909d45365..53ef4cb2f8e8210cfc2bae51b52ddc37c70a3a22 100644
--- a/plugins/ipns-ipfs.js
+++ b/plugins/ipns-ipfs.js
@@ -18,308 +18,326 @@
 |* === General stuff and setup                                           === *|
 \* ========================================================================= */
 
-var ipfs;
+// no polluting of the global namespace please
+(function () {
 
-// the pubkey of the preconfigured IPNS node
-const ipnsPubKey = 'QmYGVgGGfD5N4Xcc78CcMJ99dKcH6K6myhd4Uenv5yJwiJ'
+    var ipfs;
 
-// the IPFS gateway we're using for verification
-const IPFSGateway = 'https://gateway.ipfs.io'
+    // sane defaults
+    let defaultConfig = {
+        // the pubkey of the preconfigured IPNS node; always needs to be set in config.js
+        ipnsPubkey: null,
+        // the IPFS gateway we're using for verification when publishing; default is usually ok
+        ipfsGateway: 'https://gateway.ipfs.io'
+    }
 
-/**
- * importing stuff works differently between a browser window context
- * and a ServiceWorker context, because things just can't be easy and sane
- */
-function doImport() {
-    var args = Array.prototype.slice.call(arguments);
-    if (typeof self.importScripts !== 'undefined') {
-        self.importScripts.apply(self, args)
-    } else {
-        console.log('(COMMIT_UNKNOWN) assuming these scripts are already included:')
-        args.forEach(function(src){
-            console.log('+--', src)
-        })
+    // merge the defaults with settings from SamizdatConfig
+    let config = {...defaultConfig, ...self.SamizdatConfig.plugins["ipns+ipfs"]}
+
+    // reality check: Gun pubkey needs to be set to a non-empty string
+    if (typeof(config.ipnsPubkey) !== "string" || config.ipnsPubkey === "") {
+        let err = new Error("ipnsPubkey not confgured")
+        console.error(err)
+        throw err
     }
-}
 
-async function setup_ipfs() {
-    if (ipfs === undefined) {
-        ipfs = false // we don't want to start a few times over
-        console.log('(COMMIT_UNKNOWN) Importing IPFS-related libraries...');
-        doImport(
-            "./lib/ipfs.js");
-        console.log('(COMMIT_UNKNOWN) Setting up IPFS...')
-        ipfs = await self.Ipfs.create({
-            config: {
-                dht: {
-                    enabled: true,
-                    clientMode: true
-                }
-            },
-            libp2p: {
+    /**
+     * importing stuff works differently between a browser window context
+     * and a ServiceWorker context, because things just can't be easy and sane
+     */
+    function doImport() {
+        var args = Array.prototype.slice.call(arguments);
+        if (typeof self.importScripts !== 'undefined') {
+            self.importScripts.apply(self, args)
+        } else {
+            console.log('(COMMIT_UNKNOWN) assuming these scripts are already included:')
+            args.forEach(function(src){
+                console.log('+--', src)
+            })
+        }
+    }
+
+    async function setup_ipfs() {
+        if (ipfs === undefined) {
+            ipfs = false // we don't want to start a few times over
+            console.log('(COMMIT_UNKNOWN) Importing IPFS-related libraries...');
+            doImport(
+                "./lib/ipfs.js");
+            console.log('(COMMIT_UNKNOWN) Setting up IPFS...')
+            ipfs = await self.Ipfs.create({
                 config: {
                     dht: {
                         enabled: true,
                         clientMode: true
                     }
+                },
+                libp2p: {
+                    config: {
+                        dht: {
+                            enabled: true,
+                            clientMode: true
+                        }
+                    }
                 }
-            }
-        });
-        console.log('+-- IPFS loaded       :: ipfs is   : ' + typeof ipfs)
+            });
+            console.log('+-- IPFS loaded       :: ipfs is   : ' + typeof ipfs)
+        }
     }
-}
 
-/* ========================================================================= *\
-|* === Main functionality                                                === *|
-\* ========================================================================= */
+    /* ========================================================================= *\
+    |* === Main functionality                                                === *|
+    \* ========================================================================= */
 
-/**
- * the workhorse of this plugin
- */
-async function getContentFromIPNSAndIPFS(url) {
-    return new Error("Not implemented yet.")
-    
-    var urlArray = url.replace(/https?:\/\//, '').split('/')
-    var gunaddr = [urlArray[0], '/' + urlArray.slice(1).join('/')]
-
-    /*
-     * if the gunaddr[1] ends in '/', append 'index.html' to it
+    /**
+     * the workhorse of this plugin
      */
-    if (gunaddr[1].charAt(gunaddr[1].length - 1) === '/') {
-        console.log("NOTICE: address ends in '/', assuming '/index.html' should be appended.");    
-        gunaddr[1] += 'index.html';
-    }
+    async function getContentFromIPNSAndIPFS(url) {
+        return new Error("Not implemented yet.")
+        
+        var urlArray = url.replace(/https?:\/\//, '').split('/')
+        var gunaddr = [urlArray[0], '/' + urlArray.slice(1).join('/')]
 
-    console.log("2. Starting Gun lookup of: '" + gunaddr.join(', ') + "'");
-    console.log("   +-- gun     : " + typeof gun);
-    console.log("   +-- gunUser : " + typeof gunUser);
+        /*
+        * if the gunaddr[1] ends in '/', append 'index.html' to it
+        */
+        if (gunaddr[1].charAt(gunaddr[1].length - 1) === '/') {
+            console.log("NOTICE: address ends in '/', assuming '/index.html' should be appended.");    
+            gunaddr[1] += 'index.html';
+        }
 
-    /*
-     * naïvely assume content type based on file extension
-     * TODO: this needs a fix
-     */
-    var contentType = '';
-    switch (gunaddr.slice(-1)[0].split('.', -1)[1].toLowerCase()) {
-        case 'html':
-        case 'htm':
-            contentType = 'text/html';
-            break;
-        case 'css':
-            contentType = 'text/css';
-            break;
-        case 'js':
-            contentType = 'text/javascript';
-            break;
-        case 'svg':
-            contentType = 'image/svg+xml';
-            break;
-        case 'ico':
-            contentType = 'image/x-icon';
-            break;
-    }
-    console.log("   +-- guessed contentType : " + contentType);
+        console.log("2. Starting Gun lookup of: '" + gunaddr.join(', ') + "'");
+        console.log("   +-- gun     : " + typeof gun);
+        console.log("   +-- gunUser : " + typeof gunUser);
 
-    return getGunData(gunaddr).then(ipfsaddr => {
-        console.log("3. Starting IPFS lookup of: '" + ipfsaddr + "'");
-        return ipfs.get(ipfsaddr).next();
-    }).then(file => {
-        // we only need one
-        if (file.value.content) {
-            async function getContent(source) {
-                var content = new Uint8Array()
-                var data = await source.next()
-                while (! data.done) {
-                    var newContent = new Uint8Array(content.length + data.value.length);
-                    newContent.set(content)
-                    newContent.set(data.value, content.length)
-                    content = newContent
-                    data = await source.next()
+        /*
+        * naïvely assume content type based on file extension
+        * TODO: this needs a fix
+        */
+        var contentType = '';
+        switch (gunaddr.slice(-1)[0].split('.', -1)[1].toLowerCase()) {
+            case 'html':
+            case 'htm':
+                contentType = 'text/html';
+                break;
+            case 'css':
+                contentType = 'text/css';
+                break;
+            case 'js':
+                contentType = 'text/javascript';
+                break;
+            case 'svg':
+                contentType = 'image/svg+xml';
+                break;
+            case 'ico':
+                contentType = 'image/x-icon';
+                break;
+        }
+        console.log("   +-- guessed contentType : " + contentType);
+
+        return getGunData(gunaddr).then(ipfsaddr => {
+            console.log("3. Starting IPFS lookup of: '" + ipfsaddr + "'");
+            return ipfs.get(ipfsaddr).next();
+        }).then(file => {
+            // we only need one
+            if (file.value.content) {
+                async function getContent(source) {
+                    var content = new Uint8Array()
+                    var data = await source.next()
+                    while (! data.done) {
+                        var newContent = new Uint8Array(content.length + data.value.length);
+                        newContent.set(content)
+                        newContent.set(data.value, content.length)
+                        content = newContent
+                        data = await source.next()
+                    }
+                    return content
                 }
-                return content
-            }
-            return getContent(file.value.content).then((content)=>{
-                console.log('4. Got a Gun-addressed IPFS-stored file: ' + file.value.path + '; content is: ' + typeof content);
-                // creating and populating the blob
-                var blob = new Blob(
-                    [content],
-                    {'type': contentType}
-                );
+                return getContent(file.value.content).then((content)=>{
+                    console.log('4. Got a Gun-addressed IPFS-stored file: ' + file.value.path + '; content is: ' + typeof content);
+                    // creating and populating the blob
+                    var blob = new Blob(
+                        [content],
+                        {'type': contentType}
+                    );
 
-                return new Response(
-                    blob,
-                    {
-                        'status': 200,
-                        'statusText': 'OK',
-                        'headers': {
-                            'Content-Type': contentType,
-                            'ETag': file.value.path,
-                            'X-Samizdat-Method': 'gun+ipfs',
-                            'X-Samizdat-ETag': file.value.path
+                    return new Response(
+                        blob,
+                        {
+                            'status': 200,
+                            'statusText': 'OK',
+                            'headers': {
+                                'Content-Type': contentType,
+                                'ETag': file.value.path,
+                                'X-Samizdat-Method': 'gun+ipfs',
+                                'X-Samizdat-ETag': file.value.path
+                            }
                         }
-                    }
-                );
-            })
-        };
-    });
-}
+                    );
+                })
+            };
+        });
+    }
 
 
-/* ========================================================================= *\
-|* === Publishing stuff                                                  === *|
-\* ========================================================================= */
-/*
- * these are used for adding content to IPFS and Gun
- */
+    /* ========================================================================= *\
+    |* === Publishing stuff                                                  === *|
+    \* ========================================================================= */
+    /*
+     * these are used for adding content to IPFS and Gun
+     */
 
 
-/**
- * adding stuff to IPFS
- * accepts an array of URLs
- * 
- * returns a Promise that resolves to an object mapping URLs to IPFS hashes
- */
-let addToIPFS = (resources) => {
-    return new Error("Not implemented yet.")
-    
-    return new Promise((resolve, reject) => {
+    /**
+     * adding stuff to IPFS
+     * accepts an array of URLs
+     * 
+     * returns a Promise that resolves to an object mapping URLs to IPFS hashes
+     */
+    let addToIPFS = (resources) => {
+        return new Error("Not implemented yet.")
         
-        console.log("Adding to IPFS...")
-        console.log("+-- number of resources:", resources.length)
-        var ipfs_addresses = {};
+        return new Promise((resolve, reject) => {
+            
+            console.log("Adding to IPFS...")
+            console.log("+-- number of resources:", resources.length)
+            var ipfs_addresses = {};
 
-        resources.forEach(function(res){
-            console.log("    +-- handling internal resource:", res)
+            resources.forEach(function(res){
+                console.log("    +-- handling internal resource:", res)
+                
+                ipfs.add(Ipfs.urlSource(res))
+                    .then((result) => {
+                        // add to the list -- this is needed to add stuff to Gun
+                        // result.path is just the filename stored in IPFS, not the actual path!
+                        // res holds the full URL
+                        // what we need in ipfs_addresses is in fact the absolute path (no domain, no scheme)
+                        var abs_path = res.replace(window.location.origin, '')
+                        ipfs_addresses[abs_path] = '/ipfs/' + result.cid.string
+                        console.log("Added to IPFS: " + abs_path + ' as ' + ipfs_addresses[abs_path])
+                        // if we seem to have all we need, resolve!
+                        if (Object.keys(ipfs_addresses).length === resources.length) resolve(ipfs_addresses);
+                    })
             
-            ipfs.add(Ipfs.urlSource(res))
-                .then((result) => {
-                    // add to the list -- this is needed to add stuff to Gun
-                    // result.path is just the filename stored in IPFS, not the actual path!
-                    // res holds the full URL
-                    // what we need in ipfs_addresses is in fact the absolute path (no domain, no scheme)
-                    var abs_path = res.replace(window.location.origin, '')
-                    ipfs_addresses[abs_path] = '/ipfs/' + result.cid.string
-                    console.log("Added to IPFS: " + abs_path + ' as ' + ipfs_addresses[abs_path])
-                    // if we seem to have all we need, resolve!
-                    if (Object.keys(ipfs_addresses).length === resources.length) resolve(ipfs_addresses);
-                })
-        
-        });
-    })
-}
+            });
+        })
+    }
 
-/**
- * verification that content pushed to IPFS
- * is, in fact, available in IPFS
- * 
- * a nice side-effect is that this will pre-load the content on
- * a gateway, which tends to be a large (and fast) IPFS node
- * 
- * this is prety naïve, in that it pulls the content from an ipfs gateway
- * and assumes all is well if it get a HTTP 200 and any content
- * 
- * that is, it does *not* check that the content matches what was pushed
- * we trust IPFS here, I guess
- * 
- * finally, we're using a regular fetch() instead of just going through our
- * ipfs object because our IPFS object might have things cached and we want
- * to test a completey independent route
- * 
- * takes a object mapping paths to IPFS addresses
- * and returns a Promise that resolves to true 
- */
-let verifyInIPFS = (ipfs_addresses) => {
-    return new Error("Not implemented yet.")
-    
-    return new Promise((resolve, reject) => {
-        console.log('Checking IPFS content against a gateway...')
-        console.log('+-- gateway in use: ' + IPFSGateway)
-        // get the list of IPFS addresses
-        var updatedPaths = Object.values(ipfs_addresses)
-        for (path in ipfs_addresses) {
-            // start the fetch
-            fetch(IPFSGateway + ipfs_addresses[path])
-                .then((response) => {
-                    ipfsaddr = response.url.replace(IPFSGateway, '')
-                    if (response.ok) {
-                        console.log('+-- verified: ' + ipfsaddr)
-                        var pathIndex = updatedPaths.indexOf(ipfsaddr)
-                        if (pathIndex > -1) {
-                            updatedPaths.splice(pathIndex, 1)
-                        }
-                        if (updatedPaths.length === 0) {
-                            console.log('All updates confirmed successful!')
-                            resolve(ipfs_addresses);
+    /**
+     * verification that content pushed to IPFS
+     * is, in fact, available in IPFS
+     * 
+     * a nice side-effect is that this will pre-load the content on
+     * a gateway, which tends to be a large (and fast) IPFS node
+     * 
+     * this is prety naïve, in that it pulls the content from an ipfs gateway
+     * and assumes all is well if it get a HTTP 200 and any content
+     * 
+     * that is, it does *not* check that the content matches what was pushed
+     * we trust IPFS here, I guess
+     * 
+     * finally, we're using a regular fetch() instead of just going through our
+     * ipfs object because our IPFS object might have things cached and we want
+     * to test a completey independent route
+     * 
+     * takes a object mapping paths to IPFS addresses
+     * and returns a Promise that resolves to true 
+     */
+    let verifyInIPFS = (ipfs_addresses) => {
+        return new Error("Not implemented yet.")
+        
+        return new Promise((resolve, reject) => {
+            console.log('Checking IPFS content against a gateway...')
+            console.log('+-- gateway in use: ' + config.ipfsGateway)
+            // get the list of IPFS addresses
+            var updatedPaths = Object.values(ipfs_addresses)
+            for (path in ipfs_addresses) {
+                // start the fetch
+                fetch(config.ipfsGateway + ipfs_addresses[path])
+                    .then((response) => {
+                        ipfsaddr = response.url.replace(config.ipfsGateway, '')
+                        if (response.ok) {
+                            console.log('+-- verified: ' + ipfsaddr)
+                            var pathIndex = updatedPaths.indexOf(ipfsaddr)
+                            if (pathIndex > -1) {
+                                updatedPaths.splice(pathIndex, 1)
+                            }
+                            if (updatedPaths.length === 0) {
+                                console.log('All updates confirmed successful!')
+                                resolve(ipfs_addresses);
+                            }
+                        } else {
+                            reject(new Error('HTTP error (' + response.status + ' ' + response.statusText + ' for: ' + ipfsaddr))
                         }
-                    } else {
-                        reject(new Error('HTTP error (' + response.status + ' ' + response.statusText + ' for: ' + ipfsaddr))
-                    }
-                })
-                .catch((err) => {
-                    // it would be nice to have the failed path here somehow
-                    // alternatively, updating updatedPaths with info on failed
-                    // requests might work
-                    reject(err)
-                })
-        }
-    })
-}
+                    })
+                    .catch((err) => {
+                        // it would be nice to have the failed path here somehow
+                        // alternatively, updating updatedPaths with info on failed
+                        // requests might work
+                        reject(err)
+                    })
+            }
+        })
+    }
 
 
-/**
- * example code for of adding content to IPFS, verifying it was successfully added,
- * and adding the new addresses to Gun (and verifying changes propagated) 
- * 
- * TODO: this should accept a URL, a Response, or a list of URLs,
- *       and handle stuff appropriately
- */
-let publishContent = (resource, user, password) => {
-    return new Error("Not implemented yet.")
-    
-    if (typeof resource === 'string') {
-        // we need this as an array of strings
-        resource = [resource]
-    } else if (typeof resource === 'object') {
-        if (!Array.isArray(resource)) {
-            // TODO: this needs to be implemented such that the Response is used directly
-            //       but that would require all called functions to also accept a Response
-            //       and act accordingly; #ThisIsComplicated
-            throw new Error("Handling a Response: not implemented yet")
+    /**
+     * example code for of adding content to IPFS, verifying it was successfully added,
+     * and adding the new addresses to Gun (and verifying changes propagated) 
+     * 
+     * TODO: this should accept a URL, a Response, or a list of URLs,
+     *       and handle stuff appropriately
+     */
+    let publishContent = (resource, user, password) => {
+        return new Error("Not implemented yet.")
+        
+        if (typeof resource === 'string') {
+            // we need this as an array of strings
+            resource = [resource]
+        } else if (typeof resource === 'object') {
+            if (!Array.isArray(resource)) {
+                // TODO: this needs to be implemented such that the Response is used directly
+                //       but that would require all called functions to also accept a Response
+                //       and act accordingly; #ThisIsComplicated
+                throw new Error("Handling a Response: not implemented yet")
+            }
+        } else {
+            // everything else -- that's a paddlin'!
+            throw new TypeError("Only accepts: string, Array of string, Response.")
         }
-    } else {
-        // everything else -- that's a paddlin'!
-        throw new TypeError("Only accepts: string, Array of string, Response.")
+            
+        // add to IPFS
+        var ipfsPromise = addToIPFS(resource)
+        return Promise.all([
+            // verify stuff ended up in IPFS
+            ipfsPromise.then(verifyInIPFS),
+            // add to Gun and verify Gun updates propagation
+            ipfsPromise.then((hashes) => {
+                addToGun(user, password, hashes)
+            })
+        ])
     }
-        
-    // add to IPFS
-    var ipfsPromise = addToIPFS(resource)
-    return Promise.all([
-        // verify stuff ended up in IPFS
-        ipfsPromise.then(verifyInIPFS),
-        // add to Gun and verify Gun updates propagation
-        ipfsPromise.then((hashes) => {
-            addToGun(user, password, hashes)
-        })
-    ])
-}
 
-/* ========================================================================= *\
-|* === Initialization                                                    === *|
-\* ========================================================================= */
+    /* ========================================================================= *\
+    |* === Initialization                                                    === *|
+    \* ========================================================================= */
 
-// we probably need to handle this better
-setup_ipfs();
+    // we probably need to handle this better
+    setup_ipfs();
 
-// initialize the SamizdatPlugins array
-if (!Array.isArray(self.SamizdatPlugins)) {
-    self.SamizdatPlugins = new Array()
-}
+    // initialize the SamizdatPlugins array
+    if (!Array.isArray(self.SamizdatPlugins)) {
+        self.SamizdatPlugins = new Array()
+    }
+
+    // and add ourselves to it
+    // with some additional metadata
+    self.SamizdatPlugins.push({
+        name: 'ipns+ipfs',
+        description: 'Decentralized resource fetching using IPNS for address resolution and IPFS for content delivery.',
+        version: 'COMMIT_UNKNOWN',
+        fetch: getContentFromIPNSAndIPFS,
+        publish: publishContent
+    })
 
-// and add ourselves to it
-// with some additional metadata
-self.SamizdatPlugins.push({
-    name: 'ipns+ipfs',
-    description: 'Decentralized resource fetching using IPNS for address resolution and IPFS for content delivery.',
-    version: 'COMMIT_UNKNOWN',
-    fetch: getContentFromIPNSAndIPFS,
-    publish: publishContent
-})
+// done with not poluting the global namespace
+})()
diff --git a/service-worker.js b/service-worker.js
index 1378414810dbe764ad5d72c1690409e37f3c2003..011202d61e03553d0a456a61c0885a252efcce0c 100644
--- a/service-worker.js
+++ b/service-worker.js
@@ -1,7 +1,7 @@
 /*
  * Samizdat Service Worker.
  *
- * Strategy (not fully implemented yet):
+ * Default strategy:
  *    1. Try to load from main website.
  *    2. If loading fails, load from Samizdat.
  *    3. If loading is too slow, load from Samizdat.
@@ -14,17 +14,37 @@ if (!Array.isArray(self.SamizdatPlugins)) {
     self.SamizdatPlugins = new Array()
 }
 
+// initialize the SamizdatConfig array
+// 
+// this also sets some sane defaults,
+// which then can be modified via config.js
+if (typeof self.SamizdatConfig !== 'object' || self.SamizdatConfig === null) {
+    self.SamizdatConfig = {
+        // how long do we wait before we decide that a plugin is unresponsive,
+        // and move on?
+        defaultPluginTimeout: 10000,
+        // plugins settings namespace
+        plugins: {}
+    }
+}
+
 // load the plugins
 //
 // order in which plugins are loaded defines the order
 // in which they are called!
-self.importScripts(
+try {
+    self.importScripts(
+        "./config.js",
         "./plugins/fetch.js",
         "./plugins/cache.js",
         "./plugins/gateway-ipns.js",
         "./plugins/gun-ipfs.js");
-
-console.log('(COMMIT_UNKNOWN) SamizdatPlugins.length:', self.SamizdatPlugins.length)
+} catch(e) {
+    // we only get a cryptic "Error while registering a service worker"
+    // unless we explicitly print the errors out in the console
+    console.error(e)
+    throw e
+}
 
 /**
  * fetch counter per clientId
@@ -246,7 +266,11 @@ let samizdatFetch = (plugin, url, reqInfo) => {
     // run the plugin
     return Promise.race([
         plugin.fetch(url),
-        promiseTimeout(10000, false, `Samizdat request using ${plugin.name} timed out.`)
+        promiseTimeout(
+            self.SamizdatConfig.defaultPluginTimeout,
+            false,
+            `Samizdat request using ${plugin.name} timed out after ${self.SamizdatConfig.defaultPluginTimeout}ms.`
+        )
     ])
 }