[28397] in Source-Commits

home help back first fref pref prev next nref lref last post

account-repair commit: Initial check-in of account repair wizard

daemon@ATHENA.MIT.EDU (Jonathan D Reed)
Wed Aug 20 13:14:36 2014

Date: Wed, 20 Aug 2014 13:14:28 -0400
From: Jonathan D Reed <jdreed@mit.edu>
Message-Id: <201408201714.s7KHES3X015384@drugstore.mit.edu>
To: source-commits@mit.edu

https://github.com/mit-athena/account-repair/commit/3f217703d38ade2e0c55e7c7b43be7fcbe31890e
commit 3f217703d38ade2e0c55e7c7b43be7fcbe31890e
Author: Jonathan Reed <jdreed@mit.edu>
Date:   Wed Aug 20 13:02:59 2014 -0400

    Initial check-in of account repair wizard
    
    The initial checkin of an account repair wizard, which can
    run a variety of actions in a guided format to assist the user
    in repairing problems with their account.

 README                                |   22 ++
 account-repair-wizard                 |  206 ++++++++++++++++
 account-repair-wizard.ui              |  437 +++++++++++++++++++++++++++++++++
 actions/repair-firefox-profile.action |    7 +
 actions/reset-dotfiles.action         |    6 +
 scripts/repair-firefox-profile.sh     |   12 +
 scripts/reset-dotfiles.sh             |   41 +++
 setup.cfg                             |    2 +
 setup.py                              |   18 ++
 9 files changed, 751 insertions(+), 0 deletions(-)

diff --git a/README b/README
new file mode 100644
index 0000000..896d95d
--- /dev/null
+++ b/README
@@ -0,0 +1,22 @@
+Configuring the Account Repair Wizard:
+
+The wizard looks for files in the "actions" directory
+(/usr/share/debathena-account-repair/actions) that end in .action.
+These are INI files, similar to .desktop files, with this format:
+  [AccountWizardAction]
+  title=My First Action
+  help=Some descriptive text about the action.  It can
+   span multiple lines if intended by one space.  Line breaks
+   are not honored.
+  confirm=Some optional text to be displayed on the dialog presented
+   to the user before they run the action.
+  script=some-script-name
+
+The script in 'script' must be in the "scripts" directory
+(/usr/lib/debathena-account-repair/scripts) and may be any executable.
+The script should exit with 0 to indicate success or nonzero to indicate
+failure.  Upon exit, the contents of stdout will be displayed to the
+user.  If the script exited non-zero, the contents of stderr will be
+displayed too.
+
+$DISPLAY is not set and graphical applications should not be used.
diff --git a/account-repair-wizard b/account-repair-wizard
new file mode 100755
index 0000000..b9eacbb
--- /dev/null
+++ b/account-repair-wizard
@@ -0,0 +1,206 @@
+#!/usr/bin/env python
+#
+
+from gi.repository import Gtk, GLib, Gdk
+
+import ConfigParser
+import errno
+import fnmatch
+import logging
+import os
+import pwd
+import sys
+import subprocess
+
+from optparse import OptionParser
+
+UI_FILE="/usr/share/debathena-account-repair/account-repair-wizard.ui"
+ACTIONS_DIR="/usr/share/debathena-account-repair/actions"
+SCRIPTS_DIR="/usr/lib/debathena-account-repair/scripts"
+
+STRIP_ENVIRON = ('DISPLAY', )
+
+logger = logging.getLogger('account-repair')
+stderr_handler = logging.StreamHandler()
+stderr_handler.setFormatter(
+    logging.Formatter("%(name)s %(levelname)s: %(message)s"))
+logger.addHandler(stderr_handler)
+
+class WizardActionException(Exception):
+    pass
+
+class WizardConfigException(Exception):
+    def __init__(self, filename, *args):
+        super(WizardConfigException, self).__init__(*args)
+        self.filename = filename
+
+class WizardAction:
+    _mandatory_options = ['title', 'script']
+    _section = 'AccountWizardAction'
+
+    def __init__(self, options, filename, builder):
+        config = ConfigParser.RawConfigParser({'help': 'Help not available.',
+                                               'title': None,
+                                               'script': None,
+                                               'confirm': None})
+        config.read(os.path.join(options.actions_dir, filename))
+        if not config.has_section(self._section):
+            raise WizardConfigException(filename,
+                                        "Not a valid action file.")
+        self.script = os.path.join(options.scripts_dir,
+                                   config.get(self._section, 'script'))
+        self.title = config.get(self._section, 'title')
+        self.help = config.get(self._section, 'help')
+        self.confirm = config.get(self._section, 'confirm')
+        if self.title is None or self.script is None:
+            raise WizardConfigException(filename,
+                                        "Not a valid action file.")
+        self.builder = builder
+
+    def run_callback(self, widget, data=None):
+        ask = self.builder.get_object("confirm_dialog")
+        # Prevent the dialog from staying huge if some actions have a
+        # confirmation and others don't.
+        ask.resize(*ask.get_default_size())
+        self.builder.get_object("confirm_title_label").set_text(self.title)
+        if self.confirm is None:
+            self.builder.get_object("confirm_text_label").set_visible(False)
+        else:
+            self.builder.get_object("confirm_text_label").set_visible(True)
+            self.builder.get_object("confirm_text_label").set_text(self.confirm)
+        confirmed = ask.run() == Gtk.ResponseType.YES
+        ask.hide()
+        if not confirmed:
+            return True
+        try:
+            env = {k:v for k,v in os.environ.items() if k not in STRIP_ENVIRON}
+            p = subprocess.Popen(self.script, shell=False, stdin=None,
+                                 env=env,
+                                 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+            (out, err) = p.communicate()
+            results = self.builder.get_object("output_dialog")
+            errors = p.returncode != 0
+            if len(out) < 1:
+                out = "The command {0} successfully.".format(
+                    "did not complete" if errors else "completed")
+            if len(err) < 1 and errors:
+                err = "Unknown error."
+            self.builder.get_object("output_title_label").set_text(self.title)
+            self.builder.get_object("output_label").set_text(out)
+            self.builder.get_object("errors_label").set_text(err)
+            self.builder.get_object("errors_label").set_visible(errors)
+            self.builder.get_object("errors_occurred").set_visible(errors)
+            results.run()
+            results.hide()
+        except OSError as e:
+            dlg = Gtk.MessageDialog(self.builder.get_object("main_window"),
+                                    Gtk.DialogFlags.MODAL,
+                                    Gtk.MessageType.ERROR,
+                                    Gtk.ButtonsType.CLOSE,
+                                    "Unable to run script: " + e.strerror)
+            dlg.run()
+            dlg.destroy()
+        return True
+
+    def help_dialog(self, widget, data=None):
+        dlg = self.builder.get_object("help_dialog")
+        dlg.resize(*dlg.get_default_size())
+        self.builder.get_object("help_title_label").set_text(self.title)
+        self.builder.get_object("help_text_label").set_text(
+            self.help.replace("\n", " "))
+        dlg.run()
+        dlg.hide()
+
+    def widget(self):
+        _internal_box = Gtk.Box()
+        _lbl = Gtk.Label(label=self.title, halign=Gtk.Align.START)
+        _internal_box.pack_start(_lbl, True, True, 2)
+        _run_button = Gtk.Button(label="Run")
+        _run_button.connect("clicked", self.run_callback)
+        _internal_box.pack_start(_run_button, False, False, 2)
+        _help_button = Gtk.Button(label="Help")
+        _help_button.connect("clicked", self.help_dialog)
+        _internal_box.pack_start(_help_button, False, False, 2)
+        _internal_box.show_all()
+        return _internal_box
+
+
+class SessionFixer:
+    def __init__(self, options):
+        self.logger = logging.getLogger('fix-my-session')
+        handler = logging.StreamHandler()
+        handler.setFormatter(logging.Formatter('%(levelname)s:%(message)s'))
+        self.logger.addHandler(handler)
+        if options.debug:
+            self.logger.setLevel(logging.DEBUG)
+        self.builder = Gtk.Builder()
+        try:
+            self.builder.add_from_file(options.ui_file)
+            self.logger.debug("Builder UI loaded")
+        except GLib.GError as e:
+            self.logger.exception("Unable to load UI:")
+            sys.exit(1)
+        self.builder.connect_signals(self)
+        pwent = pwd.getpwuid(os.getuid())
+        userlabel = "{0} ({1}@mit.edu)".format(pwent.pw_gecos.split(',')[0],
+                                               pwent.pw_name)
+        self.builder.get_object("username_label").set_text(userlabel)
+        self.builder.get_object("main_window").show_all()
+        self.options = options
+        self._populate_actions()
+
+    def _populate_actions(self):
+        self.actions = []
+        box = self.builder.get_object("actions_box")
+        action_files = []
+        try:
+            action_files = fnmatch.filter(os.listdir(self.options.actions_dir),
+                                          '*.action')
+        except OSError as e:
+            if e.errno not in (errno.ENOENT, errno.EACCES):
+                logger.warning("Unexpected error: %s", e.message)
+        for f in action_files:
+            try:
+                self.actions.append(WizardAction(self.options, f, self.builder))
+            except WizardConfigException as e:
+                logger.warning("Ignoring file '%s': %s",
+                               e.filename, e.message)
+        for a in self.actions:
+            box.pack_start(a.widget(), False, False, 2)
+        if len(action_files) < 1:
+            box.pack_start(self.builder.get_object("no_actions_label"),
+                           False, False, 2)
+
+    def quit(self):
+        Gtk.main_quit()
+
+    def on_quit_button_clicked(self, widget, data=None):
+        self.quit()
+
+    def on_main_window_window_state_event(self, widget, window_state_event):
+        # Today's lesson:  gi overrides for various enums do in fact
+        # override __nonzero__ to make this work:
+        if window_state_event.new_window_state & Gdk.WindowState.ICONIFIED:
+            # Discourage minimization.  We can't call deiconify here, because
+            # technically the window is not iconified when this event fires
+            widget.present()
+
+    def on_main_window_delete_event(self, event, data=None):
+        self.quit()
+
+
+if __name__ == '__main__':
+    parser = OptionParser()
+    parser.set_defaults(debug=False,
+                        ui_file=UI_FILE,
+                        actions_dir=ACTIONS_DIR,
+                        scripts_dir=SCRIPTS_DIR)
+    parser.add_option("--debug", action="store_true", dest="debug")
+    parser.add_option("--ui", action="store", type="string", dest="ui_file")
+    parser.add_option("--actions", action="store",
+                      type="string", dest="actions_dir")
+    parser.add_option("--scripts", action="store",
+                      type="string", dest="scripts_dir")
+    (options, args) = parser.parse_args()
+    app = SessionFixer(options)
+    Gtk.main()
diff --git a/account-repair-wizard.ui b/account-repair-wizard.ui
new file mode 100644
index 0000000..e1be5ae
--- /dev/null
+++ b/account-repair-wizard.ui
@@ -0,0 +1,437 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.16.1 -->
+<interface>
+  <requires lib="gtk+" version="3.0"/>
+  <object class="GtkWindow" id="main_window">
+    <property name="can_focus">False</property>
+    <property name="title" translatable="yes">Account Repair Wizard</property>
+    <property name="window_position">center</property>
+    <property name="default_width">640</property>
+    <property name="default_height">480</property>
+    <signal name="delete-event" handler="on_main_window_delete_event" swapped="no"/>
+    <signal name="window-state-event" handler="on_main_window_window_state_event" swapped="no"/>
+    <child>
+      <object class="GtkBox" id="box1">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="GtkLabel" id="label1">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="label" translatable="yes">Repair My Account</property>
+            <attributes>
+              <attribute name="font-desc" value="Sans Bold 14"/>
+            </attributes>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="padding">5</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel" id="username_label">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="label" translatable="yes">&lt;username&gt;</property>
+            <attributes>
+              <attribute name="font-desc" value="Sans Bold 12"/>
+            </attributes>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="padding">3</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="actions_box">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="halign">center</property>
+            <property name="orientation">vertical</property>
+            <child>
+              <placeholder/>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">2</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButtonBox" id="buttonbox1">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="layout_style">end</property>
+            <child>
+              <object class="GtkButton" id="quit_button">
+                <property name="label">gtk-quit</property>
+                <property name="use_action_appearance">False</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_stock">True</property>
+                <signal name="clicked" handler="on_quit_button_clicked" swapped="no"/>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="padding">2</property>
+            <property name="pack_type">end</property>
+            <property name="position">3</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
+  <object class="GtkDialog" id="confirm_dialog">
+    <property name="can_focus">False</property>
+    <property name="title" translatable="yes">Confirm</property>
+    <property name="default_width">250</property>
+    <property name="default_height">120</property>
+    <property name="type_hint">dialog</property>
+    <property name="transient_for">main_window</property>
+    <child internal-child="vbox">
+      <object class="GtkBox" id="dialog-vbox2">
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">2</property>
+        <child internal-child="action_area">
+          <object class="GtkButtonBox" id="dialog-action_area2">
+            <property name="can_focus">False</property>
+            <property name="layout_style">end</property>
+            <child>
+              <object class="GtkButton" id="button3">
+                <property name="label">gtk-no</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_stock">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="button2">
+                <property name="label">gtk-yes</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_stock">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="box3">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="margin_left">10</property>
+            <property name="margin_right">10</property>
+            <property name="margin_top">5</property>
+            <property name="margin_bottom">5</property>
+            <property name="orientation">vertical</property>
+            <property name="spacing">10</property>
+            <child>
+              <object class="GtkLabel" id="confirm_title_label">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label" translatable="yes">&lt;title&gt;</property>
+                <attributes>
+                  <attribute name="weight" value="bold"/>
+                </attributes>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="confirm_text_label">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="ypad">2</property>
+                <property name="label" translatable="yes">&lt;confirm&gt;</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="label4">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label" translatable="yes">Would you like to run this action?</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="-9">button3</action-widget>
+      <action-widget response="-8">button2</action-widget>
+    </action-widgets>
+  </object>
+  <object class="GtkDialog" id="help_dialog">
+    <property name="can_focus">False</property>
+    <property name="title" translatable="yes">Help</property>
+    <property name="window_position">center-on-parent</property>
+    <property name="default_width">320</property>
+    <property name="default_height">150</property>
+    <property name="type_hint">dialog</property>
+    <property name="transient_for">main_window</property>
+    <child internal-child="vbox">
+      <object class="GtkBox" id="dialog-vbox1">
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">2</property>
+        <child internal-child="action_area">
+          <object class="GtkButtonBox" id="dialog-action_area1">
+            <property name="can_focus">False</property>
+            <property name="layout_style">end</property>
+            <child>
+              <object class="GtkButton" id="button1">
+                <property name="label">gtk-ok</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="can_default">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_stock">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="box4">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="margin_left">10</property>
+            <property name="margin_right">10</property>
+            <property name="margin_top">5</property>
+            <property name="margin_bottom">5</property>
+            <property name="orientation">vertical</property>
+            <property name="spacing">10</property>
+            <child>
+              <object class="GtkLabel" id="help_title_label">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label" translatable="yes">&lt;title&gt;</property>
+                <attributes>
+                  <attribute name="weight" value="bold"/>
+                </attributes>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="help_text_label">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="ypad">2</property>
+                <property name="label" translatable="yes">&lt;help&gt;</property>
+                <property name="wrap">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="-5">button1</action-widget>
+    </action-widgets>
+  </object>
+  <object class="GtkDialog" id="output_dialog">
+    <property name="can_focus">False</property>
+    <property name="title" translatable="yes">Results</property>
+    <property name="type_hint">dialog</property>
+    <property name="transient_for">main_window</property>
+    <child internal-child="vbox">
+      <object class="GtkBox" id="dialog-vbox4">
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">2</property>
+        <child internal-child="action_area">
+          <object class="GtkButtonBox" id="dialog-action_area4">
+            <property name="can_focus">False</property>
+            <property name="layout_style">end</property>
+            <child>
+              <object class="GtkButton" id="button5">
+                <property name="label">gtk-ok</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_stock">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="box2">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="margin_left">10</property>
+            <property name="margin_right">10</property>
+            <property name="margin_top">10</property>
+            <property name="margin_bottom">10</property>
+            <property name="orientation">vertical</property>
+            <child>
+              <object class="GtkLabel" id="output_title_label">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="margin_bottom">10</property>
+                <property name="label" translatable="yes">&lt;title&gt;</property>
+                <attributes>
+                  <attribute name="weight" value="bold"/>
+                </attributes>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="output_label">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="margin_right">3</property>
+                <property name="margin_top">3</property>
+                <property name="margin_bottom">3</property>
+                <property name="xalign">0</property>
+                <property name="xpad">10</property>
+                <property name="label" translatable="yes">&lt;output&gt;</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="errors_occurred">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="margin_top">10</property>
+                <property name="xalign">0</property>
+                <property name="label" translatable="yes">The following errors occurred:</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="errors_label">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="margin_right">3</property>
+                <property name="margin_top">3</property>
+                <property name="margin_bottom">3</property>
+                <property name="xalign">0</property>
+                <property name="xpad">10</property>
+                <property name="label" translatable="yes">&lt;errors&gt;</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">3</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="-5">button5</action-widget>
+    </action-widgets>
+  </object>
+  <object class="GtkLabel" id="no_actions_label">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="margin_top">20</property>
+    <property name="label" translatable="yes">Could not find any actions for the wizard to run.
+Please try another workstation.</property>
+    <property name="justify">center</property>
+  </object>
+</interface>
diff --git a/actions/repair-firefox-profile.action b/actions/repair-firefox-profile.action
new file mode 100644
index 0000000..7086a09
--- /dev/null
+++ b/actions/repair-firefox-profile.action
@@ -0,0 +1,7 @@
+[AccountWizardAction]
+title=Repair Your Firefox Certificates
+help=This will repair your MIT Personal Certificates in Firefox, by
+ resetting the security database.  You will lose all personal
+ certificates stored in the database and will have to re-obtain them.
+confirm=After you run this, you will need to obtain new Firefox certificates.
+script=repair-firefox-profile.sh
diff --git a/actions/reset-dotfiles.action b/actions/reset-dotfiles.action
new file mode 100644
index 0000000..a6e83b3
--- /dev/null
+++ b/actions/reset-dotfiles.action
@@ -0,0 +1,6 @@
+[AccountWizardAction]
+title=Reset Your "Dotfiles"
+help=This will reset your "dotfiles" (configuration files) to the
+ default settings.  Any changes you made will be saved in a backup
+ directory so you may refer to them later.
+script=reset-dotfiles.sh
diff --git a/scripts/repair-firefox-profile.sh b/scripts/repair-firefox-profile.sh
new file mode 100755
index 0000000..b890f59
--- /dev/null
+++ b/scripts/repair-firefox-profile.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+set -e
+
+# A friendlier error message if they don't have a profile
+if ! [ -d "${HOME}/.mozilla/firefox" ]; then
+    echo "No Firefox profiles found; nothing to repair."
+    exit 1
+fi
+
+athrun infoagents zap-firefox-certs
+exit 0
diff --git a/scripts/reset-dotfiles.sh b/scripts/reset-dotfiles.sh
new file mode 100755
index 0000000..43dfc73
--- /dev/null
+++ b/scripts/reset-dotfiles.sh
@@ -0,0 +1,41 @@
+#!/bin/sh
+
+set -e
+
+CUSTOM_DOTFILES=".path .cshrc.mine .bashrc.mine .environment .bash_environment .startup.X .startup.tty"
+PROTOTYPE=/usr/prototype_user
+
+BACKUP_DIR="${HOME}/dotfiles-$(date +"%Y-%m-%d-%H%M.%S")"
+if ! mkdir "$BACKUP_DIR"; then
+    echo "Unable to back up dotfiles."
+    echo "Could not create backup directory." >&2
+    exit 1
+fi
+
+echo "Backing up dotfiles to: $BACKUP_DIR"
+echo
+
+for f in $CUSTOM_DOTFILES; do
+    if [ -f "${HOME}/$f" ]; then
+	mv -f "${HOME}/$f" "$BACKUP_DIR"
+	echo "Backed up $f"
+    fi
+done
+
+for f in $(ls $PROTOTYPE/.??*); do
+    theirs="${HOME}/$(basename "$f")"
+    echo -n "Checking $(basename "$f")..."
+    if [ -f "$theirs" ]; then
+	if diff -q "$theirs" "$f" > /dev/null 2>&1; then
+	    echo "OK"
+	    continue
+	else
+	    cp -f "$theirs" "$BACKUP_DIR"
+	    cp -f "$f" "${HOME}"
+	    echo "UPDATED (original backed up)"
+	fi
+    else
+	echo "MISSING (system default restored)"
+	cp -f "$f" "${HOME}"
+    fi
+done
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..d89108a
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,2 @@
+[install]
+install-scripts=/usr/lib/debathena-account-repair
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..22807cb
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,18 @@
+#!/usr/bin/env python
+
+from distutils.core import setup
+from glob import glob
+
+setup(name='debathena-account-repair',
+      version='1.0',
+      description='Account Repair Wizard',
+      author='Debathena Project',
+      author_email='debathena@mit.edu',
+      scripts=['account-repair-wizard'],
+      data_files=[('/usr/share/debathena-account-repair',
+                   ['account-repair-wizard.ui']),
+                  ('/usr/share/debathena-account-repair/actions',
+                   glob('actions/*')),
+                  ('/usr/lib/debathena-account-repair/scripts',
+                   glob('scripts/*'))]
+)

home help back first fref pref prev next nref lref last post