diff --git a/sysctl.ini.template b/sysctl.ini.template
new file mode 100644
index 0000000..92aeb55
--- /dev/null
+++ b/sysctl.ini.template
@@ -0,0 +1,15 @@
+[LOGIN]
+nodename = The NAMES of your node - this is what it will respond to when, for example, running a command - [COMMAND>MyNode]
+username = The USERNAME of your node's Matrix account - username, NOT @username:example.not
+password = The PASSWORD of your node's Matrix account
+
+[MATRIX]
+sysctlChannel = The CHANNEL ID of the Sysctl room
+serverAddr = The SERVER ADDRESS of the Matrix server (i.e. matrix.org)
+serverWebAddr = The WEB ADDRESS of the Matrix server (usually the same as serverAddr. I use this because I don't have hairpinning.)
+admin = The username of the ADMINISTRATOR ACCOUNT - admin, NOT @admin:example.net
+
+[NODES]
+nodes = IP addresses of your nodes, separated with commas - 192.168.1.5,192.168.1.6,192.168.1.7 - THIS INCLUDES THE ONE THIS SCRIPT IS RUNNING ON
+nodeNames = The NAMES of your nodes, separated with commas - NodeOne,NodeTwo,NodeThree - THIS INCLUDES THE ONE THIS SCRIPT IS RUNNING ON
+nodeUsernames = The USERNAMES of your nodes, separated with commas - nodeone,nodetwo,nodethree - THIS INCLUDES THE ONE THIS SCRIPT IS RUNNING ON - nodeone NOT @nodeone:example.net
diff --git a/sysctl.py b/sysctl.py
new file mode 100644
index 0000000..1008ccd
--- /dev/null
+++ b/sysctl.py
@@ -0,0 +1,249 @@
+#!/bin/python3.7
+
+# (C) Innovation Science, Katie Martin, 2022
+
+import asyncio
+from nio import (AsyncClient, RoomMessageText)
+#import os
+#import time
+from ping3 import ping
+import urllib3
+import configparser
+
+
+nodename=""
+username=""
+password=""
+sysctlChannel=""
+serverAddr=""
+serverWebAddr=""
+admin=""
+loglevels=["Info", "Note", "Caution", "Warning", "Error", "FATAL", "EMERGENCY", "FAILURE"]
+
+nodes=[]
+nodeNames=[]
+nodeUsernames=[]
+nodeStatus=[]
+# Acknowledgements supresses the parser from sending the same warning over and over again.
+# For example, if node 1 is down, the first flag in the second array (counting from zero) is changed to true. This prevents parseStatus from sending the same warning.
+# 0 - Offline
+# 1 - Cannot get daemons
+# 2 - 
+# 10-19 - Each daemon (controlled by internal servers)
+acknowledgements=[]
+ContinueFlag=True
+FirstRun=True
+
+def loadConfig(): # TODO: Proper error checking here
+	global nodename
+	global username
+	global password
+	global sysctlChannel
+	global serverAddr
+	global serverWebAddr
+	global admin
+	global nodes
+	global nodeNames
+	global nodeUsernames
+	global nodeStatus
+	global acknowledgements
+
+	blankAcknowledgements = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False]
+
+	config = configparser.ConfigParser()
+	config.read('sysctl.ini')
+
+	try:
+		# Load login info
+		nodename=config['LOGIN']['nodename']
+		username=config['LOGIN']['username']
+		password=config['LOGIN']['password']
+
+		# Load Matrix server and room info
+		sysctlChannel=config['MATRIX']['sysctlChannel']
+		serverAddr=config['MATRIX']['serverAddr']
+		serverWebAddr=config['MATRIX']['serverWebAddr']
+		admin=config['MATRIX']['admin']
+
+		# Load node info
+		tempnodes=config['NODES']['nodes']
+		tempnodeNames=config['NODES']['nodeNames']
+		tempnodeUsernames=config['NODES']['nodeUsernames']
+
+		nodes=tempnodes.split(',')
+		nodeNames=tempnodeNames.split(',')
+		nodeUsernames=tempnodeUsernames.split(',')
+
+		# Prepare node online status and acknowledgements
+		for x in range(len(nodes)):
+			nodeStatus.append(False)
+			acknowledgements.append(blankAcknowledgements)
+	except KeyError:
+		print("KeyError encountered. This likely means your config.ini is missing or misconfigured.")
+		print("Please read the docs.")
+		exit(1)
+
+	print("Config loaded")
+
+# NOTE: This assumes that the user comes from the same address as the bot.
+async def fullUsername(user):
+	return "@" + user + ":" + serverAddr
+
+async def sendMessage(loglevel, message):
+	await client.room_send(
+                room_id=sysctlChannel + ":" + serverAddr,
+                message_type="m.room.message",
+                content={
+                        "msgtype": "m.text",
+                        "body": "[" + loglevels[loglevel] + "] " + message
+		}
+        )
+
+# NOTE: This can be inherently insecure. This assumes that the room is set
+# to have a required power level that only Admins and the Sysctl bot(s)
+# have. What I have below should be relatively fine, but I wouldn't trust
+# it with my car keys.
+async def parseMessage(room, event):
+	doParse = False
+	if (room.room_id == (sysctlChannel + ":" + serverAddr)):
+		for x in range(len(nodeUsernames)):
+			if(event.sender != await fullUsername(username)): # We don't want it to respond to itself.
+				if((event.sender == await fullUsername(admin)) or (event.sender == await fullUsername(nodeUsernames[x]))):
+					doParse = True
+					break
+
+	if doParse:
+		# I hate you Python.
+		bodyPreparse = (event.body).split(']')
+		if (bodyPreparse[0] == ('[COMMAND>' + nodename)):
+			await runCommand(event.sender, bodyPreparse[1])
+
+async def sendCommand(reciever, command):
+	await client.room_send(
+                room_id=sysctlChannel + ":" + serverAddr,
+                message_type="m.room.message",
+                content={
+                        "msgtype": "m.text",
+                        "body": "[COMMAND>" + reciever + "] " + command
+                }
+        )
+
+async def sendComm(reciever, command):
+	await client.room_send(
+                room_id=sysctlChannel + ":" + serverAddr,
+                message_type="m.room.message",
+                content={
+                        "msgtype": "m.text",
+                        "body": "[COMM>" + reciever + "] " + command
+                }
+        )
+
+async def runCommand(sender, command):
+	if (command[0] == " "): # Usually the command variable will have a space at the beginning.
+		command=command[1:]
+
+	# I hate Python again.
+	if (command == "test"):
+		await sendMessage(0, (sender + ", test recieved."))
+	#elif (command == ""):
+	#	await sendMessage(0, (sender + ""))
+	else:
+		await sendMessage(0, (sender + ", bad command.\nAvailable commands:\ntest"))
+	#await sendMessage(0, (sender + ", running "))
+
+async def hostIsOnline(ip):
+	response = ping(ip)
+
+	if response != False and response != None:
+		return True
+	return False
+
+async def getOnlineStatus():
+	global nodeStatus
+	global FirstRun
+	global acknowledgements
+
+	for x in range(len(nodes)):
+		if await hostIsOnline(nodes[x]):
+			nodeStatus[x]=True
+			if acknowledgements[x][0] or FirstRun:
+				await sendMessage(0, nodeNames[x] + " online.")
+			acknowledgements[x][0]=False
+		else:
+			nodeStatus[x]=False
+			if acknowledgements[x][0]==False:
+				await sendMessage(7, nodeNames[x] + " OFFLINE!")
+			acknowledgements[x][0]=True
+
+
+
+async def parseStatus():
+	global acknowledgements
+	x=0
+
+async def getDaemonStatus():
+	global acknowledgements
+
+	http = urllib3.PoolManager(retries=1, timeout=2.0)
+	for x in range(len(nodes)):
+		if nodeStatus[x]:
+			try:
+				r = http.request("GET", "http://" + nodes[x] + ":9050/servers/")
+			except urllib3.exceptions.ConnectTimeoutError:
+				o=0 # Do nothing. This is handled later.
+			if r.status == 200:
+				nodeDaemons = (r.data.decode('utf-8')).split('\n')
+
+				for y in range(len(nodeDaemons)):
+					s = http.request("GET", "http://" + nodes[x] + ":9050/servers/" + nodeDaemons[y])
+					if s.data.decode('utf-8') == "1":
+						if acknowledgements[x][10+y]:
+							await sendMessage(0, "Daemon '" + nodeDaemons[y] + "' online on " + nodeNames[x])
+						acknowledgements[x][10+y]=False
+					else:
+						if acknowledgements[x][10+y]==False:
+							await sendMessage(4, "Daemon '" + nodeDaemons[y] + "' offline on " + nodeNames[x])
+							acknowledgements[x][10+y]=True
+
+				if acknowledgements[x][1]:
+					await sendMessage(0, "Now able to get daemons - " + nodeNames[x])
+				acknowledgements[x][1]=False
+			else:
+				if acknowledgements[x][1]==False:
+					await sendMessage(4, "Could not get daemons - " + nodeNames[x])
+					acknowledgements[x][1]=True
+
+async def main():
+	global FirstRun
+	global ContinueFlag
+
+	await client.login(password)
+
+	await sendMessage(1, "Sysctl process for " + nodename + " started.")
+	await sendMessage(1, "Prepare startup checklist...")
+	await sendMessage(1, "Starting command listener...")
+	await client.sync()
+
+	client.add_event_callback(parseMessage, RoomMessageText)
+
+	while ContinueFlag:
+		try:
+			await client.sync(timeout=30000)
+			await getOnlineStatus()
+			await getDaemonStatus()
+			#await parseStatus() # This might do something someday.
+
+			if FirstRun:
+				await sendMessage(1, "Startup complete.")
+				FirstRun=False
+		except KeyboardInterrupt: # This makes it so CTRL+C begins the program exiting sequence, and so the program exits gracefully at any point during execution.
+			ContinueFlag = False
+			await sendMessage(3, "Sysctl main loop interrupted - " + nodename + " sysctl stop")
+			break
+
+	await client.close()
+
+loadConfig()
+client = AsyncClient("http://" + serverWebAddr, "@" + username + ":" + serverAddr)
+asyncio.get_event_loop().run_until_complete(main())
+