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