blob: 8b46eff3c7364365057a99c63da3e53cb1ae5e7f [file] [log] [blame]
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +08001#!/usr/bin/python -Bu
2#
3# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Factory toolkit installer.
8
9The factory toolkit is a self-extracting shellball containing factory test
10related files and this installer. This installer is invoked when the toolkit
11is deployed and is responsible for installing files.
12"""
13
14
15import argparse
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +080016from contextlib import contextmanager
Hung-Te Lineb7632b2016-07-29 15:38:34 +080017import glob
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +080018import os
Wei-Ning Huang4855e792015-06-11 15:33:39 +080019import shutil
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +080020import sys
Jon Salz4f3ade52014-02-20 17:55:09 +080021import tempfile
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +080022
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +080023import factory_common # pylint: disable=W0611
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +080024from cros.factory.test import event_log
Wei-Han Chen2ebb92d2016-01-12 14:51:41 +080025from cros.factory.test.env import paths
Jon Salz25590302014-07-11 16:07:20 +080026from cros.factory.tools import install_symlinks
Rong Chang6d65fad2014-08-22 16:00:12 +080027from cros.factory.utils import file_utils
Hung-Te Linf5f2d7f2016-01-08 17:12:46 +080028from cros.factory.utils import sys_utils
Wei-Han Chen2ebb92d2016-01-12 14:51:41 +080029from cros.factory.utils.process_utils import Spawn
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +080030
31
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +080032INSTALLER_PATH = 'usr/local/factory/py/toolkit/installer.py'
Rong Chang6d65fad2014-08-22 16:00:12 +080033MAKESELF_SHELL = '/bin/sh'
34TOOLKIT_NAME = 'install_factory_toolkit.run'
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +080035
Jon Salz4f3ade52014-02-20 17:55:09 +080036# Short and sweet help header for the executable generated by makeself.
37HELP_HEADER = """
38Installs the factory toolkit, transforming a test image into a factory test
39image. You can:
40
41- Install the factory toolkit on a CrOS device that is running a test
42 image. To do this, copy install_factory_toolkit.run to the device and
43 run it. The factory tests will then come up on the next boot.
44
45 rsync -a install_factory_toolkit.run crosdevice:/tmp
46 ssh crosdevice '/tmp/install_factory_toolkit.run && sync && reboot'
47
48- Modify a test image, turning it into a factory test image. When you
49 use the image on a device, the factory tests will come up.
50
51 install_factory_toolkit.run chromiumos_test_image.bin
52"""
53
54HELP_HEADER_ADVANCED = """
55- (advanced) Modify a mounted stateful partition, turning it into a factory
56 test image. This is equivalent to the previous command:
57
58 mount_partition -rw chromiumos_test_image.bin 1 /mnt/stateful
59 install_factory_toolkit.run /mnt/stateful
60 umount /mnt/stateful
61
62- (advanced) Unpack the factory toolkit, modify a file, and then repack it.
63
64 # Unpack but don't actually install
65 install_factory_toolkit.run --target /tmp/toolkit --noexec
66 # Edit some files in /tmp/toolkit
67 emacs /tmp/toolkit/whatever
68 # Repack
69 install_factory_toolkit.run -- --repack /tmp/toolkit \\
70 --pack-into /path/to/new/install_factory_toolkit.run
71"""
72
73# The makeself-generated header comes next. This is a little confusing,
74# so explain.
75HELP_HEADER_MAKESELF = """
76For complete usage information and advanced operations, run
77"install_factory_toolkit.run -- --help" (note the extra "--").
78
79Following is the help message from makeself, which was used to create
80this self-extracting archive.
81
82-----
83"""
84
Vic Yangdb1e20e2014-10-05 12:10:33 +080085SERVER_FILE_MASK = [
86 # Exclude Umpire server but keep Umpire client
87 '--include', 'py/umpire/__init__.*',
88 '--include', 'py/umpire/common.*',
89 '--include', 'py/umpire/client',
90 '--include', 'py/umpire/client/**',
91 '--exclude', 'py/umpire/**',
92
93 # Lumberjack is only used on Umpire server
94 '--exclude', 'py/lumberjack'
95]
96
Jon Salz4f3ade52014-02-20 17:55:09 +080097
Hung-Te Lin7b596ff2015-01-16 20:19:15 +080098class FactoryToolkitInstaller(object):
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +080099 """Factory toolkit installer.
100
101 Args:
102 src: Source path containing usr/ and var/.
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800103 dest: Installation destination path. Set this to the mount point of the
104 stateful partition if patching a test image.
105 no_enable: True to not install the tag file.
106 system_root: The path to the root of the file system. This must be left
107 as its default value except for unit testing.
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800108 """
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800109
Jon Salzb7e44262014-05-07 15:53:37 +0800110 # Whether to sudo when rsyncing; set to False for testing.
111 _sudo = True
112
Peter Ammon948b7172014-07-15 12:43:06 -0700113 def __init__(self, src, dest, no_enable, enable_presenter,
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800114 enable_device, non_cros=False, device_id=None, system_root='/',
115 apps=None):
Jon Salz4f3ade52014-02-20 17:55:09 +0800116 self._src = src
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800117 self._system_root = system_root
118 if dest == self._system_root:
119 self._usr_local_dest = os.path.join(dest, 'usr', 'local')
120 self._var_dest = os.path.join(dest, 'var')
Jon Salz4f3ade52014-02-20 17:55:09 +0800121
122 # Make sure we're on a CrOS device.
Hung-Te Linf5f2d7f2016-01-08 17:12:46 +0800123 if not non_cros and not sys_utils.InCrOSDevice():
Jon Salz4f3ade52014-02-20 17:55:09 +0800124 sys.stderr.write(
Vic Yang196d5242014-08-05 13:51:35 +0800125 "ERROR: You're not on a CrOS device (for more details, please\n"
Hung-Te Linf5f2d7f2016-01-08 17:12:46 +0800126 'check sys_utils.py:InCrOSDevice), so you must specify a test\n'
Hung-Te Lin56b18402015-01-16 14:52:30 +0800127 'image or a mounted stateful partition on which to install the\n'
128 'factory toolkit. Please run\n'
129 '\n'
130 ' install_factory_toolkit.run -- --help\n'
131 '\n'
Vic Yang70fdae92015-02-17 19:21:08 -0800132 'for help.\n'
133 '\n'
134 'If you want to install the presenter on a non-CrOS host,\n'
135 'please run\n'
136 '\n'
137 ' install_factory_toolkit.run -- \\\n'
Hung-Te Lin0cba9d52016-03-22 11:45:35 +0800138 ' --non-cros --enable-presenter\n'
Vic Yang70fdae92015-02-17 19:21:08 -0800139 '\n')
Jon Salz4f3ade52014-02-20 17:55:09 +0800140 sys.exit(1)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800141 if os.getuid() != 0:
Jon Salz4f3ade52014-02-20 17:55:09 +0800142 raise Exception('You must be root to install the factory toolkit on a '
143 'CrOS device.')
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800144 else:
145 self._usr_local_dest = os.path.join(dest, 'dev_image')
146 self._var_dest = os.path.join(dest, 'var_overlay')
147 if (not os.path.exists(self._usr_local_dest) or
148 not os.path.exists(self._var_dest)):
149 raise Exception(
150 'The destination path %s is not a stateful partition!' % dest)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800151
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800152 self._dest = dest
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800153 self._usr_local_src = os.path.join(src, 'usr', 'local')
154 self._var_src = os.path.join(src, 'var')
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800155 self._no_enable = no_enable
Vic (Chun-Ju) Yang7cc3e672014-01-20 14:06:39 +0800156 self._tag_file = os.path.join(self._usr_local_dest, 'factory', 'enabled')
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800157
Peter Ammon948b7172014-07-15 12:43:06 -0700158 self._enable_presenter = enable_presenter
159 self._presenter_tag_file = os.path.join(self._usr_local_dest, 'factory',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800160 'init', 'run_goofy_presenter')
Vic Yang7039f422014-07-07 15:38:13 -0700161
162 self._enable_device = enable_device
Ricky Liangd7716912014-07-10 11:52:24 +0800163 self._device_tag_file = os.path.join(self._usr_local_dest, 'factory',
164 'init', 'run_goofy_device')
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +0800165 self._device_id = device_id
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800166 self._apps = apps
Vic Yang7039f422014-07-07 15:38:13 -0700167
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800168 if (not os.path.exists(self._usr_local_src) or
169 not os.path.exists(self._var_src)):
170 raise Exception(
171 'This installer must be run from within the factory toolkit!')
172
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800173 def WarningMessage(self, target_test_image=None):
Jon Salz4f3ade52014-02-20 17:55:09 +0800174 with open(os.path.join(self._src, 'VERSION')) as f:
175 ret = f.read()
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800176 if target_test_image:
Jon Salz4f3ade52014-02-20 17:55:09 +0800177 ret += (
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800178 '\n'
179 '\n'
Jon Salz4f3ade52014-02-20 17:55:09 +0800180 '*** You are about to patch the factory toolkit into:\n'
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800181 '*** %s\n'
182 '***' % target_test_image)
183 else:
Jon Salz4f3ade52014-02-20 17:55:09 +0800184 ret += (
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800185 '\n'
186 '\n'
Jon Salz4f3ade52014-02-20 17:55:09 +0800187 '*** You are about to install the factory toolkit to:\n'
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800188 '*** %s\n'
189 '***' % self._dest)
190 if self._dest == self._system_root:
Vic (Chun-Ju) Yang7cc3e672014-01-20 14:06:39 +0800191 if self._no_enable:
Hung-Te Lin7b596ff2015-01-16 20:19:15 +0800192 ret += ('\n*** Factory tests will be disabled after this process is '
Hung-Te Lin56b18402015-01-16 14:52:30 +0800193 'done, but\n*** you can enable them by creating the factory '
194 'enabled tag:\n*** %s\n***' % self._tag_file)
Vic (Chun-Ju) Yang7cc3e672014-01-20 14:06:39 +0800195 else:
Hung-Te Lin7b596ff2015-01-16 20:19:15 +0800196 ret += ('\n*** After this process is done, your device will start '
Hung-Te Lin56b18402015-01-16 14:52:30 +0800197 'factory\n*** tests on the next reboot.\n***\n*** Factory '
198 'tests can be disabled by deleting the factory enabled\n*** '
199 'tag:\n*** %s\n***' % self._tag_file)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800200 return ret
201
Vic Yang7039f422014-07-07 15:38:13 -0700202 def _SetTagFile(self, name, path, enabled):
203 """Install or remove a tag file."""
204 if enabled:
205 print '*** Installing %s enabled tag...' % name
206 Spawn(['touch', path], sudo=True, log=True, check_call=True)
Ricky Liang8c88a122014-07-11 21:21:22 +0800207 Spawn(['chmod', 'go+r', path], sudo=True, log=True, check_call=True)
Vic Yang7039f422014-07-07 15:38:13 -0700208 else:
209 print '*** Removing %s enabled tag...' % name
210 Spawn(['rm', '-f', path], sudo=True, log=True, check_call=True)
211
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +0800212 def _SetDeviceID(self):
213 if self._device_id is not None:
214 with open(os.path.join(event_log.DEVICE_ID_PATH), 'w') as f:
215 f.write(self._device_id)
216
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800217 def _EnableApp(self, app, enabled):
218 """Enable / disable @app.
219
220 In factory/init/startup, a main app is considered disabled if and only:
221 1. file "factory/init/main.d/disable-@app" exists OR
222 2. file "factory/init/main.d/enable-@app" doesn't exist AND
223 file "factory/init/main.d/@app.sh" is not executable.
224
225 Therefore, we enable an app by removing file "disable-@app" and creating
226 file "enable-@app", and vise versa.
227 """
228 app_enable = os.path.join(self._usr_local_dest,
229 'factory', 'init', 'main.d', 'enable-' + app)
230 app_disable = os.path.join(self._usr_local_dest,
231 'factory', 'init', 'main.d', 'disable-' + app)
232 if enabled:
233 print '*** Enabling {app} ***'.format(app=app)
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800234 Spawn(['rm', '-f', app_disable], sudo=self._sudo, log=True,
235 check_call=True)
236 Spawn(['touch', app_enable], sudo=self._sudo, log=True, check_call=True)
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800237 else:
238 print '*** Disabling {app} ***'.format(app=app)
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800239 Spawn(['touch', app_disable], sudo=self._sudo, log=True, check_call=True)
240 Spawn(['rm', '-f', app_enable], sudo=self._sudo, log=True,
241 check_call=True)
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800242
243 def _EnableApps(self):
244 if not self._apps:
245 return
246
247 app_list = []
248 for app in self._apps:
249 if app[0] == '+':
250 app_list.append((app[1:], True))
251 elif app[0] == '-':
252 app_list.append((app[1:], False))
253 else:
254 raise ValueError(
255 'Use +{app} to enable and -{app} to disable'.format(app=app))
256
257 for app, enabled in app_list:
258 self._EnableApp(app, enabled)
259
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800260 def InstallFactorySubDir(self, sub_dirs):
261 """Install only the specified directories under factory folder."""
262
263 def _InstallOneSubDir(sub_dir_name):
264 sub_dir_dest = os.path.join(self._usr_local_dest, 'factory', sub_dir_name)
265 sub_dir_src = os.path.join(self._src, 'usr', 'local', 'factory',
266 sub_dir_name)
267 try:
268 Spawn(['mkdir', '-p', sub_dir_dest], sudo=True, log=True,
269 check_call=True)
270 except OSError as e:
271 print str(e)
272 return
273
274 Spawn(['rsync', '-a', '--force', '-v',
275 sub_dir_src + '/', sub_dir_dest],
276 sudo=self._sudo, log=True, check_call=True)
277 Spawn(['chown', '-R', 'root', sub_dir_dest],
278 sudo=self._sudo, log=True, check_call=True)
279 Spawn(['chmod', '-R', 'go+rX', sub_dir_dest],
280 sudo=self._sudo, log=True, check_call=True)
281
282 for sub_dir_name in sub_dirs:
283 _InstallOneSubDir(sub_dir_name)
284
285 self._SetTagFile('factory', self._tag_file, not self._no_enable)
286 self._EnableApps()
287
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800288 def Install(self):
289 print '*** Installing factory toolkit...'
Jon Salzb7e44262014-05-07 15:53:37 +0800290 for src, dest in ((self._usr_local_src, self._usr_local_dest),
291 (self._var_src, self._var_dest)):
292 # Change the source directory to root, and add group/world read
293 # permissions. This is necessary because when the toolkit was
294 # unpacked, the user may not have been root so the permessions
295 # may be hosed. This is skipped for testing.
Peter Ammon5ac58422014-06-09 14:45:50 -0700296 # --force is necessary to allow goofy directory from prior
297 # toolkit installations to be overwritten by the goofy symlink.
Ricky Liang5e95be22014-07-09 12:52:07 +0800298 try:
299 if self._sudo:
300 Spawn(['chown', '-R', 'root', src],
301 sudo=True, log=True, check_call=True)
302 Spawn(['chmod', '-R', 'go+rX', src],
303 sudo=True, log=True, check_call=True)
304 print '*** %s -> %s' % (src, dest)
Hung-Te Lin56b18402015-01-16 14:52:30 +0800305 Spawn(['rsync', '-a', '--force'] + SERVER_FILE_MASK +
Vic Yangdb1e20e2014-10-05 12:10:33 +0800306 [src + '/', dest], sudo=self._sudo, log=True,
307 check_output=True, cwd=src)
Ricky Liang5e95be22014-07-09 12:52:07 +0800308 finally:
309 # Need to change the source directory back to the original user, or the
310 # script in makeself will fail to remove the temporary source directory.
311 if self._sudo:
312 myuser = os.environ.get('USER')
313 Spawn(['chown', '-R', myuser, src],
314 sudo=True, log=True, check_call=True)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800315
Hung-Te Lineb7632b2016-07-29 15:38:34 +0800316 print '*** Ensure SSH keys file permission...'
317 sshkeys_dir = os.path.join(self._usr_local_dest, 'factory/misc/sshkeys')
318 sshkeys = glob.glob(os.path.join(sshkeys_dir, '*'))
319 ssh_public_keys = glob.glob(os.path.join(sshkeys_dir, '*.pub'))
320 ssh_private_keys = list(set(sshkeys) - set(ssh_public_keys))
321 if ssh_private_keys:
322 Spawn(['chmod', '600'] + ssh_private_keys, log=True, check_call=True,
323 sudo=self._sudo)
324
Jon Salz25590302014-07-11 16:07:20 +0800325 print '*** Installing symlinks...'
326 install_symlinks.InstallSymlinks(
327 '../factory/bin',
328 os.path.join(self._usr_local_dest, 'bin'),
329 install_symlinks.MODE_FULL,
330 sudo=self._sudo)
331
Vic Yang7039f422014-07-07 15:38:13 -0700332 self._SetTagFile('factory', self._tag_file, not self._no_enable)
Peter Ammon948b7172014-07-15 12:43:06 -0700333 self._SetTagFile('presenter', self._presenter_tag_file,
334 self._enable_presenter)
Vic Yang7039f422014-07-07 15:38:13 -0700335 self._SetTagFile('device', self._device_tag_file, self._enable_device)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800336
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +0800337 self._SetDeviceID()
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800338 self._EnableApps()
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +0800339
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800340 print '*** Installation completed.'
341
342
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800343@contextmanager
344def DummyContext(arg):
345 """A context manager that simply yields its argument."""
346 yield arg
347
348
Vic (Chun-Ju) Yang98b4fbc2014-02-18 19:32:32 +0800349def PrintBuildInfo(src_root):
350 """Print build information."""
351 info_file = os.path.join(src_root, 'REPO_STATUS')
352 if not os.path.exists(info_file):
353 raise OSError('Build info file not found!')
354 with open(info_file, 'r') as f:
355 print f.read()
356
357
Wei-Ning Huangb723d7f2014-11-17 17:26:32 +0800358def PackFactoryToolkit(src_root, output_path, enable_device, enable_presenter):
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800359 """Packs the files containing this script into a factory toolkit."""
360 with open(os.path.join(src_root, 'VERSION'), 'r') as f:
361 version = f.read().strip()
Jon Salz4f3ade52014-02-20 17:55:09 +0800362 with tempfile.NamedTemporaryFile() as help_header:
Hung-Te Lin56b18402015-01-16 14:52:30 +0800363 help_header.write(version + '\n' + HELP_HEADER + HELP_HEADER_MAKESELF)
Jon Salz4f3ade52014-02-20 17:55:09 +0800364 help_header.flush()
Wei-Ning Huangb723d7f2014-11-17 17:26:32 +0800365 cmd = [os.path.join(src_root, 'makeself.sh'), '--bzip2', '--nox11',
Jon Salz4f3ade52014-02-20 17:55:09 +0800366 '--help-header', help_header.name,
Wei-Ning Huangb723d7f2014-11-17 17:26:32 +0800367 src_root, output_path, version, INSTALLER_PATH, '--in-exe']
368 if not enable_device:
369 cmd.append('--no-enable-device')
370 if not enable_presenter:
371 cmd.append('--no-enable-presenter')
372 Spawn(cmd, check_call=True, log=True)
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800373 print ('\n'
Hung-Te Lin56b18402015-01-16 14:52:30 +0800374 ' Factory toolkit generated at %s.\n'
375 '\n'
376 ' To install factory toolkit on a live device running a test image,\n'
377 ' copy this to the device and execute it as root.\n'
378 '\n'
379 ' Alternatively, the factory toolkit can be used to patch a test\n'
380 ' image. For more information, run:\n'
381 ' %s --help\n'
382 '\n' % (output_path, output_path))
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800383
384
Rong Chang6d65fad2014-08-22 16:00:12 +0800385def InitUmpire(exe_path, src_root, target_board):
386 """Inits Umpire server environment."""
387 if exe_path is None:
388 parent_cmdline = open('/proc/%s/cmdline' % os.getppid(),
389 'r').read().rstrip('\0').split('\0')
390
You-Cheng Syufd644432016-10-04 13:40:51 +0800391 if len(parent_cmdline) > 1 and parent_cmdline[0] == MAKESELF_SHELL:
Rong Chang6d65fad2014-08-22 16:00:12 +0800392 # Get parent script name from parent process.
393 exe_path = parent_cmdline[1]
394 else:
395 # Set to default.
396 exe_path = TOOLKIT_NAME
397
398 if not exe_path.startswith('/'):
399 exe_path = os.path.join(os.environ.get('OLDPWD'), exe_path)
400
401 with file_utils.TempDirectory() as nano_bundle:
402 bundle_toolkit_dir = os.path.join(nano_bundle, 'factory_toolkit')
403 os.mkdir(bundle_toolkit_dir)
You-Cheng Syufd644432016-10-04 13:40:51 +0800404 os.symlink(exe_path, os.path.join(bundle_toolkit_dir, TOOLKIT_NAME))
Rong Chang6d65fad2014-08-22 16:00:12 +0800405 umpire_bin = os.path.join(src_root, 'usr', 'local', 'factory', 'bin',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800406 'umpire')
Rong Chang6d65fad2014-08-22 16:00:12 +0800407 Spawn([umpire_bin, 'init', '--board', target_board, nano_bundle],
408 check_call=True, log=True)
409 print ('\n'
Hung-Te Lin56b18402015-01-16 14:52:30 +0800410 ' Umpire initialized successfully. Upstart service is running:\n'
411 ' umpire BOARD=%(board)s.\n'
412 ' For more information, please check umpire command line:\n'
413 '\n'
414 ' umpire-%(board)s --help (if your id is in umpire group)\n'
415 ' or sudo umpire-%(board)s --help\n'
416 '\n' % {'board': target_board})
Rong Chang6d65fad2014-08-22 16:00:12 +0800417
418
Wei-Ning Huang4855e792015-06-11 15:33:39 +0800419def ExtractOverlord(src_root, output_dir):
420 output_dir = os.path.join(output_dir, 'overlord')
421 try:
422 os.makedirs(output_dir)
423 except OSError as e:
424 print str(e)
425 return
426
427 # Copy overlord binary and resource files
428 shutil.copyfile(os.path.join(src_root, 'usr/bin/overlordd'),
429 os.path.join(output_dir, 'overlordd'))
430 shutil.copytree(os.path.join(src_root, 'usr/share/overlord/app'),
431 os.path.join(output_dir, 'app'))
432
433 # Give overlordd execution permission
434 os.chmod(os.path.join(output_dir, 'overlordd'), 0755)
435 print "Extarcted overlord under '%s'" % output_dir
436
437
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800438def main():
Jon Salz4f3ade52014-02-20 17:55:09 +0800439 import logging
440 logging.basicConfig(level=logging.INFO)
441
442 # In order to determine which usage message to show, first determine
443 # whether we're in the self-extracting archive. Do this first
444 # because we need it to even parse the arguments.
445 if '--in-exe' in sys.argv:
446 sys.argv = [x for x in sys.argv if x != '--in-exe']
447 in_archive = True
448 else:
449 in_archive = False
450
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800451 parser = argparse.ArgumentParser(
Jon Salz4f3ade52014-02-20 17:55:09 +0800452 description=HELP_HEADER + HELP_HEADER_ADVANCED,
453 usage=('install_factory_toolkit.run -- [options]' if in_archive
454 else None),
455 formatter_class=argparse.RawDescriptionHelpFormatter)
Hung-Te Lin56b18402015-01-16 14:52:30 +0800456 parser.add_argument(
457 'dest', nargs='?', default='/',
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800458 help='A test image or the mount point of the stateful partition. '
459 "If omitted, install to live system, i.e. '/'.")
Vic (Chun-Ju) Yang7cc3e672014-01-20 14:06:39 +0800460 parser.add_argument('--no-enable', '-n', action='store_true',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800461 help="Don't enable factory tests after installing")
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800462 parser.add_argument('--yes', '-y', action='store_true',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800463 help="Don't ask for confirmation")
Vic (Chun-Ju) Yang98b4fbc2014-02-18 19:32:32 +0800464 parser.add_argument('--build-info', action='store_true',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800465 help='Print build information and exit')
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800466 parser.add_argument('--pack-into', metavar='NEW_TOOLKIT',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800467 help='Pack the files into a new factory toolkit')
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800468 parser.add_argument('--repack', metavar='UNPACKED_TOOLKIT',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800469 help='Repack from previously unpacked toolkit')
Vic Yang7039f422014-07-07 15:38:13 -0700470
Peter Ammon948b7172014-07-15 12:43:06 -0700471 parser.add_argument('--enable-presenter', dest='enable_presenter',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800472 action='store_true',
473 help='Run goofy in presenter mode on startup')
Peter Ammon948b7172014-07-15 12:43:06 -0700474 parser.add_argument('--no-enable-presenter', dest='enable_presenter',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800475 action='store_false', help=argparse.SUPPRESS)
Hung-Te Lin0cba9d52016-03-22 11:45:35 +0800476 parser.set_defaults(enable_presenter=False)
Vic Yang7039f422014-07-07 15:38:13 -0700477
Vic Yang70fdae92015-02-17 19:21:08 -0800478 parser.add_argument('--non-cros', dest='non_cros',
479 action='store_true',
480 help='Install on non-ChromeOS host.')
481
Vic Yang7039f422014-07-07 15:38:13 -0700482 parser.add_argument('--enable-device', dest='enable_device',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800483 action='store_true',
484 help='Run goofy in device mode on startup')
Vic Yang7039f422014-07-07 15:38:13 -0700485 parser.add_argument('--no-enable-device', dest='enable_device',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800486 action='store_false', help=argparse.SUPPRESS)
Hung-Te Lin0cba9d52016-03-22 11:45:35 +0800487 parser.set_defaults(enable_device=False)
Vic Yang423c2722014-09-24 16:05:06 +0800488
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +0800489 parser.add_argument('--device-id', dest='device_id', type=str, default=None,
490 help='Set device ID for this device')
491
Rong Chang6d65fad2014-08-22 16:00:12 +0800492 parser.add_argument('--init-umpire-board', dest='umpire_board',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800493 nargs='?', default=None,
494 help='Locally install Umpire server for specific board')
Rong Chang6d65fad2014-08-22 16:00:12 +0800495 parser.add_argument('--exe-path', dest='exe_path',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800496 nargs='?', default=None,
497 help='Current self-extracting archive pathname')
Wei-Ning Huang4855e792015-06-11 15:33:39 +0800498 parser.add_argument('--extract-overlord', dest='extract_overlord',
499 metavar='OUTPUT_DIR', type=str, default=None,
500 help='Extract overlord from the toolkit')
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800501 parser.add_argument('--install-dirs', nargs='+', default=None,
502 help=('Install only the specified directories under '
503 'factory folder. Can be used with --apps to '
504 'enable / disable some apps. Defaults to install '
505 'all folders.'))
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800506 parser.add_argument('--apps', type=lambda s: s.split(','), default=None,
507 help=('Enable or disable some apps under '
508 'factory/init/main.d/. Use prefix "-" to disable, '
509 'prefix "+" to enable, and use "," to seperate. '
510 'For example: --apps="-goofy,+whale_servo"'))
Vic Yang7039f422014-07-07 15:38:13 -0700511
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800512 args = parser.parse_args()
513
Wei-Han Chen2ebb92d2016-01-12 14:51:41 +0800514 src_root = paths.FACTORY_PATH
Vic (Chun-Ju) Yang98b4fbc2014-02-18 19:32:32 +0800515 for _ in xrange(3):
516 src_root = os.path.dirname(src_root)
517
Rong Chang6d65fad2014-08-22 16:00:12 +0800518 # --init-umpire-board creates a nano bundle, then calls umpire command
519 # line utility to install the server code and upstart configurations.
520 if args.umpire_board:
521 InitUmpire(args.exe_path, src_root, args.umpire_board)
522 return
523
Wei-Ning Huang4855e792015-06-11 15:33:39 +0800524 if args.extract_overlord is not None:
525 ExtractOverlord(src_root, args.extract_overlord)
526 return
527
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800528 # --pack-into may be called directly so this must be done before changing
529 # working directory to OLDPWD.
530 if args.pack_into and args.repack is None:
Wei-Ning Huangb723d7f2014-11-17 17:26:32 +0800531 PackFactoryToolkit(src_root, args.pack_into, args.enable_device,
532 args.enable_presenter)
Vic (Chun-Ju) Yang98b4fbc2014-02-18 19:32:32 +0800533 return
534
Jon Salz4f3ade52014-02-20 17:55:09 +0800535 if not in_archive:
536 # If you're not in the self-extracting archive, you're not allowed to
537 # do anything except the above --pack-into call.
538 parser.error('Not running from install_factory_toolkit.run; '
539 'only --pack-into (without --repack) is allowed')
540
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800541 # Change to original working directory in case the user specifies
542 # a relative path.
543 # TODO: Use USER_PWD instead when makeself is upgraded
544 os.chdir(os.environ['OLDPWD'])
545
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800546 if args.repack:
547 if args.pack_into is None:
548 parser.error('Must specify --pack-into when using --repack.')
549 Spawn([os.path.join(args.repack, INSTALLER_PATH),
550 '--pack-into', args.pack_into], check_call=True, log=True)
551 return
552
553 if args.build_info:
554 PrintBuildInfo(src_root)
555 return
556
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800557 if not os.path.exists(args.dest):
558 parser.error('Destination %s does not exist!' % args.dest)
559
560 patch_test_image = os.path.isfile(args.dest)
561
Hung-Te Linf5f2d7f2016-01-08 17:12:46 +0800562 with (sys_utils.MountPartition(args.dest, 1, rw=True) if patch_test_image
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800563 else DummyContext(args.dest)) as dest:
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800564
Hung-Te Lin56b18402015-01-16 14:52:30 +0800565 installer = FactoryToolkitInstaller(
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +0800566 src=src_root, dest=dest, no_enable=args.no_enable,
567 enable_presenter=args.enable_presenter,
568 enable_device=args.enable_device, non_cros=args.non_cros,
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800569 device_id=args.device_id,
570 apps=args.apps)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800571
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800572 print installer.WarningMessage(args.dest if patch_test_image else None)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800573
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800574 if not args.yes:
575 answer = raw_input('*** Continue? [y/N] ')
576 if not answer or answer[0] not in 'yY':
577 sys.exit('Aborting.')
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800578
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800579 if args.install_dirs:
580 installer.InstallFactorySubDir(args.install_dirs)
581 else:
582 installer.Install()
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800583
584if __name__ == '__main__':
585 main()