Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions .github/workflows/workstation-lampstand.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: workstation-lampstand

on:
pull_request:
paths:
- 'profiles/linux-dev/workstation-v0/bin/install-lampstand.sh'
- 'profiles/linux-dev/workstation-v0/bin/sourceos-search.sh'
- 'profiles/linux-dev/workstation-v0/manifest.yaml'
- '.github/workflows/workstation-lampstand.yml'
push:
branches:
- main
paths:
- 'profiles/linux-dev/workstation-v0/bin/install-lampstand.sh'
- 'profiles/linux-dev/workstation-v0/bin/sourceos-search.sh'
- 'profiles/linux-dev/workstation-v0/manifest.yaml'
- '.github/workflows/workstation-lampstand.yml'

jobs:
lampstand-installer-smoke:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Syntax check installer
run: |
set -euo pipefail
bash -n profiles/linux-dev/workstation-v0/bin/install-lampstand.sh
bash -n profiles/linux-dev/workstation-v0/bin/sourceos-search.sh

- name: Smoke: installer writes user service with stub pipx
run: |
set -euo pipefail
tmp_home=$(mktemp -d)
tmp_bin=$(mktemp -d)
tmp_src=$(mktemp -d)
printf '[project]\nname = "lampstand"\n' > "$tmp_src/pyproject.toml"

cat > "$tmp_bin/pipx" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
test "${1:-}" = install
mkdir -p "$HOME/.local/bin"
cat > "$HOME/.local/bin/lampstandd" <<'EOS'
#!/usr/bin/env bash
exit 0
EOS
chmod +x "$HOME/.local/bin/lampstandd"
EOF
chmod +x "$tmp_bin/pipx"

HOME="$tmp_home" \
XDG_CONFIG_HOME="$tmp_home/.config" \
SOURCEOS_LAMPSTAND_SRC="$tmp_src" \
PATH="$tmp_bin:$PATH" \
bash profiles/linux-dev/workstation-v0/bin/install-lampstand.sh

svc="$tmp_home/.config/systemd/user/sourceos-lampstand.service"
test -f "$svc"
grep -F 'lampstandd --root %h --rpc unixjson' "$svc" >/dev/null
26 changes: 26 additions & 0 deletions packages/search/lampstand.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{ lib, python3Packages, lampstand-src }:

python3Packages.buildPythonApplication {
pname = "lampstand";
version = "0.0.0";
src = lampstand-src;

pyproject = true;
build-system = with python3Packages; [
setuptools
wheel
];

pythonImportsCheck = [
"lampstand.cli"
"lampstand.lampstandd"
];

doCheck = false;

meta = {
description = "SourceOS desktop file indexing and search service";
license = lib.licenses.mit;
mainProgram = "lampstand";
};
}
93 changes: 93 additions & 0 deletions profiles/linux-dev/workstation-v0/bin/install-lampstand.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#!/usr/bin/env bash
set -euo pipefail

# Install Lampstand for workstation-v0 and optionally enable a user service.
# Strategy:
# - prefer a local checkout at ~/dev/lampstand (or SOURCEOS_LAMPSTAND_SRC)
# - otherwise install from the public git URL via pipx
# - write a user systemd unit for lampstandd if the daemon entrypoint exists

info(){ printf "INFO: %s\n" "$*" >&2; }
warn(){ printf "WARN: %s\n" "$*" >&2; }
err(){ printf "ERROR: %s\n" "$*" >&2; }

have(){ command -v "$1" >/dev/null 2>&1; }

lampstand_src(){
local local_src="${SOURCEOS_LAMPSTAND_SRC:-$HOME/dev/lampstand}"
if [[ -f "$local_src/pyproject.toml" ]]; then
printf '%s\n' "$local_src"
return
fi
printf '%s\n' 'git+https://github.com/SocioProphet/lampstand.git'
}

ensure_pipx(){
if have pipx; then
return 0
fi
if have brew; then
info "Installing pipx via brew (best-effort)"
brew install pipx || true
fi
have pipx
}

install_lampstand(){
local src
src="$(lampstand_src)"
info "Installing Lampstand via pipx from: $src"
pipx install --force "$src"
}

write_user_service(){
local bin="$HOME/.local/bin/lampstandd"
if [[ ! -x "$bin" ]]; then
warn "lampstandd not found at $bin; skipping user service"
return 0
fi

local svc_dir="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user"
local svc="$svc_dir/sourceos-lampstand.service"
mkdir -p "$svc_dir"

cat > "$svc" <<'EOF'
[Unit]
Description=SourceOS Lampstand search daemon
After=graphical-session.target
PartOf=graphical-session.target

[Service]
Type=simple
ExecStart=%h/.local/bin/lampstandd --root %h --rpc unixjson
Restart=on-failure
RestartSec=2

[Install]
WantedBy=default.target
EOF

info "wrote user service: $svc"

if have systemctl; then
systemctl --user daemon-reload || true
systemctl --user enable sourceos-lampstand.service || true
systemctl --user restart sourceos-lampstand.service || true
fi
}

main(){
if ! ensure_pipx; then
warn "pipx is not available; skipping Lampstand install"
exit 0
fi

install_lampstand || {
warn "Lampstand install failed (non-fatal)"
exit 0
}

write_user_service || warn "Lampstand user service setup failed (non-fatal)"
}

main "$@"
123 changes: 123 additions & 0 deletions profiles/linux-dev/workstation-v0/bin/sourceos-search.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#!/usr/bin/env bash
set -euo pipefail

# Thin Lampstand-backed local search helper for SourceOS workstation-v0.
# Keeps the launcher as an action bus and delegates file search to Lampstand.

err(){ printf "ERROR: %s\n" "$*" >&2; }
info(){ printf "INFO: %s\n" "$*" >&2; }
warn(){ printf "WARN: %s\n" "$*" >&2; }

have(){ command -v "$1" >/dev/null 2>&1; }

cache_dir(){
echo "${XDG_CACHE_HOME:-$HOME/.cache}/sourceos"
}

open_file(){
local p=$1
if have xdg-open; then
xdg-open "$p" >/dev/null 2>&1 || true
elif have open; then
open "$p" >/dev/null 2>&1 || true
else
warn "no opener found; wrote: $p"
fi
}

run_lampstand(){
if have lampstand; then
lampstand "$@"
return
fi
if have python3 && python3 -c 'import lampstand.cli' >/dev/null 2>&1; then
python3 -m lampstand.cli "$@"
return
fi
err "Lampstand is not available on PATH and the Python module is not importable."
return 127
}

SEARCH_LIMIT=20
SEARCH_SNIPPET=0
SEARCH_PROMPT=0
SEARCH_OPEN=0
SEARCH_WRITE_PATH=""
QUERY_PARTS=()

while [[ $# -gt 0 ]]; do
case "$1" in
--limit)
shift
[[ $# -gt 0 ]] || { err "--limit requires a value"; exit 2; }
SEARCH_LIMIT="$1"
shift
;;
--snippet)
SEARCH_SNIPPET=1
shift
;;
--prompt)
SEARCH_PROMPT=1
shift
;;
--open)
SEARCH_OPEN=1
shift
;;
--write)
shift
[[ $# -gt 0 ]] || { err "--write requires a path"; exit 2; }
SEARCH_WRITE_PATH="$1"
shift
;;
-h|--help)
cat <<'EOF'
Usage: sourceos-search.sh [--limit N] [--snippet] [--prompt] [--open|--write <path>] [query...]
EOF
exit 0
;;
*)
QUERY_PARTS+=("$1")
shift
;;
esac
done

if [[ ${#QUERY_PARTS[@]} -eq 0 && "$SEARCH_PROMPT" == "1" ]]; then
if have fuzzel; then
q="$(printf '\n' | fuzzel --dmenu --prompt 'search> ' || true)"
elif have wofi; then
q="$(printf '\n' | wofi --dmenu --prompt 'search> ' || true)"
elif have rofi; then
q="$(printf '\n' | rofi -dmenu -p 'search> ' || true)"
elif have fzf; then
q="$(printf '\n' | fzf --print-query --prompt='search> ' | head -n1 || true)"
else
err "no prompt-capable launcher found"
exit 2
fi
[[ -n "$q" ]] && QUERY_PARTS+=("$q")
fi

query="${QUERY_PARTS[*]:-}"
[[ -n "$query" ]] || { err "search requires a query or --prompt"; exit 2; }

args=(query "$query" --limit "$SEARCH_LIMIT")
[[ "$SEARCH_SNIPPET" == "1" ]] && args+=(--snippet)

if [[ "$SEARCH_OPEN" == "1" || -n "$SEARCH_WRITE_PATH" ]]; then
path="$SEARCH_WRITE_PATH"
if [[ -z "$path" ]]; then
mkdir -p "$(cache_dir)"
path="$(cache_dir)/search.txt"
fi
out="$(run_lampstand "${args[@]}")"
mkdir -p "$(dirname "$path")"
printf '%s\n' "$out" > "$path"
info "wrote: $path"
[[ "$SEARCH_OPEN" == "1" ]] && open_file "$path"
exit 0
fi

run_lampstand "${args[@]}"
8 changes: 8 additions & 0 deletions profiles/linux-dev/workstation-v0/manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ spec:
- kondo
- glow
- hyperfine
- pipx
- rclone
- minio-mc
- rsync
Expand Down Expand Up @@ -90,3 +91,10 @@ spec:
- rofi
trigger: "<Super>space"
command: "sourceos palette"
search:
file_backend: lampstand
invariant: no_redundant_file_search
authority: sourceos-search
notes:
- "Lampstand is the Linux-native file search backend."
- "The launcher remains an action bus and must not perform a second file-search pass."