From 017670583d17197561143c9e22e322a9beb1378b Mon Sep 17 00:00:00 2001
From: Recolic <git@me.recolic.net>
Date: Sun, 23 Mar 2025 05:07:49 +0000
Subject: [PATCH] Dev 0.1.1

---
 examples/README.md                         |   5 +-
 examples/archlinux-gnome/conf.d/basic.sh   |   4 +-
 examples/archlinux-gnome/conf.d/desktop.sh |  10 +-
 examples/archlinux-gnome/linuxconf.wrapper |   7 +-
 examples/template/linuxconf.wrapper        |   7 +-
 examples/template/masterconf.sh            |   6 +-
 linuxconf                                  | 101 ++++++++++++++++-----
 7 files changed, 106 insertions(+), 34 deletions(-)

diff --git a/examples/README.md b/examples/README.md
index be74091..4096417 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 211d855..1c8e83f 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 5b7acdf..3bbeff0 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 dc8517c..e1cadf4 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 dc8517c..e1cadf4 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 dc0425a..66179ee 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 635fad5..81789c1 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
-- 
GitLab