summaryrefslogtreecommitdiffstats
path: root/wiki/src/blueprint/Port_Tails_Installer_to_Windows.mdwn
blob: 6e3b3d77e67ebe52af988bd2e454a9a8ff18e2fd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
[[!toc levels=1]]

# Linux dependencies

The current Tails Installer version (<https://git-tails.immerda.ch/liveusb-creator/>)
has considerable changes when compared with the upstream Fedora liveusb-creator
(<https://git.fedorahosted.org/cgit/liveusb-creator.git>).

The current package dependencies for the Tails Installer in Linux are:

* dosfstools
* gdisk
* genisoimage
* gir1.2-glib-2.0
* gir1.2-gtk-3.0
* gir1.2-udisks-2.0
* mtools
* p7zip-full
* policykit-1
* python-configobj
* python-gi
* python-urlgrabber
* syslinux

If we list the set of requirements for each important source file then we have:

### \_\_init\_\_.py

    import gettext

    if sys.platform == 'win32':
        import gettext_windows
        gettext_windows.setup_env()

### creator.py

    if 'linux' in sys.platform:
        import gi
        gi.require_version('UDisks', '2.0')
        from gi.repository import UDisks, GLib

    Commands:
    * syslinux
    * sgdisk
    * dd
    * dosfslabel
    * e2label
    * extlinux
    * pkexec
    * mkdiskimage
    * sync

### gui.py

    from gi.repository import Gdk, GLib, Gtk
    urlgrabber

    In general GTK3

### launcher.py

    from gi.repository import Gtk

### utils.py

    if 'linux' in sys.platform:
        from gi.repository import GLib

# Alternatives for Windows:

The Windows specific code for the Tails Installer uses mostly
the Python win32 interfaces:

    import win32file, win32api, pywintypes

and a set of third parties tools listed here:

<https://git-tails.immerda.ch/liveusb-creator/tree/tools>

There are other tools that would be possible to explore like:
<https://labs.riseup.net/code/issues/10984>

# Analysis regarding operations on storage devices

According to <https://tails.boum.org/contribute/design/installation/>,
the steps to create a Tails bootable device are:

1. Partition the device as a GPT partition.
2. Create a FAT32 (VFAT) partition with the Tails files and Tails label.
3. (Optionally, not supported yet) Create a persistence partition with LUKS.

Next we analyze the code related to above steps. First we list
the current Linux specific operations carried out in the
target installation device and then we list the current and
proposed Windows alternatives.

## For Linux - LinuxTailsInstallerCreator/creator.py:

### detect_supported_drives:

    self._udisksclient.get_object_manager().get_objects()
    partition = obj.props.partition
    filesystem = obj.props.filesystem
    self._udisksclient.get_drive_for_block(block)
    self._udisksclient.get_partition_table(partition)

Add to that a bunch of `drive.props.*` and `block.props.*`.

### mount_device:

    self._get_object().props.filesystem
    mount = filesystem.call_mount_sync(
      arg_options = GLib.Variant('a{sv}', None),
      cancellable = None)

### unmount_device:

    filesystem = self._get_object(udi).props.filesystem
    filesystem.call_unmount_sync(
       arg_options=GLib.Variant('a{sv}', None),
       cancellable=None)

### partition_device:

    block.call_format_sync(
        'gpt',
        arg_options=GLib.Variant('a{sv}', None),
         cancellable=None)
    partition_table.call_create_partition_sync(
                    arg_offset=0,
                    arg_size=self.system_partition_size * 2**20,
                    arg_type=ESP_GUID,
                    arg_name=self.label,
                    arg_options=GLib.Variant('a{sv}', None),
                    cancellable=None)
    system_partition.call_set_type_sync(ESP_GUID, GLib.Variant('a{sv}', None))
    system_partition.call_set_name_sync(self.label, GLib.Variant('a{sv}', None))
    self._set_partition_flags(system_partition, SYSTEM_PARTITION_FLAGS)

### update_system_partition_properties:

    system_partition.call_set_type_sync(ESP_GUID, GLib.Variant('a{sv}', None))
    system_partition.call_set_name_sync(self.label, GLib.Variant('a{sv}', None))
    self._set_partition_flags(system_partition, SYSTEM_PARTITION_FLAGS)

### verify_filesystem:

    self.popen('/sbin/dosfslabel %s %s' % (
                                   self.drive['device'], self.label))
    self.popen('/sbin/e2label %s %s' % (self.drive['device'],
                                                        self.label))

### install_bootloader:

    self.popen('/usr/bin/pkexec /usr/bin/syslinux %s -d syslinux %s' % (
                    ' '.join(self.syslinux_options()),
                    self.drive['device']))

### initialize_zip_geometry:

    self.popen('/usr/lib/syslinux/mkdiskimage -4 %s 0 %d %d' % (
                   self._drive[:-1], heads, cylinders))

### format_device:

    block.call_format_sync(
                'vfat',
                arg_options=GLib.Variant(
                    'a{sv}',
                    {'label': GLib.Variant('s', self.label),
                     'update-partition-type': GLib.Variant('s', 'FALSE')}))
    self._get_object().props.block.call_rescan_sync(GLib.Variant('a{sv}', None))

## For Windows - WindowsTailsInstallerCreator/creator.py:

###    detect_supported_drives:

    win32file, win32api, pywintypes
    win32file.GetDriveType(drive) == win32file.DRIVE_REMOVABLE
    win32api.GetVolumeInformation(drive)

###    mount_device:

XXX: ?

    self.dest = self.drive['mount']

###    unmount_device:

    pass

We might use this:
<https://technet.microsoft.com/en-us/library/cc772586.aspx>

###    partition_device:

    Not implemented.

Here what we need is to perform a GPT partition on the target device.
The only realistic option we could find was to use the gdisk tools
(<http://www.rodsbooks.com/gdisk/>). This library provides two utilities
to create GPT partitions. The first is `gdisk.exe` a text mode interactive
application and the second is `sgdisk.exe`, which works by command line.
If we want to use `sgdisk.exe` which looks like the best option, we should
compile it by ourselves, because no binary is provided. The compilation
requires gnuwin32 popt library (<http://gnuwin32.sourceforge.net/packages/popt.htm>)
which looks old and might only work up to Windows XP. On the other hand,
the only thing popt does is parsing command line parameters,
so in theory it should not be so difficult to run it in other
Windows versions, but I could not find more information.
The only alternative would be to use directly the WinAPI but it could
also require a good amount of work.

###    update_system_partition_properties:

    cmd = (  [ '/sbin/sgdisk' ]
               + [ '--typecode=1:%s' % ESP_GUID ]
               + [ self.drive['parent'] ])
        self.popen(cmd, shell=False)

Currently it uses sgdisk but we need to decide on using it or not.

###    verify_filesystem:

    win32api, win32file, pywintypes
    win32file.SetVolumeLabel(self.drive['device'], self.label)

###    install_bootloader:

    self.popen('syslinux %s -m -a -d %s %s' %  (
                ' '.join(self.syslinux_options()), 'syslinux', device))

Ref: <https://www.kernel.org/pub/linux/utils/boot/syslinux/>

###    initialize_zip_geometry:

    self.popen('/usr/lib/syslinux/mkdiskimage -4 %s 0 %d %d' % (
                   self._drive[:-1], heads, cylinders))

###    format_device:

    self.popen('format /Q /X /y /V:Fedora /FS:FAT32 %s' % self.drive['device'])

Looks broken

## Persistent partition:

This is managed by the persistence-setup <https://git-tails.immerda.ch/persistence-setup>
so it is beyond the scope of the current research.

# PyGI Windows executable

I managed to create a native windows executable for a test Python/GI program under Windows 8.1

## Downloads

* Python: <https://www.python.org/downloads/windows/>
* PyGI: <http://sourceforge.net/projects/pygobjectwin32/files/>
* cx_freeze: <https://pypi.python.org/packages/3.4/c/cx_Freeze/cx_Freeze-4.3.4.win32-py3.4.exe#md5=bd087416c69ced533768a22e5d3414b8>
* listdlls: <https://technet.microsoft.com/sk-sk/sysinternals/bb896656.aspx>

## Prepare

You need a Windows 8 (virtual) machine, with the files downloaded above

* in Windows double-click `python-3.4.3` and accept default choices but select "Install entire feature"
* in Windows double-click `pygi-aio-3.14.0-rev18-setup` and choose to install only
  * GNOME libraries: GI, GTK, Pango (needed?)
  * non-GNOME libraraies: none
  * developpment files: GIR (needed?)
* in Windows double-click `cx_freeze-4.3.4.win32-py34` and accept default choices

## Test Program

### main.py
    
    import gi
    from gi.repository import Gtk
    
    dialog = Gtk.MessageDialog(None, Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.INFO, Gtk.ButtonsType.CLOSE, "Hello!")
    dialog.run()
    dialog.destroy()
    
    Gtk.main()

### setup.py

* base: <https://wiki.gnome.org/Projects/PyGObject?action=AttachFile&do=view&target=setup.py>
* update the DLL directory to gnome
* update the DLL list in `setup.py with the output of ListDLLs.exe`
  * run `main.py`
  * run `Listdlls python`
  * add all DLLs that are in the gnome directory to the `missing_dll` list in `setup.py`

Result:

    import os, site, sys
    from cx_Freeze import setup, Executable
    
    ## Get the site-package folder, not everybody will install
    ## Python into C:\PythonXX
    site_dir = site.getsitepackages()[1]
    include_dll_path = os.path.join(site_dir, "gnome")
    
    ## Collect the list of missing dll when cx_freeze builds the app
    ## This list should be updated with the output of ListDLLs.exe
    missing_dll = ['libffi-6.dll',
                   'libgirepository-1.0-1.dll',
                   'libglib-2.0-0.dll',
                   'libgobject-2.0-0.dll',
                   'libgio-2.0-0.dll',
                   'libgmodule-2.0-0.dll',
                   'libintl-8.dll',
                   'libzzz.dll',
                   'libwinpthread-1.dll',
                   'libgtk-3-0.dll',
                   'libgdk-3-0.dll',
                   'libatk-1.0-0.dll',
                   'libcairo-gobject-2.dll',
                   'libgdk_pixbuf-2.0-0.dll',
                   'libpango-1.0-0.dll',
                   'libpangocairo-1.0-0.dll',
                   'libpangowin32-1.0-0.dll',
                   'libfontconfig-1.dll',
                   'libfreetype-6.dll',
                   'libpng16-16.dll',
                   'libjasper-1.dll',
                   'libjpeg-8.dll',
                   'librsvg-2-2.dll',
                   'libpangoft2-1.0-0.dll',
                   'libwebp-5.dll',
                   'libpangoft2-1.0-0.dll',
                   'libxmlxpat.dll',
                   'libharfbuzz-gobject-0.dll'
    ]
    
    ## We also need to add the glade folder, cx_freeze will walk
    ## into it and copy all the necessary files
    glade_folder = 'glade'
    
    ## We need to add all the libraries too (for themes, etc..)
    gtk_libs = ['etc', 'lib', 'share']
    
    ## Create the list of includes as cx_freeze likes
    include_files = []
    for dll in missing_dll:
        include_files.append((os.path.join(include_dll_path, dll), dll))
    
    ## Let's add glade folder and files
    include_files.append((glade_folder, glade_folder))
    
    ## Let's add gtk libraries folders and files
    for lib in gtk_libs:
        include_files.append((os.path.join(include_dll_path, lib), lib))
    
    base = None
    
    ## Lets not open the console while running the app
    if sys.platform == "win32":
        base = "Win32GUI" 
    
    executables = [
        Executable("main.py",
                   base=base
        )
    ]
    
    buildOptions = dict(
        compressed = True,
        includes = ["gi"],
        packages = ["gi"],
        include_files = include_files
        )
    
    setup(
        name = "test_gtk3_app",
        author = "Alan",
        version = "1.0",
        description = "GTK 3 test",
        options = dict(build_exe = buildOptions),
        executables = executables
    )

## Build

* start "Command Prompt"
* `cd Documents/test/`
* `python setup.py bdist`. The resulting ZIP file lives in `dist/`. It is 37M big!
* `python setyp.py bdist_msi` created a windows installer in `dist/`.

## Test

In another fresh windows 8.1VM

* Install "Microsoft Visual C++ 2010 SP1 Redistributable Package (x86)" (<https://www.microsoft.com/en-US/download/details.aspx?id=8328>).
* extract the ZIP file
* run main.exe
* it works

## Next steps

* make a single executable file: <https://cx-freeze.readthedocs.org/en/latest/faq.html#single-file-executables>
* bundle `msvcr100.dll`: <https://msdn.microsoft.com/en-us/library/8kche8ah.aspx
* shring the ZIP:
  * <https://stackoverflow.com/questions/20067856/python3-pygobject-gtk3-and-cx-freeze-missing-dlls>
  * <https://bitbucket.org/anthony_tuininga/cx_freeze/issue/92/pygi-and-cx_freeze-error>

## Other tools

* py2exe: <https://gmigdos.wordpress.com/2014/06/29/how-to-bundle-python-gtk3-apps-on-windows-with-py2exe/>

# Conclusion

We have outlined the main requirements we should face on fully porting the Tails
Installer to Windows. The major differences in the current Tails version regarding
the former upstream Fedora version, are the usage of the Python interface for GTK3
(PyGI) and the udisks2 library for disk operations.

We have found that there are alternative libraries we could use on Windows, in order
to perform the Linux specific operations. Some of them seem currently maintained and
updated, for instance, PyGI for Windows and extlinux. Other like sgdisk and its dependencies
might need some custom maintenance, which could rise the amount of required effort.
However, even with the extra required work, it seems totally likely that we can successfully
port Tails installer to Windows.

If our objective is to ease Tails adoption for Windows users, especially people that never have
used Linux, then this project may be worth the effort. The question
then becomes: is this the cheapest and/or best way to ease Tails
adoption for Windows users?