3656: Alphabetize list of subcommands. Rename tmp -> tmp_file. Small wording change...
[arvados.git] / docker / build_tools / build.rb
1 #! /usr/bin/env ruby
2
3 require 'optparse'
4 require 'tempfile'
5 require 'yaml'
6
7 def main options
8   if not ip_forwarding_enabled?
9     warn "NOTE: IP forwarding must be enabled in the kernel."
10     warn "Turning IP forwarding on now."
11     sudo %w(/sbin/sysctl net.ipv4.ip_forward=1)
12   end
13
14   # Check that:
15   #   * Docker is installed and can be found in the user's path
16   #   * Docker can be run as a non-root user
17   #      - TODO: put the user in the docker group if necessary
18   #      - TODO: mount cgroup automatically
19   #      - TODO: start the docker service if not started
20
21   docker_path = %x(which docker.io).chomp
22
23   if docker_path.empty?
24     docker_path = %x(which docker).chomp
25   end
26
27   if docker_path.empty?
28     warn "Docker not found."
29     warn ""
30     warn "Please make sure that Docker has been installed and"
31     warn "can be found in your PATH."
32     warn ""
33     warn "Installation instructions for a variety of platforms can be found at"
34     warn "http://docs.docker.io/en/latest/installation/"
35     exit 1
36   elsif not docker_ok? docker_path
37     warn "WARNING: docker could not be run."
38     warn "Please make sure that:"
39     warn "  * You have permission to read and write /var/run/docker.sock"
40     warn "  * a 'cgroup' volume is mounted on your machine"
41     warn "  * the docker daemon is running"
42     exit 2
43   end
44
45   # Check that debootstrap is installed.
46   if not debootstrap_ok?
47     warn "Installing debootstrap."
48     sudo '/usr/bin/apt-get', 'install', 'debootstrap'
49   end
50
51   # Generate a config.yml if it does not exist or is empty
52   if not File.size? 'config.yml'
53     print "Generating config.yml.\n"
54     print "Arvados needs to know the email address of the administrative user,\n"
55     print "so that when that user logs in they are automatically made an admin.\n"
56     print "This should be an email address associated with a Google account.\n"
57     print "\n"
58     admin_email_address = ""
59     until is_valid_email? admin_email_address
60       print "Enter your Google ID email address here: "
61       admin_email_address = gets.strip
62       if not is_valid_email? admin_email_address
63         print "That doesn't look like a valid email address. Please try again.\n"
64       end
65     end
66
67     print "Arvados needs to know the shell login name for the administrative user.\n"
68     print "This will also be used as the name for your git repository.\n"
69     print "\n"
70     user_name = ""
71     until is_valid_user_name? user_name
72       print "Enter a shell login name here: "
73       user_name = gets.strip
74       if not is_valid_user_name? user_name
75         print "That doesn't look like a valid shell login name. Please try again.\n"
76       end
77     end
78
79     File.open 'config.yml', 'w' do |config_out|
80       config_out.write "# If a _PW or _SECRET variable is set to an empty string, a password\n"
81       config_out.write "# will be chosen randomly at build time. This is the\n"
82       config_out.write "# recommended setting.\n\n"
83       config = YAML.load_file 'config.yml.example'
84       config['API_AUTO_ADMIN_USER'] = admin_email_address
85       config['ARVADOS_USER_NAME'] = user_name
86       config['API_HOSTNAME'] = generate_api_hostname
87       config['API_WORKBENCH_ADDRESS'] = 'false'
88       config['PUBLIC_KEY_PATH'] = find_or_create_ssh_key(config['API_HOSTNAME'])
89       config.each_key do |var|
90         config_out.write "#{var}: #{config[var]}\n"
91       end
92     end
93   end
94
95   # If all prerequisites are met, go ahead and build.
96   if ip_forwarding_enabled? and
97       docker_ok? docker_path and
98       debootstrap_ok? and
99       File.exists? 'config.yml'
100     exit 0
101   else
102     exit 6
103   end
104 end
105
106 # sudo
107 #   Execute the arg list 'cmd' under sudo.
108 #   cmd can be passed either as a series of arguments or as a
109 #   single argument consisting of a list, e.g.:
110 #     sudo 'apt-get', 'update'
111 #     sudo(['/usr/bin/gpasswd', '-a', ENV['USER'], 'docker'])
112 #     sudo %w(/usr/bin/apt-get install lxc-docker)
113 #
114 def sudo(*cmd)
115   # user can pass a single list in as an argument
116   # to allow usage like: sudo %w(apt-get install foo)
117   warn "You may need to enter your password here."
118   if cmd.length == 1 and cmd[0].class == Array
119     cmd = cmd[0]
120   end
121   system '/usr/bin/sudo', *cmd
122 end
123
124 # is_valid_email?
125 #   Returns true if its arg looks like a valid email address.
126 #   This is a very very loose sanity check.
127 #
128 def is_valid_email? str
129   str.match /^\S+@\S+\.\S+$/
130 end
131
132 # is_valid_user_name?
133 #   Returns true if its arg looks like a valid unix username.
134 #   This is a very very loose sanity check.
135 #
136 def is_valid_user_name? str
137   # borrowed from Debian's adduser (version 3.110)
138   str.match /^[_.A-Za-z0-9][-\@_.A-Za-z0-9]*\$?$/
139 end
140
141 # generate_api_hostname
142 #   Generates a 5-character randomly chosen API hostname.
143 #
144 def generate_api_hostname
145   rand(2**256).to_s(36)[0...5]
146 end
147
148 # ip_forwarding_enabled?
149 #   Returns 'true' if IP forwarding is enabled in the kernel
150 #
151 def ip_forwarding_enabled?
152   %x(/sbin/sysctl -n net.ipv4.ip_forward) == "1\n"
153 end
154
155 # debootstrap_ok?
156 #   Returns 'true' if debootstrap is installed and working.
157 #
158 def debootstrap_ok?
159   return system '/usr/sbin/debootstrap --version > /dev/null 2>&1'
160 end
161
162 # docker_ok?
163 #   Returns 'true' if docker can be run as the current user.
164 #
165 def docker_ok?(docker_path)
166   return system "#{docker_path} images > /dev/null 2>&1"
167 end
168
169 # find_or_create_ssh_key arvados_name
170 #   Returns the SSH public key appropriate for this Arvados instance,
171 #   generating one if necessary.
172 #
173 def find_or_create_ssh_key arvados_name
174   ssh_key_file = "#{ENV['HOME']}/.ssh/arvados_#{arvados_name}_id_rsa"
175   unless File.exists? ssh_key_file
176     system 'ssh-keygen',
177            '-f', ssh_key_file,
178            '-C', "arvados@#{arvados_name}",
179            '-P', ''
180   end
181
182   return "#{ssh_key_file}.pub"
183 end
184
185 # install_docker
186 #   Determines which Docker package is suitable for this Linux distro
187 #   and installs it, resolving any dependencies.
188 #   NOTE: not in use yet.
189
190 def install_docker
191   linux_distro = %x(lsb_release --id).split.last
192   linux_release = %x(lsb_release --release).split.last
193   linux_version = linux_distro + " " + linux_release
194   kernel_release = `uname -r`
195
196   case linux_distro
197   when 'Ubuntu'
198     if not linux_release.match '^1[234]\.'
199       warn "Arvados requires at least Ubuntu 12.04 (Precise Pangolin)."
200       warn "Your system is Ubuntu #{linux_release}."
201       exit 3
202     end
203     if linux_release.match '^12' and kernel_release.start_with? '3.2'
204       # Ubuntu Precise ships with a 3.2 kernel and must be upgraded.
205       warn "Your kernel #{kernel_release} must be upgraded to run Docker."
206       warn "To do this:"
207       warn "  sudo apt-get update"
208       warn "  sudo apt-get install linux-image-generic-lts-raring linux-headers-generic-lts-raring"
209       warn "  sudo reboot"
210       exit 4
211     else
212       # install AUFS
213       sudo 'apt-get', 'update'
214       sudo 'apt-get', 'install', "linux-image-extra-#{kernel_release}"
215     end
216
217     # add Docker repository
218     sudo %w(/usr/bin/apt-key adv
219               --keyserver keyserver.ubuntu.com
220               --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9)
221     source_file = Tempfile.new('arv')
222     source_file.write("deb http://get.docker.io/ubuntu docker main\n")
223     source_file.close
224     sudo '/bin/mv', source_file.path, '/etc/apt/sources.list.d/docker.list'
225     sudo %w(/usr/bin/apt-get update)
226     sudo %w(/usr/bin/apt-get install lxc-docker)
227
228     # Set up for non-root access
229     sudo %w(/usr/sbin/groupadd docker)
230     sudo '/usr/bin/gpasswd', '-a', ENV['USER'], 'docker'
231     sudo %w(/usr/sbin/service docker restart)
232   when 'Debian'
233   else
234     warn "Must be running a Debian or Ubuntu release in order to run Docker."
235     exit 5
236   end
237 end
238
239
240 if __FILE__ == $PROGRAM_NAME
241   options = { :makefile => File.join(File.dirname(__FILE__), 'Makefile') }
242   OptionParser.new do |opts|
243     opts.on('-m', '--makefile MAKEFILE-PATH',
244             'Path to the Makefile used to build Arvados Docker images') do |mk|
245       options[:makefile] = mk
246     end
247   end
248   main options
249 end