diff --git a/examples/README.md b/examples/README.md
index be74091f712b69f3616ed24af1346c590e08becd..40964178f58f5c4e2eb9d26f48fa1eb050ceec75 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -3,6 +3,7 @@
 |Example|Note|
 |---|---|
 |[template](template)|Complete documented empty config|
-|[archlinux-gnome](archlinux-gnome)|An example config with archlinux and gnome|
-|[ubuntu-server](ubuntu-server)|An example config for someone's ubuntu server|
+|[archlinux-gnome](archlinux-gnome)|An example multi-user config with archlinux and gnome|
+|[ubuntu-server](ubuntu-server)|An example root-user config for ubuntu server|
+|[non-root](non-root)|An example config for machine without root access|
 
diff --git a/examples/archlinux-gnome/conf.d/basic.sh b/examples/archlinux-gnome/conf.d/basic.sh
index 211d855f501f6039d16724999d3874e42ffa6c94..1c8e83f5e1ebaba8e40547c5faedeb67a6a26f9d 100644
--- a/examples/archlinux-gnome/conf.d/basic.sh
+++ b/examples/archlinux-gnome/conf.d/basic.sh
@@ -2,11 +2,11 @@ lc_assert_user_is root
 
 desktop_related_setup () {
     # to install & enable gnome
-    pacman -Sy --noconfirm gnome networkmanager power-profiles-daemon nextcloud-client firefox
+    pacman -Sy --noconfirm gnome networkmanager power-profiles-daemon
     systemctl enable gdm NetworkManager power-profiles-daemon
 
     # more customization...
-    pacman -Sy --needed --noconfirm base-devel nextcloud-client firefox telegram-desktop docker shadowsocks-rust v2ray proxychains xclip adobe-source-han-sans-cn-fonts      pcsclite ccid    git inetutils wget ttf-fira-code htop tmux dos2unix nfs-utils python-pip gnome-tweaks fcitx5-im man-db man-pages  kolourpaint breeze
+    pacman -Sy --needed --noconfirm base-devel telegram-desktop docker shadowsocks-rust v2ray proxychains xclip adobe-source-han-sans-cn-fonts      pcsclite ccid    git inetutils wget ttf-fira-code htop tmux dos2unix nfs-utils fcitx5-im firefox
     pacman -Sy --needed --noconfirm recolic-aur/gnome-terminal-transparency recolic-aur/oreo-cursors-git
     
     echo '
diff --git a/examples/archlinux-gnome/conf.d/desktop.sh b/examples/archlinux-gnome/conf.d/desktop.sh
index 5b7acdf10ab27ead4832e31b8b1669fc8df6e108..3bbeff0b8255603120a1f4c71463de9e09d0b662 100644
--- a/examples/archlinux-gnome/conf.d/desktop.sh
+++ b/examples/archlinux-gnome/conf.d/desktop.sh
@@ -1,5 +1,9 @@
 lc_assert_user_is_not root
 
+lc_fsmap files/config.fish $HOME/.config/fish/config.fish
+lc_fsmap files/ssh_config $HOME/.ssh/config
+lc_fsmap files/vimrc $HOME/.vimrc
+
 config_gsettings () {
     echo "## gnome desktop config"
     gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-ac-type nothing
@@ -23,8 +27,6 @@ config_gsettings () {
     gsettings set org.gnome.desktop.wm.keybindings switch-windows-backward "['<Primary><Shift>Tab']"
     gsettings set org.gnome.desktop.wm.keybindings switch-applications "['<Super>Tab', '<Alt>Tab']"
     gsettings set org.gnome.desktop.wm.keybindings switch-applications-backward "['<Shift><Super>Tab', '<Shift><Alt>Tab']"
-
-   
 }
 
 lc_init () {
@@ -34,8 +36,6 @@ lc_init () {
     config_gsettings
 }
 
-lc_fsmap $HOME/sh/mybin /usr/mybin
-
 lc_startup () {
     firefox_config='
 user_pref("browser.tabs.tabmanager.enabled", false);
@@ -50,5 +50,7 @@ user_pref("browser.tabs.hoverPreview.enabled", false);'
 
 lc_login () {
     # echo _:1 | bash /usr/mybin/unlock_keyrings
+    echo "$(date) test-only: lc_login called" >> /tmp/note
+    chmod 777 /tmp/note
 }
 
diff --git a/examples/archlinux-gnome/linuxconf.wrapper b/examples/archlinux-gnome/linuxconf.wrapper
index dc8517c146d3db9bdb08d24793ced65482ebd809..e1cadf45bd426b1cbb3c671011b27f5afb02bf88 100755
--- a/examples/archlinux-gnome/linuxconf.wrapper
+++ b/examples/archlinux-gnome/linuxconf.wrapper
@@ -1,4 +1,9 @@
 #!/bin/bash
 
-TODO: this is a wrapper. it should download & install real linuxconf binary.
+if [ ! -f /usr/bin/linuxconf ]; then
+    curl "https://git.recolic.net/root/linuxconf/-/raw/master/linuxconf?ref_type=heads" -o /usr/bin/linuxconf ||
+        ! echo "Unable to download linuxconf executable." || exit 1
+    chmod +x /usr/bin/linuxconf
+fi
 
+/usr/bin/linuxconf "$@"
diff --git a/examples/template/linuxconf.wrapper b/examples/template/linuxconf.wrapper
index dc8517c146d3db9bdb08d24793ced65482ebd809..e1cadf45bd426b1cbb3c671011b27f5afb02bf88 100755
--- a/examples/template/linuxconf.wrapper
+++ b/examples/template/linuxconf.wrapper
@@ -1,4 +1,9 @@
 #!/bin/bash
 
-TODO: this is a wrapper. it should download & install real linuxconf binary.
+if [ ! -f /usr/bin/linuxconf ]; then
+    curl "https://git.recolic.net/root/linuxconf/-/raw/master/linuxconf?ref_type=heads" -o /usr/bin/linuxconf ||
+        ! echo "Unable to download linuxconf executable." || exit 1
+    chmod +x /usr/bin/linuxconf
+fi
 
+/usr/bin/linuxconf "$@"
diff --git a/examples/template/masterconf.sh b/examples/template/masterconf.sh
index dc0425abe4530aac58be20c7db5c3b4fd0476aff..66179eecfedade7ee628354d3f33665212ea8cda 100644
--- a/examples/template/masterconf.sh
+++ b/examples/template/masterconf.sh
@@ -37,8 +37,10 @@ function lc_startup () {
 }
 
 function lc_login () {
-    # warning: less useful. happens again if user logout/login again.
-    # (no plan to support in first ver)
+    # Your desktop environment must implement "XDG autostart". Ref: https://wiki.archlinux.org/title/XDG_Autostart
+    # Otherwise... Use lc_init to your task into wherever u'd like.
+    #
+    # Warning: Could be called multiple times if user logout/login again.
     lc_login_is_x11?
 }
 
diff --git a/linuxconf b/linuxconf
index 635fad56b4400a95748bbef28a15f25860dd4fe2..81789c14e8ef724db38d891768e6c72d3ea772ba 100755
--- a/linuxconf
+++ b/linuxconf
@@ -109,7 +109,7 @@ function lci_overwrite_conf () {
         [[ "$oldpath" = "$newpath" ]] && return 0
     fi
 
-    echo "masterconf=$newpath" | tee "$fname" > /dev/null || err "lci_overwrite_conf: unable to create $fname" || return $?
+    echo -e "#autogenerated config, could be overwritten without warning.\nmasterconf=$newpath" | tee "$fname" > /dev/null || err "lci_overwrite_conf: unable to create $fname" || return $?
     chmod ugo+rw "$fname"
 }
 
@@ -122,18 +122,22 @@ function lci_register () {
     lci_overwrite_conf /etc/linuxconf.conf "$confpath" || die "lci_register cannot write new conf"
 }
 
-function lci_init_if_needed () {
-    local uname="$(whoami)"
-    lci_state_file_contains /etc/linuxconf.conf init_done "$uname" && return 0
-    echo2 "RDEBUG: init needed for $uname"
-
-    # call lc_init()
-    export LCI_SUBSHELL_OP=lc_init
+function lci_call () {
+    # calls an lc function in masterconf (and included subconf)
+    [ "$1" = "" ] && die "logic error: lci_call without arg"
+    export LCI_SUBSHELL_OP="$1"
     local masterconf="$(lci_conf_get_masterconf_path /etc/linuxconf.conf)" || die "unable to call lc_init. Cannot read masterconf path from /etc/linuxconf.conf"
     local workdir="$(dirname "$masterconf")"
     cd "$workdir" || die "unable to enter config directory: $workdir"
     lc_include "$masterconf"
     export LCI_SUBSHELL_OP=__lc_operation_undefined
+}
+
+function lci_init_if_needed () {
+    local uname="$(whoami)"
+    lci_state_file_contains /etc/linuxconf.conf init_done "$uname" && return 0
+
+    lci_call lc_init
     
     lci_state_file_append /etc/linuxconf.conf init_done "$uname" || die "lc_init functions succeeded, but unable to update /etc/linuxconf.conf"
 }
@@ -148,14 +152,8 @@ function lci_startup_if_needed () {
 
     lci_state_file_contains $state_file startup_done "$uname" && return 0
 
-    # call lc_startup()
-    export LCI_SUBSHELL_OP=lc_startup
-    local masterconf="$(lci_conf_get_masterconf_path /etc/linuxconf.conf)" || die "unable to call lc_init. Cannot read masterconf path from /etc/linuxconf.conf"
-    local workdir="$(dirname "$masterconf")"
-    cd "$workdir" || die "unable to enter config directory: $workdir"
-    lc_include "$masterconf"
-    export LCI_SUBSHELL_OP=__lc_operation_undefined
-    
+    lci_call lc_startup
+   
     lci_state_file_append $state_file startup_done "$uname" || die "lc_startup functions succeeded, but unable to update $state_file"
 }
 
@@ -166,21 +164,81 @@ function lci_usage () {
     exit 1
 }
 
-lci_version=0.1.0
+# All install hook function should:
+#   be safe for duplicate call,
+#   works for root / non-root,
+#   clears non-root alternative when called with root.
+function lci_install_startup_hook () {
+    # if is root:
+    #   if root config exists, overwrite it
+    #   if non-root config exists, clear it
+    # else:
+    #   if root config exists, return
+    #   if non-root config exists, overwrite it
+    local service_file=W1VuaXRdCkRlc2NyaXB0aW9uPWxpbnV4Y29uZiBzdGFydHVwIGhvb2sgYXMgcm9vdCB1c2VyCkFmdGVyPW5ldHdvcmsudGFyZ2V0CltTZXJ2aWNlXQpUeXBlPW9uZXNob3QKRXhlY1N0YXJ0PS91c3IvYmluL2xpbnV4Y29uZiBfc3RhcnR1cF9hbGwKUmVtYWluQWZ0ZXJFeGl0PXllcwpbSW5zdGFsbF0KV2FudGVkQnk9bXVsdGktdXNlci50YXJnZXQK
+
+    if [[ "$(whoami)" = root ]]; then
+        if command -v systemctl > /dev/null ; then
+            echo "$service_file" | base64 -d > /etc/systemd/system/lc-hook.service &&
+            systemctl daemon-reload &&
+            systemctl enable lc-hook.service
+        elif [ -f /etc/rc.local ]; then
+            echo '/usr/bin/linuxconf _startup_all' >> /usr/bin/linuxconf _startup_all
+        else
+            err "neither systemd nor /etc/rc.local available."
+        fi || return $?
+        #TODO: check if any user installed non-root startup hook
+    else
+        [ -f /etc/systemd/system/lc-hook.service ] && return 0
+        grep '/usr/bin/linuxconf _startup_all' /etc/rc.local >/dev/null 2>&1 && return 0
+        # TODO: install non-root startup hook
+        die "non-root startup hook not supported yet"
+    fi
+}
+function lci_install_login_hook () {
+    # https://wiki.archlinux.org/title/XDG_Autostart
+    [ "$XDG_CONFIG_DIRS" = "" ] && local XDG_CONFIG_DIRS=/etc/xdg
+    [ "$XDG_CONFIG_HOME" = "" ] && local XDG_CONFIG_HOME="$HOME/.config"
+    local desktop_file=W0Rlc2t0b3AgRW50cnldClR5cGU9QXBwbGljYXRpb24KTmFtZT1sY194ZGdfbG9naW4KRXhlYz0vdXNyL2Jpbi9saW51eGNvbmYgX3hkZ19sb2dpbgpYLUdOT01FLUF1dG9zdGFydC1lbmFibGVkPXRydWUK
+
+    # if is root:
+    #   if root config exists, overwrite it
+    #   if non-root config exists, clear it
+    # else:
+    #   if root config exists, return
+    #   if non-root config exists, overwrite it
+
+    if [[ "$(whoami)" = root ]]; then
+        mkdir -p "$XDG_CONFIG_DIRS/autostart" &&
+        echo "$desktop_file" | base64 -d > "$XDG_CONFIG_DIRS/autostart/lc-hook.desktop" || return $?
+
+        for uconfig in /home/*/.config/autostart/lc-hook.desktop "$XDG_CONFIG_HOME/autostart/lc-hook.desktop"; do
+            [ -f "$uconfig" ] && rm -f "$uconfig"
+        done
+    else
+        [ -f "$XDG_CONFIG_DIRS/autostart/lc-hook.desktop" ] && return 0
+
+        mkdir -p "$XDG_CONFIG_HOME/autostart" &&
+        echo "$desktop_file" | base64 -d > "$XDG_CONFIG_HOME/autostart/lc-hook.desktop" || return $?
+    fi
+}
+
+lci_version=0.1.1
 subcommand="$1"
 if [[ "$subcommand" != register ]] && [[ "$subcommand" != "" ]]; then
     [[ ! -f /etc/linuxconf.conf ]] && die "Please run '$0 register <path/to/masterconf.sh>' at least once"
 fi
 if [[ "$subcommand" = register ]]; then
     lci_register "$2"
+    lci_install_startup_hook || die "failed to install on_startup hook"
+    lci_install_login_hook || die "failed to install on_login hook"
     lci_init_if_needed
     lci_startup_if_needed
 elif [[ "$subcommand" = _cron ]]; then
     # TODO: implement cron. with crontab or same systemd service?
     :
 elif [[ "$subcommand" = _startup ]]; then
-    lci_startup_if_needed # TODO: no need to check "if needed"
-    # TODO: for current user, check if desktop environment 'autostart' dir exists. If so, update autostart/linuxsync_on_login.desktop
+    lci_startup_if_needed
 elif [[ "$subcommand" = _startup_all ]]; then
     # systemd should call this service as root, and it will spawn subprocess for all users with sudo
     [[ "$(whoami)" != root ]] && die "$0 _startup_all started as non-root. Exit because sudo might fail."
@@ -189,9 +247,8 @@ elif [[ "$subcommand" = _startup_all ]]; then
         echo2 "Spawn subprocess '$0 _startup' as user $uname..."
         sudo -u "$uname" "$0" _startup
     done
-elif [[ "$subcommand" = _de_login ]]; then
-    # TODO: call lc_login. no need to check "if needed"
-    :
+elif [[ "$subcommand" = _xdg_login ]]; then
+    lci_call lc_login
 else
     lci_usage
     exit