diff --git a/actions.py b/actions.py
new file mode 100644
index 0000000..1fb6ee6
--- /dev/null
+++ b/actions.py
@@ -0,0 +1,18 @@
+"""
+This module mostly serves as a shim between the elite_api library and the rest of our program.
+"""
+
+from elite_api import companion
+from elite_api.inara import InaraSession
+
+
+def do_logins(settings):
+  inara_session = InaraSession(settings.get('inara', 'username'), settings.get('inara', 'password'))
+  companion.login(settings.get('ed_companion', 'username'), settings.get('ed_companion', 'password'))
+  return inara_session
+
+
+def update_inara(inara_session):
+  data = companion.get_data()
+  inara_session.update_credits(data['commander']['credits'])
+  inara_session.update_location(data['lastSystem']['name'])
diff --git a/gui.py b/gui.py
new file mode 100644
index 0000000..2c0159d
--- /dev/null
+++ b/gui.py
@@ -0,0 +1,146 @@
+import actions
+from datetime import datetime
+import Tkinter as tk
+import tkSimpleDialog, tkMessageBox
+import utils
+
+class UpdateWindow(object):
+  def __init__(self, parent, settings):
+    self.parent = parent
+    if settings is not None:
+      self.settings = settings
+    else:
+      self.settings = utils.update_settings(self._render_config_dialog, self.settings)
+    self.frame = tk.Frame(parent)
+    self.frame.pack(expand=True, fill=tk.BOTH)
+
+    self.message = tk.StringVar()
+    self.message.set("Click Update to update!")
+    message_label = tk.Label(self.frame, textvariable=self.message)
+    message_label.grid(columnspan=2, padx=20, pady=20)
+
+    self.update_button = tk.Button(self.frame, text="Update", height=2, width=4,
+                                   command=self._update_inara)
+    self.update_button.grid(row=1, column=0, pady=10)
+
+    config_button = tk.Button(self.frame, text="Config", height=1, width=2,
+                              command=self._update_settings)
+    config_button.grid(row=1, column=1, sticky=tk.E+tk.S, padx=5, pady=5)
+
+    self._try_login()
+
+  def _update_inara(self, second_try=False):
+    self.message.set("Updating, please wait...")
+    self.parent.update()
+    try:
+      actions.update_inara(self.session)
+      self.message.set("Update successful! (Last update: %s)" %
+                       datetime.now().isoformat(' ')[:16])
+    except:
+      if second_try:
+        self.message.set("Error updating! Double-check your config,\nor try again later.")
+      else:
+        # We don't use self._try_login() here because we don't want to disable the update button in this case.
+        self.session = actions.do_logins(self.settings)
+        self._update_inara(True)
+
+  def _update_settings(self):
+    self.settings = utils.update_settings(self._render_config_dialog, self.settings)
+    self._try_login()
+
+  def _try_login(self):
+    try:
+      self.session = actions.do_logins(self.settings)
+      self.update_button['state'] = tk.NORMAL
+    except:
+      self.update_button['state'] = tk.DISABLED
+      self.message.set("Error logging in. Double-check your config!")
+
+  def _render_config_dialog(self, settings):
+    dialog = ConfigDialog(self.frame, settings)
+
+
+class ShipFrame(tk.Frame):
+  INSURANCE = {'0': .05, '1': .04, '2': .02}
+  
+  def __init__(self, parent, *args, **kwargs):
+    tk.Frame.__init__(self, parent, *args, **kwargs)
+    self.ship_data = []
+    name_header = tk.Label(self, text="Ship Name")
+    name_header.grid()
+    rebuy_header = tk.Label(self, text="Rebuy")
+    name_header.grid(column=1)
+    value_header = tk.Label(self, text="Value")
+    name_header.grid(column=2)
+
+  def add_ship(self, data):
+    """
+    'data' should contain the following keys: name, id, rebuy, insurance, date, main, star, description, config, image.
+    Some of these are probably blank, but we need to propagate all of them to the Inara form eventually.
+    """
+    if not self._validate_data(data):
+      return False
+    label = tk.Label(self, text="%s:" % data['name'])
+    label.grid()
+    entry = tk.Entry(self, text=value)
+    entry.grid(column=1)
+
+    ship_value = int(data['rebuy']) / INSURANCE[data['insurance']]
+    value_label = tk.Label(self, text=str(ship_value))
+    value_label.grid(column=2)
+
+    data['rebuy_entry'] = entry
+    self.ship_data.append(data)
+
+  def _validate_date(self, data):
+    return all(key in data.keys() for key in
+               ('name', 'id', 'rebuy', 'insurance', 'date', 'main', 'star', 'description', 'config', 'image'))
+      
+
+
+class ConfigDialog(tkSimpleDialog.Dialog):
+  def __init__(self, parent, settings, title="Authentication Data"):
+    self.settings = settings
+    self.entries = []
+    self.data = []
+    tkSimpleDialog.Dialog.__init__(self, parent, title)
+    
+  def body(self, parent):
+    i = 0
+    values = []
+
+    for section, value in (('ed_companion', 'username'),
+                           ('ed_companion', 'password'),
+                           ('inara', 'username'),
+                           ('inara', 'password')):
+      if self.settings.has_option(section, value):
+        values.append(self.settings.get(section, value))
+      else:
+        values.append("")
+        
+    for field in ("Elite Username (email address):",
+                  "Elite Password:",
+                  "Inara Username:",
+                  "Inara Password:"):
+      label = tk.Label(parent, text=field)
+      label.grid(row=i, column=0, sticky=tk.W)
+      entry = tk.Entry(parent, width=30)
+      entry.insert(0, values[i])
+      entry.grid(row=i, column=1, sticky=tk.E)
+      self.entries.append(entry)
+      i += 1
+    return self.entries[0]
+
+  def validate(self):
+    for entry in self.entries:
+      if entry.get().strip() == "":
+        tkMessageBox.showwarning("Missing Data",
+                                 "You must provide a value for every field.")
+        return False
+    return True
+
+  def apply(self):
+    self.settings.set('ed_companion', 'username', self.entries[0].get().strip())
+    self.settings.set('ed_companion', 'password', self.entries[1].get().strip())
+    self.settings.set('inara', 'username', self.entries[2].get().strip())
+    self.settings.set('inara', 'password', self.entries[3].get().strip())
diff --git a/update_inara.py b/update_inara.py
index 0af4eda..954a15e 100755
--- a/update_inara.py
+++ b/update_inara.py
@@ -1,9 +1,8 @@
 #!/usr/bin/python
 
+import actions
 import argparse
-from datetime import datetime
-from elite_api import companion
-from elite_api.inara import InaraSession
+import gui
 import Tkinter as tk
 import utils
 
@@ -12,63 +11,12 @@ arg_parser.add_argument("--no-gui",
                         help="Just update and report to the command line.",
                         action="store_false", dest="gui")
 
-
-def do_logins(settings):
-  inara_session = InaraSession(settings.get('inara', 'username'), settings.get('inara', 'password'))
-  companion.login(settings.get('ed_companion', 'username'), settings.get('ed_companion', 'password'))
-  return inara_session
-
-
-def update_inara(inara_session):
-  data = companion.get_data()
-  inara_session.update_credits(data['commander']['credits'])
-  inara_session.update_location(data['lastSystem']['name'])
-
-
-class UpdateWindow(object):
-  def __init__(self, parent, settings):
-    self.parent = parent
-    self.settings = settings
-    self.frame = tk.Frame(parent)
-    self.frame.pack(expand=True, fill=tk.BOTH)
-
-    self.message = tk.StringVar()
-    self.message.set("Click Update to update!")
-    message_label = tk.Label(self.frame, textvariable=self.message)
-    message_label.grid(columnspan=2, padx=20, pady=20)
-
-    self.update_button = tk.Button(self.frame, text="Update", height=2, width=4,
-                                   command=self._update_inara)
-    self.update_button.grid(row=1, column=0, pady=10)
-
-    config_button = tk.Button(self.frame, text="Config", height=1, width=2,
-                              command=self._update_settings)
-    config_button.grid(row=1, column=1, sticky=tk.E+tk.S, padx=5, pady=5)
-
-    self._try_login()
-
-  def _update_inara(self):
-    self.message.set("Updating, please wait...")
-    self.parent.update()
-    try:
-      update_inara(self.session)
-      self.message.set("Update successful! (Last update: %s)" %
-                       datetime.now().isoformat(' ')[:16])
-    except:
-      self.message.set("Error updating! Double-check your config,\nor try again later.")
-      
-  def _update_settings(self):
-    self.settings = utils.update_settings(True, self.parent, self.settings)
-    self._try_login()
-
-  def _try_login(self):
-    try:
-      self.session = do_logins(self.settings)
-      self.update_button['state'] = tk.NORMAL
-    except:
-      self.update_button['state'] = tk.DISABLED
-      self.message.set("Error logging in. Double-check your config!")
-
+def _settings_prompt_cli(settings):
+  settings.set('ed_companion', 'username', raw_input("Elite Username (email address): "))
+  settings.set('ed_companion', 'password', raw_input("Elite Password: "))
+  settings.set('inara', 'username', raw_input("Inara Username: "))
+  settings.set('inara', 'password', raw_input("Inara Password: "))
+  print "To change these settings later, edit " + filename
 
 def main():
   args = arg_parser.parse_args()
@@ -76,13 +24,16 @@ def main():
   if args.gui:
     root = tk.Tk()
     root.wm_title("Inara Updater")
-    settings = utils.get_settings(True, root)
-    app = UpdateWindow(root, settings)
+    settings = utils.get_settings()
+    app = gui.UpdateWindow(root, settings)
     root.mainloop()
+
   else:
-    settings = utils.get_settings(False)
-    inara_session = do_logins(settings)
-    update_inara(inara_session)
+    settings = utils.get_settings()
+    if settings is None:
+      util.update_settings(_settings_prompt_cli, settings)
+    inara_session = actions.do_logins(settings)
+    actions.update_inara(inara_session)
     print("Inara updated!")
 
 if __name__ == '__main__':
diff --git a/utils.py b/utils.py
index b0c72a5..d127479 100644
--- a/utils.py
+++ b/utils.py
@@ -1,7 +1,5 @@
 from ConfigParser import ConfigParser
 import os
-import Tkinter as tk
-import tkSimpleDialog, tkMessageBox
 import platform
 
 def get_config_dir(make=False):
@@ -15,91 +13,35 @@ def get_config_dir(make=False):
 def get_settings(use_gui=True, parent=None):
   """
   Try to read the settings from file into ConfigParser object.
-  If the config file isn't found, initialize it.
+  If the config file isn't found, return None.
   """
   filename = os.path.join(get_config_dir(), 'settings.conf')
   settings = ConfigParser()
   
   if os.path.isfile(filename):
     settings.read(filename)
+    return settings
   else:
     try:
       os.makedirs(get_config_dir())
     except:
       pass
+    return None
 
-    settings = update_settings(use_gui, parent)
-
-  return settings
-
-
-def update_settings(gui=True, parent=None, settings=None):
+def update_settings(config_func, settings=None):
+  """
+  This function will initialize settings if it is None, call the passed function
+  with the settings object as a parameter, then write the settings to the config
+  file.
+  """
   if settings is None:
     settings = ConfigParser()
     settings.add_section('ed_companion')
     settings.add_section('inara')
-  if gui:
-    dialog = ConfigDialog(parent, settings)
-  else:
-    _settings_prompt_cli(settings)
-    print "To change these settings later, edit " + filename
 
+  config_func(settings)
+    
   with open(os.path.join(get_config_dir(), 'settings.conf'), 'wb') as f:
     settings.write(f)
 
   return settings
-
-
-def _settings_prompt_cli(settings):
-  settings.set('ed_companion', 'username', raw_input("Elite Username (email address): "))
-  settings.set('ed_companion', 'password', raw_input("Elite Password: "))
-  settings.set('inara', 'username', raw_input("Inara Username: "))
-  settings.set('inara', 'password', raw_input("Inara Password: "))
-
-
-class ConfigDialog(tkSimpleDialog.Dialog):
-  def __init__(self, parent, settings, title="Authentication Data"):
-    self.settings = settings
-    self.entries = []
-    self.data = []
-    tkSimpleDialog.Dialog.__init__(self, parent, title)
-    
-  def body(self, parent):
-    i = 0
-    values = []
-
-    for section, value in (('ed_companion', 'username'),
-                           ('ed_companion', 'password'),
-                           ('inara', 'username'),
-                           ('inara', 'password')):
-      if self.settings.has_option(section, value):
-        values.append(self.settings.get(section, value))
-      else:
-        values.append("")
-        
-    for field in ("Elite Username (email address):",
-                  "Elite Password:",
-                  "Inara Username:",
-                  "Inara Password:"):
-      label = tk.Label(parent, text=field)
-      label.grid(row=i, column=0, sticky=tk.W)
-      entry = tk.Entry(parent, width=30)
-      entry.insert(0, values[i])
-      entry.grid(row=i, column=1, sticky=tk.E)
-      self.entries.append(entry)
-      i += 1
-    return self.entries[0]
-
-  def validate(self):
-    for entry in self.entries:
-      if entry.get().strip() == "":
-        tkMessageBox.showwarning("Missing Data",
-                                 "You must provide a value for every field.")
-        return False
-    return True
-
-  def apply(self):
-    self.settings.set('ed_companion', 'username', self.entries[0].get().strip())
-    self.settings.set('ed_companion', 'password', self.entries[1].get().strip())
-    self.settings.set('inara', 'username', self.entries[2].get().strip())
-    self.settings.set('inara', 'password', self.entries[3].get().strip())