#!/usr/bin/python # -*- coding: utf-8 -*- # vital_linux_tweaks.v.2.0.py # 2007 Nov 09 . ccr u"""*vital_linux_tweaks.py* updates your Linux configuration. Copyright © 2007 Charles Curtis Rhode This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. A copy of the GNU General Public License is available as /usr/share/common-licenses/GPL in the Debian GNU/Linux distribution or on the World Wide Web at http://www.gnu.org/copyleft/gpl.html. You can also obtain it by writing to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Charles Curtis Rhode, CRhode@LacusVeris, 1518 N 3rd, Sheboygan, WI 53081 This script changes the look-and-feel of bash, GNOME, Firefox, et al. It demonstrates how messy life in a GUI world really is. Rather than configure myriads upon myriads of options, settings, and properties via the GUI interfaces of various applications, this script tweaks their Gnome config entries and their text-based configuration files, which are scattered throughout the root and user directories. This script is run from the command line. It solicits no input from the console. Obviously no single script can set up a Gnome/Linux desktop environment ideally for all users or even for any two users. This script sets up my environment for me. You may be able to use parts of it. Obviously this script is good only for specific versions of software and is dangerous to run unless you are absolutely certain what levels are installed. It was developed for Debian Gnu/Linux 4.0 r1 _Etch_ i386, but v.2.0 is for Debian Gnu/Linux 5.0 r1 _Lenny_. o It makes look-and-feel portable between user accounts and between computers running identical software-revision levels. o It makes recovery of look-and-feel semi-reliable and quick after reloads. o It is more-or-less stateless. It can be run and yet re-run when in doubt about the current state of affairs. o By showing what changes are made where, it documents those changes. o It is ad hoc. No-body and no-thing generates a unified log of the changes you make through the GUI interfaces. Instead, you have to scribble each change down, research the impact of it, and replicate its effect on sundry config files. One may ask whence the need for yet another configuration tool like *vital_linux_tweaks.py* when one might just as well use *debconf*. That is a very good question! The answer has much to do with remembering which things you changed as opposed to those you didn't. Also, it has to do with documenting in one place what your changes really do, which this script does. Users should read the code below the comment, \"Mainline begins here.\" Those who understand the why's and wherefore's of this script are encouraged to use it and adapt it to their own needs. To reiterate: This script employs DANGEROUS techniques. Please use it with an informed level of care. Particularly, change the list of USERS, the list of HOSTS, and the global constants, below. Thank you for trying *vital_linux_tweaks.py*. -- ccr \"I hate to advocate drugs, alcohol, violence or insanity to anyone, but they've always worked for me.\" -- Hunter S. Thompson PS: There are aspects of configuration beyond those that can be addressed by this script. o The setups for certain packages ought to be brought forward (ported) from an old PC rather than be tweaked or overlaid. Copying all the files in a user's \"home\" directory from the previous system takes care of this mostly. Pay special attention, though, to any packages that run at root authority. *fetchmail* may be the prime example. Its config file is in the *root* directory. o Note: *HylaFAX* emails monthly reports (and notice of FAXes received) to \"faxmaster.\" *exim4* is typically configured to route all outbound mail through your ISP's SmartHost without attempting any local delivery. Thus, you need to configure at least an alias for \"faxmaster\" inbound eMail at your ISP. Here are the packages I use and assume to be installed: ------------- Configuration ------------- o printconf o synaptic -------------- Communications -------------- o curl o fetchmail o flashplugin-nonfree o gnome-ppp o gnubiff o ntp o pan o pcmcia-cs o procmail o rblcheck o rsync o ssh o update-mgr o update-notifier o wireless-tools o wpasupplicant ----------- Development ----------- o automake o binutils o dpkg-dev o dselect o firebug o g++ o linux-kernel-headers o make o mtools o python-gtk-dev ------ Editor ------ o emacs o mc o python-mode o trash-cli ----------- Look & Feel ----------- o boinc_client o boinc_manager o clamav o devilspie o esound o esound-clients o fam o gnome-audio o libesd-alsa0 o rhythmbox o ttf-bitstream-vera o ttf-opensymbol ------------ Productivity ------------ o dcraw o exiv2 o gimp o gnomebaker o gnucash o hylafax o librsvg2-bin o netpbm o openoffice.org o xsane ----------- Third Party ----------- o elementtree from http://effbot.org/downloads/#elementtree o iniparse from http://code.google.com/p/iniparse/ o metar from http://pypi.python.org/pypi/metar/1.3.0 o prefbar from http://prefbar.mozdev.org """ # 2009 Feb 25 . ccr . Revised for Debian 5.0 (Lenny). # 2007 Jul 22 . ccr . Revert to dialup. from __future__ import division import sys import os import stat import codecs import time import datetime import re import shlex import subprocess import pwd import gconf import StringIO import urllib2 import base64 try: import iniparse IniFileParser = iniparse # from http://code.google.com/p/iniparse/ # *iniparse* is *not* part of the standard Python distribution. # However, it preserves the comments and section ordering in # config files, which it reads and writes. The *ConfigParser* in # the standard distribution does not. Many Linux config files # have embedded commentary that should be conserved against the # probability of having to do maintenance on them. except ImportError: import ConfigParser IniFileParser = ConfigParser PY_VER = sys.version if PY_VER[:3] in ['2.5']: import xml.etree.ElementTree ElementTree = xml.etree.ElementTree else: import elementtree.ElementTree # from http://effbot.org/zone/element-index.htm ElementTree = elementtree.ElementTree ZERO = 0 SPACE = ' ' NULL = '' NUL = '\x00' NA = -1 def when(*circumstances): """Protects invocation of each mainline component. This function returns true when the situation constant (below) covers any of the instant circumstances the function was called with. You change the situation (here) to allow execution of different selections of mainline components. """ situation = [ # 'obsolete', # 'long', # 'configuration', # 'look_and_feel', # 'communications', # 'news', # 'mail', # 'browser', # 'editor', # 'printing', # 'fax', # 'sound', # 'personal', # 'desktop_icons', 'development', ] quals = set(situation).intersection(circumstances) result = bool(quals) return result class User(object): """Record of ID for each user. o *name* is the username. name='xxx' name='CRhode' # for example. o *real_name* is the human-readable name of the user for the headers of newsgroup postings and possibly outbound eMail. real_name='Xxx Xxx' real_name='Chuck Rhode' # for example. o *email* is the inbound eMail address of the user. email='xxx@xxx.xxx' email='CRhode@LacusVeris.com' # for example. o *sig* is a shell command that prints a signature suitable for appending to outbound eMail. sig='xxx xxx' sig='echo -e --\nChuck Rhode' # default. o *wallpaper* is the path to a *.jpg file suitable for the background of the user's desktop. wallpaper='xxx.jpg' wallpaper='/home/crhode/photo_album/background.jpg' # for example. """ def __init__( self, name, real_name, email, sig=None, wallpaper=NULL, wallpaper_style = 'scaled', background_colors=('#006699', '#0099CC'), ): self.name = name self.real_name = real_name self.email = email if sig is None: self.sig = 'echo -e --\n%s' % self.real_name else: self.sig = sig self.wallpaper = wallpaper self.wallpaper_style = wallpaper_style self.background_colors = background_colors pwdb = pwd.getpwnam(self.name) self.uid = pwdb.pw_uid self.gid = pwdb.pw_gid return class Host(object): """ Record of ID for each PC. This is for initializing the */etc/hosts* file and */etc/network/interfaces*: o *host_name* is the mnemonic name of the PC on the local-area-network (LAN). host_name='xxx' host_name='loki' # for example o *static_ip* is the IP-quad address of the PC on the LAN. static_ip='255.255.255.255' static_ip='192.168.1.20' # for example. """ def __init__(self, host_name, static_ip, **attribs): self.host_name = host_name self.static_ip = static_ip self.attribs = attribs self.gateway = GATEWAY self.fqdn = '%s.%s' % (self.host_name, FQDN) return # ----- # You need to change these configuration global constants to reflect # your situation, not mine: # ----- # Your domain name: DOMAIN = 'LacusVeris' # This script plugs in this value wherever the domain name should # appear in various config files -- such as that of the mail # transfer agent (MTA). # DOMAIN = 'xxx' # DOMAIN = 'LacusVeris' # for example. # The fully-qualified domain name: FQDN = '%s.com' % DOMAIN # This script plugs in this value wherever the fully-qualified # domain name should appear in various config files. # FQDN = 'xxx.xxx' # FQDN = 'LacusVeris.com' # for example. # The user's Web site: WWW = 'www.%s' % FQDN # This script plugs in this value where directed -- such as in the # newsgroup signature block. # WWW = 'www.xxx.xxx' # WWW = 'www.LacusVeris.com' # for example. # The IP addr of the Internet gateway: GATEWAY = '192.168.1.1' # This is the IP-quad address of a local router (if any); # otherwise, it is the address of the Internet Service Provider's # (the ISPs) router. It is used to initialize # */etc/network/interfaces*. # GATEWAY = '255.255.255.255' # GATEWAY = '192.168.1.1' # for example. # Your username and password for authenticating outbound mail: SMARTHOST_USER = 'mail.%s' % FQDN SMARTHOST_PASSWORD = '***' # This is the ID required by your ISP to accept your outbound # eMail. It is used in the MTA configuration. # SMARTHOST_USER = 'mail.xxx.xxx'; SMARTHOST_PASSWORD = '***' # SMARTHOST_USER = 'mail.LacusVeris.com'; SMARTHOST_PASSWORD = '***' # for example. # Your username and password for FTP access to maintain your Web site: FTP_USER = 'lacusver' FTP_PASSWORD = '***' # This is the ID required by your Web-hosting provider. It is used # to configure default FTP access such as through Midnight # Commander (*mc*). # FTP_USER = 'xxx'; FTP_PASSWORD = '***' # FTP_USER = 'lacusver'; FTP_PASSWORD = '***' # for example. # IDs for the GUI users you want to enable on this PC: USERS = [ User( name='crhode', real_name='Chuck Rhode', email='CRhode@%s' % FQDN, sig='/home/crhode/cgi-bin/WX/Sig.py /home/crhode/.rec.moto.sig "http://%s/WX/OneLiner.shtml?State=WI&County=WIC117&Zone=WIZ052&WFO=MKW&Station=KSBM"' % WWW, wallpaper='/home/crhode/photo_album/American Monet, US 2, MT 2004-07-30 21.58.40.jpg', ), ] # IDs for the PCs on your LAN that you want to ping mnemonically: HOSTS = [ Host( host_name='loki', static_ip = '192.168.1.20', desktop=True, ), Host( host_name='thor', static_ip = '192.168.1.21', laptop=True, ), ] # Relative path for the "Personal Slideshow" screensaver repertoire # folder: SCREENSAVER_PERSONAL_SLIDE_SHOW_FRAMES = '.screensaver-personal-slideshow-frames' # This is set for all users. That is: Each user has a similarly # named folder in his "home" directory: # SCREENSAVER_PERSONAL_SLIDE_SHOW_FRAMES = '.xxx' # SCREENSAVER_PERSONAL_SLIDE_SHOW_FRAMES = 'Pictures' # Default # MAC address for WIFI card: WIFI_MAC = 'ff:ff:ff:ff:ff:ff' # This is used to set a persistent device mnemonic for the WIFI # connection. # WIFI_MAC = 'ff:ff:ff:ff:ff:ff' # Use lowercase. # MAC address for Ethernet card: ETH0_MAC_DESKTOP = 'ff:ff:ff:ff:ff:ff' # This is used to set a persistent device mnemonic for the Ethernet # connection. # ETH0_MAC_DESKTOP = 'ff:ff:ff:ff:ff:ff' # Use lowercase. ETH0_MAC_LAPTOP = 'ff:ff:ff:ff:ff:ff' # This is used to set a persistent device mnemonic for the Ethernet # connection. # ETH0_MAC_LAPTOP = 'ff:ff:ff:ff:ff:ff' # Use lowercase. # FAX MODEM: FAX_MODEM = 'ttyS1' # This is the serial port of your FAX device. # FAX_MODEM = 'ttyS9' # FAX_MODEM = 'ttyS3' # for example. # FAX Area Code: FAX_AREA_CODE = '999' # This is the area code of your FAX phone number. # FAX_AREA_CODE = '999' # FAX_AREA_CODE = '920' # for example. # FAX Phone: FAX_PHONE = '+1.999.999.9999' # This is the actual telephone number you use for outbound (and # inbound) FAXes. # FAX_PHONE = '+1.999.999.9999' # FAX_PHONE = '+1.920.459.9492' # for example. # FAX ID: # This is your company name. FAX_ID = '' # FAX_ID = 'xxx' # FAX_ID = FAX_PHONE # default ISP_PHONE = '4520513' # This is the dial-up phone of your Internet Service Provider's # (ISPs) MODEM bank. # ISP_PHONE = '1.999.999.9999' # ISP_PHONE = '4520513' # for example. PPP_USER = '***' PPP_PASSWORD = '***' # This is the dialup ID required by your Internet Service Provider. # PPP_USER = 'xxx'; PPP_PASSWORD = '***' # PPP_USER = 'xxx'; PPP_PASSWORD = '***' # for example. # ----- # End of configuration global constants. # ----- # What follows are implementation details. You may skip ahead to # Mainline. try: from vital_passwords import * except ImportError: pass TODAY = datetime.date.today() TODAY_STRING = TODAY.strftime('%Y %b %d') TEMPLATE_XML_APP = """ action_type /schemas/apps/panel/objects/action_type lock attached_toplevel_id /schemas/apps/panel/objects/attached_toplevel_id bonobo_iid /schemas/apps/panel/objects/bonobo_iid %(bonobo_iid)s custom_icon /schemas/apps/panel/objects/custom_icon launcher_location /schemas/apps/panel/objects/launcher_location locked /schemas/apps/panel/objects/locked false menu_path /schemas/apps/panel/objects/menu_path applications:/ object_type /schemas/apps/panel/objects/object_type bonobo-applet panel_right_stick /schemas/apps/panel/objects/panel_right_stick %(right_stick)s position /schemas/apps/panel/objects/position 1 tooltip /schemas/apps/panel/objects/tooltip toplevel_id /schemas/apps/panel/objects/toplevel_id %(panel)s use_custom_icon /schemas/apps/panel/objects/use_custom_icon false use_menu_path /schemas/apps/panel/objects/use_menu_path false """ TEMPLATE_XML_APP_SYSTEM_MONITOR = """ action_type /schemas/apps/panel/objects/action_type lock attached_toplevel_id /schemas/apps/panel/objects/attached_toplevel_id bonobo_iid /schemas/apps/panel/objects/bonobo_iid %(bonobo_iid)s custom_icon /schemas/apps/panel/objects/custom_icon launcher_location /schemas/apps/panel/objects/launcher_location locked /schemas/apps/panel/objects/locked false menu_path /schemas/apps/panel/objects/menu_path applications:/ object_type /schemas/apps/panel/objects/object_type bonobo-applet panel_right_stick /schemas/apps/panel/objects/panel_right_stick %(right_stick)s position /schemas/apps/panel/objects/position 1 tooltip /schemas/apps/panel/objects/tooltip toplevel_id /schemas/apps/panel/objects/toplevel_id %(panel)s use_custom_icon /schemas/apps/panel/objects/use_custom_icon false use_menu_path /schemas/apps/panel/objects/use_menu_path false prefs/cpuload_color0 /schemas/apps/multiload/prefs/cpuload_color0 #0072b3 prefs/cpuload_color1 /schemas/apps/multiload/prefs/cpuload_color1 #0092e6 prefs/cpuload_color2 /schemas/apps/multiload/prefs/cpuload_color2 #00a3ff prefs/cpuload_color3 /schemas/apps/multiload/prefs/cpuload_color3 #ffffff prefs/cpuload_color4 /schemas/apps/multiload/prefs/cpuload_color4 #000000 prefs/diskload_color0 /schemas/apps/multiload/prefs/diskload_color0 #C65000 prefs/diskload_color1 /schemas/apps/multiload/prefs/diskload_color1 #FF6700 prefs/diskload_color2 /schemas/apps/multiload/prefs/diskload_color2 #000000 prefs/loadavg_color0 /schemas/apps/multiload/prefs/loadavg_color0 #d50000 prefs/loadavg_color1 /schemas/apps/multiload/prefs/loadavg_color1 #000000 prefs/memload_color0 /schemas/apps/multiload/prefs/memload_color0 #00b35b prefs/memload_color1 /schemas/apps/multiload/prefs/memload_color1 #00e675 prefs/memload_color2 /schemas/apps/multiload/prefs/memload_color2 #00ff82 prefs/memload_color3 /schemas/apps/multiload/prefs/memload_color3 #AAF5D0 prefs/memload_color4 /schemas/apps/multiload/prefs/memload_color4 #000000 prefs/netload_color0 /schemas/apps/multiload/prefs/netload_color0 #00b0b3 prefs/netload_color1 /schemas/apps/multiload/prefs/netload_color1 #00e2e6 prefs/netload_color2 /schemas/apps/multiload/prefs/netload_color2 #00fbff prefs/netload_color3 /schemas/apps/multiload/prefs/netload_color3 #B7FEFF prefs/netload_color4 /schemas/apps/multiload/prefs/netload_color4 #000000 prefs/size /schemas/apps/multiload/prefs/size 40 prefs/speed /schemas/apps/multiload/prefs/speed 500 prefs/swapload_color0 /schemas/apps/multiload/prefs/swapload_color0 #8b00c3 prefs/swapload_color1 /schemas/apps/multiload/prefs/swapload_color1 #000000 prefs/view_cpuload /schemas/apps/multiload/prefs/view_cpuload %(view_load_cpu)s prefs/view_diskload /schemas/apps/multiload/prefs/view_diskload %(view_load_io)s prefs/view_loadavg /schemas/apps/multiload/prefs/view_loadavg %(view_load_average)s prefs/view_memload /schemas/apps/multiload/prefs/view_memload %(view_load_memory)s prefs/view_netload /schemas/apps/multiload/prefs/view_netload %(view_load_network)s prefs/view_swapload /schemas/apps/multiload/prefs/view_swapload %(view_load_swap)s """ TEMPLATE_BASH_PROMPT = r'''#!/bin/bash # .bash_prompt.sh EMPH_IN="" EMPH_OUT="" if [ "$TERM" != "dumb" ]; then EMPH_IN="\e[34;47m" EMPH_OUT="\e[0m" fi export PS1="\[$EMPH_IN\]\t \w\[$EMPH_OUT\] \\$ " # Note the elegant bracketing of the escape sequences. # This keeps them from being counted in the length of the prompt # so that line wrapping works correctly. unset EMPH_IN unset EMPH_OUT # fin! ''' TEMPLATE_NETWORK_INTERFACES = r''' # This file describes the network interfaces available on your system # and how to activate them. For more information, see interfaces(5). # The loopback network interface auto lo iface lo inet loopback # The primary network interface manual eth0 #iface eth0 inet dhcp iface eth0 inet static address %(static_ip)s netmask 255.255.255.0 gateway %(gateway)s # Wireless network interface auto wifi0 allow-hotplug wifi0 iface wifi0 inet dhcp # Dialup interface manual ppp0 iface ppp0 inet ppp provider ppp0 ''' TEMPLATE_DEVILS_PIE = '''; %(init_file_name)s (if (%(test)s (%(property_)s) "%(key)s") (%(action)s)) ''' TEMPLATE_DESKTOP_ENTRY = '''[Desktop Entry] Encoding=UTF-8 Version=1.0 TryExec= Icon=%(icon)s X-GNOME-DocPath= Name=%(desktop_name)s Name[en_US]=%(desktop_name)s GenericName=%(categories)s GenericName[en_US]=%(categories)s Comment=%(comment)s Comment[en_US]=%(comment)s ''' TEMPLATE_DESKTOP_LAUNCHER = TEMPLATE_DESKTOP_ENTRY + '''Type=Application Terminal=false Exec=%(exec_)s ''' TEMPLATE_DESKTOP_LAUNCHER_TERM = TEMPLATE_DESKTOP_ENTRY + '''Type=Application Terminal=true Exec=%(exec_)s ''' TEMPLATE_DESKTOP_LINK = TEMPLATE_DESKTOP_ENTRY + '''Type=Link Terminal=false URL=%(link)s ''' TEMPLATE_PAN_SERVERS = r''' news.aioe.org 119 15 4 2 news.excel.net 119 15 4 1 ''' TEMPLATE_PAN_ID = r''' %(id)s
%(email)s
2 %(sig_command)s On %%d, %%n wrote:
''' TEMPLATE_WALLPAPER = r''' color_shading_type /schemas/desktop/gnome/background/color_shading_type vertical-gradient draw_background /schemas/desktop/gnome/background/draw_background true picture_filename /schemas/desktop/gnome/background/picture_filename %(picture)s picture_opacity /schemas/desktop/gnome/background/picture_opacity 100 picture_options /schemas/desktop/gnome/background/picture_options %(style)s primary_color /schemas/desktop/gnome/background/primary_color %(color_top)s secondary_color /schemas/desktop/gnome/background/secondary_color %(color_bottom)s ''' TEMPLATE_TERM_PROFILE_TRANSPARENT_HOLD = r''' allow_bold /schemas/apps/gnome-terminal/profiles/Default/allow_bold true background_color /schemas/apps/gnome-terminal/profiles/Default/background_color #FFFFDD background_darkness /schemas/apps/gnome-terminal/profiles/Default/background_darkness 0.27860695123672485 background_image /schemas/apps/gnome-terminal/profiles/Default/background_image background_type /schemas/apps/gnome-terminal/profiles/Default/background_type transparent backspace_binding /schemas/apps/gnome-terminal/profiles/Default/backspace_binding ascii-del custom_command /schemas/apps/gnome-terminal/profiles/Default/custom_command default_show_menubar /schemas/apps/gnome-terminal/profiles/Default/default_show_menubar true delete_binding /schemas/apps/gnome-terminal/profiles/Default/delete_binding escape-sequence exit_action /schemas/apps/gnome-terminal/profiles/Default/exit_action hold font /schemas/apps/gnome-terminal/profiles/Default/font monospace 12 foreground_color /schemas/apps/gnome-terminal/profiles/Default/foreground_color #000000 icon /schemas/apps/gnome-terminal/profiles/Default/icon gnome-terminal.png login_shell /schemas/apps/gnome-terminal/profiles/Default/login_shell false no_aa_without_render /schemas/apps/gnome-terminal/profiles/Default/no_aa_without_render true palette /schemas/apps/gnome-terminal/profiles/Default/palette #2E2E34343636:#CCCC00000000:#4E4E9A9A0606:#C4C4A0A00000:#34346565A4A4:#757550507B7B:#060698209A9A:#D3D3D7D7CFCF:#555557575353:#EFEF29292929:#8A8AE2E23434:#FCFCE9E94F4F:#72729F9FCFCF:#ADAD7F7FA8A8:#3434E2E2E2E2:#EEEEEEEEECEC scroll_background /schemas/apps/gnome-terminal/profiles/Default/scroll_background true scroll_on_keystroke /schemas/apps/gnome-terminal/profiles/Default/scroll_on_keystroke true scroll_on_output /schemas/apps/gnome-terminal/profiles/Default/scroll_on_output false scrollback_lines /schemas/apps/gnome-terminal/profiles/Default/scrollback_lines 500 scrollbar_position /schemas/apps/gnome-terminal/profiles/Default/scrollbar_position right silent_bell /schemas/apps/gnome-terminal/profiles/Default/silent_bell false title /schemas/apps/gnome-terminal/profiles/Default/title Hold title_mode /schemas/apps/gnome-terminal/profiles/Default/title_mode replace update_records /schemas/apps/gnome-terminal/profiles/Default/update_records true use_custom_command /schemas/apps/gnome-terminal/profiles/Default/use_custom_command false use_skey /schemas/apps/gnome-terminal/profiles/Default/use_skey true use_system_font /schemas/apps/gnome-terminal/profiles/Default/use_system_font true use_theme_colors /schemas/apps/gnome-terminal/profiles/Default/use_theme_colors true visible_name /schemas/apps/gnome-terminal/profiles/Default/visible_name Hold word_chars /schemas/apps/gnome-terminal/profiles/Default/word_chars -A-Za-z0-9,./?%%&#:_ x_font /schemas/apps/gnome-terminal/profiles/Default/x_font -misc-fixed-medium-r-semicondensed--*-120-*-*-c-*-*-* ''' TEMPLATE_EXIM_CONF = r"""# /etc/exim4/update-exim4.conf.conf # # Edit this file and /etc/mailname by hand and execute update-exim4.conf # yourself or use 'dpkg-reconfigure exim4-config' # # Please note that this is _not_ a dpkg-conffile and that automatic changes # to this file might happen. The code handling this will honor your local # changes, so this is usually fine, but will break local schemes that mess # around with multiple versions of the file. # # update-exim4.conf uses this file to determine variable values to replace # the DEBCONFsomethingDEBCONF strings in the configuration template files. # # Most settings found in here do have corresponding questions in the # Debconf configuration, but not all of them. # # This is a Debian specific file dc_eximconfig_configtype='smarthost' dc_other_host_names='%(host_name)s' dc_local_interfaces='127.0.0.1' dc_readhost='' dc_relay_domains='' dc_minimaldns='false' dc_relay_nets='' dc_smarthost='%(smarthost)s::%(port)s' CFILEMODE='644' dc_use_split_config='true' dc_hide_mailname='false' dc_mailname_in_oh='true' dc_localdelivery='mail_spool' """ TEMPLATE_EXIM_PASSWORD = r"""# /etc/passwd.client # # Password file used when the local exim is authenticating to a remote # host as a client. # # see exim4_passwd_client(5) for more documentation # # Example: ### target.mail.server.example:login:password %(autho_reverse_domain)s:%(autho_user)s:%(autho_password)s """ TEMPLATE_RSYNCD = """ # This is a basic rsync configuration file # It exports a single module without user authentication. motd file = /home/rsync/welcome.msg use chroot = yes [localhost] path = /home/rsync comment = Default rsync module read only = yes list = yes uid = rsyncd gid = rsyncd """ TEMPLATE_HYLAFAX_CONFIG = """ LogFacility:\t\tdaemon CountryCode:\t\t1 AreaCode:\t\t%(fax_area_code)s LongDistancePrefix:\t1 InternationalPrefix:\t011 DialStringRules:\tetc/dialrules ServerTracing:\t\t1 """ TEMPLATE_HYLAFAX_MODEM = """ # $Id: usr-xon,v 1.11 2005/11/29 21:28:57 lhoward Exp $ # # HylaFAX Facsimile Software # # Copyright (c) 1990-1996 Sam Leffler # Copyright (c) 1991-1996 Silicon Graphics, Inc. # HylaFAX is a trademark of Silicon Graphics, Inc. # # Permission to use, copy, modify, distribute, and sell this software and # its documentation for any purpose is hereby granted without fee, provided # that (i) the above copyright notices and this permission notice appear in # all copies of the software and related documentation, and (ii) the names of # Sam Leffler and Silicon Graphics may not be used in any advertising or # publicity relating to the software without the specific, prior written # permission of Sam Leffler and Silicon Graphics. # # THE SOFTWARE IS PROVIDED \"AS-IS\" AND WITHOUT WARRANTY OF ANY KIND, # EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY # WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. # # IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR # ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, # OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, # WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF # LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE # OF THIS SOFTWARE. # # # Configuration for using the Class 1 command interface with # a USR Courier or Sportster modem and XON/XOFF flow control. # # CountryCode:\t\t1 AreaCode:\t\t%(fax_area_code)s FAXNumber:\t\t%(fax_phone)s LongDistancePrefix:\t1 InternationalPrefix:\t011 DialStringRules:\tetc/dialrules ServerTracing:\t\t1 SessionTracing:\t\t11 RecvFileMode:\t\t0600 LogFileMode:\t\t0600 DeviceMode:\t\t0600 RingsBeforeAnswer:\t3 SpeakerVolume:\t\tmedium GettyArgs:\t\t\"-h %%l dx_%%s\" LocalIdentifier:\t%(fax_id)s TagLineFont:\t\tetc/lutRS18.pcf TagLineFormat:\t\t\"From %%%%l|%%c|Page %%%%P of %%%%T\" MaxRecvPages:\t\t25 # # # Modem-related stuff: should reflect modem command interface # and hardware connection/cabling (e.g. flow control). # ModemType:\t\tClass1\t\t# use class 1 interface ModemRate:\t\t19200\t\t# rate for DCE-DTE communication ModemFlowControl:\txonxoff\t\t# software flow control # ModemSetupDTRCmd:\tATS13=1&D2\t# setup so DTR drop resets modem ModemSetupDCDCmd:\tAT&C1\t\t# setup so DCD reflects carrier (or not) ModemNoFlowCmd:\t\tAT&H0&I0&R1\t# setup modem for no flow control ModemHardFlowCmd:\tAT&H1&I0&R2\t# setup modem for hardware flow control ModemSoftFlowCmd:\tAT&H2&I2&R1\t# setup modem for software flow control ModemResultCodesCmd:\tATQ0X4\t\t# enable result codes # ModemMfrQueryCmd:\t!USR ModemModelQueryCmd:\tATI3 ModemRevQueryCmd:\tATI7\t\t# XXX returns a multi-line result # # When AT+FCLASS=1 is issued the modem automatically switches # to software flow control; these parameters let the fax software # reset flow control as needed after entering Class 1. # Class1NFLOCmd:\t\tAT&H0&I0&R1\t# setup modem for no flow control Class1HFLOCmd:\t\tAT&H1&I0&R2\t# setup modem for hardware flow control Class1SFLOCmd:\t\t""\t\t# modem does this automatically # # This should resolve \"DIS/DTC received 3 times\" errors: # Class1ResponseWaitCmd:\tAT+FRS=1\t# wait after sending TCF for response # # The remainder of this configuration is included so that the # modem \"idles\" in Class 0 while not sending or receiving facsimile. # ModemSetupAACmd:\tAT+FCLASS=0\t# leave modem idling in class 0 ModemAnswerCmd:\t\tAT+FCLASS=1A\t# answer in Class 1 # # When using AT+FRS=n we see USR modems reset themselves in the middle of sessions # this is not good. So, we seem to work-around that problem by not using the # command. Unfortunately, this isn't an ideal thing. # Class1SwitchingCmd:\t\"\" """ TEMPLATE_ALSA_MIXER = """pcm.card0 { type hw card 0 } pcm.!default { type plug slave.pcm \"dmixer\" } pcm.dmixer { type dmix ipc_key 1025 slave { pcm \"hw:0,0\" period_time 0 period_size 2048\t#1024 buffer_size 32768\t#4096 \t\t\t#periods 128 rate 48000\t\t#44100 } bindings { 0 0 1 1 } } """ TEMPLATE_ESOUND = r"""[esd] auto_spawn=1 spawn_options=-terminate -nobeeps -as 2 -d default spawn_wait_ms=100 # default options are used in spawned and non-spawned mode default_options= """ TEMPLATE_EMACS = r"""(custom-set-variables ;; custom-set-variables was added by Custom. ;; If you edit it by hand, you could mess it up, so be careful. ;; Your init file should contain only one such instance. ;; If there is more than one, they won't work right. ) (custom-set-faces ;; custom-set-faces was added by Custom. ;; If you edit it by hand, you could mess it up, so be careful. ;; Your init file should contain only one such instance. ;; If there is more than one, they won't work right. ) """ TEMPLATE_GNUBIFF = r""" """ TEMPLATE_MAILBOX_EMPTY = """iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAKnRFWHRDcmVhdGlvbiBUaW1lAE1p IDMgU2VwIDIwMDMgMTA6NTU6NDggKzAxMDDmBteMAAAAB3RJTUUH1AcHCBk7TpQBtAAAAAlwSFlz AAALEQAACxEBf2RfkQAAAARnQU1BAACxjwv8YQUAAAQNSURBVHja7VXPS2NXGP3yEl9iNI6tTieK WrURYZABl+KmzLpbN/V/KF12MwuXheKqpYtClw4D3Q0Up4VSlJYicdTiNNoYJzGOJho15nfez55z +94gM+kM3XTVC4eX3HfvOd/5vu/eJ/L/eMsI/Jv5xcVFbWFhoatQKESCwWA4Go3G2+32CMC5Z0tL SzksMwG3I1EymZyq1Wpf9PX1feg4TpdpmtJoNJqu64Y0TQsbhhGybRs/NQkEAhKJRAK9vb2CtXJ6 emrv7e09WllZebCzs/MCdG1yhnzytbW1mXq9/vPw8PAAFsjQ0JDMz8/zVffb0oAAKBgcGxv7uFKp DKVSqU8QTJoiQW9BYHt7+3uQfrC8vCy5XE62traEQhSBK0VCwIFgswJSI61WS5rNpnoODg7SzUSx WKxmMhkKVJXA7Ozs/fHx8c9WV1cFeZVYLCZIkyKBKxkdHZVqtaoEfGIf/O+LcCBtcnh4qG9sbKTw 91SlKBQK3We++ZI5vWkdi2Vubk65IBGKK5ZlKSf+0wdFuCcej09jO7GtBLBwmItJzuLdHNwIy4qM m/25TkBDKA40gY5lg0BYCTAyEofD4dcE2CF+GkhCpw5rQVAMjWhD17VsiWnoUDhF6iy8oohGgQA2 qZbqJOBHzVrYWlCaDoptmBL4IyU9mZTEiwdy+ywjDqLff/CNNGI9DML2z4JygAmDT13XOwow7w1E OPD4oYz/+kTePT+VW33Y3M1TBQeOJu1rW67Taem+N0PHvoDr18Dg4WGRO/U4BXg8Z5KP5Sh/Ic+q IgXtPbljadKtW9J4URJpIDtIIU6nL+D4DsDhGiTyBXwXfHKeztxmQ3746FMpnRelmkiI2xOTme++ lF6jLMcFCKDhdbR2G/VCraybAiyKwWJSgKQEe5y9f3V1pQrroouMqUmJvD8iEaw1Q7p0a5boVku1 iomYKiCPohZwYHoCzssU+aQsJnuencO0+Q4oYmLOBTnOjWgIxq1cS7BVF50scHBUKDjHW5vb6+vr v2OmzPKGvFaslUolmZ6eVtHz9+Xl5d+RQ4CEbFG65Hs1D7HycQnRN9x0K/T8oaU9/fHrryp1RijC WzUJXKurAmTZcrlcw23qnJycDIAwgktPJicnpb+/Xx2efD6vROiS4rnMwcWjQO9Pn+/kf/m2bv65 Z9sZCD/lvQlsAM+Bmt+TdBIH7qFjEhMTE2NwMwKBu1NTU3cTiURwf39fzs7OWul0+rfNzc2jbDbb hrtz7MkCB8AxcEVS76p2X/0eUKQH4GV0C7gDjCL6MYgM8xzu7u5aiLLqpSDjkV94pC11sF8Znb5c nNN4MQJRT/Ad4Lb3vgigL6Uiqvvl5amVfyB70/Dfd3l3S8Czb76J9D8dfwGCF56MUIpd7AAAAABJ RU5ErkJggg==""" TEMPLATE_MAILBOX_FULL = """iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAKnRFWHRDcmVhdGlvbiBUaW1lAE1p IDMgU2VwIDIwMDMgMTA6NTU6NDggKzAxMDDmBteMAAAAB3RJTUUH1AcHCBk7TpQBtAAAAAlwSFlz AAALEQAACxEBf2RfkQAAAARnQU1BAACxjwv8YQUAAATqSURBVHjavVVLbFRVGP7mzr0zwwztAG1J H5YK08EWKCZKkAZjFSwaYqIm1KSJCxbozrWJ6MKNwYQYZOECCRsTYliIiTESrAEt0gLVdkzT0mYI pQ4tTB/zujOd+/Y7d2a0bVDQhTf5cs49/zn/4/v//xzgv32eMh5p4yN/Cw2hI/NaccftrKVpHhSL DgoZE2NvAz9QXCTs1Wfkf2PAt7n20GNO8qXfby5hvQ8IKsBkmspVzFA8Qairz0gPoWAF1KIh2RZn Dl0V4Pyez1fTffDgKz09Pdsp8a9mZcXPlStXWgzDOB4Oh7sdx/Hpuo4lfrZtywiu9def/EBpif8o XYvrCDGCNTwzu3Mv6k5+jsTkpDUxMXHu7Nmz78disbsUaSsoGhgYiFBXf0tLS8PIyAhqamrQ1dUl RGsqe2a/qCnRTLc8AoyiujqMp9vbBbw815vNZhvGx8ffoXNxkZc/DeTz+dONjU0Nx459DMkrg17j 0uV+HD36HqPII1BVBZOcOFzPp0ruOSTY5wsgx0jNfB6RSASdnZ3P79u3780LFy6c4pZp18Cl/utP 1dbWdV3su4jG+lp4rBxtq9Bnr+LrMx+iu/ddzKezME0Dhmpj8/lv0HDgRWZQgqUVkU4mYRoGgsGg a6S1tfUFwbgoPPnMEXwUO73nkKIEPdKSijqvm0NYZMI0gdhX32Hvy4ehSgFYVBLmetGyqPQe9EIB FucVkGKXjfr6+ic4CIzIOQOvd+62o0V6zChZ3EQBEHvVXKnMJm58j/Ud+yGRHi8XEjMzsFqa6L22 wgBpZk6qGZjEEkCtqCpZzWNwcRFt6zY2I9S8B46/jWltp2gHDCWKxSx5rs6hkE/AL+qSBuYW5hHK ZGDSgMiVAKvORS6Xg6Zppmgb0QZypojBVAqHm3bsQsdr55DKgJvgRpPLCJ7mSI0OzVThNUqNWtAN LAl6SFlFsfiEIREFS90udQstpFPoT6XhJH477/a+u2yXIEmOCJfl6KFXSzAdesryFAp1KjeWQfSM MGAycZybZQOOdOpn3MqmMO1jQDd/+gys0FKNe0p9KAwIexYPOlSkuJ1sw6DC1QbESHqEEatyL4kc 6oUirmZJS3zwBBT/Xwa83pIBYcimgts7n8O5J19FuDUCnYqWGxBRKYpS+TcrBkQfOLonGFtYKPQq UhyLiWnIwU0rIhCjyXpH2zbYka1w2A8WDQpZIBCALMvc78Hc3JxbTYzCqBDtXnZV23vUhSSwthoY v3wCsiK8d/uIo8LDsnvQrRpdg0yh3+9nF/tcSkRiE4kEJicn7b6+vl+JS1SbJgy3k6sefzY7E/sS /oCGoW8/xTNvfEJlS8il77EfkqxtkTzHTaDwWtAgRlVVkUqlnGQyOTU8PPzL6OholmusP9whhoiM S0RrdOu6/Q13j9taflf1pt3NHQfe2hBtj6KxqdlNnqDo2rXrrLKi+59hD1Dp4tjY2ABvTk6T4mqa JW6VIeYp8T5UrmsRSSOxh55FeKPWtLW1NW7ZsmVbNBrdTsjxeBz379/XOA4ODQ3dmZqa0liW8zxz mxA3Z6KiVBROJcnL3wMyjzARKmMj0RwKhTbxAmtiIgOkgAHoFQri5XG+rFQ8mdaDXq4HrUnl1ylI rCU2EHVlWbKMrGjqslLnb17Ghz76FblSvls85ZfK+Cel/+v3B7RFv8Z4bYcVAAAAAElFTkSuQmCC""" def is_root(): return os.geteuid() == ZERO def go_home(file_name): if GLOBAL_USER is None: user_name = NULL else: user_name = GLOBAL_USER.name return os.path.join('~%s' % user_name, file_name) class ConfigFile(object): def __init__( self, file_name=None, coding='utf-8', permissions=None, ): if file_name is None: self.file_name = None else: self.file_name = os.path.expanduser(file_name) self.coding = coding if (self.file_name is None) or (not os.path.exists(self.file_name)): self.uid = NA self.gid = NA self.mode = None else: state = os.stat(self.file_name) self.uid = state.st_uid self.gid = state.st_gid self.mode = stat.S_IMODE(state.st_mode) if permissions is None: self.permissions = self.mode else: self.permissions = permissions self.text = None self.is_dirty = False return def get_unit(self, mode='r'): return codecs.open(self.file_name, mode, self.coding) def set_permissions(self): if self.permissions is None: pass else: os.chmod(self.file_name, self.permissions) return self def load(self): try: unit = self.get_unit() self.text = unit.read() unit.close() except IOError: self.text = None self.is_dirty = False return self def save(self): if self.is_dirty: self.roll_file() unit = self.get_unit(mode='w') unit.write(self.text) unit.close() self.set_permissions() self.is_dirty = False return self def trash_file(self, path=None): if path is None: path = self.file_name proc = subprocess.Popen( args='trash %s' % path, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, ) return proc.wait() def is_day_old(self, path): if os.path.exists(path): state = os.stat(path) stamp = state.st_mtime date = datetime.date.fromtimestamp(stamp) result = (date < TODAY) else: result = False return result def roll_file(self, path=None): if path is None: path = self.file_name bak = '%s~' % path orig = '%s.original' % path is_rolled = False if os.path.exists(orig): pass else: try: os.rename(path, orig) is_rolled = True except OSError: pass if is_rolled: pass else: if self.is_day_old(path) or (not os.path.exists(bak)): self.trash_file(bak) try: os.rename(path, bak) except OSError: pass return self def set_dirty(self, state=True): self.is_dirty = state return self def set_text(self, text): self.text = text self.set_dirty() return self def set_text_formatted(self, text, dict=None): if dict is None: result = self.set_text(text % self.__dict__) else: result = self.set_text(text % dict) return result def search_text(self, pattern): if hasattr(pattern, 'match'): pass else: pattern = re.compile(pattern) return pattern.search(self.text) def replace_text(self, pattern, replacement, count=1): if hasattr(pattern, 'match'): pass else: pattern = re.compile(pattern) self.text = pattern.sub(replacement, self.text, count=count) self.set_dirty() return self def insert_text(self, text, pos=ZERO): if self.text is None: self.text = text else: self.text = self.text[:pos] + text + self.text[pos:] self.set_dirty() return self def append_text(self, text): return self.insert_text(text, pos=sys.maxint) class ConfigFileScript(ConfigFile): sig = '%s . by vital_linux_tweaks.py' % TODAY_STRING pat_tweaks_header = re.compile(r'^.*?by vital_linux_tweaks.*?\n') pat_newline = re.compile(r'\n') pat_shebang = re.compile(r'^#!(.*?)\n') def __init__( self, file_name, coding='utf-8', permissions=None, comment_interstitial='# ', comment_inline=' # ', ): ConfigFile.__init__( self, file_name=file_name, coding=coding, permissions=permissions, ) self.comment_interstitial = comment_interstitial self.comment_inline = comment_inline return def force_trailing_newline(self): if self.text.endswith('\n'): pass else: self.append_text('\n') return self def insert_sig_headline(self): if self.comment_interstitial is None: pass else: replacement = '%s%s . %s\n' % (self.comment_interstitial, self.file_name, self.sig) if self.search_text(self.pat_tweaks_header) is None: match = self.search_text(self.pat_shebang) if match is None: self.insert_text(replacement, pos=ZERO) else: self.insert_text(replacement, pos=match.end(1)) else: self.replace_text(pat_tweaks_header, replacement) return self def wrap_sig_inline(self, text): if self.comment_inline is None: result = text else: replacement = '%s%s\n' % (self.comment_inline, self.sig) result = self.pat_newline.sub(replacement, text, count=1) return result class ConfigFileJavaScript(ConfigFileScript): pass class ConfigFileJavaScriptFF(ConfigFileJavaScript): pass class Token(object): def __init__(self, value, lpos): self.value = value self.is_list = None self.lpos = lpos self.rpos = None return def __str__(self): return str(self.value) class Tokens(list): """Tokenize Emacs Lisp. Inspired by: o Bjorndalen, Ole Martin. \"readlisp.py.\" 7 June 2001. 11 Nov. 2007. . """ def load(self, unit): parser = shlex.shlex(unit) parser.commenters = ';' parser.quotes = '"' parser.wordchars += '#.e+_-' self.tokenize(unit, parser) return self def tokenize(self, unit, parser): while True: token = Token(parser.get_token(), unit.tell()) if token.value in ['(']: token.value = Tokens().tokenize(unit, parser) # Push recursion level. token.is_list = True elif token.value in [')', None, NULL]: break # Pop recursion level. else: token.is_list = False token.rpos = unit.tell() self.append(token) return self def has_token(self, key): for token in self: if token.value == key: return True return False def find_sexp(self, key): for token in self: sexp = token.value if isinstance(sexp, Tokens): if sexp.has_token(key): return token else: token = sexp.find_sexp(key) if token is None: pass else: return token return None def __str__(self): values = [str(value) for value in self] result = ', '.join(values) return '[%s]' % result class ConfigFileLisp(ConfigFile): def load(self): ConfigFile.load(self) self.tokenize() return self def tokenize(self): self.tokens = Tokens() if self.text is None: pass else: self.tokens.load(StringIO.StringIO(self.text)) return self def find_sexp(self, key): return self.tokens.find_sexp(key) class ConfigFileXml(ConfigFile): def __init__( self, file_name=None, coding='utf-8', permissions=None, ): ConfigFile.__init__( self, file_name=file_name, coding=coding, permissions=permissions, ) self.tree = None return def load(self): self.tree = ElementTree.parse(self.file_name) self.invert_namespace() self.is_dirty = False return self def save(self): if self.is_dirty: self.roll_file() if self.tree is None: unit = self.get_unit(mode='w') unit.write(self.text) unit.close() else: unit = self.get_unit(mode='w') self.tree.write(unit, self.coding) unit.write('\n') unit.close() self.set_permissions() self.is_dirty = False return self def findall(self, key): return self.tree.findall(key) def getroot(self): return self.tree.getroot() def append(self, elt): self.getroot().append(elt) return self def invert_namespace(self): self.namespace = {} for (key, val) in ElementTree._namespace_map.iteritems(): self.namespace[val] = key return self def solve(self, tag): tuple = tag.split(':') if len(tuple) == 2: (prefix, local) = tuple uri = self.namespace.get(prefix.lower()) if uri is None: result = tag else: result = '{%s}%s' % (uri, local) else: result = tag return result class ConfigFileIni(ConfigFile): def __init__( self, file_name=None, coding='utf-8', permissions=None, ): ConfigFile.__init__( self, file_name=file_name, coding=coding, permissions=permissions, ) self.config = None return def load(self, defaults=None): try: unit = self.get_unit() self.config = IniFileParser.ConfigParser(defaults) self.config.readfp(unit) unit.close() except IOError: self.config = None self.is_dirty = False return self def save(self): if self.is_dirty: self.roll_file() unit = self.get_unit(mode='w') if self.config is None: unit.write(self.text) else: self.config.write(unit) unit.close() self.set_permissions() self.is_dirty = False return self class ConfigFileBase64(ConfigFile): def get_unit(self, mode='r'): return open(self.file_name, mode) def save(self): if self.is_dirty: self.roll_file() unit = self.get_unit(mode='wb') unit.write(base64.decodestring(self.text)) unit.close() self.set_permissions() self.is_dirty = False return self class Role(object): def __init__(self): proc = subprocess.Popen( args='hostname', stdout=subprocess.PIPE, shell=True, ) retcd = proc.wait() self.host_name = proc.stdout.read().strip() return def is_laptop(self): host = self.get_host() result = host.attribs.get('laptop', False) return result def is_desktop(self): host = self.get_host() result = host.attribs.get('desktop', False) return result def get_host(self): for host in HOSTS: if host.host_name == self.host_name: return host raise KeyError class Change(object): def __init__(self, name): self.name = name return def enact(self): self.show('%s:' % self.name.ljust(32)) return self def show(self, text): sys.stdout.write(text) sys.stdout.flush() return self class ChangeMkdir(Change): def __init__(self, name, path, mode=0755): Change.__init__(self, name) self.path = os.path.expanduser(path) self.mode = mode return def enact(self): Change.enact(self) if os.path.exists(self.path): self.show('"%s" exists.\n' % self.path) else: os.makedirs(self.path, self.mode) self.show('"%s" created.\n' % self.path) return self class ChangeSymbolicLink(Change): def __init__(self, name, source, destination): Change.__init__(self, name) self.source = os.path.expanduser(source) self.destination = os.path.expanduser(destination) return def enact(self): Change.enact(self) (src_dir, src_file) = os.path.split(self.source) (dst_dir, dst_file) = os.path.split(self.destination) if os.path.isdir(self.destination): self.destination = os.path.join(self.destination, src_file) if os.path.islink(self.destination): src_old = os.readlink(self.destination) src_old = os.path.join(dst_dir, src_old) if src_old == self.source: self.show('"%s" already linked to "%s."\n' % (self.destination, self.source)) else: ConfigFile(self.destination).roll_file() os.symlink(self.source, self.destination) self.show('"%s" link changed to "%s."\n' % (self.destination, self.source)) else: if os.path.exists(self.destination): self.show('"%s" is an actual path and can\'t be linked.\n' % self.destination) else: os.symlink(self.source, self.destination) self.show('"%s" linked to "%s."\n' % (self.destination, self.source)) return self class ChangeRemovefile(Change): def __init__(self, name, file_name): Change.__init__(self, name) self.file_name = os.path.expanduser(file_name) return def enact(self): Change.enact(self) if os.path.exists(self.file_name): ConfigFile().trash_file(self.file_name) self.show('"%s" deleted.\n' % self.file_name) else: self.show('"%s" not found.\n' % self.file_name) return self class ChangeCommand(Change): def __init__(self, name, command): Change.__init__(self, name) self.command = command return def enact(self): Change.enact(self) proc = subprocess.Popen( args=self.command, shell=True, ) retcd = proc.wait() self.show('"%s" run with returncode=%i.\n' % (self.command, retcd)) return self class ChangeGConf(Change): get = { bool: gconf.Client.get_bool, int: gconf.Client.get_int, str: gconf.Client.get_string, list: gconf.Client.get_list, } set = { bool: gconf.Client.set_bool, int: gconf.Client.set_int, str: gconf.Client.set_string, list: gconf.Client.set_list, } def __init__(self, name): Change.__init__(self, name) self.client = gconf.client_get_default() return def set_gconf(self, key, value): type_ = type(value) if isinstance(value, list): if isinstance(value[ZERO], str): list_type = gconf.VALUE_STRING elif isinstance(value[ZERO], int): list_type = gconf.VALUE_STRING else: list_type = None self.set[type_](self.client, key, list_type, value) else: self.set[type_](self.client, key, value) return self def get_gconf(self, key, type_, list_type=None): if list_type is None: result = self.get[type_](self.client, key) else: result = self.get[type_](self.client, key, list_type) return result class ChangeGConfKeyValue(ChangeGConf): def __init__(self, name, key, value): ChangeGConf.__init__(self, name) self.key = key self.value = value return def enact(self): Change.enact(self) type_ = type(self.value) if isinstance(self.value, list): if isinstance(self.value[ZERO], str): list_type = gconf.VALUE_STRING elif isinstance(self.value[ZERO], int): list_type = gconf.VALUE_STRING else: list_type = None old_value = self.get_gconf(self.key, type_, list_type=list_type) else: old_value = self.get_gconf(self.key, type_) if old_value == self.value: self.show('"%s" already %s.\n' % (self.key, self.value)) else: self.set_gconf(self.key, self.value) self.show('"%s" set to %s.\n' % (self.key, self.value)) return self class RefreshRate(object): def __init__(self, rate, current): rate = float(rate) rate = '%i' % int(rate) self.rate = rate self.current = current return def __str__(self): if self.current: result = '*%s' % self.rate else: result = self.rate return result class RefreshRates(dict): def parse_line(self, line): rates = line.split() for rate in rates: if rate.endswith('*'): obj = RefreshRate(rate[:-1], True) else: obj = RefreshRate(rate, False) self[obj.rate] = obj return self def __str__(self): items = [str(val) for val in self.itervalues()] items.sort() return SPACE.join(items) class Resolution(object): def __init__(self, hor, ver, rates): self.hor = hor self.ver = ver self.rates = rates return def __str__(self): result = '%sx%s %s' % (self.hor, self.ver, self.rates) return result class Resolutions(dict): # pat_randr = re.compile(r'\s*(\*?)\d*\s*(\d*)\s*x\s*(\d*)\s*\([^)]*\)\s*(.*)') pat_randr = re.compile(r'\s*(\d*)\s*x\s*(\d*)\s*(.*)') def parse_line(self, line): match = self.pat_randr.match(line) if match is None: pass else: (hor, ver, refresh_rates) = match.group(1, 2, 3) rates = RefreshRates().parse_line(refresh_rates) obj = Resolution(hor, ver, rates) key = '%sx%s' % (obj.hor, obj.ver) self[key] = obj return self def __str__(self): items = [str(val) for val in self.itervalues()] items.sort() return '\n'.join(items) class ChangeScreenMode(ChangeGConf): pat_res = re.compile(r'(\d*)\s*x\s*(\d*)') def __init__(self, name, res, refresh): ChangeGConf.__init__(self, name) match = self.pat_res.search(res) (self.hor, self.ver) = match.group(1,2) self.refresh = refresh return def enact(self): Change.enact(self) proc = subprocess.Popen( args='xrandr -q', stdout=subprocess.PIPE, shell=True, ) retcd = proc.wait() lines = proc.stdout.readlines() resolutions = Resolutions() for line in lines: resolutions.parse_line(line) key = '%sx%s' % (self.hor, self.ver) req_res = resolutions.get(key) if req_res is None: req_refresh = None else: req_refresh = req_res.rates.get(self.refresh) if None in [req_res, req_refresh]: self.show('"%sx%s %sHz" is not available.\n' % (self.hor, self.ver, self.refresh)) else: if req_refresh.current: self.show('"%sx%s %sHz" is already selected.\n' % (self.hor, self.ver, self.refresh)) else: self.set_gconf( key='/desktop/gnome/screen/default/0/resolution', value='%sx%s' % (self.hor, self.ver), ) self.set_gconf( key='/desktop/gnome/screen/default/0/rate', value=int(self.refresh), ) command = 'xrandr --size %sx%s --rate %s' % \ (self.hor, self.ver, self.refresh) proc = subprocess.Popen( args=command, shell=True, ) retcd = proc.wait() self.show('"%sx%s %sHz" selected with returncode=%i.\n' % (self.hor, self.ver, self.refresh, retcd)) return self class ChangeEmacs(Change): def __init__(self, name, key, value): Change.__init__(self, name) self.key = key self.value = value return def enact(self): Change.enact(self) init_file = ConfigFileLisp(go_home('.emacs')) init_file.load() if init_file.text is None: init_file.text = TEMPLATE_EMACS init_file.tokenize() sexp = init_file.find_sexp(self.key) if sexp is None: sexp = init_file.find_sexp('custom-set-variables') if sexp is None: self.show('"custom-set-variables" not found.\n') else: init_file.insert_text("'(%s %s)\n " % (self.key, self.value), sexp.rpos - 1) self.show('"%s" initialized to %s.\n' % (self.key, self.value)) else: prolog = init_file.text[:sexp.lpos] epilog = init_file.text[sexp.rpos:] sexp_text = init_file.text[sexp.lpos:sexp.rpos] pat = re.compile(r'%s\s*(.+?)\)$' % self.key, re.DOTALL) match = pat.search(sexp_text) if match is None: self.show('"%s" could not be set.\n' % self.key) else: old_value = match.group(1) if old_value == self.value: self.show('"%s" already %s.\n' % (self.key, self.value)) else: repl = '%s %s)' % (self.key, self.value) sexp_text = pat.sub(repl, sexp_text) init_file.text = prolog + sexp_text + epilog init_file.set_dirty() self.show('"%s" changed to %s.\n' % (self.key, self.value)) init_file.save() return self class ChangeGConfLoadXml(ChangeGConf): def __init__(self, name, text): ChangeGConf.__init__(self, name) self.text = text return def enact(self): ChangeGConf.enact(self) proc = subprocess.Popen( args='gconftool-2 --load=-', stdin=subprocess.PIPE, shell=True, ) proc.stdin.write(self.text) proc.stdin.close() self.retcd = proc.wait() return self class ChangeAddNewPanelApp(ChangeGConfLoadXml): """Add an Application to a Panel. Inspired by: o McLoughlin, Mark. Reply to Brian Cameron. \"Re: GConf Stability - How to Add Icons to Panel.\" Desktop Devel . 4 Feb. 2005. 13 Nov. 2007. . """ key = '/apps/panel/general/applet_id_list' def __init__( self, name, app, bonobo_iid, right_stick=True, panel='top_panel_screen0', text=TEMPLATE_XML_APP, ): self.app_name = app self.bonobo_iid = bonobo_iid self.right_stick=right_stick self.panel=panel xml = ConfigFileXml().set_text_formatted(text, dict=self.__dict__) ChangeGConfLoadXml.__init__(self, name, xml.text) return def enact(self): ChangeGConfLoadXml.enact(self) old_value = self.get_gconf( key=self.key, type_=list, list_type=gconf.VALUE_STRING, ) if self.app_name in old_value: self.show('"%s" already installed.\n' % self.app_name) else: old_value.append(self.app_name) self.set_gconf( key=self.key, value=old_value, ) if self.retcd in [ZERO]: self.show('"%s" installed with returncode=%i.\n' % ( self.app_name, self.retcd, )) else: self.show('"%s" not installed with returncode=%i.\n' % ( self.app_name, self.retcd, )) return self class ChangeAddNewPanelAppSystemMonitor(ChangeAddNewPanelApp): def __init__( self, name, app, bonobo_iid, right_stick=True, panel='top_panel_screen0', text=TEMPLATE_XML_APP_SYSTEM_MONITOR, ): self.view_load_cpu = True self.view_load_memory = False self.view_load_io = False self.view_load_network = False self.view_load_swap = False self.view_load_average = False ChangeAddNewPanelApp.__init__( self, name=name, app=app, bonobo_iid=bonobo_iid, right_stick=right_stick, panel=panel, text=text, ) return class ChangeDelPanelObj(ChangeGConf): """Remove a launcher from a panel. """ key = '/apps/panel/general/object_id_list' def __init__( self, name, obj_name, ): ChangeGConf.__init__(self, name) self.obj_name=obj_name return def enact(self): ChangeGConf.enact(self) old_value = self.get_gconf( key=self.key, type_=list, list_type=gconf.VALUE_STRING, ) if self.obj_name in old_value: old_value.remove(self.obj_name) self.set_gconf( key=self.key, value=old_value, ) self.show('"%s" disabled.\n' % self.obj_name) else: self.show('"%s" not enabled.\n' % self.obj_name) return self class ChangeFFProfileName(Change): """Move the random Mozilla profile name to a standard location. Symlink the old name to the standard name. This makes it possible to clone Mozilla installations. """ def __init__(self, name): Change.__init__(self, name) self.path = os.path.expanduser(go_home('.mozilla/firefox')) return def enact(self): Change.enact(self) old_profile_name = old_profile_link = self.get_profile_name() new_profile_name = os.path.join(self.path, 'Profile0.default') is_old_link = os.path.islink(old_profile_name) if is_old_link: old_profile_name = os.path.realpath(old_profile_name) is_old_profile = os.path.exists(old_profile_name) is_new_profile = os.path.exists(new_profile_name) if old_profile_name == new_profile_name: self.show('"%s" already points to "%s."\n' % (old_profile_link, new_profile_name)) else: if is_new_profile: if is_old_profile: if is_old_link: pass else: ConfigFile(old_profile_name).roll_file() else: if is_old_profile: os.rename(old_profile_name, new_profile_name) # print 'new=%s, old=%s' % (new_profile_name, old_profile_link) os.symlink(new_profile_name, old_profile_link) self.show('"%s" linked to "%s."\n' % (old_profile_link, new_profile_name)) return self def get_profile_name(self): ini = ConfigFileIni(file_name=os.path.join(self.path, 'profiles.ini')) ini.load() section = None for ndx in ['0', '1', '2']: section = 'Profile%s' % ndx if ini.config.has_option(section, 'Path'): break else: raise NotImplementedError profile_name = ini.config.get(section, 'Path') return os.path.join(self.path, profile_name) class ChangeFFUserPref(Change): def __init__(self, name, key, value): Change.__init__(self, name) self.key = key self.value = value self.pattern = re.compile(r'^user_pref\("%s",\s*(.*?)\);$' % self.key, re.MULTILINE) self.replacement = 'user_pref("%s", %s);' % (self.key, self.value) return def enact(self): Change.enact(self) init_file = ConfigFileJavaScriptFF(go_home('.mozilla/firefox/Profile0.default/user.js')) init_file.load() if init_file.text is None: init_file.text = NULL match = init_file.search_text(self.pattern) if match is None: init_file.insert_text("%s\n" % self.replacement) self.show('"%s" initialized to %s.\n' % (self.key, self.value)) else: old_value = match.group(1) if old_value == self.value: self.show('"%s" already %s.\n' % (self.key, self.value)) else: init_file.replace_text(self.pattern, self.replacement) self.show('"%s" changed to %s.\n' % (self.key, self.value)) init_file.save() return self class ChangeFFLocalStore(Change): def __init__(self, name, key, **attributes): Change.__init__(self, name) self.key = key self.attributes = attributes return def enact(self): Change.enact(self) init_file = ConfigFileXml(go_home('.mozilla/firefox/Profile0.default/localstore.rdf')) init_file.load() descriptions = init_file.findall(init_file.solve('RDF:Description')) elts = [ desc for desc in descriptions if desc.get(init_file.solve('RDF:about')) == 'chrome://browser/content/browser.xul#%s' % self.key ] if elts: elt = elts[-1] for (key, value) in self.attributes.iteritems(): elt.set(key, value) init_file.set_dirty() init_file.save() self.show('"%s" set to %s.\n' % (self.key, str(self.attributes))) else: elt = ElementTree.Element(init_file.solve('RDF:Description')) elt.set(init_file.solve('RDF:about', 'chrome://browser/content/browser.xul#%s' % self.key)) for (key, value) in self.attributes.iteritems(): elt.set(key, value) init_file.append(elt) init_file.set_dirty() init_file.save() self.show('"%s" initialized to %s.\n' % (self.key, str(self.attributes))) return self class ChangeDisablePrefBarButtons(Change): def __init__(self, name, key): Change.__init__(self, name) self.key = key return def enact(self): Change.enact(self) init_file = ConfigFileXml(go_home('.mozilla/firefox/Profile0.default/prefbar.rdf')) init_file.load() sequences = init_file.findall(init_file.solve('RDF:Seq')) seq_enabled = None seq_disabled = None for seq in sequences: if seq.get(init_file.solve('RDF:about')) == 'urn:prefbar:browserbuttons:enabled': seq_enabled = seq if seq.get(init_file.solve('RDF:about')) == 'urn:prefbar:browserbuttons:disabled': seq_disabled = seq if None in [seq_enabled, seq_disabled]: self.show('"%s" could not be disabled.\n' % self.key) else: buttons = seq_enabled.findall(init_file.solve('RDF:li')) target = 'urn:prefbar:buttons:%s' % self.key for button in buttons: if button.get(init_file.solve('RDF:resource')) == target: seq_enabled.remove(button) seq_disabled.insert(ZERO, button) init_file.set_dirty() init_file.save() self.show('"%s" button disabled.\n' % self.key) break else: self.show('"%s" button not found or already disabled.\n' % self.key) return self class ChangeEnablePrefBarButtons(Change): def __init__(self, name, key, before): Change.__init__(self, name) self.key = key self.before = before return def enact(self): Change.enact(self) init_file = ConfigFileXml(go_home('.mozilla/firefox/Profile0.default/prefbar.rdf')) init_file.load() sequences = init_file.findall(init_file.solve('RDF:Seq')) seq_enabled = None seq_disabled = None for seq in sequences: if seq.get(init_file.solve('RDF:about')) == 'urn:prefbar:browserbuttons:enabled': seq_enabled = seq if seq.get(init_file.solve('RDF:about')) == 'urn:prefbar:browserbuttons:disabled': seq_disabled = seq if None in [seq_enabled, seq_disabled]: self.show('"%s" could not be enabled.\n' % self.key) else: buttons = seq_enabled.findall(init_file.solve('RDF:li')) target = 'urn:prefbar:buttons:%s' % self.before for (ndx, button) in enumerate(buttons): if button.get(init_file.solve('RDF:resource')) == target: before_button = ndx break else: before_button = None if before_button is None: self.show('"%s" place could not be found.\n' % self.before) else: buttons = seq_disabled.findall(init_file.solve('RDF:li')) target = 'urn:prefbar:buttons:%s' % self.key for button in buttons: if button.get(init_file.solve('RDF:resource')) == target: seq_disabled.remove(button) seq_enabled.insert(before_button, button) init_file.set_dirty() init_file.save() self.show('"%s" button enabled.\n' % self.key) break else: self.show('"%s" button not found or already enabled.\n' % self.key) return self class ChangeBashPrompt(Change): reg_bash_prompt = 'source ~/.bash_prompt.sh' pat_bash_prompt = re.compile(r'^%s' % reg_bash_prompt, re.MULTILINE) def __init__(self, name): Change.__init__(self, name) return def enact(self): Change.enact(self) script_name = go_home('.bash_prompt.sh') script = ConfigFileScript(script_name) script.load() if script.text is None: script.set_text(TEMPLATE_BASH_PROMPT) script.insert_sig_headline() script.save() bashrc_name = go_home('.bashrc') bashrc = ConfigFileScript(bashrc_name) bashrc.load() match = bashrc.search_text(self.pat_bash_prompt) if match is None: bashrc.force_trailing_newline() command = bashrc.wrap_sig_inline('%s\n' % self.reg_bash_prompt) bashrc.append_text(command) bashrc.save() self.show('Done.\n') else: self.show('Already set.\n') return self class ChangeIni(Change): def __init__(self, name, ini_file_name, section, dict): Change.__init__(self, name) self.ini_file_name = ini_file_name self.section = section self.dict = dict return def enact(self): Change.enact(self) ini_file = ConfigFileIni(self.ini_file_name) ini_file.load() if ini_file.config.has_section(self.section): pass else: ini_file.config.add_section(self.section) ini_file.set_dirty() for (key, value) in self.dict.iteritems(): if ini_file.config.has_option(self.section, key): old_value = ini_file.config.get(self.section, key) if old_value == value: pass else: ini_file.config.set(self.section, key, value) ini_file.set_dirty() else: ini_file.config.set(self.section, key, value) ini_file.set_dirty() if ini_file.is_dirty: self.show('Section "%s" initialized/updated.\n' % self.section) else: self.show('Section "%s" unchanged.\n' % self.section) ini_file.save() return self class ChangeScriptAppend(Change): def __init__(self, name, script_name, command, **attribs): Change.__init__(self, name) self.script_name = script_name self.command = command self.attribs = attribs return def enact(self): Change.enact(self) init_file = ConfigFileScript(self.script_name, **self.attribs) init_file.load() if init_file.text is None: init_file.set_text(self.command) init_file.force_trailing_newline() init_file.insert_sig_headline() self.show('"%s" initialized.\n' % self.script_name) else: pat = re.compile(r'^%s' % self.command, re.MULTILINE) match = init_file.search_text(pat) if match is None: init_file.force_trailing_newline() command = init_file.wrap_sig_inline("%s\n" % self.command) init_file.append_text(command) self.show('"%s" appended to %s.\n' % (self.command, self.script_name)) else: self.show('"%s" already in %s.\n' % (self.command, self.script_name)) init_file.save() return self class ChangeConfigOverwrite(Change): def __init__( self, name, init_file_name, text, is_formatted=True, constructor=ConfigFileScript, permissions=None, comment_interstitial='# ', comment_inline=' # ', ): Change.__init__(self, name) self.init_file_name = init_file_name self.permissions = permissions self.text = text self.constructor = constructor self.is_formatted = is_formatted self.comment_interstitial = comment_interstitial self.comment_inline = comment_inline return def enact(self): Change.enact(self) init_file = self.constructor( self.init_file_name, permissions=self.permissions, ) if isinstance(init_file, ConfigFileScript): init_file.comment_interstitial = self.comment_interstitial init_file.comment_inline = self.comment_inline if self.is_formatted: init_file.set_text_formatted(self.text, dict=self.__dict__) else: init_file.set_text(self.text) if isinstance(init_file, ConfigFileScript): init_file.insert_sig_headline() init_file.force_trailing_newline() init_file.save() self.show('"%s" overwritten.\n' % self.init_file_name) return self class ChangeNetInterface(ChangeConfigOverwrite): def __init__( self, name, host, text=TEMPLATE_NETWORK_INTERFACES, ): self.static_ip = host.static_ip self.gateway = host.gateway ChangeConfigOverwrite.__init__( self, name=name, init_file_name='/etc/network/interfaces', text=text, ) return class ChangeDevilsPieRule(ChangeConfigOverwrite): def __init__( self, name, key, path, test='contains', property_='window_name', action='maximize', text = TEMPLATE_DEVILS_PIE, ): self.key = key self.path = path self.test = test self.property_ = property_ self.action = action self.text = text self.init_file_name = os.path.join(self.path, '%s_%s.ds' % (self.action, self.key)) ChangeConfigOverwrite.__init__( self, name, init_file_name=self.init_file_name, text= self.text, comment_interstitial='; ', comment_inline=None, ) return class ChangeSearchReplace(Change): def __init__( self, name, file_name, targets, permissions=None, ): Change.__init__(self, name) self.file_name = file_name self.targets = targets self.permissions = permissions return def enact(self): Change.enact(self) config_file = ConfigFile(self.file_name, permissions=self.permissions) config_file.load() if config_file.text is None: pass else: for (target, replacement) in self.targets: match = config_file.search_text(target) if match is None: pass else: config_file.replace_text(target, replacement) if config_file.is_dirty: self.show('Targets replaced in %s.\n' % self.file_name) else: self.show('Targets not found in %s this time.\n' % self.file_name) config_file.save() return self class ChangeDesktop(ChangeConfigOverwrite): def __init__( self, name, desktop_name, path=None, icon=NULL, categories=NULL, comment=NULL, text=None, ): self.desktop_name = desktop_name self.icon = icon self.categories = categories self.comment = comment if path is None: init_file_name = go_home('Desktop/my-%s.desktop' % self.desktop_name) else: init_file_name = go_home(path) ChangeConfigOverwrite.__init__( self, name, init_file_name=init_file_name, constructor=ConfigFileIni, text=text, ) return class ChangeDesktopLauncher(ChangeDesktop): def __init__( self, name, desktop_name, exec_, path=None, icon=NULL, categories=NULL, comment=NULL, text=TEMPLATE_DESKTOP_LAUNCHER, ): self.exec_ = exec_ ChangeDesktop.__init__( self, name=name, desktop_name=desktop_name, path=path, icon=icon, categories=categories, comment=comment, text=text, ) return class ChangeDesktopLauncherTerm(ChangeDesktopLauncher): def __init__( self, name, desktop_name, exec_, path=None, icon=NULL, categories=NULL, comment=NULL, text=TEMPLATE_DESKTOP_LAUNCHER_TERM, ): ChangeDesktopLauncher.__init__( self, name=name, desktop_name=desktop_name, exec_=exec_, path=path, icon=icon, categories=categories, comment=comment, text=text, ) return class ChangeDesktopLink(ChangeDesktop): def __init__( self, name, desktop_name, link, path=None, icon=NULL, categories=NULL, comment=NULL, text=TEMPLATE_DESKTOP_LINK, ): self.link = link ChangeDesktop.__init__( self, name=name, desktop_name=desktop_name, path=path, icon=icon, categories=categories, comment=comment, text=text, ) return class ChangeNewsId(ChangeConfigOverwrite): def __init__( self, name, id, email, sig_command, text=TEMPLATE_PAN_ID, ): self.id = id self.email = email self.sig_command = sig_command init_file_name=go_home('.pan2/posting.xml') ChangeConfigOverwrite.__init__( self, name, init_file_name=init_file_name, text=text, constructor=ConfigFileXml, ) return class ChangeHosts(Change): pat_host = re.compile(r'^\s*127.0.0.1\s+([^\s#]{,60}).*$', re.MULTILINE) repertoire = [ 'http://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&showintro=1&mimetype=plaintext', 'http://someonewhocares.org/hosts/hosts', # 'http://www.mvps.org/winhelp2002/hosts.txt', ] buffer=r'''# # The following lines are desirable for IPv6 capable hosts ::1 ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters ff02::3 ip6-allhosts ''' def __init__(self, name): Change.__init__(self, name) return def enact(self): Change.enact(self) domains = [] for url in self.repertoire: domains.extend(self.get_hosts(url)) domains = self.elim_dups(domains) lines = ['127.0.0.1\tlocalhost'] for host in HOSTS: lines.append('%s\t%s %s' % (host.static_ip, host.fqdn, host.host_name)) lines.append(self.buffer) lines.append('# Blacklist Internet popup and advertising hosts:') for domain in domains: lines.append('127.0.0.1 %s' % domain) init_file_name = '/etc/hosts' init_file = ConfigFileScript(init_file_name) init_file.set_text('\n'.join(lines)) init_file.insert_sig_headline() init_file.force_trailing_newline() init_file.save() self.show('"%s" replaced (%i entries).\n' % (init_file_name, len(domains))) return self def get_hosts(self, url): result = [] unit = urllib2.urlopen(url) text = unit.read() unit.close() lines = self.pat_host.split(text) while True: if lines: lines.pop(ZERO) if lines: result.append(lines.pop(ZERO)) else: break return result def elim_dups(self, domains): domains.sort() result = [] while domains: domain = domains.pop(ZERO) result.append(domain) while domains: if domains[ZERO] == domain: domains.pop(ZERO) else: break return result class ChangePrinter(ChangeCommand): def __init__( self, name, printer_name, caption, physical_location, connection, model, driver, ): command = 'foomatic-configure -n "%s" -N "%s" -L "%s" -c "%s" -p "%s" -d "%s"' % ( printer_name, caption, physical_location, connection, model, driver, ) ChangeCommand.__init__(self, name, command) return class ChangeMimeAssociations(Change): pat_mime_cache = re.compile(r'\[MIME Cache\]') def __init__( self, name, path_local_apps, ): Change.__init__(self, name) self.path_local_apps = path_local_apps return def enact(self): Change.enact(self) mimeinfo_cache_file_name = os.path.join(self.path_local_apps, 'mimeinfo.cache') defaults_list_file_name = os.path.join(self.path_local_apps, 'defaults.list') old = ConfigFile(mimeinfo_cache_file_name) old.load() new = ConfigFile(defaults_list_file_name) new.set_text(old.text) new.replace_text(self.pat_mime_cache, '[Default Applications]') new.save() self.show('"%s" created.\n' % defaults_list_file_name) return class ChangeBootScripts(Change): def __init__( self, name, key, start_kill, run_levels=None, ): Change.__init__(self, name) self.key = key if start_kill.lower() in ['start']: self.new_sentinel = 'S' self.old_sentinel = 'K' elif start_kill.lower() in ['kill']: self.new_sentinel = 'K' self.old_sentinel = 'S' else: raise NotImplementedError self.disable = self.new_sentinel in ['K'] self.run_levels = run_levels if self.run_levels is None: self.run_levels = [2, 3, 4, 5] return def enact(self): Change.enact(self) pat = re.compile(r'^%s(\d{2})%s$' % (self.old_sentinel, self.key)) is_dirty = False for run_level in self.run_levels: path = '/etc/rc%i.d' % run_level for old_symbolic_link in os.listdir(path): match = pat.match(old_symbolic_link) if match is None: pass else: old_seq = int(match.group(1), 10) new_seq = 100 - old_seq new_symbolic_link = '%s%02i%s' % (self.new_sentinel, new_seq, self.key) old_symbolic_link = os.path.join(path, old_symbolic_link) new_symbolic_link = os.path.join(path, new_symbolic_link) # print 'mv %s %s' % (old_symbolic_link, new_symbolic_link) os.rename(old_symbolic_link, new_symbolic_link) is_dirty = True if is_dirty: if self.disable: self.show('"%s" disabled.\n' % self.key) else: self.show('"%s" enabled.\n' % self.key) else: if self.disable: self.show('"%s" already disabled.\n' % self.key) else: self.show('"%s" already enabled.\n' % self.key) return self class ChangeWallpaper(ChangeGConfLoadXml): def __init__( self, name, picture=NULL, colors=('#006699', '#0099CC'), style='scaled', text=TEMPLATE_WALLPAPER, ): self.picture = picture self.style = style self.color_top = colors[ZERO] self.color_bottom = colors[1] xml = ConfigFileXml().set_text_formatted(text, dict=self.__dict__) ChangeGConfLoadXml.__init__(self, name, xml.text) return def enact(self): ChangeGConfLoadXml.enact(self) self.show('Wallpaper loaded with returncode=%i.\n' % self.retcd) return self class ChangeGnomeTerminalProfile(ChangeGConfLoadXml): def __init__( self, name, profile_name='Hold', text=TEMPLATE_TERM_PROFILE_TRANSPARENT_HOLD, ): self.profile_name = profile_name xml = ConfigFileXml().set_text_formatted(text, dict=self.__dict__) ChangeGConfLoadXml.__init__(self, name, xml.text) return def enact(self): ChangeGConfLoadXml.enact(self) self.show('"Hold" profile loaded with returncode=%i.\n' % self.retcd) return self class ChangeExim(Change): '''Configure *exim4* (*sendmail*). Do the work of *dpkg-reconfigure exim4-config* as described in: Zugschlus [Marc Haber.] "Debian Exim4 User FAQ." 16 Aug. 2007. _Debian Wiki_. 15 Dec. 2007 . The end result is a peculiarly placed config at */var/lib/exim4/config.autogenerated*. ''' def __init__( self, name, mail_name, host_name, smarthost, port, autho_reverse_domain, autho_user, autho_password, ): Change.__init__(self, name) self.mail_name = mail_name self.host_name = host_name self.smarthost = smarthost self.port = port self.autho_reverse_domain = autho_reverse_domain self.autho_user = autho_user self.autho_password = autho_password return def enact(self): Change.enact(self) mail_name_file = ConfigFile('/etc/mailname', permissions=0644) mail_name_file.set_text('%s\n' % self.mail_name) mail_name_file.save() conf_conf_file = ConfigFileScript('/etc/exim4/update-exim4.conf.conf', permissions=0644) conf_conf_file.set_text_formatted(TEMPLATE_EXIM_CONF, dict=self.__dict__) conf_conf_file.insert_sig_headline() conf_conf_file.save() proc = subprocess.Popen( args='update-exim4.conf', shell=True, ) retcd = proc.wait() password_file = ConfigFileScript('/etc/exim4/passwd.client', permissions=0640) password_file.set_text_formatted(TEMPLATE_EXIM_PASSWORD, dict=self.__dict__) password_file.insert_sig_headline() password_file.save() self.show('Done with returncode=%i.\n' % retcd) return self class ChangeXml(Change): def __init__( self, name, init_file_name, path, tags=('name', 'value'), attributes=None, ): Change.__init__(self, name) self.init_file_name = init_file_name self.path = path self.var_name = tags[ZERO] self.val_name = tags[1] self.attributes = attributes return def enact(self): Change.enact(self) doc = ConfigFileXml(self.init_file_name) doc.load() if doc.tree is None: self.show('"%s" not found.\n' % self.init_file_name) else: elts = doc.findall(self.path) if elts: for elt in elts: key = elt.get(self.var_name) old_val = elt.get(self.val_name) if key in self.attributes: new_val = self.attributes[key] if old_val == new_val: pass else: elt.set(self.val_name, new_val) doc.set_dirty() if doc.is_dirty: self.show('"%s" attributes set.\n' % self.path) else: self.show('"%s" attributes not set.\n' % self.path) else: self.show('"%s" not present.\n' % self.path) doc.save() return self class ChangeGnuBiffXml(ChangeXml): pass class ChangeUdevPersistentDeviceName(Change): def __init__( self, name, device, mac, init_file_name='/etc/udev/rules.d/70-persistent-net.rules' ): Change.__init__(self, name) self.device = device self.mac = mac self.init_file_name = init_file_name return def enact(self): Change.enact(self) self.replacement = 'SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="%s", NAME="%s"' % \ (self.mac, self.device) pat_mac = re.compile( r'^SUBSYSTEM=="net", ACTION=="add", DRIVERS=="\?\*", ATTR{address}=="%s",.*?NAME="([^"]*)"$' % self.mac, re.MULTILINE, ) pat_dev = re.compile( r'^SUBSYSTEM=="net", ACTION=="add", DRIVERS=="\?\*", ATTR{address}=="([^"]*)",.*?NAME="%s"$' % self.device, re.MULTILINE, ) init_file = ConfigFileScript(self.init_file_name) init_file.load() if init_file.text is None: init_file.set_text_formatted( r''' # # You can modify this file, as long as you keep each rule on a single line. # MAC addresses must be written in lowercase. # %(name)s %(replacement)s ''', dict=self.__dict__) init_file.insert_sig_headline() self.show('"%s" created.\n' % init_file_name) else: match = init_file.search_text(pat_mac) if match is None: match = init_file.search_text(pat_dev) if match is None: text = ''' # %(name)s %(replacement)s ''' % self.__dict__ init_file.append_text(text) self.show('Device %s added to "%s."\n' % (self.device, self.init_file_name)) else: init_file.replace_text(pat_dev, self.replacement) self.show('"%s" changed from %s to %s.\n' % (self.device, match.group(1), self.mac)) elif self.device == match.group(1): self.show('"%s" already %s.\n' % (self.mac, match.group(1))) else: init_file.replace_text(pat_mac, self.replacement) self.show('"%s" changed from %s to %s.\n' % (self.mac, match.group(1), self.device)) init_file.save() return self class ChangeHylafax(Change): def __init__( self, name, fax_area_code=FAX_AREA_CODE, fax_phone=FAX_PHONE, fax_id=FAX_ID, fax_modem=FAX_MODEM, ): self.fax_area_code = fax_area_code self.fax_phone = fax_phone self.fax_id = fax_id self.fax_modem = fax_modem Change.__init__(self, name) return def enact(self): Change.enact(self) hylafax_config_file = ConfigFileScript('/etc/hylafax/config') hylafax_config_file.set_text_formatted(TEMPLATE_HYLAFAX_CONFIG, dict=self.__dict__) hylafax_config_file.insert_sig_headline() hylafax_config_file.save() hylafax_modem_file = ConfigFileScript('/etc/hylafax/config.%s' % self.fax_modem) hylafax_modem_file.set_text_formatted(TEMPLATE_HYLAFAX_MODEM, dict=self.__dict__) hylafax_modem_file.insert_sig_headline() hylafax_modem_file.save() self.show('"/etc/hylafax/config*" created.\n') return self # Mainline begins here. MACHINE = Role() print MACHINE.host_name sys.stdout.write('Please be sure nothing else is running (such as a browser).\n') IS_PANEL_DIRTY = False IS_DESKTOP_DIRTY = False if not is_root(): sys.exit('... requires root authority') GLOBAL_USER = None if when('obsolete'): ChangeMkdir( name='Make wastebasket', path=go_home('.Trash'), ).enact() if when('configuration'): ChangeMkdir( name='Make mount-point for "guest"', path='/mnt/guest', ).enact() if when('configuration'): ChangeIni( name='Enable root login to *gdm*', ini_file_name='/etc/gdm/gdm.conf', section='security', dict={'AllowRoot': 'true'}, ).enact() if when('look_and_feel'): ChangeBashPrompt( name='Change *bash* prompt', ).enact() if when('communications'): ChangeCommand( name='Allow users to *wvdial*', command='chmod a+rx /usr/sbin/pppd', ).enact() ChangeCommand( name='Allow users to *wvdial*', command='chmod a+s /usr/sbin/pppd', ).enact() ChangeScriptAppend( name='Allow users to *wvdial*', script_name='/etc/ppp/options', command='privgroup dialout', ).enact() if when('communications'): ChangeUdevPersistentDeviceName( name='Persistent name for Wifi', device='wifi0', mac=WIFI_MAC, ).enact() if MACHINE.is_laptop(): ChangeUdevPersistentDeviceName( name='Persistent name for ETH0', device='eth0', mac=ETH0_MAC_LAPTOP, ).enact() else: ChangeUdevPersistentDeviceName( name='Persistent name for ETH0', device='eth0', mac=ETH0_MAC_DESKTOP, ).enact() ChangeNetInterface( name='Configure LAN and PPP', host=MACHINE.get_host(), ).enact() if when('long'): ChangeHosts( name='Block Internet ad hosts (long)', ).enact() if when('look_and_feel'): ChangeSearchReplace( name='Screensaver default pix folder', file_name='/usr/share/applications/screensavers/personal-slideshow.desktop', targets=[ ('Exec=slideshow .*\n', 'Exec=slideshow --location=%s\n' % SCREENSAVER_PERSONAL_SLIDE_SHOW_FRAMES), ], ).enact() if when('configuration'): ChangeScriptAppend( name='Add Debian Backports Channel', script_name='/etc/apt/sources.list', command='deb http://www.backports.org/debian etch-backports main contrib non-free', comment_interstitial=None, comment_inline=None, ).enact() if when('configuration'): ChangeMkdir( name='Make .mc folder.', path=go_home('.mc'), ).enact() ChangeScriptAppend( name='*mc* FTP hotlink for Web site', script_name=go_home('.mc/hotlist'), command='ENTRY "%s" URL "/#ftp:%s:%s@%s/vservers/%s"' % (FQDN, FTP_USER, FTP_PASSWORD, FQDN, FTP_USER), ).enact() if when('configuration'): ChangeConfigOverwrite( name='Config *rsyncd*', init_file_name='/etc/rsyncd.conf', text=TEMPLATE_RSYNCD, is_formatted=False, ).enact() if when('browser'): ChangeCommand( name='Make FF the default browser', command='update-alternatives --set x-www-browser /usr/bin/iceweasel', ).enact() # ChangeCommand( # name='Make FF the default browser', # command='update-alternatives --set gnome-www-browser /usr/bin/iceweasel', # ).enact() # Debian 5.0 (Lenny) offers one and only one "alternative" for # *gnome-www-browser*. Nuts to that noise! *gnome-www-browser* is # needed to render text/html mimetypes when, for instance, *mutt* # asks *gnome* to display an attachment. There is no earthly # reason why *iceweasel* can't do that job as well as *epiphany* # does. ChangeSymbolicLink( name='Make FF the default browser', source='/usr/bin/iceweasel', destination='/etc/alternatives/gnome-www-browser', ).enact() if when('configuration'): ChangeBootScripts( name='Disable *HylaFAX* daemons', key='hylafax', start_kill='kill', ).enact() ChangeBootScripts( name='Disable *freshclam* daemon', key='clamav-freshclam', start_kill='kill', ).enact() ChangeBootScripts( name='Disable *exim4* daemon', key='exim4', start_kill='kill', ).enact() ChangeBootScripts( name='Disable *fetchmail* daemon', key='fetchmail', start_kill='kill', ).enact() ChangeBootScripts( name='Disable *ssh* daemon', key='ssh', start_kill='kill', ).enact() ChangeBootScripts( name='Disable *rsync* daemon', key='rsync', start_kill='kill', ).enact() if when('printing'): ChangeConfigOverwrite( name='Make *cups* a *foomatic* spooler.', init_file_name='/etc/foomatic/defaultspooler', text='cups\n', is_formatted=False, constructor=ConfigFile, ).enact() ChangePrinter( name='Add CUPS Printer', printer_name='BJC-240L', caption='Canon BJC-240L', physical_location='Office', connection='parallel:/dev/lp0', model='Canon-BJC-240', driver='bjc600', ).enact() if when('mail'): ChangeExim( name='Configure *sendmail*', mail_name=FQDN, host_name=DOMAIN, smarthost=SMARTHOST_USER, port='50', autho_reverse_domain='*.safesecureweb.com', autho_user='root@%s' % FQDN, autho_password=SMARTHOST_PASSWORD, ).enact() ChangeCommand( name='Set *sendmail* owners', command='chown -R root:Debian-exim /etc/exim4/passwd.client', ).enact() if when('mail'): ChangeCommand( name='Create mail log', command='touch /var/log/procmail.log', ).enact() ChangeCommand( name='Set mail log permissions', command='chmod 666 /var/log/procmail.log', ).enact() if when('personal'): for script in [ 'origip.py', 'is_mail_fresh.py', 'open_mailto.py', ]: ChangeSymbolicLink( name='Make %s executable for *procmail*' % script, source='/home/crhode/lab/email_admin/%s' % script, destination='/usr/bin', ).enact() if when('sound'): # Inspired by: # # o Varunus. "Howto: Hear Multiple Sounds at Once." 22 # Dec. 2004. Online posting. Ubuntu Forums. 7 Jan. 2008 # . ChangeConfigOverwrite( name='Config *alsa* mixer', init_file_name='/etc/asound.conf', text=TEMPLATE_ALSA_MIXER, is_formatted=False, constructor=ConfigFile, ).enact() ChangeConfigOverwrite( name='Config *esd*', init_file_name='/etc/esound/esd.conf', text=TEMPLATE_ESOUND, is_formatted=False, constructor=ConfigFile, ).enact() if when('look_and_feel', 'sound'): ChangeGConfKeyValue( name='Make *rhythmbox* default audio CD player', key='/desktop/gnome/volume_manager/autoplay_cda_command', value='rhythmbox %d', ).enact() IMG_NEW_MAIL = '/usr/share/gnubiff/mailbox_full.png' IMG_NO_MAIL = '/usr/share/gnubiff/mailbox_empty.png' if when('mail'): ChangeConfigOverwrite( name='Create mailbox icon', init_file_name=IMG_NEW_MAIL, text=TEMPLATE_MAILBOX_FULL, is_formatted=False, constructor=ConfigFileBase64, ).enact() ChangeConfigOverwrite( name='Create mailbox icon', init_file_name=IMG_NO_MAIL, text=TEMPLATE_MAILBOX_EMPTY, is_formatted=False, constructor=ConfigFileBase64, ).enact() if when('fax'): ChangeHylafax( name='Config *HylaFAX*', ).enact() ChangeSymbolicLink( name='Make *spadmin* executable', source='/usr/lib/openoffice/program/spadmin', destination='/usr/bin', ).enact() ChangeSearchReplace( name='Create *HylaFAX* pidfile', file_name='/etc/init.d/hylafax', targets=[ ('''\n\nexit 0''', '''\n\nrm -f /var/run/hylafax.pid || true # %s . by vital_linux_tweaks.py pgrep faxq > /var/run/hylafax.pid || true exit 0''' % TODAY_STRING), ('\$\{FAXMODEM\} `echo \$device \| cut -d . -f 2` >/dev/null 2>\&1