]> git.mar77i.info Git - dotfiles/commitdiff
initial commit
authormar77i <mar77i@protonmail.ch>
Sat, 12 Jul 2025 21:01:22 +0000 (23:01 +0200)
committermar77i <mar77i@protonmail.ch>
Sat, 12 Jul 2025 21:01:22 +0000 (23:01 +0200)
rc.xml [new file with mode: 0644]
sync.sh [new file with mode: 0755]

diff --git a/rc.xml b/rc.xml
new file mode 100644 (file)
index 0000000..2fddc44
--- /dev/null
+++ b/rc.xml
@@ -0,0 +1,593 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<openbox_config xmlns="http://openbox.org/3.4/rc" xmlns:xi="http://www.w3.org/2001/XInclude">
+  <resistance>
+    <strength>10</strength>
+    <screen_edge_strength>20</screen_edge_strength>
+  </resistance>
+  <focus>
+    <focusNew>yes</focusNew>
+    <!-- always try to focus new windows when they appear. other rules do
+       apply -->
+    <followMouse>yes</followMouse>
+    <!-- move focus to a window when you move the mouse into it -->
+    <focusLast>yes</focusLast>
+    <!-- focus the last used window when changing desktops, instead of the one
+       under the mouse pointer. when followMouse is enabled -->
+    <underMouse>no</underMouse>
+    <!-- move focus under the mouse, even when the mouse is not moving -->
+    <focusDelay>200</focusDelay>
+    <!-- when followMouse is enabled, the mouse must be inside the window for
+       this many milliseconds (1000 = 1 sec) before moving focus to it -->
+    <raiseOnFocus>no</raiseOnFocus>
+    <!-- when followMouse is enabled, and a window is given focus by moving the
+       mouse into it, also raise the window -->
+  </focus>
+  <placement>
+    <policy>Smart</policy>
+    <!-- 'Smart' or 'UnderMouse' -->
+    <center>yes</center>
+    <!-- whether to place windows in the center of the free area found or
+       the top left corner -->
+    <monitor>Active</monitor>
+    <!-- with Smart placement on a multi-monitor system, try to place new windows
+       on: 'Any' - any monitor, 'Mouse' - where the mouse is, 'Active' - where
+       the active window is, 'Primary' - only on the primary monitor -->
+    <primaryMonitor>1</primaryMonitor>
+    <!-- The monitor where Openbox should place popup dialogs such as the
+       focus cycling popup, or the desktop switch popup.  It can be an index
+       from 1, specifying a particular monitor.  Or it can be one of the
+       following: 'Mouse' - where the mouse is, or
+                  'Active' - where the active window is -->
+  </placement>
+  <theme>
+    <name>Artix-dark</name>
+    <titleLayout>NLIMC</titleLayout>
+    <!--
+      available characters are NDSLIMC, each can occur at most once.
+      N: window icon
+      L: window label (AKA title).
+      I: iconify
+      M: maximize
+      C: close
+      S: shade (roll up/down)
+      D: omnipresent (on all desktops).
+  -->
+    <keepBorder>no</keepBorder>
+    <animateIconify>no</animateIconify>
+    <font place="ActiveWindow">
+      <name>sans</name>
+      <size>8</size>
+      <!-- font size in points -->
+      <weight>bold</weight>
+      <!-- 'bold' or 'normal' -->
+      <slant>normal</slant>
+      <!-- 'italic' or 'normal' -->
+    </font>
+    <font place="InactiveWindow">
+      <name>sans</name>
+      <size>8</size>
+      <!-- font size in points -->
+      <weight>bold</weight>
+      <!-- 'bold' or 'normal' -->
+      <slant>normal</slant>
+      <!-- 'italic' or 'normal' -->
+    </font>
+    <font place="MenuHeader">
+      <name>sans</name>
+      <size>9</size>
+      <!-- font size in points -->
+      <weight>normal</weight>
+      <!-- 'bold' or 'normal' -->
+      <slant>normal</slant>
+      <!-- 'italic' or 'normal' -->
+    </font>
+    <font place="MenuItem">
+      <name>sans</name>
+      <size>9</size>
+      <!-- font size in points -->
+      <weight>normal</weight>
+      <!-- 'bold' or 'normal' -->
+      <slant>normal</slant>
+      <!-- 'italic' or 'normal' -->
+    </font>
+    <font place="ActiveOnScreenDisplay">
+      <name>sans</name>
+      <size>9</size>
+      <!-- font size in points -->
+      <weight>bold</weight>
+      <!-- 'bold' or 'normal' -->
+      <slant>normal</slant>
+      <!-- 'italic' or 'normal' -->
+    </font>
+    <font place="InactiveOnScreenDisplay">
+      <name>sans</name>
+      <size>9</size>
+      <!-- font size in points -->
+      <weight>bold</weight>
+      <!-- 'bold' or 'normal' -->
+      <slant>normal</slant>
+      <!-- 'italic' or 'normal' -->
+    </font>
+  </theme>
+  <desktops>
+    <!-- this stuff is only used at startup, pagers allow you to change them
+       during a session
+
+       these are default values to use when other ones are not already set
+       by other applications, or saved in your session
+
+       use obconf if you want to change these without having to log out
+       and back in -->
+    <number>1</number>
+    <firstdesk>1</firstdesk>
+    <names>
+      <!-- set names up here if you want to, like this:
+    <name>desktop 1</name>
+    <name>desktop 2</name>
+    -->
+    </names>
+    <popupTime>0</popupTime>
+    <!-- The number of milliseconds to show the popup for when switching
+       desktops.  Set this to 0 to disable the popup. -->
+  </desktops>
+  <resize>
+    <drawContents>yes</drawContents>
+    <popupShow>Nonpixel</popupShow>
+    <!-- 'Always', 'Never', or 'Nonpixel' (xterms and such) -->
+    <popupPosition>Center</popupPosition>
+    <!-- 'Center', 'Top', or 'Fixed' -->
+    <popupFixedPosition>
+      <!-- these are used if popupPosition is set to 'Fixed' -->
+      <x>10</x>
+      <!-- positive number for distance from left edge, negative number for
+         distance from right edge, or 'Center' -->
+      <y>10</y>
+      <!-- positive number for distance from top edge, negative number for
+         distance from bottom edge, or 'Center' -->
+    </popupFixedPosition>
+  </resize>
+  <!-- You can reserve a portion of your screen where windows will not cover when
+     they are maximized, or when they are initially placed.
+     Many programs reserve space automatically, but you can use this in other
+     cases. -->
+  <margins>
+    <top>0</top>
+    <bottom>0</bottom>
+    <left>0</left>
+    <right>0</right>
+  </margins>
+  <dock>
+    <position>TopLeft</position>
+    <!-- (Top|Bottom)(Left|Right|)|Top|Bottom|Left|Right|Floating -->
+    <floatingX>0</floatingX>
+    <floatingY>0</floatingY>
+    <noStrut>no</noStrut>
+    <stacking>Above</stacking>
+    <!-- 'Above', 'Normal', or 'Below' -->
+    <direction>Vertical</direction>
+    <!-- 'Vertical' or 'Horizontal' -->
+    <autoHide>no</autoHide>
+    <hideDelay>300</hideDelay>
+    <!-- in milliseconds (1000 = 1 second) -->
+    <showDelay>300</showDelay>
+    <!-- in milliseconds (1000 = 1 second) -->
+    <moveButton>Middle</moveButton>
+    <!-- 'Left', 'Middle', 'Right' -->
+  </dock>
+  <keyboard>
+    <chainQuitKey>C-q</chainQuitKey>
+    <keybind key="W-o">
+      <action name="Reconfigure"/>
+    </keybind>
+    <keybind key="W-d">
+      <action name="ToggleShowDesktop"/>
+    </keybind>
+    <keybind key="W-x">
+      <action name="Exit"/>
+    </keybind>
+    <keybind key="W-w">
+      <action name="ToggleDecorations"/>
+    </keybind>
+    <keybind key="W-Escape">
+      <action name="Lower"/>
+      <action name="FocusToBottom"/>
+      <action name="Unfocus"/>
+    </keybind>
+    <keybind key="W-Space">
+      <action name="ShowMenu">
+        <menu>client-menu</menu>
+      </action>
+    </keybind>
+    <!-- Keybindings for window switching -->
+    <keybind key="W-Tab">
+      <action name="NextWindow">
+        <finalactions>
+          <action name="Focus"/>
+          <action name="Raise"/>
+          <action name="Unshade"/>
+        </finalactions>
+      </action>
+    </keybind>
+    <keybind key="W-S-Tab">
+      <action name="PreviousWindow">
+        <finalactions>
+          <action name="Focus"/>
+          <action name="Raise"/>
+          <action name="Unshade"/>
+        </finalactions>
+      </action>
+    </keybind>
+    <keybind key="W-F2">
+      <action name="Execute">
+        <command>lxqt-runner</command>
+      </action>
+    </keybind>
+    <keybind key="C-W-F2">
+      <action name="Execute">
+        <command>pactl set-sink-volume @DEFAULT_SINK@ -5%</command>
+      </action>
+    </keybind>
+    <keybind key="C-W-F3">
+      <action name="Execute">
+        <command>pactl set-sink-volume @DEFAULT_SINK@ +5%</command>
+      </action>
+    </keybind>
+    <keybind key="C-W-F1 W-m">
+      <action name="Execute">
+        <command>pactl set-sink-mute @DEFAULT_SINK@ toggle</command>
+      </action>
+    </keybind>
+    <keybind key="W-0x31">
+      <action name="Execute">
+        <command>dbus-send --print-reply --dest=org.kde.StatusNotifierWatcher /global_key_shortcuts/panel/taskbar2/task_1 org.lxqt.global_key_shortcuts.client.activated</command>
+      </action>
+    </keybind>
+    <keybind key="W-1">
+      <action name="Execute">
+        <command>dbus-send --print-reply --dest=org.kde.StatusNotifierWatcher /global_key_shortcuts/panel/taskbar2/task_2 org.lxqt.global_key_shortcuts.client.activated</command>
+      </action>
+    </keybind>
+    <keybind key="W-2">
+      <action name="Execute">
+        <command>dbus-send --print-reply --dest=org.kde.StatusNotifierWatcher /global_key_shortcuts/panel/taskbar2/task_3 org.lxqt.global_key_shortcuts.client.activated</command>
+      </action>
+    </keybind>
+    <keybind key="W-3">
+      <action name="Execute">
+        <command>dbus-send --print-reply --dest=org.kde.StatusNotifierWatcher /global_key_shortcuts/panel/taskbar2/task_4 org.lxqt.global_key_shortcuts.client.activated</command>
+      </action>
+    </keybind>
+    <keybind key="W-4">
+      <action name="Execute">
+        <command>dbus-send --print-reply --dest=org.kde.StatusNotifierWatcher /global_key_shortcuts/panel/taskbar2/task_5 org.lxqt.global_key_shortcuts.client.activated</command>
+      </action>
+    </keybind>
+    <keybind key="W-5">
+      <action name="Execute">
+        <command>dbus-send --print-reply --dest=org.kde.StatusNotifierWatcher /global_key_shortcuts/panel/taskbar2/task_6 org.lxqt.global_key_shortcuts.client.activated</command>
+      </action>
+    </keybind>
+    <keybind key="W-6">
+      <action name="Execute">
+        <command>dbus-send --print-reply --dest=org.kde.StatusNotifierWatcher /global_key_shortcuts/panel/taskbar2/task_7 org.lxqt.global_key_shortcuts.client.activated</command>
+      </action>
+    </keybind>
+    <keybind key="W-7">
+      <action name="Execute">
+        <command>dbus-send --print-reply --dest=org.kde.StatusNotifierWatcher /global_key_shortcuts/panel/taskbar2/task_8 org.lxqt.global_key_shortcuts.client.activated</command>
+      </action>
+    </keybind>
+    <keybind key="W-8">
+      <action name="Execute">
+        <command>dbus-send --print-reply --dest=org.kde.StatusNotifierWatcher /global_key_shortcuts/panel/taskbar2/task_9 org.lxqt.global_key_shortcuts.client.activated</command>
+      </action>
+    </keybind>
+    <keybind key="W-9">
+      <action name="Execute">
+        <command>dbus-send --print-reply --dest=org.kde.StatusNotifierWatchers /global_key_shortcuts/panel/taskbar2/task_10 org.lxqt.global_key_shortcuts.client.activated</command>
+      </action>
+    </keybind>
+<?ignore
+    <keybind key="W-s">
+      <action name="Execute">
+        <command>dbus-send --system --print-reply --dest=org.freedesktop.login1 /org/freedesktop/login1 "org.freedesktop.login1.Manager.Suspend" boolean:true</command>
+      </action>
+    </keybind>
+?>
+    <keybind key="W-r">
+      <action name="Execute">
+        <command>dbus-send --system --print-reply --dest=org.freedesktop.login1 /org/freedesktop/login1 "org.freedesktop.login1.Manager.Reboot" boolean:true</command>
+      </action>
+    </keybind>
+    <keybind key="W-p">
+      <action name="Execute">
+        <command>dbus-send --system --print-reply --dest=org.freedesktop.login1 /org/freedesktop/login1 "org.freedesktop.login1.Manager.PowerOff" boolean:true</command>
+      </action>
+    </keybind>
+    <keybind key="W-l">
+      <action name="Execute">
+        <command>lxqt-leave</command>
+      </action>
+    </keybind>
+    <keybind key="W-c">
+      <action name="Close"/>
+    </keybind>
+    <keybind key="W-y">
+      <action name="ShowMenu">
+        <menu>root-menu</menu>
+      </action>
+    </keybind>
+    <keybind key="W-0x6f">
+      <action name="DirectionalTargetWindow">
+        <direction>north</direction>
+      </action>
+    </keybind>
+    <keybind key="W-0x74">
+      <action name="DirectionalTargetWindow">
+        <direction>south</direction>
+      </action>
+    </keybind>
+    <keybind key="W-0x71">
+      <action name="DirectionalTargetWindow">
+        <direction>west</direction>
+      </action>
+    </keybind>
+    <keybind key="W-0x72">
+      <action name="DirectionalTargetWindow">
+        <direction>east</direction>
+      </action>
+    </keybind>
+    <keybind key="S-W-0x6f">
+      <action name="MoveResizeTo">
+        <y>-100%</y>
+      </action>
+    </keybind>
+    <keybind key="S-W-0x74">
+      <action name="MoveResizeTo">
+        <y>100%</y>
+      </action>
+    </keybind>
+    <keybind key="S-W-0x71">
+      <action name="MoveResizeTo">
+        <x>-100%</x>
+      </action>
+    </keybind>
+    <keybind key="S-W-0x72">
+      <action name="MoveResizeTo">
+        <x>100%</x>
+      </action>
+    </keybind>
+  </keyboard>
+  <mouse>
+    <dragThreshold>1</dragThreshold>
+    <!-- number of pixels the mouse must move before a drag begins -->
+    <doubleClickTime>500</doubleClickTime>
+    <!-- in milliseconds (1000 = 1 second) -->
+    <screenEdgeWarpTime>400</screenEdgeWarpTime>
+    <!-- Time before changing desktops when the pointer touches the edge of the
+       screen while moving a window, in milliseconds (1000 = 1 second).
+       Set this to 0 to disable warping -->
+    <screenEdgeWarpMouse>false</screenEdgeWarpMouse>
+    <!-- Set this to TRUE to move the mouse pointer across the desktop when
+       switching due to hitting the edge of the screen -->
+    <context name="Frame">
+      <mousebind button="W-Left" action="Drag">
+        <action name="Move"/>
+      </mousebind>
+      <mousebind button="W-Right" action="Drag">
+        <action name="Resize"/>
+      </mousebind>
+      <mousebind button="W-Middle" action="Click">
+        <action name="ToggleMaximize"/>
+      </mousebind>
+    </context>
+    <context name="Titlebar">
+      <mousebind button="Left" action="Drag">
+        <action name="Move"/>
+      </mousebind>
+      <mousebind button="Left" action="DoubleClick">
+        <action name="ToggleMaximize"/>
+      </mousebind>
+      <mousebind button="Up" action="Click">
+        <action name="if">
+          <shaded>no</shaded>
+          <then>
+            <action name="Shade"/>
+            <action name="FocusToBottom"/>
+            <action name="Unfocus"/>
+            <action name="Lower"/>
+          </then>
+        </action>
+      </mousebind>
+      <mousebind button="Down" action="Click">
+        <action name="if">
+          <shaded>yes</shaded>
+          <then>
+            <action name="Unshade"/>
+            <action name="Raise"/>
+          </then>
+        </action>
+      </mousebind>
+    </context>
+    <context name="Titlebar Top Right Bottom Left TLCorner TRCorner BRCorner BLCorner">
+      <mousebind button="Left" action="Press">
+        <action name="Focus"/>
+        <action name="Raise"/>
+        <action name="Unshade"/>
+      </mousebind>
+      <mousebind button="Middle" action="Press">
+        <action name="Lower"/>
+        <action name="FocusToBottom"/>
+        <action name="Unfocus"/>
+      </mousebind>
+      <mousebind button="Right" action="Press">
+        <action name="Focus"/>
+        <action name="Raise"/>
+        <action name="ShowMenu">
+          <menu>client-menu</menu>
+        </action>
+      </mousebind>
+    </context>
+    <context name="TRCorner BRCorner TLCorner BLCorner">
+      <mousebind button="Left" action="Press">
+        <action name="Focus"/>
+        <action name="Raise"/>
+        <action name="Unshade"/>
+      </mousebind>
+      <mousebind button="Left" action="Drag">
+        <action name="Resize"/>
+      </mousebind>
+    </context>
+    <context name="Client">
+      <mousebind button="Left" action="Press">
+        <action name="Focus"/>
+        <action name="Raise"/>
+      </mousebind>
+      <mousebind button="Middle" action="Press">
+        <action name="Focus"/>
+        <action name="Raise"/>
+      </mousebind>
+      <mousebind button="Right" action="Press">
+        <action name="Focus"/>
+        <action name="Raise"/>
+      </mousebind>
+      <mousebind button="W-Left" action="Drag">
+        <action name="Move"/>
+      </mousebind>
+      <mousebind button="W-Right" action="Drag">
+        <action name="Resize"/>
+      </mousebind>
+    </context>
+    <context name="Icon">
+      <mousebind button="Left" action="Press">
+        <action name="Focus"/>
+        <action name="Raise"/>
+        <action name="Unshade"/>
+        <action name="ShowMenu">
+          <menu>client-menu</menu>
+        </action>
+      </mousebind>
+      <mousebind button="Right" action="Press">
+        <action name="Focus"/>
+        <action name="Raise"/>
+        <action name="ShowMenu">
+          <menu>client-menu</menu>
+        </action>
+      </mousebind>
+    </context>
+    <context name="AllDesktops">
+      <mousebind button="Left" action="Press">
+        <action name="Focus"/>
+        <action name="Raise"/>
+        <action name="Unshade"/>
+      </mousebind>
+      <mousebind button="Left" action="Click">
+        <action name="ToggleOmnipresent"/>
+      </mousebind>
+    </context>
+    <context name="Shade">
+      <mousebind button="Left" action="Press">
+        <action name="Focus"/>
+        <action name="Raise"/>
+      </mousebind>
+      <mousebind button="Left" action="Click">
+        <action name="ToggleShade"/>
+      </mousebind>
+    </context>
+    <context name="Iconify">
+      <mousebind button="Left" action="Press">
+        <action name="Focus"/>
+        <action name="Raise"/>
+      </mousebind>
+      <mousebind button="Left" action="Click">
+        <action name="Iconify"/>
+      </mousebind>
+    </context>
+    <context name="Maximize">
+      <mousebind button="Left" action="Press">
+        <action name="Focus"/>
+        <action name="Raise"/>
+        <action name="Unshade"/>
+      </mousebind>
+      <mousebind button="Middle" action="Press">
+        <action name="Focus"/>
+        <action name="Raise"/>
+        <action name="Unshade"/>
+      </mousebind>
+      <mousebind button="Right" action="Press">
+        <action name="Focus"/>
+        <action name="Raise"/>
+        <action name="Unshade"/>
+      </mousebind>
+      <mousebind button="Left" action="Click">
+        <action name="ToggleMaximize"/>
+      </mousebind>
+      <mousebind button="Middle" action="Click">
+        <action name="ToggleMaximize">
+          <direction>vertical</direction>
+        </action>
+      </mousebind>
+      <mousebind button="Right" action="Click">
+        <action name="ToggleMaximize">
+          <direction>horizontal</direction>
+        </action>
+      </mousebind>
+    </context>
+    <context name="Close">
+      <mousebind button="Left" action="Press">
+        <action name="Focus"/>
+        <action name="Raise"/>
+        <action name="Unshade"/>
+      </mousebind>
+      <mousebind button="Left" action="Click">
+        <action name="Close"/>
+      </mousebind>
+    </context>
+    <context name="Desktop">
+      <mousebind button="Left" action="Press">
+        <action name="Focus"/>
+        <action name="Raise"/>
+      </mousebind>
+      <mousebind button="Right" action="Press">
+        <action name="Focus"/>
+        <action name="Raise"/>
+      </mousebind>
+    </context>
+    <context name="Root">
+      <!-- Menus -->
+      <mousebind button="Middle" action="Press">
+        <action name="ShowMenu">
+          <menu>client-list-combined-menu</menu>
+        </action>
+      </mousebind>
+    </context>
+  </mouse>
+  <menu>
+    <file>menu.xml</file>
+    <hideDelay>200</hideDelay>
+    <!-- if a press-release lasts longer than this setting (in milliseconds), the
+       menu is hidden again -->
+    <middle>no</middle>
+    <!-- center submenus vertically about the parent entry -->
+    <submenuShowDelay>100</submenuShowDelay>
+    <!-- time to delay before showing a submenu after hovering over the parent
+       entry.
+       if this is a negative value, then the delay is infinite and the
+       submenu will not be shown until it is clicked on -->
+    <submenuHideDelay>400</submenuHideDelay>
+    <!-- time to delay before hiding a submenu when selecting another
+       entry in parent menu
+       if this is a negative value, then the delay is infinite and the
+       submenu will not be hidden until a different submenu is opened -->
+    <showIcons>yes</showIcons>
+    <!-- controls if icons appear in the client-list-(combined-)menu -->
+    <manageDesktops>no</manageDesktops>
+    <!-- show the manage desktops section in the client-list-(combined-)menu -->
+  </menu>
+  <applications>
+    <application class="*">
+      <decor>no</decor>
+      <!--<maximized>yes</maximized>-->
+    </application>
+  </applications>
+</openbox_config>
diff --git a/sync.sh b/sync.sh
new file mode 100755 (executable)
index 0000000..b166a42
--- /dev/null
+++ b/sync.sh
@@ -0,0 +1,103 @@
+#!/usr/bin/env bash
+
+cd "$(dirname "$(realpath -Pe "${0}")")"
+
+actions=(sync_to_tree sync_to_dest)
+sources=("${HOME}/.config/openbox/rc.xml")
+cache_file="${HOME}/.cache/dotfiles.cache"
+
+force=0
+action=sync_to_tree
+
+in_array() {
+    local search="${1}"
+    shift
+    for arg in "${@}"; do
+        if [[ "${arg}" == "${search}" ]]; then
+            return 0
+        fi
+    done
+    return 1
+}
+
+update_cache() {
+    local new_hash="$(sha256sum "${1}")"
+    if ! grep -Fq "${new_hash}" "${cache_file}"; then
+        echo "${new_hash}" >> "${cache_file}"
+        echo "Cache updated. ${new_hash}"
+    fi
+}
+
+is_file_dirty() {
+    ! git diff-files --quiet -- "${1}" 2>/dev/null \
+    || [[ $(git ls-files --others -- "${1}") ]]
+}
+
+sync_to_tree() {
+    # fetch configurations to the working tree.
+    # make sure that the file is unchanged on the working tree
+    localnbbv source dest
+    for source in "${sources[@]}"; do
+        dest="$(basename "${source}")"
+        if diff -q "${source}" "${dest}" &>/dev/null; then
+            continue
+        fi
+        if (( force == 0 )) && is_file_dirty "${dest}"; then
+            echo "Error: ${dest} contains changes. Can not continue." >&2
+            exit 1
+        fi
+        if [[ -e "${dest}" ]]; then
+            rm "${dest}"
+        fi
+        cp -a "${source}" "${dest}"
+        echo "Updated ${dest}"
+        update_cache "${dest}"
+    done
+}
+
+sync_to_dest() {
+    # deploy configurations to destination directory.
+    # but first make sure, any old version of the file is deployed.
+    local source dest
+    for dest in "${sources[@]}"; do
+        source="$(basename "${dest}")"
+        if diff -q "${source}" "${dest}" &>/dev/null; then
+            continue
+        fi
+        if (( force == 0 )) && ! grep -Fq "$(sha256sum "${dest}")" "${cache_file}"; then
+            echo "Error: Previously unknown changes in '${dest}' found." >&2
+            exit 1
+        fi
+        cp -a "${source}" "${dest}"
+        echo "Deployed ${dest}"
+        update_cache "${dest}"
+    done
+}
+
+
+while (( $# )); do
+    case "${1}" in
+    --action|-a)
+        if (( $# == 1 )); then
+            echo "Error: Argument expected: ${1}" >&2
+            exit 1
+        fi
+        action="${2}"
+        if ! in_array "${action}" "${actions[@]}"; then
+            echo "Error: unknown action: '${action}'" >&2
+            exit 1
+        fi
+        shift
+        ;;
+    --force|-f)
+        force=1
+        ;;
+    esac
+    shift
+done
+
+if ! git status >/dev/null; then
+    exit 1
+fi
+
+"${action}"