tails-additional-software 7.2 KB
Newer Older
1 2 3 4
#!/usr/bin/env python

import gettext
import os.path
5
import pwd
6
import subprocess
7 8
import sys
import syslog
9

10 11 12
PERSISTENCE_DIR = "/live/persistence/TailsData_unlocked"
PACKAGES_LIST_FILE = PERSISTENCE_DIR + "/live-additional-software.conf"
ACTIVATION_FILE = "/var/run/live-additional-software/activated"
13

14
def _launch_apt_get(specific_args):
15 16 17
    """Launch apt-get with given args
    
    Launch apt-get with given arguments list, log its standard and error output
Tails developers's avatar
Tails developers committed
18
    and return its returncode"""
19
    apt_get_env = os.environ.copy()
20 21
    # The environnment provided in GDM PostLogin hooks doesn't contain /sbin/
    # which is required by dpkg. Let's use the default path for root in Tails.
22
    apt_get_env['PATH'] = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
23 24
    # We will log the output and want it in English when included in bug reports
    apt_get_env['LANG'] = "C"
25 26
    args = ["apt-get", "--quiet", "--yes"]
    args.extend(specific_args)
27 28 29 30 31 32 33 34 35
    apt_get = subprocess.Popen(
        args,
        env=apt_get_env,
        stderr=subprocess.STDOUT,
        stdout=subprocess.PIPE)
    for line in iter(apt_get.stdout.readline, ''):
        if not line.startswith('('):
            syslog.syslog(line.rstrip())
    apt_get.wait()
Tails developers's avatar
Tails developers committed
36 37 38
    if apt_get.returncode:
        syslog.syslog(syslog.LOG_WARNING,
            "apt-get exited with returncode %i" % apt_get.returncode)
39 40 41 42 43
    return apt_get.returncode

def _notify(title, body):
    """Display a notification to the user of the live system
    """
44
    cmd = "/usr/local/sbin/tails-notify-user"
45
    try:
Tails developers's avatar
Tails developers committed
46 47
        # XXX: replace with check_output when Tails will be based on Wheezy
        # (which includes Python 2.7)
48 49 50 51 52 53 54 55 56 57
        notify_user = subprocess.Popen([cmd, title, body],
                                       stderr=subprocess.STDOUT, stdout=subprocess.PIPE)
        notify_user_output = notify_user.stdout.read()
        notify_user.wait()
        if notify_user.returncode != 0:
            syslog.syslog(syslog.LOG_WARNING, "Warning: unable to notify the user. %s returned with exit code %s"
                 % (cmd, notify_user.returncode))
            syslog.syslog(syslog.LOG_WARNING, "%s output follows: %s."
                 % (cmd, notify_user_output))
            syslog.syslog(syslog.LOG_WARNING, "The notification was: %s %s" % (title, body))
58 59 60
    except OSError, e:
        syslog.syslog(syslog.LOG_WARNING, "Warning: unable to notify the user. %s" % e)
        syslog.syslog(syslog.LOG_WARNING, "The notification was: %s %s" % (title, body))
61

62 63
def has_additional_packages_list():
    """Return true iff PACKAGES_LIST_FILE exists
64
    """
65
    return os.path.isfile(PACKAGES_LIST_FILE)
66 67

def get_additional_packages():
68
    """Returns the list of all the additional packages
69 70
    """
    packages = []
71 72 73 74 75 76
    if has_additional_packages_list():
        with open(PACKAGES_LIST_FILE) as f:
            for line in f:
                line = line.strip()
                if line: packages.append(line)
        f.closed
77 78 79
    return packages

def install_additional_packages():
80
    """The subcommand which activates and installs all additional packages
81 82
    """
    syslog.syslog("Starting to install additional software...")
83 84
    if has_additional_packages_list():
        syslog.syslog("Found additional packages list")
85
    elif os.path.isdir(PERSISTENCE_DIR):
86 87
        syslog.syslog(syslog.LOG_WARNING, "Warning: no configuration file found, creating an empty one.")
        create_additional_packages_list()
88
        return True
89 90 91
    else:
        syslog.syslog(syslog.LOG_WARNING, "Warning: persistence is not mounted, exiting")
        return True
92 93
    packages = get_additional_packages()
    if not packages:
94
        syslog.syslog(syslog.LOG_WARNING, "Warning: no packages to install, exiting")
95
        return True
96
    set_activated()
97
    syslog.syslog("Will install the following packages: %s" % " ".join(packages))
98
    apt_get_returncode = _launch_apt_get(["--no-remove",
99
        "--option", "DPkg::Options::=--force-confold",
100
        "install"] + packages)
101
    if apt_get_returncode:
102
        syslog.syslog(syslog.LOG_WARNING, "Warning: installation of %s failed" % " ".join(packages))
103 104
        return False
    else:
Tails developers's avatar
Tails developers committed
105
        syslog.syslog("Installation completed successfully.")
106 107 108
        return True

def upgrade_additional_packages():
109
    """The subcommand which upgrades all additional packages if they are activated
110
    """
111
    if not is_activated():
112
        syslog.syslog(syslog.LOG_WARNING, "Warning: additional packages not activated, exiting")
113
        return True
114
    syslog.syslog("Starting to upgrade additional software...")
115
    apt_get_returncode = _launch_apt_get(["update"])
116
    if apt_get_returncode:
117
        syslog.syslog(syslog.LOG_WARNING, "Warning: the update failed.")
Tails developers's avatar
Tails developers committed
118
        _notify(_("Your additional software"),
119
             _("The upgrade failed. This might be due to a network problem. \
Tails developers's avatar
Tails developers committed
120
Please check your network connection, try to restart Tails, or read the system \
121
log to understand better the problem."))
122 123
        return False
    if install_additional_packages():
Tails developers's avatar
Tails developers committed
124
        _notify(_("Your additional software"),
Tails developers's avatar
Tails developers committed
125
             _("The upgrade was successful."))
126 127
        return True
    else:
Tails developers's avatar
Tails developers committed
128
        _notify(_("Your additional software"),
129
             _("The upgrade failed. This might be due to a network problem. \
Tails developers's avatar
Tails developers committed
130
Please check your network connection, try to restart Tails, or read the system \
131
log to understand better the problem."))
132 133
        return False

134
def create_additional_packages_list():
135 136 137 138
    """Creates the additional packages list

    Creates the additional packages list file with the right permissions.
    The caller must ensure the file doesn't already exist.
139
    """
140 141 142 143 144 145 146 147
    assert not has_additional_packages_list(), "%s already exists" % PACKAGES_LIST_FILE
    syslog.syslog("Creating additional software configuration file")
    f = open(PACKAGES_LIST_FILE, 'w')
    f.closed
    os.chmod(PACKAGES_LIST_FILE, 0600)
    os.chown(PACKAGES_LIST_FILE,
             pwd.getpwnam('tails-persistence-setup').pw_uid,
             pwd.getpwnam('tails-persistence-setup').pw_gid)
148

149
def is_activated():
150 151
    """Check if additional software has been activated
    """
Tails developers's avatar
Tails developers committed
152
    return os.path.isfile(ACTIVATION_FILE)
153

154
def set_activated():
155 156 157 158 159 160 161 162 163 164 165
    """Save that additional software has been activated
    """
    syslog.syslog("Activating persistent software packages")
    activation_file_dir = os.path.dirname(ACTIVATION_FILE)
    if not os.path.exists(activation_file_dir):
        os.makedirs(activation_file_dir)
    try:
        f = open(ACTIVATION_FILE, 'w')
    finally:
        if f: f.close()

166 167 168 169 170
def print_help():
    """The subcommand which displays help
    """
    sys.stderr.write("Usage: %s <subcommand>\n" % program_name)
    sys.stderr.write("""Subcommands:
171 172
    install: activate and install additional software
    upgrade: upgrade additional software if activated\n""")
173 174 175 176 177

if __name__ == "__main__":
    program_name = os.path.basename(sys.argv[0])

    syslog.openlog("%s[%i]" % (program_name, os.getpid()))
178
    gettext.install("tails")
179 180 181

    if len(sys.argv) < 2:
         print_help()
182
         sys.exit(4)
183 184 185 186 187 188 189 190 191

    if sys.argv[1] == "install":
        if not install_additional_packages():
            sys.exit(1)
    elif sys.argv[1] == "upgrade":
        if not upgrade_additional_packages():
            sys.exit(2)
    else:
        print_help()
192
        sys.exit(4)