Skip to content

Instantly share code, notes, and snippets.

@tarao
Created November 29, 2012 15:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tarao/4169765 to your computer and use it in GitHub Desktop.
Save tarao/4169765 to your computer and use it in GitHub Desktop.
Configure virtualized guest environment on laptop
#!/usr/bin/env ruby
require 'optparse'
require 'rubygems'
require 'dbus'
class Notification
BUS = 'org.freedesktop.Notifications'
SERVICE = '/org/freedesktop/Notifications'
INTERFACE = BUS
def initialize
bus = DBus::SessionBus.instance
service = bus.service(BUS)
@notifier = service.object(SERVICE)
@notifier.introspect
@notifier.default_iface = INTERFACE
end
def notify(args={})
return @notifier.Notify(
args[:app] || 'ruby',
args[:replace_id] || 0,
args[:icon] || '',
args[:summary] || '',
args[:body] || '',
args[:actions] || [],
args[:hint] || {},
args[:expire] || 1000)
end
end
class Notification
class CLI
URGENCY = {
'low' => 0,
'normal' => 1,
'critical' => 2,
}
DBUS_TYPE = Hash[DBus::Type::TypeMapping.invert.map do |k,v|
[ k[0].downcase, v ]
end].merge({
'int' => 'i',
'uint' => 'u',
})
TYPE_DECODER = {
0 => :to_s,
?y => :to_c,
?b => proc{|b| b =~ /^(true|t|yes|y|1)$/i ? true : false},
?n => :to_i,
?q => :to_i,
?i => :to_i,
?u => :to_i,
?x => :to_i,
?t => :to_i,
?d => :to_f,
?s => :to_s,
}
def initialize(argv)
@args = { :hint => {} }
@flag = {}
@opt = OptionParser.new
@opt.on('-p', '--print-id', 'Print the notification ID.', &flag(:print))
@opt.on('-r', '--replace-id=REPLACE_ID',
'The ID of the notification to replace.', &args(:replace_id, :to_i))
@opt.on('-u', '--urgency=LEVEL', [
'Specifies the urgency level (low, normal, critical).'
].join(' '), &hint(:urgency, ?y, method(:urgency)))
@opt.on('-t', '--expire-time=TIME', [
'Specifies the timeout in milliseconds at which to expire',
' the notification.'
].join(' '), &args(:expire, :to_i))
@opt.on('-i', '--icon=ICON', [
'Specifies an icon filename or stock icon to display.'
].join(' '), &args(:icon, :to_s))
@opt.on('-c', '--category=TYPE[,TYPE...]', [
'Specifies the notification category.'
].join(' '), &hint(:category, ?s, :to_s))
@opt.on('-h', '--hint=TYPE:NAME:VALUE', [
'Specifies basic extra data to pass.',
'Valid types are int, double, string and byte.'
].join(' '), &method(:hints))
@opt.on('-?', '--help', 'Show this help message.', &flag(:help))
@opt.parse!(argv)
@args[:summary], @args[:body] = argv
end
def flag(name)
return proc do |v|
@flag[name] = v
end
end
def args(name, decoder)
return proc do |v|
@args[name] = decoder.to_proc[v]
end
end
def hint(name, type, decoder)
return proc do |v|
@args[:hint][name.to_s] = DBus.variant(type.to_s, decoder.to_proc[v])
end
end
def urgency(name) return URGENCY[name] || 1 end
def hints(spec)
type, name, value = spec.split(':')
type = DBUS_TYPE[type] || 0
return hint(name, type, TYPE_DECODER[type])[value]
end
def run()
if @flag[:help]
puts @opt.help
else
id = Notification.new.notify(@args)
puts id if @flag[:print]
end
end
end
end
Notification::CLI.new(ARGV).run
#!/bin/sh -e
base=`basename "$0"`
app='virt-laptop'
lf='
'
[ $(id -ru) = 0 ] && bin='/usr/local/bin' || bin=$(dirname "$0")
auth_command_url='http://raw.github.com/gist/4108520/auth-command.sh'
auth_command_user='root'
auth_command_config="$HOME/.ssh/$app/config"
ssh_options='-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null'
start_hook_url='http://raw.github.com/gist/4169176/virt-start-hook.sh'
virt_volctl_url='http://raw.github.com/gist/4169765/virt-volctl.sh'
notify_send_url='http://raw.github.com/gist/4169765/notify-send.rb'
test=0
verbose=0
help() {
cat <<EOF
Usage: $base install OPTIONS <host> [<name>...]
$base list
Options:
-v Verbose messages.
-t Test mode; print what will be done instead of actually doing it.
Arguments:
<host> The virtualization host.
<name> Name of a feature.
EOF
exit $1
}
features=$(cat <<EOF
ntp:Synchronize the guest's clock with the host's clock.
audio:Enable audio.
audio-volume:Audio volume button handling.
gnome-control-center:Invoke gnome-control-center on the host OS from menu.
network-manager:Attach network manager applet on the host OS to the desktop.
power-manager:Attach power manager applet on the host OS to the desktop.
EOF
)
testing() {
[ $test != 0 ] && return 0
}
info() {
[ $verbose != 0 ] && echo $1 >&2
return 0
}
error() {
echo "$1" >&2
exit 1
}
argument_error() {
echo "$1" >&2
help 1
}
argument_require_action() {
opt="$1"; shift
valid_action=0
for x in $@; do
[ "x$x" = "x$action" ] && valid_action=1
done
[ $valid_action = 1 ] || argument_error "$opt: requires action \"$1\""
return 0
}
argument_require() {
[ -z "$1" ] && argument_error "$2: insufficient arguments"
return 0
}
sed_escape() {
echo "$1" | sed -e 's/\//\\\//g' | sed -e 's/\([*]\)/\\\1/g'
}
shell_escape1() {
printf %s\\n "$1" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/'/"
}
shell_escape() {
args=$(shell_escape1 "$1"); shift
for x in "$@"; do
args="$args $(shell_escape1 "$x")"
done
echo "$args"
}
run() {
cmd=$(shell_escape "$1"); shift
for x in "$@"; do
[ "x$x" = 'x|' -o "x$x" = 'x>' -o "x$x" = 'x<' ] && {
cmd="$cmd $x"
} || {
cmd="$cmd $(shell_escape "$x")"
}
done
testing && echo "$cmd" || eval "$cmd"
}
ssh_control_persist_available() {
msg='Missing ControlPersist argument'
ssh -o ControlPersist 2>&1 | grep "$msg" > /dev/null && return 0
}
require_ssh() {
[ -n "$ssh_required" ] && return 0
ssh_required=1
ssh_control_persist_available && {
options=$(shell_escape \
-o 'ControlMaster auto' \
-o "ControlPath ~/.ssh/$app-%r@%h:%p" \
-o 'ControlPersist 60')
ssh_options="$ssh_options $options"
}
return 0
}
ssh() {
require_ssh
args=''
for x in "$@"; do
args="$args $(shell_escape "$x")"
done
eval "command ssh $ssh_options$args"
}
download_auth_command() {
run wget -O "$1/auth-command" "$auth_command_url"
run chmod a+x "$1/auth-command"
}
require_auth_command() {
[ -n "$auth_command" ] && return 0
dir=$(dirname "$auth_command_config")
if type 'auth-command' >/dev/null 2>&1; then
auth_command='auth-command'
elif [ -x "$dir/auth-command" ]; then
auth_command="$dir/auth-command"
else # install
if [ $(id -ru) = 0 ]; then
download_auth_command "$bin"
auth_command="$bin/auth-command"
else
run mkdir -p "$dir"
download_auth_command "$dir"
auth_command="$dir/auth-command"
fi
fi
# initialize
[ -f "$auth_command_config" ] || {
run $auth_command init -X -l "$auth_command_user" \
-c "$auth_command_config" "$host"
}
return 0
}
auth_command_web() {
wget -O - "$auth_command_url" | sh -s "$@"
}
auth_command() {
require_auth_command
require_ssh
auth_command_options="-c $(shell_escape "$auth_command_config")"
[ $verbose != 0 ] && auth_command_options="-v $auth_command_options"
action="$1"; shift
args=''
for x in "$@"; do
args="$args $(shell_escape "$x")"
done
eval "run $auth_command $action $auth_command_options $ssh_options$args"
}
local_require() {
info "Installing $@..."
type aptitude >/dev/null 2>&1 && {
run yes '|' sudo aptitude install $@
}
info 'done'
}
host_require() {
info "Installing $@ on $host..."
run=$(cat <<EOF
type aptitude >/dev/null 2>&1 && {
yes | aptitude install $@
}
EOF
)
run ssh -l "$auth_command_user" "$host" "/bin/sh -c '$run'"
info 'done'
}
register_autostart() {
dir="$HOME/.config/autostart"
file="$dir/$app-$1.desktop"
[ -f "$file" ] && return 0
info "Register \"$1\" to autostart"
data=$(cat <<EOF
[Desktop Entry]
Type=Application
Exec=$2
Hidden=false
X-GNOME-Autostart-enabled=true
Name=$3
Comment=$4
Icon=$5
EOF
)
run mkdir -p "$dir"
testing && echo "cat > \"$file\" <<EOF
$data
EOF"
testing || cat > "$file" <<EOF
$data
EOF
}
register_menu() {
dir="$HOME/.local/share/applications"
file="$dir/$app-$1.desktop"
[ -f "$file" ] && return 0
info "Register \"$1\" to menu"
data=$(cat <<EOF
[Desktop Entry]
Type=Application
Exec=$2
Name=$3
Categories=$4;
Icon=$5
Terminal=false
EOF
)
run mkdir -p "$dir"
testing && echo "cat > \"$file\" <<EOF
$data
EOF"
testing || cat > "$file" <<EOF
$data
EOF
}
feature_ntp() {
local_require ntp
conf='/etc/ntp.conf'
confname=$(basename "$conf")
grep '^tinker panic 0$' "$conf" >/dev/null 2>&1 || {
[ -f "$conf" ] && {
run sed -e '2itinker panic 0' "$conf" '>' "/tmp/$confname"
run sudo mv "$conf" "$conf.bak"
run sudo mv "/tmp/$confname" "$conf"
[ -f "/tmp/$confname" ] && rm "/tmp/$confname"
}
}
host_require wget sudo
guest=$(hostname)
cmd='/etc/init.d/ntp restart'
run=$(cat <<EOF
export http_proxy="$http_proxy"
wget -O - "$start_hook_url" | sh -s add -v "$guest" "$guest/ntp" $cmd
EOF
)
run ssh -t -l "$auth_command_user" "$host" "/bin/sh -c '$run'" '<' /dev/tty
return 0
}
feature_audio() {
host_require alsa-utils
run=$(cat <<EOF
type amixer >/dev/null && {
[ $verbose != 0 ] && echo "Enable master volume" >&2
amixer set Master unmute >/dev/null
amixer set Master "90%" >/dev/null
amixer set Speaker unmute >/dev/null
amixer set Speaker "90%" >/dev/null
amixer set PCM "90%" >/dev/null
amixer get Master
amixer get Speaker
amixer get PCM
}
group=audio
conf=/etc/libvirt/qemu.conf
etcgrp=/etc/group
subst="\\\\\\\\1"
[ -z "\$user" ] && [ -f "\$conf" ] && {
pat="s/^user\\\\s*=\\\\s*\\"\\\\([^\\"]\\\\+\\\\)\\".*\$/\$subst/p"
user=\$(sed -n -e "\$pat" "\$conf")
}
[ -z "\$user" ] && grep "^libvirt-qemu:" /etc/passwd >/dev/null && {
user="libvirt-qemu" # debian default
}
[ -n "\$user" ] && {
[ $verbose != 0 ] && echo "Add \\"\$user\\" to \\"\$group\\" group" >&2
grep="^\$group:.*:\\\\(.*,\\\\)*\$user\\\\(,.*\\\\)*$"
type adduser >/dev/null && {
adduser "\$user" \$group
} || ( type usermod >/dev/null && {
usermod -a -G \$group "\$user"
} ) || grep "\$grep" \$etcgrp >/dev/null || {
pat1="s/\\\\(\$group:[^:]*:[^:]*:.\\\\+\\\\)$/\$subst,\$user/"
pat2="s/\\\\(\$group:[^:]*:[^:]*:\\\\)$/\$subst\$user/"
cp \$etcgrp \$etcgrp.bak
sed -e "\$pat1" \$etcgrp.bak | sed -e "\$pat2" > \$etcgrp
}
}
var="vnc_allow_host_audio"
fixed() {
grep "^\\\\s*\$var\\\\s*=\\\\s*1.*\$" "\$1" >/dev/null
}
[ -f "\$conf" ] && { fixed "\$conf" || {
[ $verbose != 0 ] && echo "Fix \\"\$conf\\"" >&2
pat1="s/^\\\\(\\\\s*\$var\\\\s*=\\\\s*0.*$\\\\)/#\$subst/g"
pat2="/^#\\\\s*\$var\\\\s*=.*$/ {
p
i\\\\
\\\\\\\\\\\\\\\\\$var = 1
b rest
}
p
d
:rest
n
p
b rest"
cp "\$conf" "\$conf.bak"
cat "\$conf.bak" | sed -e "\$pat1" | sed -n -e "\$pat2" > "\$conf.new"
fixed "\$conf.new" || echo "\$var = 1" >> "\$conf.new"
mv "\$conf.new" "\$conf"
} }
EOF
)
run ssh -l "$auth_command_user" "$host" "/bin/sh -c '$run'"
name='audio-mixer'
auth_command add -t "$host" "$name" alsamixer
register_menu "$name" \
"gnome-terminal --command \"$auth_command run -c '$auth_command_config' $name\"" \
"Audio Mixer (on $host)" \
'GNOME;GTK;Settings' \
'preferences-sound'
return 0
}
feature_audio_volume() {
info "Installing remote volume control script"
host_require alsa-utils
amixer_script_dir=`mktemp -d`
amixer_script="$amixer_script_dir/amixer.sh"
cat > "$amixer_script" <<EOF
#!/bin/sh
amixer \$SSH_ORIGINAL_COMMAND
EOF
auth_command add "$host" amixer -s "$amixer_script"
rm -r "$amixer_script_dir"
info "Installing local volume control script"
local_require alsa-utils vorbis-tools ruby dconf-cli
volcmd="$HOME/bin/virt-volctl"
run gem install ruby-dbus
run mkdir -p "$HOME/bin"
run wget -O "$HOME/bin/notify-send.rb" "$notify_send_url"
run chmod +x "$HOME/bin/notify-send.rb"
run wget -O "$volcmd" "$virt_volctl_url"
run chmod +x "$volcmd"
info "Setting keybindings..."
media_keys='org.gnome.settings-daemon.plugins.media-keys'
media_keys_path='/org/gnome/settings-daemon/plugins/media-keys'
# disable deafult bindings
run gsettings set "$media_keys" volume-down ''
run gsettings set "$media_keys" volume-up ''
# new bindings
keys=`gsettings get "$media_keys" "custom-keybindings"`
key_volume_up='virt-volume-up'
key_volume_down='virt-volume-down'
custom="$media_keys_path/custom-keybindings"
if [ "$keys" = '@as []' ]; then
run gsettings set "$media_keys" "custom-keybindings" \
"['$custom/$key_volume_up/', '$custom/$key_volume_down/']"
else
add=", '$custom/$key_volume_up/', '$custom/$key_volume_down/'"
keys=`echo "$keys" | sed 's/^\[\(.*\)\]$/\1/'`
keys=`echo "$keys" | sed "s!$add!!g"`
run gsettings set "$media_keys" "custom-keybindings" "[$keys$add]"
fi
schema="$media_keys.custom-keybinding"
path="$schema:$custom"
run gsettings set "$path/$key_volume_up/" name 'Volume up'
run gsettings set "$path/$key_volume_up/" command "$volcmd up"
run gsettings set "$path/$key_volume_up/" binding 'XF86AudioRaiseVolume'
run gsettings set "$path/$key_volume_down/" name 'Volume down'
run gsettings set "$path/$key_volume_down/" command "$volcmd down"
run gsettings set "$path/$key_volume_down/" binding 'XF86AudioLowerVolume'
info "done"
}
feature_gnome_control_center() {
name='gnome-control-center'
host_require gnome-control-center
auth_command add "$host" "$name" gnome-control-center
register_menu "$name" \
"$auth_command run -c '$auth_command_config' $name" \
"System Settings (on $host)" \
'GNOME;GTK;Settings' \
'preferences-system'
}
feature_network_manager() {
name='network-manager'
host_require network-manager-gnome
auth_command add "$host" "$name" nm-applet
register_autostart "$name" \
"$auth_command run -c '$auth_command_config' $name" \
"Network (on $host)" \
'Manage your network connections' \
'nm-device-wireless'
}
feature_power_manager() {
name='power-manager'
host_require xfce4-power-manager
auth_command add "$host" "$name" xfce4-power-manager --no-daemon
register_autostart "$name" \
"$auth_command run -c '$auth_command_config' $name" \
"Power Manager (on $host)" \
'Manage your battery' \
'battery'
}
feature_func() {
feature=$(echo "$1" | tr '-' '_')
echo "feature_$feature"
}
install1() {
info "Feature \"$1\""
$(feature_func "$1")
}
install() {
for x in $@; do
type $(feature_func "$x") >/dev/null 2>&1 || {
error "No such feature named \"$x\""
}
done
for x in $@; do
install1 "$x" || true
done
return 0
}
feature_names1() {
for x in $features; do
echo "$x" | cut -f 1 -d :
done
}
feature_names() {
IFS="$lf" feature_names1
}
list1() {
for x in $features; do
name=$(echo "$x" | cut -f 1 -d :)
desc=$(echo "$x" | cut -f 2 -d :)
echo "$name"
echo " $desc"
done
}
list() {
IFS="$lf" list1
}
action="$1"
[ -z "$action" ] && help
shift
parsing=1
while [ $parsing = 1 ] && [ -n "$1" ]; do
case "$1" in
-t) shift
test=1
;;
-v) shift
verbose=1
;;
*) parsing=0
;;
esac
done
case "$action" in
install)
host="$1"; argument_require "$1" "install <host>"; shift
[ -z "$1" ] && install $(feature_names "$features") || install "$@"
;;
list)
[ -n "$1" ] && argument_error "$base list: unknown argument \"$1\""
list
;;
help)
help
;;
*) argument_error "Unknown action: \"$action\""
;;
esac
#!/bin/sh
base=$(basename "$0")
virt_laptop_dir="$HOME/.ssh/virt-laptop"
default_step=10
scontrol='Master'
replace_id_file="/tmp/$USER-virt-volume-notify-id"
notifier="$HOME/bin/notify-send.rb"
virt_laptop() {
"$virt_laptop_dir/auth-command" run -c "$virt_laptop_dir/config" "$@"
}
virt_amixer() {
virt_laptop amixer "$@" 2>/dev/null
}
virt_audio_mixer() {
virt_laptop audio-mixer 2>/dev/null
}
parse_volume() {
sed 's/^.*Playback.*\[\([0-9]\+\)%\].*$/\1/;/^[0-9]\+$/p;d'
}
notify() {
icon='audio-volume-medium-symbolic'
if [ "$1" = 0 ]; then
icon='audio-volume-muted-symbolic'
elif [ "$1" -lt 34 ]; then
icon='audio-volume-low-symbolic'
elif [ "$1" -lt 67 ]; then
icon='audio-volume-medium-symbolic'
else
icon='audio-volume-high-symbolic'
fi
sound="/usr/share/sounds/freedesktop/stereo/audio-volume-change.oga"
timestamp=`date --iso=ns`
echo "$timestamp" > "$replace_id_file.timestamp"
( flock 9
[ "$timestamp" = `cat "$replace_id_file.timestamp"` ] && {
[ -f "$replace_id_file" ] && replace_id=`cat "$replace_id_file"`
[ -n "$replace_id" ] && replace="-r $replace_id"
hash=`date '+%N'`
"$notifier" -t 1000 -i "$icon" -h "int:value:$1" -p $replace \
"Volume: ${1}%" >&9
type ogg123 >/dev/null && ogg123 "$sound"
}
flock -u 9
) 9> "$replace_id_file"
}
sync() {
parse_volume | while read volume; do
echo "${volume}%"
notify "$volume"
amixer set "$scontrol" "${volume}%" >/dev/null
done
}
case "$1" in
help|--help|-h|'')
echo "Usage: $base up|down|mixer [<step>]"
;;
up|down)
suffix='+'
[ "$1" = 'down' ] && suffix='-'
step="$2"
[ -z "$step" ] && step="$default_step"
virt_amixer set "$scontrol" "${step}%$suffix" | sync
;;
mixer)
virt_audio_mixer
;;
esac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment