Resurrect another blueprint.
[besstails.git] / Rakefile
blobea3ef3871d5576c621c2c487dc6f69624d2ef9fc
1 # -*- coding: utf-8 -*-
2 # -*- mode: ruby -*-
3 # vi: set ft=ruby :
5 # Tails: The Amnesic Incognito Live System
6 # Copyright © 2012 Tails developers <tails@boum.org>
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
21 require 'rbconfig'
22 require 'rubygems'
23 require 'vagrant'
24 require 'uri'
26 $:.unshift File.expand_path('../vagrant/lib', __FILE__)
27 require 'tails_build_settings'
29 # Path to the directory which holds our Vagrantfile
30 VAGRANT_PATH = File.expand_path('../vagrant', __FILE__)
32 # Branches that are considered 'stable' (used to select SquashFS compression)
33 STABLE_BRANCH_NAMES = ['stable', 'testing']
35 # Environment variables that will be exported to the build script
36 EXPORTED_VARIABLES = ['http_proxy', 'MKSQUASHFS_OPTIONS', 'TAILS_RAM_BUILD', 'TAILS_CLEAN_BUILD', 'TAILS_BOOTSTRAP_CACHE']
38 # Let's save the http_proxy set before playing with it
39 EXTERNAL_HTTP_PROXY = ENV['http_proxy']
41 # In-VM proxy URL
42 INTERNEL_HTTP_PROXY = "http://#{VIRTUAL_MACHINE_HOSTNAME}:3142"
44 def current_vm_memory
45   env = Vagrant::Environment.new(:cwd => VAGRANT_PATH, :ui_class => Vagrant::UI::Basic)
46   uuid = env.primary_vm.uuid
47   info = env.primary_vm.driver.execute 'showvminfo', uuid, '--machinereadable'
48   $1.to_i if info =~ /^memory=(\d+)/
49 end
51 def current_vm_cpus
52   env = Vagrant::Environment.new(:cwd => VAGRANT_PATH, :ui_class => Vagrant::UI::Basic)
53   uuid = env.primary_vm.uuid
54   info = env.primary_vm.driver.execute 'showvminfo', uuid, '--machinereadable'
55   $1.to_i if info =~ /^cpus=(\d+)/
56 end
58 def vm_running?
59   env = Vagrant::Environment.new(:cwd => VAGRANT_PATH, :ui_class => Vagrant::UI::Basic)
60   env.primary_vm.state == :running
61 end
63 def enough_free_memory?
64   return false unless RbConfig::CONFIG['host_os'] =~ /linux/i
66   begin
67     usable_free_mem = `free`.split[16].to_i
68     usable_free_mem > VM_MEMORY_FOR_RAM_BUILDS * 1024
69   rescue
70     false
71   end
72 end
74 def is_release?
75   branch_name = `git name-rev --name-only HEAD`
76   tag_name = `git describe --exact-match HEAD 2> /dev/null`
77   STABLE_BRANCH_NAMES.include? branch_name.chomp or tag_name.chomp.length > 0
78 end
80 def system_cpus
81   return nil unless RbConfig::CONFIG['host_os'] =~ /linux/i
83   begin
84     File.read('/proc/cpuinfo').scan(/^processor\s+:/).count
85   rescue
86     nil
87   end
88 end
90 task :parse_build_options do
91   options = ''
93   # Default to in-memory builds if there is enough RAM available
94   options += 'ram ' if enough_free_memory?
96   # Use in-VM proxy unless an external proxy is set
97   options += 'vmproxy ' unless EXTERNAL_HTTP_PROXY
99   # Default to fast compression on development branches
100   options += 'gzipcomp ' unless is_release?
102   # Make sure release builds are clean
103   options += 'cleanall ' if is_release?
105   # Default to the number of system CPUs when we can figure it out
106   cpus = system_cpus
107   options += "cpus=#{cpus} " if cpus
109   options += ENV['TAILS_BUILD_OPTIONS'] if ENV['TAILS_BUILD_OPTIONS']
110   options.split(' ').each do |opt|
111     case opt
112     # Memory build settings
113     when 'ram'
114       unless vm_running? || enough_free_memory?
115         abort "Not enough free memory to do an in-memory build. Aborting."
116       end
117       ENV['TAILS_RAM_BUILD'] = '1'
118     when 'noram'
119       ENV['TAILS_RAM_BUILD'] = nil
120     # Bootstrap cache settings
121     when 'cache'
122       ENV['TAILS_BOOTSTRAP_CACHE'] = '1'
123     when 'nocache'
124       ENV['TAILS_BOOTSTRAP_CACHE'] = nil
125     # HTTP proxy settings
126     when 'extproxy'
127       abort "No HTTP proxy set, but one is required by TAILS_BUILD_OPTIONS. Aborting." unless EXTERNAL_HTTP_PROXY
128       ENV['http_proxy'] = EXTERNAL_HTTP_PROXY
129     when 'vmproxy'
130       ENV['http_proxy'] = INTERNEL_HTTP_PROXY
131     when 'noproxy'
132       ENV['http_proxy'] = nil
133     # SquashFS compression settings
134     when 'gzipcomp'
135       ENV['MKSQUASHFS_OPTIONS'] = '-comp gzip'
136     when 'defaultcomp'
137       ENV['MKSQUASHFS_OPTIONS'] = nil
138     # Clean-up settings
139     when 'cleanall'
140       ENV['TAILS_CLEAN_BUILD'] = '1'
141     # Virtual CPUs settings
142     when /cpus=(\d+)/
143       ENV['TAILS_BUILD_CPUS'] = $1
144     # Git settings
145     when 'ignorechanges'
146       ENV['TAILS_BUILD_IGNORE_CHANGES'] = '1'
147     end
148   end
151 task :ensure_clean_repository do
152   unless `git status --porcelain`.empty?
153     if ENV['TAILS_BUILD_IGNORE_CHANGES']
154       $stderr.puts <<-END_OF_MESSAGE.gsub(/^        /, '')
156         You have uncommited changes in the Git repository. They will
157         be ignored for the upcoming build.
159       END_OF_MESSAGE
160     else
161       $stderr.puts <<-END_OF_MESSAGE.gsub(/^        /, '')
163         You have uncommited changes in the Git repository. Due to limitations
164         of the build system, you need to commit them before building Tails.
166         If you don't care about those changes and want to build Tails nonetheless,
167         please add `ignorechanges` to the TAILS_BUILD_OPTIONS environment
168         variable.
170       END_OF_MESSAGE
171       abort 'Uncommited changes. Aborting.'
172     end
173   end
176 task :validate_http_proxy do
177   if ENV['http_proxy']
178     proxy_host = URI.parse(ENV['http_proxy']).host
180     if proxy_host.nil?
181       ENV['http_proxy'] = nil
182       $stderr.puts "Ignoring invalid HTTP proxy."
183       return
184     end
186     if ['localhost', '[::1]'].include?(proxy_host) || proxy_host.start_with?('127.0.0.')
187       abort 'Using an HTTP proxy listening on the loopback is doomed to fail. Aborting.'
188     end
190     $stderr.puts "Using HTTP proxy: #{ENV['http_proxy']}"
191   else
192     $stderr.puts "No HTTP proxy set."
193   end
196 desc 'Build Tails'
197 task :build => ['parse_build_options', 'ensure_clean_repository', 'validate_http_proxy', 'vm:up'] do
198   exported_env = EXPORTED_VARIABLES.select { |k| ENV[k] }.
199                   collect { |k| "#{k}='#{ENV[k]}'" }.join(' ')
201   env = Vagrant::Environment.new(:cwd => VAGRANT_PATH)
202   status = env.primary_vm.channel.execute("#{exported_env} build-tails",
203                                           :error_check => false) do |fd, data|
204     (fd == :stdout ? $stdout : $stderr).write data
205   end
207   # Move build products to the current directory
208   FileUtils.mv Dir.glob("#{VAGRANT_PATH}/tails-*"),
209                File.expand_path('..', __FILE__), :force => true
211   exit status
214 namespace :vm do
215   desc 'Start the build virtual machine'
216   task :up => ['parse_build_options', 'validate_http_proxy'] do
217     env = Vagrant::Environment.new(:cwd => VAGRANT_PATH, :ui_class => Vagrant::UI::Basic)
218     case env.primary_vm.state
219     when :not_created
220       # Do not use non-existant in-VM proxy to download the basebox
221       if ENV['http_proxy'] == INTERNEL_HTTP_PROXY
222         ENV['http_proxy'] = nil
223         restore_internal_proxy = true
224       end
226       $stderr.puts <<-END_OF_MESSAGE.gsub(/^      /, '')
228         This is the first time that the Tails builder virtual machine is
229         started. The virtual machine template is about 300 MB to download,
230         so the process might take some time.
232         Please remember to shut the virtual machine down once your work on
233         Tails is done:
235             $ rake vm:halt
237       END_OF_MESSAGE
238     when :poweroff
239       $stderr.puts <<-END_OF_MESSAGE.gsub(/^      /, '')
241         Starting Tails builder virtual machine. This might take a short while.
242         Please remember to shut it down once your work on Tails is done:
244             $ rake vm:halt
246       END_OF_MESSAGE
247     when :running
248       if ENV['TAILS_RAM_BUILD'] && current_vm_memory < VM_MEMORY_FOR_RAM_BUILDS
249         $stderr.puts <<-END_OF_MESSAGE.gsub(/^          /, '')
251           The virtual machine is not currently set with enough memory to
252           perform an in-memory build. Either remove the `ram` option from
253           the TAILS_BUILD_OPTIONS environment variable, or shut the
254           virtual machine down using `rake vm:halt` before trying again.
256         END_OF_MESSAGE
257         abort 'Not enough memory for the virtual machine to run an in-memory build. Aborting.'
258       end
259       if ENV['TAILS_BUILD_CPUS'] && current_vm_cpus != ENV['TAILS_BUILD_CPUS'].to_i
260         $stderr.puts <<-END_OF_MESSAGE.gsub(/^          /, '')
262           The virtual machine is currently running with #{current_vm_cpus}
263           virtual CPU(s). In order to change that number, you need to
264           stop the VM first, using `rake vm:halt`. Otherwise, please
265           adjust the `cpus` options accordingly.
267         END_OF_MESSAGE
268         abort 'The virtual machine needs to be reloaded to change the number of CPUs. Aborting.'
269       end
270     end
271     result = env.cli('up')
272     abort "'vagrant up' failed" unless result
274     ENV['http_proxy'] = INTERNEL_HTTP_PROXY if restore_internal_proxy
275   end
277   desc 'Stop the build virtual machine'
278   task :halt do
279     env = Vagrant::Environment.new(:cwd => VAGRANT_PATH, :ui_class => Vagrant::UI::Basic)
280     result = env.cli('halt')
281     abort "'vagrant halt' failed" unless result
282   end
284   desc 'Re-run virtual machine setup'
285   task :provision => ['parse_build_options', 'validate_http_proxy'] do
286     env = Vagrant::Environment.new(:cwd => VAGRANT_PATH, :ui_class => Vagrant::UI::Basic)
287     result = env.cli('provision')
288     abort "'vagrant provision' failed" unless result
289   end
291   desc 'Destroy build virtual machine (clean up all files)'
292   task :destroy do
293     env = Vagrant::Environment.new(:cwd => VAGRANT_PATH, :ui_class => Vagrant::UI::Basic)
294     result = env.cli('destroy', '--force')
295     abort "'vagrant destroy' failed" unless result
296   end
299 namespace :basebox do
300   task :create_preseed_cfg => 'validate_http_proxy' do
301     require 'erb'
303     preseed_cfg_path = File.expand_path('../vagrant/definitions/squeeze/preseed.cfg', __FILE__)
304     template = ERB.new(File.read("#{preseed_cfg_path}.erb"))
305     File.open(preseed_cfg_path, 'w') do |f|
306       f.write template.result
307     end
308   end
310   desc 'Create virtual machine template (a.k.a. basebox)'
311   task :create_basebox => [:create_preseed_cfg] do
312     # veewee is pretty stupid regarding path handling
313     Dir.chdir(VAGRANT_PATH) do
314       require 'veewee'
316       # Veewee assumes a separate process for each task. So we mimic that.
318       env = Vagrant::Environment.new(:ui_class => Vagrant::UI::Basic)
320       Process.fork do
321         env.cli('basebox', 'build', 'squeeze')
322       end
323       Process.wait
324       abort "Building the basebox failed (exit code: #{$?.exitstatus})." if $?.exitstatus != 0
326       Process.fork do
327         env.cli('basebox', 'validate', 'squeeze')
328       end
329       Process.wait
330       abort "Validating the basebox failed (exit code: #{$?.exitstatus})." if $?.exitstatus != 0
332       Process.fork do
333         env.cli('basebox', 'export', 'squeeze')
334       end
335       Process.wait
336       abort "Exporting the basebox failed (exit code: #{$?.exitstatus})." if $?.exitstatus != 0
337     end
338   end