11#! /usr/bin/env bash
22#
33# my-unicorn-installer.sh
4- # User‑level installer & updater for "my-unicorn"
5- # Handles venv creation, wrapper install, and shell rc setup
64# ------------------------------------------------
5+ # User-level installer & updater for "my-unicorn"
6+ # - Copies project files into XDG user data directory (~/.local/share)
7+ # - Creates/updates a Python virtual environment
8+ # - Installs a wrapper script in ~/.local/bin for easy command execution
9+ # - Ensures ~/.local/bin is in PATH for both bash and zsh shells
10+ #
11+ # Usage:
12+ # ./my-unicorn-installer.sh install # Install or reinstall
13+ # ./my-unicorn-installer.sh update # Update venv without touching files
14+ #
15+ # Exit immediately if:
16+ # - a command exits with a non-zero status (`-e`)
17+ # - an unset variable is used (`-u`)
18+ # - a pipeline fails anywhere (`-o pipefail`)
719set -euo pipefail
820
9- # -- Configuration --
21+ # -- Configuration -----------------------------------------------------------
22+
23+ # Where user-specific data should be stored
1024XDG_DATA_HOME=" ${XDG_DATA_HOME:- $HOME / .local/ share} "
25+
26+ # Final install location of our project
1127INSTALL_DIR=" $XDG_DATA_HOME /my-unicorn"
28+
29+ # Virtual environment directory inside install dir
1230VENV_DIR=" $INSTALL_DIR /venv"
31+
32+ # Virtual environment bin folder (where python/pip are located)
1333BIN_DIR=" $VENV_DIR /bin"
34+
35+ # Source of our wrapper script (inside repo)
1436WRAPPER_SRC=" $INSTALL_DIR /scripts/venv-wrapper.bash"
37+
38+ # Destination of wrapper script in user's PATH
1539WRAPPER_DST=" $HOME /.local/bin/my-unicorn"
1640
41+ # The line to add to shell rc files to ensure ~/.local/bin is in PATH
1742EXPORT_LINE=' export PATH="$HOME/.local/bin:$PATH"'
1843
19- # -- Helpers --
44+ # -- Helper functions ------------------------------------------------------ --
2045
46+ # Get absolute directory path of currently running script (resolves symlinks)
2147script_dir () {
22- # Resolve path to the currently running script
2348 local src=" ${BASH_SOURCE[0]} "
2449 while [ -h " $src " ]; do
2550 src=" $( readlink " $src " ) "
2651 done
2752 dirname " $src "
2853}
2954
55+ # Detect which shell rc file to update for PATH (bash or zsh)
3056detect_rc_file () {
3157 local rc user_shell
3258 user_shell=" $( basename " ${SHELL:- } " ) "
@@ -42,27 +68,61 @@ detect_rc_file() {
4268 rc=" $HOME /.bashrc"
4369 ;;
4470 esac
71+ # Create the file if it doesn't exist
4572 [[ ! -f " $rc " ]] && touch " $rc "
4673 echo " $rc "
4774}
4875
49- ensure_path_in_rc () {
76+ # Ensure PATH line is present in a given file
77+ # $1 = rc file path
78+ # $2 = position (optional: prepend|append, default append)
79+ ensure_path_in_file () {
5080 local rc_file=" $1 "
81+ local position=" ${2:- append} "
82+
83+ [[ ! -f " $rc_file " ]] && touch " $rc_file "
84+
5185 if ! grep -Fxq " $EXPORT_LINE " " $rc_file " ; then
52- printf " \n# Added by my-unicorn installer\n%s\n" " $EXPORT_LINE " >> " $rc_file "
53- echo " ✅ Added '$HOME /.local/bin' to PATH in $rc_file "
86+ if [[ " $position " == " prepend" ]]; then
87+ # Put PATH export at the very top
88+ {
89+ echo " # Added by my-unicorn installer"
90+ echo " $EXPORT_LINE "
91+ echo
92+ cat " $rc_file "
93+ } > " $rc_file .tmp" && mv " $rc_file .tmp" " $rc_file "
94+ echo " Added PATH to top of $rc_file "
95+ else
96+ # Add PATH export at the bottom
97+ printf " \n# Added by my-unicorn installer\n$EXPORT_LINE \n" >> " $rc_file "
98+ echo " Added PATH to bottom of $rc_file "
99+ fi
54100 else
55101 echo " ℹ️ PATH already configured in $rc_file "
56- fi
102+ fi
57103}
58104
105+ # Ensure PATH is set for both bash and zsh shells
106+ # - Prepend for bashrc so it’s loaded before anything else
107+ # - Append for zshrc so it runs after bashrc in login shell chains
108+ ensure_path_for_shells () {
109+ local bashrc=" $HOME /.bashrc"
110+ local zshrc=" $HOME /.zshrc"
111+
112+ [[ -f " $HOME /.config/zsh/.zshrc" ]] && zshrc=" $HOME /.config/zsh/.zshrc"
113+
114+ ensure_path_in_file " $bashrc " prepend
115+ ensure_path_in_file " $zshrc " append
116+ }
117+
118+ # Copy source files to install directory
59119copy_source_to_install_dir () {
60120 echo " 📁 Copying source files to $INSTALL_DIR ..."
61121 local src_dir
62122 src_dir=" $( script_dir) "
63123 mkdir -p " $INSTALL_DIR "
64124
65- # List of directories/ files to copy
125+ # Source files to copy
66126 for item in my_unicorn scripts pyproject.toml " $( basename " $0 " ) " ; do
67127 local src_path=" $src_dir /$item "
68128 local dst_path=" $INSTALL_DIR /$item "
@@ -79,6 +139,7 @@ copy_source_to_install_dir() {
79139 done
80140}
81141
142+ # Create or update virtual environment and install package in editable mode
82143setup_venv () {
83144 echo " 🐍 Creating/updating virtual environment in $VENV_DIR ..."
84145 python3 -m venv " $VENV_DIR "
@@ -88,35 +149,38 @@ setup_venv() {
88149 python3 -m pip install -e " $INSTALL_DIR "
89150}
90151
152+ # Install wrapper script into ~/.local/bin so `my-unicorn` works globally
91153install_wrapper () {
92154 echo " 🔧 Installing wrapper to $WRAPPER_DST ..."
93155 mkdir -p " $( dirname " $WRAPPER_DST " ) "
94156 cp " $WRAPPER_SRC " " $WRAPPER_DST "
95157 chmod +x " $WRAPPER_DST "
96158}
97159
160+ # Full installation process
98161install_my_unicorn () {
99- echo " === Installing my‑ unicorn ==="
162+ echo " === Installing my- unicorn ==="
100163 copy_source_to_install_dir
101164 setup_venv
102165 install_wrapper
103-
166+ ensure_path_for_shells
167+
104168 local rc
105- rc=" $( detect_rc_file) "
106- ensure_path_in_rc " $rc "
169+ rc=$( detect_rc_file)
107170
108171 echo " ✅ Installation complete."
109172 echo " Restart your shell or run 'source $rc ' to apply PATH."
110173 echo " Run 'my-unicorn --help' to get started."
111174}
112175
176+ # Update only the virtual environment, keep existing files
113177update_my_unicorn () {
114- echo " === Updating my‑ unicorn ==="
178+ echo " === Updating my- unicorn ==="
115179 setup_venv
116180 echo " ✅ Update complete."
117181}
118182
119- # -- Entry point --
183+ # -- Entry point -------------------------------------------------------------
120184case " ${1-} " in
121185 install|" " ) install_my_unicorn ;;
122186 update) update_my_unicorn ;;
130194 exit 1
131195 ;;
132196esac
133-
0 commit comments