[28397] in Source-Commits
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"><username></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"><title></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"><confirm></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"><title></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"><help></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"><title></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"><output></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"><errors></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/*'))]
+)