kameleon-builder 2.0.0.dev
Sign up to get free protection for your applications and to get access to all the features.
- data/.editorconfig +23 -0
- data/.env +51 -0
- data/.gitignore +22 -0
- data/AUTHORS +19 -0
- data/CHANGELOG +36 -0
- data/COPYING +340 -0
- data/Gemfile +4 -0
- data/README.md +53 -0
- data/Rakefile +24 -0
- data/Vagrantfile +68 -0
- data/bin/kameleon +16 -0
- data/contrib/kameleon_bashrc.sh +138 -0
- data/contrib/scripts/VirtualBox_deploy.sh +12 -0
- data/contrib/scripts/chroot_env +9 -0
- data/contrib/scripts/create_passwd.py +17 -0
- data/contrib/scripts/umount-chroot.sh +290 -0
- data/contrib/steps/bootstrap/debian/bootstrap_if_needed.yaml +47 -0
- data/contrib/steps/bootstrap/debian/bootstrap_static.yaml +38 -0
- data/contrib/steps/setup/add_timestamp.yaml +6 -0
- data/contrib/steps/setup/autologin.yaml +16 -0
- data/contrib/steps/setup/copy_ssh_auth_file.yaml +10 -0
- data/contrib/steps/setup/debian/add_network_interface.yaml +7 -0
- data/contrib/steps/setup/debian/cluster_tools_install.yaml +16 -0
- data/contrib/steps/setup/debian/network_config_static.yaml +17 -0
- data/contrib/steps/setup/generate_user_ssh_key.yaml +15 -0
- data/contrib/steps/setup/install_my_ssh_key.yaml +26 -0
- data/contrib/steps/setup/make_swap_file.yaml +9 -0
- data/contrib/steps/setup/root_ssh_config.yaml +18 -0
- data/contrib/steps/setup/set_user_password.yaml +7 -0
- data/contrib/steps/setup/system_optimization.yaml +8 -0
- data/docs/.gitignore +1 -0
- data/docs/Makefile +177 -0
- data/docs/make.bat +242 -0
- data/docs/source/_static/.gitignore +0 -0
- data/docs/source/aliases.rst +29 -0
- data/docs/source/checkpoint.rst +28 -0
- data/docs/source/cli.rst +3 -0
- data/docs/source/commands.rst +62 -0
- data/docs/source/conf.py +254 -0
- data/docs/source/context.rst +42 -0
- data/docs/source/faq.rst +3 -0
- data/docs/source/getting_started.rst +3 -0
- data/docs/source/index.rst +38 -0
- data/docs/source/installation.rst +3 -0
- data/docs/source/recipe.rst +256 -0
- data/docs/source/why.rst +3 -0
- data/docs/source/workspace.rst +11 -0
- data/kameleon-builder.gemspec +37 -0
- data/lib/kameleon.rb +75 -0
- data/lib/kameleon/cli.rb +176 -0
- data/lib/kameleon/context.rb +83 -0
- data/lib/kameleon/engine.rb +357 -0
- data/lib/kameleon/environment.rb +38 -0
- data/lib/kameleon/error.rb +51 -0
- data/lib/kameleon/logger.rb +53 -0
- data/lib/kameleon/recipe.rb +474 -0
- data/lib/kameleon/shell.rb +290 -0
- data/lib/kameleon/step.rb +213 -0
- data/lib/kameleon/utils.rb +45 -0
- data/lib/kameleon/version.rb +3 -0
- data/templates/COPYRIGHT +21 -0
- data/templates/aliases/defaults.yaml +83 -0
- data/templates/checkpoints/docker.yaml +14 -0
- data/templates/checkpoints/qcow2.yaml +44 -0
- data/templates/debian-wheezy-chroot.yaml +98 -0
- data/templates/debian-wheezy-docker.yaml +97 -0
- data/templates/fedora-docker.yaml +96 -0
- data/templates/steps/bootstrap/debian/debootstrap.yaml +13 -0
- data/templates/steps/bootstrap/fedora/docker_bootstrap.yaml +25 -0
- data/templates/steps/bootstrap/fedora/yum_bootstrap.yaml +22 -0
- data/templates/steps/bootstrap/prepare_appliance_with_nbd.yaml +93 -0
- data/templates/steps/bootstrap/prepare_docker.yaml +38 -0
- data/templates/steps/bootstrap/start_chroot.yaml +53 -0
- data/templates/steps/bootstrap/start_docker.yaml +12 -0
- data/templates/steps/export/build_appliance_from_docker.yaml +105 -0
- data/templates/steps/export/clean_appliance.yaml +3 -0
- data/templates/steps/export/save_appliance_from_nbd.yaml +54 -0
- data/templates/steps/setup/create_user.yaml +12 -0
- data/templates/steps/setup/debian/kernel_install.yaml +20 -0
- data/templates/steps/setup/debian/keyboard_config.yaml +10 -0
- data/templates/steps/setup/debian/network_config.yaml +30 -0
- data/templates/steps/setup/debian/software_install.yaml +15 -0
- data/templates/steps/setup/debian/system_config.yaml +12 -0
- data/templates/steps/setup/fedora/kernel_install.yaml +27 -0
- data/templates/steps/setup/fedora/software_install.yaml +10 -0
- data/tests/helper.rb +22 -0
- data/tests/recipes/dummy_recipe.yaml +48 -0
- data/tests/recipes/steps/bootstrap/dummy_distro/dummy_bootstrap_static.yaml +4 -0
- data/tests/recipes/steps/export/dummy_save_appliance.yaml +9 -0
- data/tests/recipes/steps/setup/default/dummy_root_passwd.yaml +8 -0
- data/tests/recipes/steps/setup/dummy_distro/dummy_software_install.yaml +7 -0
- data/tests/test_context.rb +16 -0
- data/tests/test_recipe.rb +15 -0
- data/tests/test_version.rb +9 -0
- metadata +300 -0
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# Kameleon
|
2
|
+
|
3
|
+
Kameleon should be seen as a simple but powerful tool to generate customized
|
4
|
+
appliances. With Kameleon, you make your recipe that describes how to create
|
5
|
+
step by step your own distribution. At start Kameleon is used to create custom
|
6
|
+
kvm, LXC, VirtualBox, iso images, ..., but as it is designed to be very
|
7
|
+
generic you can probably do a lot more than that.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
Simply install it from the Gem repository (not working yet):
|
11
|
+
|
12
|
+
gem install kameleon
|
13
|
+
|
14
|
+
Or from source:
|
15
|
+
|
16
|
+
git clone git://scm.gforge.inria.fr/kameleon/kameleon.git
|
17
|
+
cd kameleon
|
18
|
+
gem build kameleon.gemspec
|
19
|
+
gem install kameleon-<version>.gem
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
Just type:
|
24
|
+
|
25
|
+
kameleon
|
26
|
+
|
27
|
+
## Quick start
|
28
|
+
|
29
|
+
First, you should select a template. To see the available templates use:
|
30
|
+
|
31
|
+
kameleon templates
|
32
|
+
|
33
|
+
Then, create a new recipe from the template you've just choose. This will
|
34
|
+
create a `recipes` folder in the current directory. (use `-w` option to set a
|
35
|
+
different workspace).
|
36
|
+
|
37
|
+
kameleon new my_test_recipe -t template_name
|
38
|
+
|
39
|
+
Then build your new recipe with the build command:
|
40
|
+
|
41
|
+
kameleon build my_test_recipe
|
42
|
+
|
43
|
+
A `builds` directory was created and contains your new image!
|
44
|
+
|
45
|
+
To go further, get more documentation in the docs folder.
|
46
|
+
|
47
|
+
## Contributing
|
48
|
+
|
49
|
+
1. Fork it
|
50
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
51
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
52
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
53
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'coveralls/rake/task'
|
6
|
+
|
7
|
+
Coveralls::RakeTask.new
|
8
|
+
task :ci => ['set_coverage', 'test', 'coveralls:push']
|
9
|
+
rescue LoadError; end
|
10
|
+
|
11
|
+
|
12
|
+
task :set_coverage do
|
13
|
+
ENV['COVERAGE'] = 'true'
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
Rake::TestTask.new do |t|
|
18
|
+
t.libs << 'tests'
|
19
|
+
t.pattern = "tests/test_*.rb"
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
desc 'Default task which runs all tests with code coverage enabled'
|
24
|
+
task :default => ['set_coverage', 'test']
|
data/Vagrantfile
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
# -*- mode: ruby -*-
|
2
|
+
# vi: set ft=ruby :
|
3
|
+
|
4
|
+
ENV['VAGRANT_DEFAULT_PROVIDER'] = 'libvirt'
|
5
|
+
|
6
|
+
Vagrant.configure("2") do |config|
|
7
|
+
config.vm.box = "debian7-dev"
|
8
|
+
config.vm.box_url = "http://cdn.quicker.fr/vagrant/libvirt/debian7-dev.box"
|
9
|
+
config.vm.hostname = "kameleon-devel"
|
10
|
+
|
11
|
+
# Config provider
|
12
|
+
config.vm.provider :libvirt do |vm|
|
13
|
+
vm.memory = 2024
|
14
|
+
vm.cpus = 2
|
15
|
+
end
|
16
|
+
|
17
|
+
# shared folders
|
18
|
+
config.vm.synced_folder ".", "/vagrant", :nfs => true
|
19
|
+
|
20
|
+
# Provision
|
21
|
+
config.vm.provision "shell", privileged: true, inline: <<-EOF
|
22
|
+
export DEBIAN_FRONTEND=noninteractive
|
23
|
+
apt-get update
|
24
|
+
apt-get -y --force-yes install git python-pip debootstrap \
|
25
|
+
rsync sed qemu-utils
|
26
|
+
|
27
|
+
apt-get -y --force-yes install ruby1.9.1 ruby1.9.1-dev \
|
28
|
+
rubygems1.9.1 irb1.9.1 ri1.9.1 rdoc1.9.1 \
|
29
|
+
build-essential libopenssl-ruby1.9.1 libssl-dev zlib1g-dev
|
30
|
+
|
31
|
+
update-alternatives --install /usr/bin/ruby ruby /usr/bin/ruby1.9.1 400 \
|
32
|
+
--slave /usr/share/man/man1/ruby.1.gz ruby.1.gz \
|
33
|
+
/usr/share/man/man1/ruby1.9.1.1.gz \
|
34
|
+
--slave /usr/bin/ri ri /usr/bin/ri1.9.1 \
|
35
|
+
--slave /usr/bin/irb irb /usr/bin/irb1.9.1 \
|
36
|
+
--slave /usr/bin/rdoc rdoc /usr/bin/rdoc1.9.1
|
37
|
+
|
38
|
+
# choose your interpreter
|
39
|
+
# changes symlinks for /usr/bin/ruby , /usr/bin/gem
|
40
|
+
# /usr/bin/irb, /usr/bin/ri and man (1) ruby
|
41
|
+
update-alternatives --set ruby /usr/bin/ruby1.9.1
|
42
|
+
|
43
|
+
gem install bundle
|
44
|
+
|
45
|
+
# Helpful tools
|
46
|
+
pip install pyped
|
47
|
+
EOF
|
48
|
+
|
49
|
+
config.vm.provision "shell", privileged: false, inline: <<-EOF
|
50
|
+
cat > ~/.bash_profile <<< "
|
51
|
+
export FORCE_AUTOENV=1
|
52
|
+
source ~/.profile
|
53
|
+
source /vagrant/.env
|
54
|
+
cd /vagrant
|
55
|
+
"
|
56
|
+
cd /vagrant && git stash && bundle install && git stash pop
|
57
|
+
EOF
|
58
|
+
|
59
|
+
# shared folders
|
60
|
+
if File.exists? File.expand_path('~/.dotfiles')
|
61
|
+
config.vm.synced_folder "~/.dotfiles", "/home/vagrant/.dotfiles", :nfs => true
|
62
|
+
config.vm.provision "shell", privileged: false, inline: "python /home/vagrant/.dotfiles/install.py"
|
63
|
+
end
|
64
|
+
|
65
|
+
# Network
|
66
|
+
config.ssh.forward_agent = true
|
67
|
+
config.vm.network :private_network, ip: "10.10.10.120"
|
68
|
+
end
|
data/bin/kameleon
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Exit cleanly from an early interrupt
|
4
|
+
Signal.trap("INT") { exit 1 }
|
5
|
+
|
6
|
+
# Stdout/stderr should not buffer output
|
7
|
+
$stdout.sync = true
|
8
|
+
$stderr.sync = true
|
9
|
+
|
10
|
+
require 'kameleon'
|
11
|
+
|
12
|
+
require 'kameleon/cli'
|
13
|
+
# Force Thor to raise exceptions so we can exit non-zero.
|
14
|
+
ENV["THOR_DEBUG"] = "1"
|
15
|
+
|
16
|
+
Kameleon.with_friendly_errors { Kameleon::CLI.start }
|
@@ -0,0 +1,138 @@
|
|
1
|
+
# If not running interactively, don't do anything
|
2
|
+
export USER=${USER:-"root"}
|
3
|
+
export HOME=${HOME:-"/root"}
|
4
|
+
export PATH=/usr/bin:/usr/sbin:/bin:/sbin:$PATH
|
5
|
+
export LC_ALL=${LC_ALL:-"POSIX"}
|
6
|
+
|
7
|
+
export DEBIAN_FRONTEND=noninteractive
|
8
|
+
|
9
|
+
mkdir -p $(dirname <%= @bash_history_file %>) ; touch "<%= @bash_history_file %>"
|
10
|
+
mkdir -p $(dirname <%= @bash_env_file %>) ; touch "<%= @bash_env_file %>"
|
11
|
+
|
12
|
+
source /etc/bash.bashrc 2> /dev/null
|
13
|
+
|
14
|
+
export KAMELEON_CONTEXT_NAME="<%= @context_name %>_context"
|
15
|
+
export HISTFILE="<%= @bash_history_file %>"
|
16
|
+
|
17
|
+
## functions
|
18
|
+
|
19
|
+
function fail {
|
20
|
+
echo $@ 1>&2
|
21
|
+
false
|
22
|
+
}
|
23
|
+
|
24
|
+
## aliases
|
25
|
+
if [ -t 1 ] ; then
|
26
|
+
# restore previous env
|
27
|
+
source "<%= @bash_env_file %>" 2> /dev/null
|
28
|
+
export TERM=xterm
|
29
|
+
# for fast typing
|
30
|
+
alias h='history'
|
31
|
+
alias g='git status'
|
32
|
+
alias l='ls -lah'
|
33
|
+
alias ll='ls -lh'
|
34
|
+
alias la='ls -Ah'
|
35
|
+
|
36
|
+
# for human readable output
|
37
|
+
alias ls='ls -h'
|
38
|
+
alias df='df -h'
|
39
|
+
alias du='du -h'
|
40
|
+
|
41
|
+
# simple history browsing
|
42
|
+
export HISTCONTROL=erasedups
|
43
|
+
export HISTSIZE=10000
|
44
|
+
export HISTIGNORE="history*"
|
45
|
+
shopt -s histappend
|
46
|
+
bind '"\e[A"':history-search-backward
|
47
|
+
bind '"\e[B"':history-search-forward
|
48
|
+
|
49
|
+
# check the window size after each command and, if necessary,
|
50
|
+
# update the values of LINES and COLUMNS.
|
51
|
+
shopt -s checkwinsize
|
52
|
+
|
53
|
+
# make less more friendly for non-text input files, see lesspipe(1)
|
54
|
+
[ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)"
|
55
|
+
|
56
|
+
# If this is an xterm set the title to user@host:dir
|
57
|
+
PROMPT_COMMAND='echo -ne "\033]0;${KAMELEON_CONTEXT_NAME:+($KAMELEON_CONTEXT_NAME)}${USER}@${HOSTNAME}: ${PWD}\007"'
|
58
|
+
|
59
|
+
# set variable to show git branch when in a git repository
|
60
|
+
# source: https://github.com/jimeh/git-aware-prompt/blob/master/prompt.sh
|
61
|
+
# added highlighting of repo part in path
|
62
|
+
function find_git_branch {
|
63
|
+
git_subpath='/'
|
64
|
+
local dir=${PWD} head
|
65
|
+
until [ "$dir" = "" ]; do
|
66
|
+
if [ -f "$dir/.git/HEAD" ]; then
|
67
|
+
head=$(< "$dir/.git/HEAD")
|
68
|
+
if [[ $head == ref:\ refs/heads/* ]]; then
|
69
|
+
git_branch=" (${head#*/*/})"
|
70
|
+
elif [[ $head != '' ]]; then
|
71
|
+
git_describe=$(git describe --always)
|
72
|
+
git_branch=" (detached: $git_describe)"
|
73
|
+
else
|
74
|
+
git_branch=' (unknown)'
|
75
|
+
fi
|
76
|
+
prompt_dir="${dir/$HOME/~}"
|
77
|
+
return
|
78
|
+
fi
|
79
|
+
git_subpath="/${dir##*/}$git_subpath"
|
80
|
+
dir="${dir%/*}"
|
81
|
+
done
|
82
|
+
git_branch=''
|
83
|
+
prompt_dir="${PWD/$HOME/~}"
|
84
|
+
git_subpath=''
|
85
|
+
}
|
86
|
+
function find_git_dirty {
|
87
|
+
st=$(git status -s 2>/dev/null | tail -n 1)
|
88
|
+
if [[ $st == "" ]]; then
|
89
|
+
git_dirty=''
|
90
|
+
else
|
91
|
+
git_dirty='*'
|
92
|
+
fi
|
93
|
+
}
|
94
|
+
export find_git_branch
|
95
|
+
export find_git_dirty
|
96
|
+
PROMPT_COMMAND="find_git_branch; find_git_dirty; history -a ; $PROMPT_COMMAND"
|
97
|
+
|
98
|
+
# set a fancy prompt (non-color, unless we know we "want" color)
|
99
|
+
case "$TERM" in
|
100
|
+
xterm-color) color_prompt=yes;;
|
101
|
+
esac
|
102
|
+
|
103
|
+
# if the terminal has the capability; turned
|
104
|
+
# off by default to not distract the user: the focus in a terminal window
|
105
|
+
# should be on the output of commands, not on the prompt
|
106
|
+
force_color_prompt=yes
|
107
|
+
|
108
|
+
if [ -n "$force_color_prompt" ]; then
|
109
|
+
if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then
|
110
|
+
# We have color support; assume it's compliant with Ecma-48
|
111
|
+
# (ISO/IEC-6429). (Lack of such support is extremely rare, and such
|
112
|
+
# a case would tend to support setf rather than setaf.)
|
113
|
+
color_prompt=yes
|
114
|
+
else
|
115
|
+
color_prompt=
|
116
|
+
fi
|
117
|
+
fi
|
118
|
+
|
119
|
+
if [ "$color_prompt" = yes ]; then
|
120
|
+
PS1='${KAMELEON_CONTEXT_NAME:+($KAMELEON_CONTEXT_NAME) }\[\033[01;32m\]\u@\h\[\033[00m\]: \[\e[1;37m\]$prompt_dir\[\e[1;36m\]$git_subpath\[\e[0;31m\]$git_branch\[\e[1;33m\]$git_dirty\[\033[01;34m\] \$\[\033[00m\] '
|
121
|
+
else
|
122
|
+
PS1='${KAMELEON_CONTEXT_NAME:+($KAMELEON_CONTEXT_NAME) }\u@\h: $prompt_dir$git_subpath$git_branch$git_dirty \$ '
|
123
|
+
fi
|
124
|
+
|
125
|
+
# colors
|
126
|
+
if [ -x /usr/bin/dircolors ]; then
|
127
|
+
eval "`dircolors -b`"
|
128
|
+
alias ls='ls --color=auto'
|
129
|
+
#alias dir='dir --color=auto'
|
130
|
+
#alias vdir='vdir --color=auto'
|
131
|
+
|
132
|
+
alias grep='grep --color=auto'
|
133
|
+
alias fgrep='fgrep --color=auto'
|
134
|
+
alias egrep='egrep --color=auto'
|
135
|
+
else
|
136
|
+
alias ls='ls -G'
|
137
|
+
fi
|
138
|
+
fi
|
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
NAME=$1
|
3
|
+
VBOX_DISK=$2
|
4
|
+
|
5
|
+
VBoxManage createvm --name $NAME --register
|
6
|
+
VBoxManage modifyvm $NAME --memory 512
|
7
|
+
VBoxManage storagectl $NAME --name SATA --add sata --controller IntelAhci --bootable on --sataportcount 1
|
8
|
+
VBoxManage storageattach $NAME --storagectl SATA --port 0 --device 0 --type hdd --medium $VBOX_DISK
|
9
|
+
#VBoxManage modifyvm $NAME --nic1 hostonly
|
10
|
+
#VBoxManage modifyvm $NAME --nic1 nat
|
11
|
+
VBoxManage startvm $NAME
|
12
|
+
#VBoxManage unregistervm $NAME --delete
|
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
### WARNING ###
|
3
|
+
# requires Python >= 3.3
|
4
|
+
|
5
|
+
import sys, crypt, getpass;
|
6
|
+
|
7
|
+
# this script generate a password using salted SHA512 whitch is the default on debian wheezy
|
8
|
+
|
9
|
+
cleartext = getpass.getpass("Password:")
|
10
|
+
cleartext2 = getpass.getpass("Again:")
|
11
|
+
if cleartext2 != cleartext:
|
12
|
+
print ('Not matched!')
|
13
|
+
sys.exit(1)
|
14
|
+
|
15
|
+
salt = crypt.mksalt(crypt.METHOD_SHA512)
|
16
|
+
print (crypt.crypt(cleartext, salt))
|
17
|
+
|
@@ -0,0 +1,290 @@
|
|
1
|
+
#!/bin/sh -e
|
2
|
+
# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
|
3
|
+
# Use of this source code is governed by a BSD-style license that can be
|
4
|
+
# found in the LICENSE file.
|
5
|
+
|
6
|
+
APPLICATION="${0##*/}"
|
7
|
+
ALLCHROOTS=''
|
8
|
+
BINDIR="`dirname "\`readlink -f "$0"\`"`"
|
9
|
+
CHROOTS="`readlink -f "$BINDIR/../chroots"`"
|
10
|
+
EXCLUDEROOT=''
|
11
|
+
FORCE=''
|
12
|
+
PRINT=''
|
13
|
+
SIGNAL='TERM'
|
14
|
+
TRIES=5
|
15
|
+
YES=''
|
16
|
+
|
17
|
+
USAGE="$APPLICATION [options] name [...]
|
18
|
+
|
19
|
+
Unmounts one or more chroots, optionally killing any processes still running
|
20
|
+
inside them.
|
21
|
+
|
22
|
+
By default, it will run in interactive mode where it will ask to kill any
|
23
|
+
remaining processes if unable to unmount the chroot within 5 seconds.
|
24
|
+
|
25
|
+
Options:
|
26
|
+
-a Unmount all chroots in the CHROOTS directory.
|
27
|
+
-c CHROOTS Directory the chroots are in. Default: $CHROOTS
|
28
|
+
-f Forces a chroot to unmount, potentially breaking or killing
|
29
|
+
other instances of the same chroot.
|
30
|
+
-k KILL Send the processes SIGKILL instead of SIGTERM.
|
31
|
+
-p Print to STDOUT the processes stopping a chroot from unmounting.
|
32
|
+
-t TRIES Number of seconds to try before signalling the processes.
|
33
|
+
Use -t inf to be exceedingly patient. Default: $TRIES
|
34
|
+
-x Keep the root directory of the chroot mounted.
|
35
|
+
-y Signal any remaining processes without confirmation.
|
36
|
+
Automatically escalates from SIGTERM to SIGKILL."
|
37
|
+
|
38
|
+
# Function to exit with exit code $1, spitting out message $@ to stderr
|
39
|
+
error() {
|
40
|
+
local ecode="$1"
|
41
|
+
shift
|
42
|
+
echo "$*" 1>&2
|
43
|
+
exit "$ecode"
|
44
|
+
}
|
45
|
+
|
46
|
+
# Process arguments
|
47
|
+
while getopts 'ac:fkpt:xy' f; do
|
48
|
+
case "$f" in
|
49
|
+
a) ALLCHROOTS='y';;
|
50
|
+
c) CHROOTS="`readlink -f "$OPTARG"`";;
|
51
|
+
f) FORCE='y';;
|
52
|
+
k) SIGNAL="KILL";;
|
53
|
+
p) PRINT='y';;
|
54
|
+
t) TRIES="$OPTARG";;
|
55
|
+
x) EXCLUDEROOT='y';;
|
56
|
+
y) YES='a';;
|
57
|
+
\?) error 2 "$USAGE";;
|
58
|
+
esac
|
59
|
+
done
|
60
|
+
shift "$((OPTIND-1))"
|
61
|
+
|
62
|
+
# Need at least one chroot listed, or -a; not both.
|
63
|
+
if [ $# = 0 -a -z "$ALLCHROOTS" ] || [ ! $# = 0 -a -n "$ALLCHROOTS" ]; then
|
64
|
+
error 2 "$USAGE"
|
65
|
+
fi
|
66
|
+
|
67
|
+
# Make sure TRIES is valid
|
68
|
+
if [ "$TRIES" = inf ]; then
|
69
|
+
TRIES=-1
|
70
|
+
elif [ "$TRIES" -lt -1 ]; then
|
71
|
+
error 2 "$USAGE"
|
72
|
+
fi
|
73
|
+
|
74
|
+
# We need to run as root
|
75
|
+
if [ ! "$USER" = root -a ! "$UID" = 0 ]; then
|
76
|
+
error 2 "$APPLICATION must be run as root."
|
77
|
+
fi
|
78
|
+
|
79
|
+
# Check if a chroot is running with this directory. We detect the
|
80
|
+
# appropriate commands by checking if the command's parent root is not equal
|
81
|
+
# to the pid's root. This avoids not unmounting due to a lazy-quitting
|
82
|
+
# background application within the chroot. We also don't consider processes
|
83
|
+
# that have a parent PID of 1 (which would mean an orphaned process in this
|
84
|
+
# case), as enter-chroot never orphans its children, and we don't consider
|
85
|
+
# processes that have CROUTON=CORE in the environment.
|
86
|
+
# $1: $base; the canonicalized base path of the chroot
|
87
|
+
# Returns: non-zero if the chroot is in use.
|
88
|
+
checkusage() {
|
89
|
+
if [ -n "$FORCE" ]; then
|
90
|
+
return 0
|
91
|
+
fi
|
92
|
+
local b="${1%/}/" pid ppid proot prootdir root rootdir
|
93
|
+
for root in /proc/*/root; do
|
94
|
+
if [ ! -r "$root" ]; then
|
95
|
+
continue
|
96
|
+
fi
|
97
|
+
rootdir="`readlink -f "$root"`"
|
98
|
+
rootdir="${rootdir%/}/"
|
99
|
+
if [ "${rootdir#"$b"}" = "$rootdir" ]; then
|
100
|
+
continue
|
101
|
+
fi
|
102
|
+
pid="${root#/proc/}"
|
103
|
+
pid="${pid%/root}"
|
104
|
+
ppid="`ps -p "$pid" -o ppid= 2>/dev/null | sed 's/ //g'`"
|
105
|
+
if [ -z "$ppid" ] || [ "$ppid" -eq 1 ]; then
|
106
|
+
continue
|
107
|
+
fi
|
108
|
+
proot="/proc/$ppid/root"
|
109
|
+
if [ -r "$proot" ]; then
|
110
|
+
prootdir="`readlink -f "$proot"`"
|
111
|
+
if [ "${prootdir%/}/" = "$rootdir" ]; then
|
112
|
+
continue
|
113
|
+
fi
|
114
|
+
fi
|
115
|
+
if grep -q 'CROUTON=CORE' "/proc/$pid/environ" 2>/dev/null; then
|
116
|
+
continue
|
117
|
+
fi
|
118
|
+
if [ -n "$PRINT" ]; then
|
119
|
+
ps -p "$pid" -o pid= -o cmd= || true
|
120
|
+
fi
|
121
|
+
return 1
|
122
|
+
done
|
123
|
+
return 0
|
124
|
+
}
|
125
|
+
|
126
|
+
# If we specified all chroots, bring in all chroots.
|
127
|
+
if [ -n "$ALLCHROOTS" ]; then
|
128
|
+
set -- "$CHROOTS/"*
|
129
|
+
fi
|
130
|
+
|
131
|
+
# Follows and fixes dangerous symlinks, returning the canonicalized path.
|
132
|
+
fixabslinks() {
|
133
|
+
local p="$CHROOT/$1" c
|
134
|
+
# Follow and fix dangerous absolute symlinks
|
135
|
+
while c="`readlink -m "$p"`" && [ ! "$c" = "$p" ]; do
|
136
|
+
p="$CHROOT${c#"$CHROOT"}"
|
137
|
+
done
|
138
|
+
echo "$p"
|
139
|
+
}
|
140
|
+
|
141
|
+
# Unmount each chroot
|
142
|
+
ret=0
|
143
|
+
for NAME in "$@"; do
|
144
|
+
if [ -z "$NAME" ]; then
|
145
|
+
continue
|
146
|
+
fi
|
147
|
+
|
148
|
+
NAME="${NAME#"$CHROOTS/"}"
|
149
|
+
|
150
|
+
# Check for existence
|
151
|
+
CHROOT="$CHROOTS/$NAME"
|
152
|
+
if [ ! -d "$CHROOT" ]; then
|
153
|
+
echo "$CHROOT not found." 1>&2
|
154
|
+
ret=1
|
155
|
+
continue
|
156
|
+
fi
|
157
|
+
|
158
|
+
# Switch to the unencrypted mount for encrypted chroots.
|
159
|
+
if [ -f "$CHROOT/.ecryptfs" ]; then
|
160
|
+
CHROOT="$CHROOTS/.secure/$NAME"
|
161
|
+
fi
|
162
|
+
|
163
|
+
base="`readlink -f "$CHROOT"`"
|
164
|
+
|
165
|
+
if ! checkusage "$base"; then
|
166
|
+
echo "Not unmounting $CHROOT as another instance is using it." 1>&2
|
167
|
+
ret=1
|
168
|
+
continue
|
169
|
+
fi
|
170
|
+
|
171
|
+
# Kill the chroot's system dbus if one is running; failure is fine
|
172
|
+
env -i chroot "$CHROOT" su -s '/bin/sh' -c '
|
173
|
+
pidfile="/var/run/dbus/pid"
|
174
|
+
if [ ! -f "$pidfile" ]; then
|
175
|
+
exit 0
|
176
|
+
fi
|
177
|
+
pid="`cat "$pidfile"`"
|
178
|
+
if ! grep -q "^dbus-daemon" "/proc/$pid/cmdline" 2>/dev/null; then
|
179
|
+
exit 0
|
180
|
+
fi
|
181
|
+
kill $pid' - root 2>/dev/null || true
|
182
|
+
|
183
|
+
# Unmount all mounts
|
184
|
+
ntries=0
|
185
|
+
if [ -z "$EXCLUDEROOT" ]; then
|
186
|
+
echo "Unmounting $CHROOT..." 1>&2
|
187
|
+
else
|
188
|
+
echo "Pruning $CHROOT mounts..." 1>&2
|
189
|
+
fi
|
190
|
+
baseesc="`echo "$base" | sed 's= =//=g'`"
|
191
|
+
|
192
|
+
# Define the mountpoint filter to only unmount specific mounts.
|
193
|
+
# The filter is run on the escaped version of the mountpoint.
|
194
|
+
filter() {
|
195
|
+
if [ -z "$EXCLUDEROOT" ]; then
|
196
|
+
grep "^$baseesc\\(/.*\\)\\?\$"
|
197
|
+
else
|
198
|
+
# Don't include the base directory
|
199
|
+
grep "^$baseesc/."
|
200
|
+
fi
|
201
|
+
}
|
202
|
+
|
203
|
+
# Sync for safety
|
204
|
+
sync
|
205
|
+
|
206
|
+
# Make sure the chroot's system media bind-mount is marked as slave to avoid
|
207
|
+
# unmounting devices system-wide. We still want to unmount locally-mounted
|
208
|
+
# media, though.
|
209
|
+
media="`fixabslinks '/var/host/media'`"
|
210
|
+
if mountpoint -q "$media"; then
|
211
|
+
mount --make-rslave "$media"
|
212
|
+
fi
|
213
|
+
|
214
|
+
while ! sed "s=\\\\040=//=g" /proc/mounts | cut -d' ' -f2 \
|
215
|
+
| filter | sed 's=//= =g' | xargs --no-run-if-empty -d '
|
216
|
+
' -n 50 umount 2>/dev/null; do
|
217
|
+
if [ "$ntries" -eq "$TRIES" ]; then
|
218
|
+
# Send signal to all processes running under the chroot
|
219
|
+
# ...but confirm first.
|
220
|
+
printonly=''
|
221
|
+
if [ "${YES#[Aa]}" = "$YES" ]; then
|
222
|
+
echo -n "Failed to unmount $CHROOT. Kill processes? [a/k/y/p/N] " 1>&2
|
223
|
+
read YES
|
224
|
+
if [ ! "${YES#[Kk]}" = "$YES" ]; then
|
225
|
+
SIGNAL='KILL'
|
226
|
+
elif [ ! "${YES#[Pp]}" = "$YES" ]; then
|
227
|
+
printonly=y
|
228
|
+
elif [ "${YES#[AaYy]}" = "$YES" ]; then
|
229
|
+
echo "Skipping unmounting of $CHROOT" 1>&2
|
230
|
+
ret=1
|
231
|
+
break
|
232
|
+
fi
|
233
|
+
fi
|
234
|
+
if [ -z "$printonly" ]; then
|
235
|
+
echo "Sending SIG$SIGNAL to processes under $CHROOT..." 1>&2
|
236
|
+
fi
|
237
|
+
for root in /proc/*/root; do
|
238
|
+
if [ ! -r "$root" ] \
|
239
|
+
|| [ ! "`readlink -f "$root"`" = "$base" ]; then
|
240
|
+
continue
|
241
|
+
fi
|
242
|
+
pid="${root#/proc/}"
|
243
|
+
pid="${pid%/root}"
|
244
|
+
if [ -z "$FORCE" ] \
|
245
|
+
&& grep -q 'CROUTON=CORE' \
|
246
|
+
"/proc/$pid/environ" 2>/dev/null; then
|
247
|
+
continue
|
248
|
+
fi
|
249
|
+
if [ -n "${printonly:-"$PRINT"}" ]; then
|
250
|
+
ps -p "$pid" -o pid= -o cmd= || true
|
251
|
+
fi
|
252
|
+
if [ -z "$printonly" ]; then
|
253
|
+
kill "-$SIGNAL" "$pid" 2>/dev/null || true
|
254
|
+
fi
|
255
|
+
done
|
256
|
+
|
257
|
+
# Escalate
|
258
|
+
if [ ! "${YES#[Aa]}" = "$YES" ]; then
|
259
|
+
SIGNAL='KILL'
|
260
|
+
fi
|
261
|
+
|
262
|
+
if [ -z "$printonly" ]; then
|
263
|
+
ntries=0
|
264
|
+
fi
|
265
|
+
else
|
266
|
+
ntries="$((ntries+1))"
|
267
|
+
fi
|
268
|
+
sleep 1
|
269
|
+
if ! checkusage "$base"; then
|
270
|
+
echo "Aborting unmounting $CHROOT as another instance has begun using it." 1>&2
|
271
|
+
ret=1
|
272
|
+
break
|
273
|
+
fi
|
274
|
+
done
|
275
|
+
|
276
|
+
# More sync for more safety
|
277
|
+
sync
|
278
|
+
done
|
279
|
+
|
280
|
+
# Re-disable USB persistence (the Chromium OS default) if we no longer
|
281
|
+
# have chroots running with a root in removable media
|
282
|
+
if checkusage /media; then
|
283
|
+
for usbp in /sys/bus/usb/devices/*/power/persist; do
|
284
|
+
if [ -e "$usbp" ]; then
|
285
|
+
echo 0 > "$usbp"
|
286
|
+
fi
|
287
|
+
done
|
288
|
+
fi
|
289
|
+
|
290
|
+
exit $ret
|