kameleon-builder 2.0.0.dev
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|