Created
November 29, 2012 15:23
-
-
Save tarao/4169765 to your computer and use it in GitHub Desktop.
Configure virtualized guest environment on laptop
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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