aboutsummaryrefslogtreecommitdiffstats
path: root/packaging/install.sh
blob: 657085a15e066dff7d40c25dd13f79b7babd316a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#!/usr/bin/env bash
# install.sh -- installs sl0ppi on this machine. Ships inside the tarball.
#
#   ./install.sh [--prefix DIR] [--force-config] [--dry-run] [--uninstall]
#
# Defaults to a per-user install under ~/.local, touching nothing outside:
#
#   $PREFIX/bin/{sl0ppi,sl0ppij,pi,pir}  (sl0ppi and pi run on the bundled bun)
#   $PREFIX/libexec/sl0ppi/            bundled bun + pi
#   $PREFIX/share/sl0ppi/              config templates, layouts, themes, plugins
#   ~/.config/sl0ppij/                 live config (never clobbered without --force-config)
#   ~/.pi/agent/extensions/            pi extensions
#
# The bundle is self-contained: no system node, npm or rust needed.
set -euo pipefail

PREFIX="${PREFIX:-$HOME/.local}"
CONFIG_DIR="${SL0PPIJ_CONFIG_DIR:-$HOME/.config/sl0ppij}"
PI_EXT_DIR="${PI_EXT_DIR:-$HOME/.pi/agent/extensions}"
force_config=0
dry_run=0
uninstall=0

while [ $# -gt 0 ]; do
    case "$1" in
        --prefix) PREFIX="$2"; shift 2 ;;
        --prefix=*) PREFIX="${1#*=}"; shift ;;
        --force-config) force_config=1; shift ;;
        -n|--dry-run) dry_run=1; shift ;;
        --uninstall) uninstall=1; shift ;;
        -h|--help) sed -n '2,17p' "$0" | sed 's/^# \?//'; exit 0 ;;
        *) echo "install.sh: unknown argument '$1'" >&2; exit 2 ;;
    esac
done

SRC="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
VERSION="$(cat "$SRC/VERSION")"
LIBEXEC="$PREFIX/libexec/sl0ppi"
SHARE="$PREFIX/share/sl0ppi"

say()  { printf '\033[36m==>\033[0m %s\n' "$*"; }
warn() { printf '\033[33mwarn:\033[0m %s\n' "$*" >&2; }
run()  { if [ "$dry_run" = 1 ]; then echo "  would: $*"; else "$@"; fi; }

if [ "$uninstall" = 1 ]; then
    say "removing sl0ppi from $PREFIX (config and pi extensions are left alone)"
    for f in sl0ppi sl0ppij pi pir; do run rm -f "$PREFIX/bin/$f"; done
    run rm -rf "$LIBEXEC" "$SHARE"
    say "done. Config still at $CONFIG_DIR"
    exit 0
fi

say "installing sl0ppi $VERSION -> $PREFIX"

# --- binaries and payload --------------------------------------------------
run mkdir -p "$PREFIX/bin" "$LIBEXEC" "$SHARE"
run install -m755 "$SRC/bin/sl0ppij" "$PREFIX/bin/sl0ppij"
run install -m755 "$SRC/bin/pir"     "$PREFIX/bin/pir"
run install -m755 "$SRC/libexec/bun" "$LIBEXEC/bun"
run install -m644 "$SRC/libexec/sl0ppi.js" "$LIBEXEC/sl0ppi.js"
run rm -rf "$LIBEXEC/pi"
run cp -a "$SRC/libexec/pi" "$LIBEXEC/pi"
run rm -rf "$SHARE/sl0ppij" "$SHARE/pi-ext"
run cp -a "$SRC/share/sl0ppij" "$SRC/share/pi-ext" "$SHARE/"
run install -m644 "$SRC/MANIFEST" "$SHARE/MANIFEST"

# pi and sl0ppi are generated wrappers: bundled runtime, no system node.
if [ "$dry_run" = 1 ]; then
    echo "  would: generate $PREFIX/bin/pi and $PREFIX/bin/sl0ppi wrappers"
else
    cat > "$PREFIX/bin/pi" <<EOF
#!/usr/bin/env bash
# generated by sl0ppi $VERSION -- runs the bundled pi on the bundled bun
exec "$LIBEXEC/bun" "$LIBEXEC/pi/@earendil-works/pi-coding-agent/dist/cli.js" "\$@"
EOF
    chmod +x "$PREFIX/bin/pi"

    # SL0PPI_BIN is pinned to the sl0ppij we just installed, so the orchestrator
    # never drives some other zellij that happens to come first on PATH.
    cat > "$PREFIX/bin/sl0ppi" <<EOF
#!/usr/bin/env bash
# generated by sl0ppi $VERSION -- the orchestrator, on the bundled bun
export SL0PPI_BIN="\${SL0PPI_BIN:-$PREFIX/bin/sl0ppij}"
exec "$LIBEXEC/bun" "$LIBEXEC/sl0ppi.js" "\$@"
EOF
    chmod +x "$PREFIX/bin/sl0ppi"
fi

# --- config: install, never silently clobber -------------------------------
run mkdir -p "$CONFIG_DIR/layouts" "$CONFIG_DIR/themes" "$CONFIG_DIR/plugins"

install_config_file() {
    local src="$1" dst="$2"
    if [ ! -e "$dst" ]; then
        run install -m644 "$src" "$dst"
    elif cmp -s "$src" "$dst"; then
        :
    elif [ "$force_config" = 1 ]; then
        run cp -a "$dst" "$dst.bak.$(date +%Y%m%d%H%M%S)"
        run install -m644 "$src" "$dst"
        warn "replaced $dst (backup kept)"
    else
        warn "kept your $dst -- new version at ${dst}.new (use --force-config to replace)"
        run install -m644 "$src" "$dst.new"
    fi
}

install_config_file "$SHARE/sl0ppij/config.kdl" "$CONFIG_DIR/config.kdl"
for f in "$SHARE/sl0ppij/layouts/"*.kdl; do
    install_config_file "$f" "$CONFIG_DIR/layouts/$(basename "$f")"
done
for f in "$SHARE/sl0ppij/themes/"*.kdl; do
    install_config_file "$f" "$CONFIG_DIR/themes/$(basename "$f")"
done
# plugins are build outputs, not user files: always overwrite. Install whatever
# the package contains rather than a hardcoded list -- the shipped layout needs
# zjstatus.wasm as well as pane-finder.wasm, and a missing one only shows up as
# a runtime "failed to load plugin from disk" once a session starts.
for f in "$SHARE/sl0ppij/plugins/"*.wasm; do
    [ -e "$f" ] || { warn "package contains no plugins"; break; }
    run install -m644 "$f" "$CONFIG_DIR/plugins/$(basename "$f")"
done

# --- pi extensions ---------------------------------------------------------
run mkdir -p "$PI_EXT_DIR"
for f in "$SHARE/pi-ext/"*.ts; do
    run install -m644 "$f" "$PI_EXT_DIR/$(basename "$f")"
done

# --- report ----------------------------------------------------------------
say "installed:"
echo "    $PREFIX/bin/{sl0ppi,sl0ppij,pi,pir}"
echo "    $CONFIG_DIR"
echo "    $PI_EXT_DIR"

case ":$PATH:" in
    *":$PREFIX/bin:"*) ;;
    *) warn "$PREFIX/bin is not on your PATH; add it to your shell rc" ;;
esac

if command -v zellij >/dev/null 2>&1; then
    say "note: a stock zellij is also installed. That is fine -- sl0ppij keeps its"
    echo "    own config dir, cache dir, socket dir and sessions, and the two can"
    echo "    run at the same time."
fi

say "start with: sl0ppi up      (in a directory that holds your projects)"
echo "    sl0ppi doctor            check this installation"
echo "    sl0ppi init              make the current directory a project"
echo "    sl0ppij                  the multiplexer on its own, if you prefer"
echo
echo "    On the very first launch the status bar plugin asks for permissions"
echo "    (\"... Allow? (y/n)\") -- press y once and it is remembered."