diff --git a/dylan-testing/config.json b/dylan-testing/config.json
new file mode 100644
index 0000000000000000000000000000000000000000..f30002be68497d2e94e014fceb4e354c4a5766f7
--- /dev/null
+++ b/dylan-testing/config.json
@@ -0,0 +1,4 @@
+{
+	"serviceUser":"uccportal",
+	"varPath":"/var/lib/uccportal"
+}
diff --git a/dylan-testing/core/init.js b/dylan-testing/core/init.js
new file mode 100644
index 0000000000000000000000000000000000000000..5d8204aa19617eb72548ec785f14b6ff1cf9f8c3
--- /dev/null
+++ b/dylan-testing/core/init.js
@@ -0,0 +1,102 @@
+var fs = require("fs");
+var cproc = require("child_process");
+
+function getUserIds(username){
+    var ids = {uid:NaN, gid:NaN};
+
+    try{
+        ids.uid = Number.parseInt(cproc.execFileSync("id", ["-u",username]));
+        ids.gid = Number.parseInt(cproc.execFileSync("id", ["-g",username]));
+        if(ids.uid == NaN || ids.gid == NaN)
+            throw new Error("The `id` command returned an invalid uid or gid");
+    }
+    catch(e){
+        throw new Error(
+            "Error looking up uid and gid of \"" + username + "\": "
+            + e.message
+        );
+    }
+
+    return ids;
+}
+
+function initConfig(configPath){
+    var rawConfig, config;
+
+    // Try to open config file
+    try {rawConfig = fs.readFileSync(configPath);}
+    catch(e){
+        throw new Error(
+            "Could not open config file \"" + configPath + "\": " + e.message
+        );
+    }
+    
+    // Try to parse config file as JSON
+    try {config = JSON.parse(rawConfig);}
+    catch(e){
+        throw new Error(
+            "\"" + configPath + "\" is not in a valid JSON format: "
+            + e.message
+        );
+    }
+
+    // Verify options provided in config file
+    try {
+        // varPath: verify that it exists and is a directory
+        try {fs.readdirSync(config.varPath);}
+        catch(e){
+            throw new Error(
+                "varPath: the provided directory could not be opened: "
+                + e.message
+            );
+        }
+
+        // serviceUser: verify that a user and group of this name exist,
+        // then set serviceUid and serviceGid for later lookup
+        var ids = getUserIds(config.serviceUser);
+        config.serviceUid = ids.uid;
+        config.serviceGid = ids.gid;
+
+    }
+    catch(e){
+        throw new Error(
+            "Error while validating \"" + configPath + "\": " + e.message
+        );
+    }
+
+    return config;
+}
+
+module.exports = function main(){
+	try {
+        // Verify that we're currently running as root
+        if(process.getuid() != 0){
+            throw new Error(
+                "UCCPortal must be run as root to function correctly.\n"
+                + "If you're worried about security, privileges will be dropped "
+                + "to that of the serviceUser listed in config.json, once "
+                + "initialisation is complete. An exception to this is "
+                + "auth-service.js, which requires it for PAM to function "
+                + "correctly."
+            );
+        }
+        
+        // Attempt to load config.json, or fail
+        var config = initConfig("config.json");
+        console.dir(config);
+        
+        // Launch services
+        var nodejs = process.argv[0];
+        var spawnOpts = {
+            stdio:"inherit", uid:config.serviceUid, gid:config.serviceGid
+        };
+        cproc.spawn(nodejs, ["core/pam-service.js"], spawnOpts);
+        cproc.spawn(nodejs, ["core/proxy-service.js"], spawnOpts);
+        cproc.spawn(nodejs, ["core/login-service.js"], spawnOpts);
+    }
+    catch(e){
+        //console.error("Fatal Error: " + e.message);
+        throw e;
+        process.exitCode = 1;
+    }
+}
diff --git a/dylan-testing/core/login-service.js b/dylan-testing/core/login-service.js
new file mode 100644
index 0000000000000000000000000000000000000000..664ade79430d72f39f58816a0d3eb18f1b02979d
--- /dev/null
+++ b/dylan-testing/core/login-service.js
@@ -0,0 +1 @@
+while(true);
diff --git a/dylan-testing/core/pam-service.js b/dylan-testing/core/pam-service.js
new file mode 100644
index 0000000000000000000000000000000000000000..664ade79430d72f39f58816a0d3eb18f1b02979d
--- /dev/null
+++ b/dylan-testing/core/pam-service.js
@@ -0,0 +1 @@
+while(true);
diff --git a/dylan-testing/core/proxy-service.js b/dylan-testing/core/proxy-service.js
new file mode 100644
index 0000000000000000000000000000000000000000..664ade79430d72f39f58816a0d3eb18f1b02979d
--- /dev/null
+++ b/dylan-testing/core/proxy-service.js
@@ -0,0 +1 @@
+while(true);
diff --git a/dylan-testing/core/user-session-worker.js b/dylan-testing/core/user-session-worker.js
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/dylan-testing/package.json b/dylan-testing/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..3c5ca475175069335baac089084da3f76d2ff188
--- /dev/null
+++ b/dylan-testing/package.json
@@ -0,0 +1,15 @@
+{
+  "name": "ucc-portal",
+  "version": "0.0.1",
+  "description": "An easy to use self-service portal for UCC members to configure their shell, mail forwarding address, and other things that would normally require knowledge of the Linux command line.",
+  "main": "uccportal.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://gitlab.ucc.asn.au/frekk/uccportal.git"
+  },
+  "author": "dylanh333; frekk",
+  "license": "GPL-3.0"
+}
diff --git a/dylan-testing/uccportal.js b/dylan-testing/uccportal.js
new file mode 100755
index 0000000000000000000000000000000000000000..3c8e7716fe24292e308a52c1233ead6dc029f014
--- /dev/null
+++ b/dylan-testing/uccportal.js
@@ -0,0 +1,2 @@
+#!/usr/bin/nodejs
+require("./core/init.js")();