Rakefile 11 KB
Newer Older
1
# -*- coding: utf-8 -*-
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
# -*- mode: ruby -*-
# vi: set ft=ruby :
#
# Tails: The Amnesic Incognito Live System
# Copyright © 2012 Tails developers <tails@boum.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

21
require 'rbconfig'
22 23
require 'rubygems'
require 'vagrant'
24
require 'uri'
25

26 27 28
$:.unshift File.expand_path('../vagrant/lib', __FILE__)
require 'tails_build_settings'

29 30 31
# Path to the directory which holds our Vagrantfile
VAGRANT_PATH = File.expand_path('../vagrant', __FILE__)

32 33 34
# Branches that are considered 'stable' (used to select SquashFS compression)
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']
37

38 39 40
# Let's save the http_proxy set before playing with it
EXTERNAL_HTTP_PROXY = ENV['http_proxy']

41 42 43
# In-VM proxy URL
INTERNEL_HTTP_PROXY = "http://#{VIRTUAL_MACHINE_HOSTNAME}:3142"

44 45 46 47 48 49
def current_vm_memory
  env = Vagrant::Environment.new(:cwd => VAGRANT_PATH, :ui_class => Vagrant::UI::Basic)
  uuid = env.primary_vm.uuid
  info = env.primary_vm.driver.execute 'showvminfo', uuid, '--machinereadable'
  $1.to_i if info =~ /^memory=(\d+)/
end
50

51 52 53 54 55 56 57
def current_vm_cpus
  env = Vagrant::Environment.new(:cwd => VAGRANT_PATH, :ui_class => Vagrant::UI::Basic)
  uuid = env.primary_vm.uuid
  info = env.primary_vm.driver.execute 'showvminfo', uuid, '--machinereadable'
  $1.to_i if info =~ /^cpus=(\d+)/
end

58 59 60 61 62
def vm_running?
  env = Vagrant::Environment.new(:cwd => VAGRANT_PATH, :ui_class => Vagrant::UI::Basic)
  env.primary_vm.state == :running
end

63 64 65 66 67 68 69 70 71 72 73
def enough_free_memory?
  return false unless RbConfig::CONFIG['host_os'] =~ /linux/i

  begin
    usable_free_mem = `free`.split[16].to_i
    usable_free_mem > VM_MEMORY_FOR_RAM_BUILDS * 1024
  rescue
    false
  end
end

74
def is_release?
75
  branch_name = `git name-rev --name-only HEAD`
76 77
  tag_name = `git describe --exact-match HEAD 2> /dev/null`
  STABLE_BRANCH_NAMES.include? branch_name.chomp or tag_name.chomp.length > 0
78 79
end

80 81 82 83 84 85 86 87 88 89
def system_cpus
  return nil unless RbConfig::CONFIG['host_os'] =~ /linux/i

  begin
    File.read('/proc/cpuinfo').scan(/^processor\s+:/).count
  rescue
    nil
  end
end

90
task :parse_build_options do
91 92
  options = ''

93 94 95
  # Default to in-memory builds if there is enough RAM available
  options += 'ram ' if enough_free_memory?

96 97 98
  # Use in-VM proxy unless an external proxy is set
  options += 'vmproxy ' unless EXTERNAL_HTTP_PROXY

99
  # Default to fast compression on development branches
100
  options += 'gzipcomp ' unless is_release?
101

102 103 104
  # Make sure release builds are clean
  options += 'cleanall ' if is_release?

105 106 107 108
  # Default to the number of system CPUs when we can figure it out
  cpus = system_cpus
  options += "cpus=#{cpus} " if cpus

109
  options += ENV['TAILS_BUILD_OPTIONS'] if ENV['TAILS_BUILD_OPTIONS']
110 111
  options.split(' ').each do |opt|
    case opt
112 113
    # Memory build settings
    when 'ram'
114 115 116
      unless vm_running? || enough_free_memory?
        abort "Not enough free memory to do an in-memory build. Aborting."
      end
117 118 119
      ENV['TAILS_RAM_BUILD'] = '1'
    when 'noram'
      ENV['TAILS_RAM_BUILD'] = nil
120 121 122 123 124
    # Bootstrap cache settings
    when 'cache'
      ENV['TAILS_BOOTSTRAP_CACHE'] = '1'
    when 'nocache'
      ENV['TAILS_BOOTSTRAP_CACHE'] = nil
125 126 127 128 129
    # HTTP proxy settings
    when 'extproxy'
      abort "No HTTP proxy set, but one is required by TAILS_BUILD_OPTIONS. Aborting." unless EXTERNAL_HTTP_PROXY
      ENV['http_proxy'] = EXTERNAL_HTTP_PROXY
    when 'vmproxy'
130
      ENV['http_proxy'] = INTERNEL_HTTP_PROXY
131 132
    when 'noproxy'
      ENV['http_proxy'] = nil
133 134 135 136 137
    # SquashFS compression settings
    when 'gzipcomp'
      ENV['MKSQUASHFS_OPTIONS'] = '-comp gzip'
    when 'defaultcomp'
      ENV['MKSQUASHFS_OPTIONS'] = nil
138 139 140
    # Clean-up settings
    when 'cleanall'
      ENV['TAILS_CLEAN_BUILD'] = '1'
141 142 143
    # Virtual CPUs settings
    when /cpus=(\d+)/
      ENV['TAILS_BUILD_CPUS'] = $1
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
    # Git settings
    when 'ignorechanges'
      ENV['TAILS_BUILD_IGNORE_CHANGES'] = '1'
    end
  end
end

task :ensure_clean_repository do
  unless `git status --porcelain`.empty?
    if ENV['TAILS_BUILD_IGNORE_CHANGES']
      $stderr.puts <<-END_OF_MESSAGE.gsub(/^        /, '')

        You have uncommited changes in the Git repository. They will
        be ignored for the upcoming build.

      END_OF_MESSAGE
    else
      $stderr.puts <<-END_OF_MESSAGE.gsub(/^        /, '')

        You have uncommited changes in the Git repository. Due to limitations
        of the build system, you need to commit them before building Tails.

        If you don't care about those changes and want to build Tails nonetheless,
        please add `ignorechanges` to the TAILS_BUILD_OPTIONS environment
        variable.

      END_OF_MESSAGE
      abort 'Uncommited changes. Aborting.'
172 173 174 175
    end
  end
end

176 177 178 179
task :validate_http_proxy do
  if ENV['http_proxy']
    proxy_host = URI.parse(ENV['http_proxy']).host

180 181 182 183 184 185
    if proxy_host.nil?
      ENV['http_proxy'] = nil
      $stderr.puts "Ignoring invalid HTTP proxy."
      return
    end

186 187 188 189 190 191 192 193 194 195
    if ['localhost', '[::1]'].include?(proxy_host) || proxy_host.start_with?('127.0.0.')
      abort 'Using an HTTP proxy listening on the loopback is doomed to fail. Aborting.'
    end

    $stderr.puts "Using HTTP proxy: #{ENV['http_proxy']}"
  else
    $stderr.puts "No HTTP proxy set."
  end
end

196
desc 'Build Tails'
197
task :build => ['parse_build_options', 'ensure_clean_repository', 'validate_http_proxy', 'vm:up'] do
198 199 200 201 202 203 204 205
  exported_env = EXPORTED_VARIABLES.select { |k| ENV[k] }.
                  collect { |k| "#{k}='#{ENV[k]}'" }.join(' ')

  env = Vagrant::Environment.new(:cwd => VAGRANT_PATH)
  status = env.primary_vm.channel.execute("#{exported_env} build-tails",
                                          :error_check => false) do |fd, data|
    (fd == :stdout ? $stdout : $stderr).write data
  end
206 207 208 209 210

  # Move build products to the current directory
  FileUtils.mv Dir.glob("#{VAGRANT_PATH}/tails-*"),
               File.expand_path('..', __FILE__), :force => true

211 212 213
  exit status
end

214 215
namespace :vm do
  desc 'Start the build virtual machine'
216
  task :up => ['parse_build_options', 'validate_http_proxy'] do
217 218 219
    env = Vagrant::Environment.new(:cwd => VAGRANT_PATH, :ui_class => Vagrant::UI::Basic)
    case env.primary_vm.state
    when :not_created
220 221 222 223 224 225
      # Do not use non-existant in-VM proxy to download the basebox
      if ENV['http_proxy'] == INTERNEL_HTTP_PROXY
        ENV['http_proxy'] = nil
        restore_internal_proxy = true
      end

226 227 228 229 230 231 232
      $stderr.puts <<-END_OF_MESSAGE.gsub(/^      /, '')

        This is the first time that the Tails builder virtual machine is
        started. The virtual machine template is about 300 MB to download,
        so the process might take some time.

        Please remember to shut the virtual machine down once your work on
233
        Tails is done:
234 235 236 237 238 239 240 241

            $ rake vm:halt

      END_OF_MESSAGE
    when :poweroff
      $stderr.puts <<-END_OF_MESSAGE.gsub(/^      /, '')

        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:
243 244 245 246

            $ rake vm:halt

      END_OF_MESSAGE
247 248 249 250 251 252 253 254 255 256 257 258
    when :running
      if ENV['TAILS_RAM_BUILD'] && current_vm_memory < VM_MEMORY_FOR_RAM_BUILDS
        $stderr.puts <<-END_OF_MESSAGE.gsub(/^          /, '')

          The virtual machine is not currently set with enough memory to
          perform an in-memory build. Either remove the `ram` option from
          the TAILS_BUILD_OPTIONS environment variable, or shut the
          virtual machine down using `rake vm:halt` before trying again.

        END_OF_MESSAGE
        abort 'Not enough memory for the virtual machine to run an in-memory build. Aborting.'
      end
259 260 261 262 263 264 265 266 267 268 269
      if ENV['TAILS_BUILD_CPUS'] && current_vm_cpus != ENV['TAILS_BUILD_CPUS'].to_i
        $stderr.puts <<-END_OF_MESSAGE.gsub(/^          /, '')

          The virtual machine is currently running with #{current_vm_cpus}
          virtual CPU(s). In order to change that number, you need to
          stop the VM first, using `rake vm:halt`. Otherwise, please
          adjust the `cpus` options accordingly.

        END_OF_MESSAGE
        abort 'The virtual machine needs to be reloaded to change the number of CPUs. Aborting.'
      end
270 271 272
    end
    result = env.cli('up')
    abort "'vagrant up' failed" unless result
273 274

    ENV['http_proxy'] = INTERNEL_HTTP_PROXY if restore_internal_proxy
275 276 277 278 279 280 281 282 283 284
  end

  desc 'Stop the build virtual machine'
  task :halt do
    env = Vagrant::Environment.new(:cwd => VAGRANT_PATH, :ui_class => Vagrant::UI::Basic)
    result = env.cli('halt')
    abort "'vagrant halt' failed" unless result
  end

  desc 'Re-run virtual machine setup'
285
  task :provision => ['parse_build_options', 'validate_http_proxy'] do
286 287 288 289 290 291 292 293 294 295 296 297
    env = Vagrant::Environment.new(:cwd => VAGRANT_PATH, :ui_class => Vagrant::UI::Basic)
    result = env.cli('provision')
    abort "'vagrant provision' failed" unless result
  end

  desc 'Destroy build virtual machine (clean up all files)'
  task :destroy do
    env = Vagrant::Environment.new(:cwd => VAGRANT_PATH, :ui_class => Vagrant::UI::Basic)
    result = env.cli('destroy', '--force')
    abort "'vagrant destroy' failed" unless result
  end
end
298 299

namespace :basebox do
300
  task :create_preseed_cfg => 'validate_http_proxy' do
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
    require 'erb'

    preseed_cfg_path = File.expand_path('../vagrant/definitions/squeeze/preseed.cfg', __FILE__)
    template = ERB.new(File.read("#{preseed_cfg_path}.erb"))
    File.open(preseed_cfg_path, 'w') do |f|
      f.write template.result
    end
  end

  desc 'Create virtual machine template (a.k.a. basebox)'
  task :create_basebox => [:create_preseed_cfg] do
    # veewee is pretty stupid regarding path handling
    Dir.chdir(VAGRANT_PATH) do
      require 'veewee'

      # Veewee assumes a separate process for each task. So we mimic that.

      env = Vagrant::Environment.new(:ui_class => Vagrant::UI::Basic)

      Process.fork do
        env.cli('basebox', 'build', 'squeeze')
      end
      Process.wait
      abort "Building the basebox failed (exit code: #{$?.exitstatus})." if $?.exitstatus != 0

      Process.fork do
        env.cli('basebox', 'validate', 'squeeze')
      end
      Process.wait
      abort "Validating the basebox failed (exit code: #{$?.exitstatus})." if $?.exitstatus != 0

      Process.fork do
        env.cli('basebox', 'export', 'squeeze')
      end
      Process.wait
      abort "Exporting the basebox failed (exit code: #{$?.exitstatus})." if $?.exitstatus != 0
    end
  end
end