Add setup scripts

This commit is contained in:
Albert Armea 2026-01-01 13:48:17 -05:00
parent 3b1a2fb166
commit fc0c1ce51d
15 changed files with 1847 additions and 89 deletions

View file

@ -8,20 +8,6 @@ on:
env: env:
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
SYSTEM_DEPS: >-
build-essential
pkg-config
libglib2.0-dev
libgtk-4-dev
libadwaita-1-dev
libcairo2-dev
libpango1.0-dev
libgdk-pixbuf-xlib-2.0-dev
libwayland-dev
libx11-dev
libxkbcommon-dev
libgirepository1.0-dev
libgtk4-layer-shell-dev
jobs: jobs:
build: build:
@ -30,17 +16,18 @@ jobs:
container: container:
image: ubuntu:25.10 image: ubuntu:25.10
steps: steps:
- name: Install git and dependencies - name: Install git
run: | run: |
apt-get update apt-get update
apt-get install -y git curl ${{ env.SYSTEM_DEPS }} apt-get install -y git curl
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup Rust toolchain - name: Install build dependencies
run: | run: ./scripts/shepherd deps install build
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
echo "$HOME/.cargo/bin" >> $GITHUB_PATH - name: Add Rust to PATH
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Cache cargo registry and build - name: Cache cargo registry and build
uses: actions/cache@v4 uses: actions/cache@v4
@ -56,7 +43,7 @@ jobs:
- name: Build - name: Build
run: | run: |
. "$HOME/.cargo/env" . "$HOME/.cargo/env"
cargo build --all-targets ./scripts/shepherd build
test: test:
name: Test name: Test
@ -64,17 +51,18 @@ jobs:
container: container:
image: ubuntu:25.10 image: ubuntu:25.10
steps: steps:
- name: Install git and dependencies - name: Install git
run: | run: |
apt-get update apt-get update
apt-get install -y git curl ${{ env.SYSTEM_DEPS }} apt-get install -y git curl
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup Rust toolchain - name: Install build dependencies
run: | run: ./scripts/shepherd deps install build
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
echo "$HOME/.cargo/bin" >> $GITHUB_PATH - name: Add Rust to PATH
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Cache cargo registry and build - name: Cache cargo registry and build
uses: actions/cache@v4 uses: actions/cache@v4
@ -98,19 +86,23 @@ jobs:
container: container:
image: ubuntu:25.10 image: ubuntu:25.10
steps: steps:
- name: Install git and dependencies - name: Install git
run: | run: |
apt-get update apt-get update
apt-get install -y git curl ${{ env.SYSTEM_DEPS }} apt-get install -y git curl
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup Rust toolchain - name: Install build dependencies
run: ./scripts/shepherd deps install build
- name: Add clippy component
run: | run: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
. "$HOME/.cargo/env" . "$HOME/.cargo/env"
rustup component add clippy rustup component add clippy
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Add Rust to PATH
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Cache cargo registry and build - name: Cache cargo registry and build
uses: actions/cache@v4 uses: actions/cache@v4
@ -127,3 +119,21 @@ jobs:
run: | run: |
. "$HOME/.cargo/env" . "$HOME/.cargo/env"
cargo clippy --all-targets -- -D warnings cargo clippy --all-targets -- -D warnings
shellcheck:
name: ShellCheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install ShellCheck
run: |
sudo apt-get update
sudo apt-get install -y shellcheck
- name: Run ShellCheck
run: |
# SC1091: Not following sourced files (info only, safe to ignore)
shellcheck -e SC1091 scripts/shepherd scripts/dev scripts/admin
shellcheck -e SC1091 scripts/lib/*.sh
shellcheck -e SC1091 run-dev

60
run-dev
View file

@ -1,59 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Development run script - thin wrapper for shepherd dev run
# This script is kept for backwards compatibility with existing workflows (i.e. lazy devs)
set -e exec ./scripts/shepherd dev run "$@"
# Set up dev runtime directory
DEV_RUNTIME="./dev-runtime"
DATA_DIR="$DEV_RUNTIME/data"
SOCKET_PATH="$DEV_RUNTIME/shepherd.sock"
mkdir -p "$DATA_DIR"
# Kill any existing shepherd dev instances before starting
echo "Cleaning up any existing dev instances..."
pkill -f "sway -c ./sway.conf" 2>/dev/null || true
pkill -f "shepherdd" 2>/dev/null || true
pkill -f "shepherd-launcher" 2>/dev/null || true
pkill -f "shepherd-hud" 2>/dev/null || true
# Remove stale socket
rm -f "$SOCKET_PATH"
sleep 0.5
# Export environment variables for shepherd binaries
export SHEPHERD_SOCKET="$SOCKET_PATH"
export SHEPHERD_DATA_DIR="$DATA_DIR"
# Note: Since shepherdd now runs inside sway, spawned apps automatically
# use the nested compositor's display. No SHEPHERD_WAYLAND_DISPLAY override needed.
# Build all binaries
echo "Building shepherd binaries..."
cargo build
# Function to cleanup background processes on exit
cleanup() {
echo "Cleaning up..."
# Kill the nested sway - this will clean up everything inside it (including shepherdd)
if [ ! -z "$SWAY_PID" ]; then
kill $SWAY_PID 2>/dev/null || true
fi
# Also explicitly kill any shepherd processes that might have escaped
pkill -f "shepherdd" 2>/dev/null || true
pkill -f "shepherd-launcher" 2>/dev/null || true
pkill -f "shepherd-hud" 2>/dev/null || true
# Remove socket
rm -f "$SOCKET_PATH"
}
trap cleanup EXIT
# Note: shepherdd is started by sway.conf so it runs INSIDE the nested compositor.
# This ensures all spawned processes (games, apps) use the nested compositor's display.
# Start sway with the launcher and HUD
# The HUD and launcher are started by sway.conf so they run INSIDE the nested compositor
echo "Starting nested sway with shepherd-launcher..."
WLR_BACKENDS=wayland WLR_LIBINPUT_NO_DEVICES=1 sway -c ./sway.conf &
SWAY_PID=$!
# Wait for sway to exit
wait $SWAY_PID

148
scripts/README.md Normal file
View file

@ -0,0 +1,148 @@
# Shepherd Scripts System
This directory contains the unified script system for shepherd-launcher.
## Quick Reference
```sh
# Main entry point
./shepherd --help
# Dependencies
./shepherd deps print build|run|dev
./shepherd deps install build|run|dev
# Building
./shepherd build [--release]
# Development
./shepherd dev run
# Installation
./shepherd install all --user USER [--prefix PREFIX]
./shepherd install bins [--prefix PREFIX]
./shepherd install config --user USER
# Hardening
./shepherd harden apply --user USER
./shepherd harden revert --user USER
```
## Structure
```
scripts/
├── shepherd # Main CLI dispatcher
├── dev # Wrapper → shepherd dev run
├── admin # Wrapper → shepherd install/harden
├── lib/ # Shared libraries
│ ├── common.sh # Logging, error handling, sudo helpers
│ ├── deps.sh # Dependency management
│ ├── build.sh # Cargo build logic
│ ├── sway.sh # Nested sway execution
│ ├── install.sh # Installation logic
│ └── harden.sh # User hardening/unhardening
└── deps/ # Package lists
├── build.pkgs # Build-time dependencies
├── run.pkgs # Runtime dependencies
└── dev.pkgs # Development extras
```
## Design Principles
1. **Single source of truth**: All dependency lists are defined once in `deps/*.pkgs`
2. **Composable**: Each command can be called independently
3. **Reversible**: All destructive actions (hardening, installation) can be undone
4. **Shared logic**: Business logic lives in libraries, not duplicated across scripts
5. **Clear separation**: Build-only, runtime-only, and development dependencies are separate
## Usage Examples
### For Developers
```sh
# First time setup (installs system packages + Rust via rustup)
./shepherd deps install dev
./shepherd dev run
# Or use the convenience wrapper
./run-dev
```
### For CI
```sh
# Install only build dependencies (includes Rust via rustup)
./shepherd deps install build
# Build release binaries
./shepherd build --release
```
### For Production Deployment
```sh
# On a runtime-only system
sudo ./shepherd deps install run
./shepherd build --release
sudo ./shepherd install all --user kiosk --prefix /usr
# Optional: lock down the kiosk user
sudo ./shepherd harden apply --user kiosk
```
### For Package Maintainers
```sh
# Print package lists for your distro
./shepherd deps print build > build-deps.txt
./shepherd deps print run > runtime-deps.txt
# Install with custom prefix and DESTDIR
make -j$(nproc) # or equivalent
sudo DESTDIR=/tmp/staging ./shepherd install bins --prefix /usr
```
## Dependency Sets
- **build**: Packages needed to compile the Rust code (GTK, Wayland dev libs, etc.) + Rust toolchain via rustup
- **run**: Packages needed to run the compiled binaries (Sway, GTK runtime libs)
- **dev**: Union of build + run + dev-specific tools (git, gdb, strace) + Rust toolchain
The dev set is computed as the union of all three package lists, automatically deduplicated.
## Hardening
The hardening system makes reversible changes to restrict a user to kiosk mode:
```sh
# Apply hardening
sudo ./shepherd harden apply --user kiosk
# Check status
sudo ./shepherd harden status --user kiosk
# Revert all changes
sudo ./shepherd harden revert --user kiosk
```
All changes are tracked in `/var/lib/shepherdd/hardening/<user>/` for rollback.
Applied restrictions:
- SSH access denied
- Console (TTY) login restricted
- Sudo access denied
- Shell restricted to Sway sessions only
- Home directory permissions secured
## Adding New Dependencies
Edit the appropriate package list in `deps/`:
- `deps/build.pkgs` - Build-time dependencies
- `deps/run.pkgs` - Runtime dependencies
- `deps/dev.pkgs` - Developer tools
Format: One package per line, `#` for comments.
The CI workflow will automatically use these lists.

42
scripts/admin Executable file
View file

@ -0,0 +1,42 @@
#!/usr/bin/env bash
# Admin wrapper - convenience script for administrative tasks
# Provides quick access to install and harden commands
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
usage() {
cat <<EOF
admin - Administrative wrapper for shepherd
Usage: admin <command> [options]
This is a convenience wrapper. All commands are equivalent to:
shepherd install ...
shepherd harden ...
Commands:
install <subcommand> Install shepherd components
harden <subcommand> Manage user hardening
Examples:
admin install all --user kiosk
admin harden apply --user kiosk
admin harden revert --user kiosk
Run 'shepherd install help' or 'shepherd harden help' for details.
EOF
}
case "${1:-}" in
install|harden)
exec "$SCRIPT_DIR/shepherd" "$@"
;;
-h|--help|help|"")
usage
;;
*)
echo "Unknown command: $1" >&2
echo "Run 'admin help' for usage." >&2
exit 1
;;
esac

28
scripts/deps/build.pkgs Normal file
View file

@ -0,0 +1,28 @@
# Build-time system packages for shepherd-launcher
# These are required to compile the Rust code
# One package per line, comments start with #
# Core build tools
build-essential
pkg-config
# GLib/GTK development libraries
libglib2.0-dev
libgtk-4-dev
libadwaita-1-dev
libcairo2-dev
libpango1.0-dev
libgdk-pixbuf-xlib-2.0-dev
# Wayland development libraries
libwayland-dev
libxkbcommon-dev
# X11 (for XWayland support)
libx11-dev
# GObject introspection
libgirepository1.0-dev
# Layer shell for HUD overlay
libgtk4-layer-shell-dev

11
scripts/deps/dev.pkgs Normal file
View file

@ -0,0 +1,11 @@
# Developer-only packages for shepherd-launcher
# These are extras useful during development
# The full dev set is: build.pkgs + run.pkgs + dev.pkgs
# One package per line, comments start with #
# Git for version control
git
# Useful debugging tools
strace
gdb

14
scripts/deps/run.pkgs Normal file
View file

@ -0,0 +1,14 @@
# Runtime system packages for shepherd-launcher
# These are required to run the compiled binaries
# One package per line, comments start with #
# Sway compositor (required for kiosk mode)
sway
# Wayland session utilities
swayidle
# Runtime libraries (may be pulled in automatically, but explicit is safer)
libgtk-4-1
libadwaita-1-0
libgtk4-layer-shell0

6
scripts/dev Executable file
View file

@ -0,0 +1,6 @@
#!/usr/bin/env bash
# Development wrapper - thin convenience script for developers
# Equivalent to: shepherd dev run
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
exec "$SCRIPT_DIR/shepherd" dev run "$@"

143
scripts/lib/build.sh Executable file
View file

@ -0,0 +1,143 @@
#!/usr/bin/env bash
# Build logic for shepherd-launcher
# Wraps cargo build with project-specific settings
# Get the directory containing this script
BUILD_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Source common utilities
# shellcheck source=common.sh
source "$BUILD_LIB_DIR/common.sh"
# Binary names produced by the build
SHEPHERD_BINARIES=(
"shepherdd"
"shepherd-launcher"
"shepherd-hud"
)
# Get the target directory for binaries
get_target_dir() {
local release="${1:-false}"
local repo_root
repo_root="$(get_repo_root)"
if [[ "$release" == "true" ]]; then
echo "$repo_root/target/release"
else
echo "$repo_root/target/debug"
fi
}
# Get the path to a specific binary
get_binary_path() {
local binary="$1"
local release="${2:-false}"
echo "$(get_target_dir "$release")/$binary"
}
# Check if all binaries exist
binaries_exist() {
local release="${1:-false}"
local target_dir
target_dir="$(get_target_dir "$release")"
for binary in "${SHEPHERD_BINARIES[@]}"; do
if [[ ! -x "$target_dir/$binary" ]]; then
return 1
fi
done
return 0
}
# Build the project
build_cargo() {
local release="${1:-false}"
local repo_root
repo_root="$(get_repo_root)"
verify_repo
require_command cargo rust
cd "$repo_root" || die "Failed to change directory to $repo_root"
local build_type
if [[ "$release" == "true" ]]; then
build_type="release"
info "Building shepherd (release mode)..."
cargo build --release
else
build_type="debug"
info "Building shepherd (debug mode)..."
cargo build
fi
# Verify binaries were created
if ! binaries_exist "$release"; then
die "Build completed but some binaries are missing"
fi
local target_dir
target_dir="$(get_target_dir "$release")"
success "Built binaries ($build_type):"
for binary in "${SHEPHERD_BINARIES[@]}"; do
info " $target_dir/$binary"
done
}
# Clean build artifacts
build_clean() {
local repo_root
repo_root="$(get_repo_root)"
verify_repo
require_command cargo rust
cd "$repo_root" || die "Failed to change directory to $repo_root"
info "Cleaning build artifacts..."
cargo clean
success "Build artifacts cleaned"
}
# Main build command dispatcher
build_main() {
local release=false
# Parse arguments
while [[ $# -gt 0 ]]; do
case "$1" in
--release|-r)
release=true
shift
;;
clean)
build_clean
return
;;
help|-h|--help)
cat <<EOF
Usage: shepherd build [OPTIONS]
Options:
--release, -r Build in release mode (optimized)
clean Clean build artifacts
help Show this help
Examples:
shepherd build # Debug build
shepherd build --release # Release build
shepherd build clean # Clean artifacts
EOF
return
;;
*)
die "Unknown build option: $1 (try: shepherd build help)"
;;
esac
done
build_cargo "$release"
}

193
scripts/lib/common.sh Executable file
View file

@ -0,0 +1,193 @@
#!/usr/bin/env bash
# Common utilities for shepherd scripts
# Logging, error handling, and sudo helpers
set -euo pipefail
# Colors for terminal output (disabled if not a tty)
if [[ -t 1 ]]; then
RED='\033[0;31m'
YELLOW='\033[0;33m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
else
RED=''
YELLOW=''
GREEN=''
BLUE=''
NC=''
fi
# Logging functions
info() {
echo -e "${BLUE}[INFO]${NC} $*" >&2
}
warn() {
echo -e "${YELLOW}[WARN]${NC} $*" >&2
}
error() {
echo -e "${RED}[ERROR]${NC} $*" >&2
}
success() {
echo -e "${GREEN}[OK]${NC} $*" >&2
}
die() {
error "$@"
exit 1
}
# Check if running as root
is_root() {
[[ $EUID -eq 0 ]]
}
# Require root or exit
require_root() {
if ! is_root; then
die "This command must be run as root (use sudo)"
fi
}
# Run a command with sudo if not already root
maybe_sudo() {
if is_root; then
"$@"
else
sudo "$@"
fi
}
# Check if a command exists
command_exists() {
command -v "$1" &>/dev/null
}
# Require a command or exit with helpful message
require_command() {
local cmd="$1"
local pkg="${2:-$1}"
if ! command_exists "$cmd"; then
die "Required command '$cmd' not found. Install it with: apt install $pkg"
fi
}
# Get the repository root directory
get_repo_root() {
local script_dir
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
(cd "$script_dir/../.." && pwd)
}
# Verify we're in the shepherd repository
verify_repo() {
local repo_root
repo_root="$(get_repo_root)"
if [[ ! -f "$repo_root/Cargo.toml" ]]; then
die "Not in shepherd repository (Cargo.toml not found at $repo_root)"
fi
# Check it's the right project
if ! grep -q 'shepherd-launcher-ui' "$repo_root/Cargo.toml" 2>/dev/null; then
die "This doesn't appear to be the shepherd-launcher repository"
fi
}
# Check Ubuntu version and warn if not supported
check_ubuntu_version() {
local min_version="${1:-25.10}"
if [[ ! -f /etc/os-release ]]; then
warn "Cannot determine OS version (not Linux or missing /etc/os-release)"
return 0
fi
# shellcheck source=/dev/null
source /etc/os-release
if [[ "${ID:-}" != "ubuntu" ]]; then
warn "This system is not Ubuntu (detected: ${ID:-unknown}). Some features may not work."
return 0
fi
local version="${VERSION_ID:-0}"
if [[ "$(printf '%s\n' "$min_version" "$version" | sort -V | head -n1)" != "$min_version" ]]; then
warn "Ubuntu version $version detected. Recommended: $min_version or higher."
fi
}
# Safe file backup - creates timestamped backup
backup_file() {
local file="$1"
local backup_dir="${2:-}"
if [[ ! -e "$file" ]]; then
return 0
fi
local timestamp
timestamp="$(date +%Y%m%d_%H%M%S)"
local backup_name
if [[ -n "$backup_dir" ]]; then
mkdir -p "$backup_dir"
backup_name="$backup_dir/$(basename "$file").$timestamp"
else
backup_name="$file.$timestamp.bak"
fi
cp -a "$file" "$backup_name"
echo "$backup_name"
}
# Create directory with proper permissions
ensure_dir() {
local dir="$1"
local mode="${2:-0755}"
local owner="${3:-}"
if [[ ! -d "$dir" ]]; then
mkdir -p "$dir"
chmod "$mode" "$dir"
if [[ -n "$owner" ]]; then
chown "$owner" "$dir"
fi
fi
}
# Validate username exists
validate_user() {
local user="$1"
if ! id "$user" &>/dev/null; then
die "User '$user' does not exist"
fi
}
# Get user's home directory
get_user_home() {
local user="$1"
getent passwd "$user" | cut -d: -f6
}
# Kill processes matching a pattern (silent if none found)
kill_matching() {
local pattern="$1"
pkill -f "$pattern" 2>/dev/null || true
}
# Wait for a file/socket to appear
wait_for_file() {
local file="$1"
local timeout="${2:-30}"
local elapsed=0
while [[ ! -e "$file" ]] && [[ $elapsed -lt $timeout ]]; do
sleep 0.5
elapsed=$((elapsed + 1))
done
[[ -e "$file" ]]
}

206
scripts/lib/deps.sh Executable file
View file

@ -0,0 +1,206 @@
#!/usr/bin/env bash
# Dependency management for shepherd-launcher
# Provides functions to read, union, and install package sets
# Get the directory containing this script
DEPS_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Source common utilities
# shellcheck source=common.sh
source "$DEPS_LIB_DIR/common.sh"
# Directory containing package lists
DEPS_DIR="$(get_repo_root)/scripts/deps"
# Rust installation URL
RUSTUP_URL="https://sh.rustup.rs"
# Check if Rust is installed
is_rust_installed() {
command_exists rustc && command_exists cargo
}
# Install Rust via rustup
install_rust() {
if is_rust_installed; then
info "Rust is already installed ($(rustc --version))"
return 0
fi
info "Installing Rust via rustup..."
# Download and run rustup installer
curl --proto '=https' --tlsv1.2 -sSf "$RUSTUP_URL" | sh -s -- -y --profile minimal
# Source cargo env for current session
# shellcheck source=/dev/null
source "$HOME/.cargo/env" 2>/dev/null || true
if is_rust_installed; then
success "Rust installed successfully ($(rustc --version))"
else
die "Rust installation failed. Please run: curl --proto '=https' --tlsv1.2 -sSf $RUSTUP_URL | sh"
fi
}
# Read a package file, stripping comments and empty lines
read_package_file() {
local file="$1"
if [[ ! -f "$file" ]]; then
die "Package file not found: $file"
fi
# Strip comments (# to end of line) and empty lines, trim whitespace
grep -v '^\s*#' "$file" | grep -v '^\s*$' | sed 's/#.*//' | tr -s '[:space:]' '\n' | grep -v '^$'
}
# Get packages for a specific set
get_packages() {
local set_name="$1"
case "$set_name" in
build)
read_package_file "$DEPS_DIR/build.pkgs"
;;
run)
read_package_file "$DEPS_DIR/run.pkgs"
;;
dev)
# Union of all three sets, deduplicated
{
read_package_file "$DEPS_DIR/build.pkgs"
read_package_file "$DEPS_DIR/run.pkgs"
read_package_file "$DEPS_DIR/dev.pkgs"
} | sort -u
;;
*)
die "Unknown package set: $set_name (valid: build, run, dev)"
;;
esac
}
# Print packages for a set (one per line)
deps_print() {
local set_name="${1:-}"
if [[ -z "$set_name" ]]; then
die "Usage: shepherd deps print <build|run|dev>"
fi
get_packages "$set_name"
}
# Install packages for a set
deps_install() {
local set_name="${1:-}"
if [[ -z "$set_name" ]]; then
die "Usage: shepherd deps install <build|run|dev>"
fi
check_ubuntu_version
info "Installing $set_name dependencies..."
# Get the package list
local packages
packages=$(get_packages "$set_name" | tr '\n' ' ')
if [[ -z "$packages" ]]; then
warn "No packages to install for set: $set_name"
return 0
fi
info "Packages: $packages"
# Install using apt
maybe_sudo apt-get update
# shellcheck disable=SC2086
maybe_sudo apt-get install -y $packages
# For build and dev sets, also install Rust
if [[ "$set_name" == "build" ]] || [[ "$set_name" == "dev" ]]; then
install_rust
fi
success "Installed $set_name dependencies"
}
# Check if all packages for a set are installed
deps_check() {
local set_name="${1:-}"
if [[ -z "$set_name" ]]; then
die "Usage: shepherd deps check <build|run|dev>"
fi
local packages
packages=$(get_packages "$set_name")
local missing=()
while IFS= read -r pkg; do
if ! dpkg -l "$pkg" &>/dev/null; then
missing+=("$pkg")
fi
done <<< "$packages"
# For build and dev sets, also check Rust
if [[ "$set_name" == "build" ]] || [[ "$set_name" == "dev" ]]; then
if ! is_rust_installed; then
warn "Rust is not installed"
return 1
fi
fi
if [[ ${#missing[@]} -gt 0 ]]; then
warn "Missing packages: ${missing[*]}"
return 1
fi
success "All $set_name dependencies are installed"
return 0
}
# Main deps command dispatcher
deps_main() {
local subcmd="${1:-}"
shift || true
case "$subcmd" in
print)
deps_print "$@"
;;
install)
deps_install "$@"
;;
check)
deps_check "$@"
;;
""|help|-h|--help)
cat <<EOF
Usage: shepherd deps <command> <set>
Commands:
print <set> Print packages in the set (one per line)
install <set> Install packages from the set
check <set> Check if all packages from the set are installed
Package sets:
build Build-time dependencies (+ Rust via rustup)
run Runtime dependencies only
dev All dependencies (build + run + dev extras + Rust)
Note: The 'build' and 'dev' sets automatically install Rust via rustup.
Examples:
shepherd deps print build
shepherd deps install dev
shepherd deps check run
EOF
;;
*)
die "Unknown deps command: $subcmd (try: shepherd deps help)"
;;
esac
}

425
scripts/lib/harden.sh Executable file
View file

@ -0,0 +1,425 @@
#!/usr/bin/env bash
# User hardening logic for shepherd-launcher
# Applies and reverts kiosk-style user restrictions
# Get the directory containing this script
HARDEN_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Source common utilities
# shellcheck source=common.sh
source "$HARDEN_LIB_DIR/common.sh"
# State directory for hardening rollback
HARDENING_STATE_DIR="/var/lib/shepherdd/hardening"
# Get the state directory for a user
get_user_state_dir() {
local user="$1"
echo "$HARDENING_STATE_DIR/$user"
}
# Check if a user is currently hardened
is_hardened() {
local user="$1"
local state_dir
state_dir="$(get_user_state_dir "$user")"
[[ -f "$state_dir/hardened" ]]
}
# Save a file for later restoration
save_for_restore() {
local user="$1"
local file="$2"
local state_dir
state_dir="$(get_user_state_dir "$user")"
local relative_path="${file#/}"
local backup_path="$state_dir/backup/$relative_path"
mkdir -p "$(dirname "$backup_path")"
if [[ -e "$file" ]]; then
cp -a "$file" "$backup_path"
echo "exists" > "$backup_path.meta"
else
echo "absent" > "$backup_path.meta"
fi
}
# Restore a previously saved file
restore_file() {
local user="$1"
local file="$2"
local state_dir
state_dir="$(get_user_state_dir "$user")"
local relative_path="${file#/}"
local backup_path="$state_dir/backup/$relative_path"
local meta_file="$backup_path.meta"
if [[ ! -f "$meta_file" ]]; then
warn "No backup metadata for $file, skipping"
return 0
fi
local original_state
original_state="$(cat "$meta_file")"
if [[ "$original_state" == "exists" ]]; then
if [[ -e "$backup_path" ]]; then
cp -a "$backup_path" "$file"
info " Restored: $file"
else
warn "Backup file missing for $file"
fi
else
# File didn't exist originally, remove it
rm -f "$file"
info " Removed: $file (didn't exist before)"
fi
}
# Record a change action for rollback
record_action() {
local user="$1"
local action="$2"
local target="$3"
local state_dir
state_dir="$(get_user_state_dir "$user")"
echo "$action|$target" >> "$state_dir/actions.log"
}
# Apply hardening to a user
harden_apply() {
local user="$1"
require_root
validate_user "$user"
local state_dir
state_dir="$(get_user_state_dir "$user")"
local user_home
user_home="$(get_user_home "$user")"
if is_hardened "$user"; then
warn "User $user is already hardened. Use 'shepherd harden revert' first."
return 0
fi
info "Applying hardening to user: $user"
# Create state directory
mkdir -p "$state_dir/backup"
chmod 0700 "$state_dir"
# Initialize actions log
: > "$state_dir/actions.log"
# =========================================================================
# 1. Set user shell to restricted shell or nologin for non-sway access
# =========================================================================
info "Configuring user shell..."
local original_shell
original_shell="$(getent passwd "$user" | cut -d: -f7)"
echo "$original_shell" > "$state_dir/original_shell"
# Keep bash for sway to work, but we'll restrict other access methods
# The shell restriction is handled by PAM and session limits instead
record_action "$user" "shell" "$original_shell"
# =========================================================================
# 2. Configure user's .bashrc to be restricted
# =========================================================================
info "Configuring shell restrictions..."
local bashrc="$user_home/.bashrc"
save_for_restore "$user" "$bashrc"
# Append restriction to bashrc (if not in sway, exit)
cat >> "$bashrc" <<'EOF'
# Shepherd hardening: restrict to sway session only
if [[ -z "${WAYLAND_DISPLAY:-}" ]] && [[ -z "${SWAYSOCK:-}" ]]; then
echo "This account is restricted to the Shepherd kiosk environment."
exit 1
fi
EOF
chown "$user:$user" "$bashrc"
record_action "$user" "file" "$bashrc"
# =========================================================================
# 3. Disable SSH access for this user
# =========================================================================
info "Restricting SSH access..."
local shepherd_sshd_config="/etc/ssh/sshd_config.d/shepherd-$user.conf"
save_for_restore "$user" "$shepherd_sshd_config"
# Create a drop-in config to deny this user
mkdir -p /etc/ssh/sshd_config.d
cat > "$shepherd_sshd_config" <<EOF
# Shepherd hardening: deny SSH access for kiosk user
DenyUsers $user
EOF
chmod 0644 "$shepherd_sshd_config"
record_action "$user" "file" "$shepherd_sshd_config"
# Reload sshd if running
if systemctl is-active --quiet sshd 2>/dev/null || systemctl is-active --quiet ssh 2>/dev/null; then
systemctl reload sshd 2>/dev/null || systemctl reload ssh 2>/dev/null || true
fi
# =========================================================================
# 4. Disable virtual console (TTY) access via PAM
# =========================================================================
info "Restricting console access..."
local pam_access="/etc/security/access.conf"
local shepherd_access_marker="# Shepherd hardening for user: $user"
save_for_restore "$user" "$pam_access"
# Add rule to deny console access (but allow via display managers)
if ! grep -q "$shepherd_access_marker" "$pam_access" 2>/dev/null; then
cat >> "$pam_access" <<EOF
$shepherd_access_marker
# Deny console login for kiosk user (allow display manager access)
-:$user:tty1 tty2 tty3 tty4 tty5 tty6 tty7
EOF
fi
record_action "$user" "file" "$pam_access"
# =========================================================================
# 5. Set up autologin to shepherd session (systemd override)
# =========================================================================
info "Configuring auto-login (if applicable)..."
# Create getty override for auto-login (optional - only if desired)
# This doesn't force auto-login, but prepares the override if needed
local getty_override_dir="/etc/systemd/system/getty@tty1.service.d"
local getty_override="$getty_override_dir/shepherd-autologin.conf"
save_for_restore "$user" "$getty_override"
mkdir -p "$getty_override_dir"
cat > "$getty_override" <<EOF
# Shepherd hardening: auto-login for kiosk user
# Uncomment the following lines to enable auto-login to tty1
# [Service]
# ExecStart=
# ExecStart=-/sbin/agetty --autologin $user --noclear %I \$TERM
EOF
chmod 0644 "$getty_override"
record_action "$user" "file" "$getty_override"
# =========================================================================
# 6. Lock down sudo access
# =========================================================================
info "Restricting sudo access..."
local sudoers_file="/etc/sudoers.d/shepherd-$user"
save_for_restore "$user" "$sudoers_file"
# Explicitly deny sudo for this user
cat > "$sudoers_file" <<EOF
# Shepherd hardening: deny sudo access for kiosk user
$user ALL=(ALL) !ALL
EOF
chmod 0440 "$sudoers_file"
record_action "$user" "file" "$sudoers_file"
# =========================================================================
# 7. Set restrictive file permissions on user home
# =========================================================================
info "Securing home directory permissions..."
# Save original permissions
stat -c "%a" "$user_home" > "$state_dir/home_perms"
# Set restrictive permissions
chmod 0700 "$user_home"
record_action "$user" "perms" "$user_home"
# =========================================================================
# Mark as hardened
# =========================================================================
date -Iseconds > "$state_dir/hardened"
echo "$user" > "$state_dir/user"
success "Hardening applied to user: $user"
info ""
info "The following restrictions are now active:"
info " - SSH access denied"
info " - Console (TTY) login restricted"
info " - Sudo access denied"
info " - Shell restricted to Sway sessions"
info " - Home directory secured (mode 0700)"
info ""
info "To revert: shepherd harden revert --user $user"
}
# Revert hardening from a user
harden_revert() {
local user="$1"
require_root
validate_user "$user"
local state_dir
state_dir="$(get_user_state_dir "$user")"
local user_home
user_home="$(get_user_home "$user")"
if ! is_hardened "$user"; then
warn "User $user is not currently hardened."
return 0
fi
info "Reverting hardening for user: $user"
# =========================================================================
# Restore all saved files
# =========================================================================
if [[ -f "$state_dir/actions.log" ]]; then
while IFS='|' read -r action target; do
case "$action" in
file)
restore_file "$user" "$target"
;;
perms)
if [[ -f "$state_dir/home_perms" ]]; then
local original_perms
original_perms="$(cat "$state_dir/home_perms")"
chmod "$original_perms" "$target"
info " Restored permissions on: $target"
fi
;;
shell)
# Shell wasn't changed, nothing to revert
;;
esac
done < "$state_dir/actions.log"
fi
# =========================================================================
# Reload services that may have been affected
# =========================================================================
if systemctl is-active --quiet sshd 2>/dev/null || systemctl is-active --quiet ssh 2>/dev/null; then
systemctl reload sshd 2>/dev/null || systemctl reload ssh 2>/dev/null || true
fi
# =========================================================================
# Clean up state directory
# =========================================================================
rm -rf "$state_dir"
success "Hardening reverted for user: $user"
info ""
info "All restrictions have been removed. The user can now:"
info " - Access via SSH"
info " - Login at console"
info " - Use sudo (if previously allowed)"
}
# Show hardening status
harden_status() {
local user="$1"
validate_user "$user"
local state_dir
state_dir="$(get_user_state_dir "$user")"
if is_hardened "$user"; then
local hardened_date
hardened_date="$(cat "$state_dir/hardened")"
echo "User '$user' is HARDENED (since $hardened_date)"
if [[ -f "$state_dir/actions.log" ]]; then
echo ""
echo "Applied restrictions:"
while IFS='|' read -r action target; do
echo " - $action: $target"
done < "$state_dir/actions.log"
fi
else
echo "User '$user' is NOT hardened"
fi
}
# Main harden command dispatcher
harden_main() {
local subcmd="${1:-}"
shift || true
local user=""
# Parse remaining arguments
while [[ $# -gt 0 ]]; do
case "$1" in
--user)
user="$2"
shift 2
;;
*)
die "Unknown option: $1"
;;
esac
done
case "$subcmd" in
apply)
if [[ -z "$user" ]]; then
die "Usage: shepherd harden apply --user USER"
fi
harden_apply "$user"
;;
revert)
if [[ -z "$user" ]]; then
die "Usage: shepherd harden revert --user USER"
fi
harden_revert "$user"
;;
status)
if [[ -z "$user" ]]; then
die "Usage: shepherd harden status --user USER"
fi
harden_status "$user"
;;
""|help|-h|--help)
cat <<EOF
Usage: shepherd harden <command> --user USER
Commands:
apply Apply kiosk hardening to a user
revert Revert hardening and restore original state
status Show hardening status for a user
Options:
--user USER Target user for hardening operations (required)
Hardening includes:
- Denying SSH access
- Restricting console (TTY) login
- Denying sudo access
- Restricting shell to Sway sessions only
- Securing home directory permissions
State is preserved in: $HARDENING_STATE_DIR/<user>/
Examples:
shepherd harden apply --user kiosk
shepherd harden status --user kiosk
shepherd harden revert --user kiosk
EOF
;;
*)
die "Unknown harden command: $subcmd (try: shepherd harden help)"
;;
esac
}

306
scripts/lib/install.sh Executable file
View file

@ -0,0 +1,306 @@
#!/usr/bin/env bash
# Installation logic for shepherd-launcher
# Handles binary installation, config deployment, and desktop entry setup
# Get the directory containing this script
INSTALL_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Source common utilities
# shellcheck source=common.sh
source "$INSTALL_LIB_DIR/common.sh"
# Source build utilities for binary paths
# shellcheck source=build.sh
source "$INSTALL_LIB_DIR/build.sh"
# Default installation paths
DEFAULT_PREFIX="/usr/local"
DEFAULT_BINDIR="bin"
# Standard sway config location
SWAY_CONFIG_DIR="/etc/sway"
SHEPHERD_SWAY_CONFIG="shepherd.conf"
# Desktop entry location
DESKTOP_ENTRY_DIR="share/wayland-sessions"
DESKTOP_ENTRY_NAME="shepherd.desktop"
# Install release binaries
install_bins() {
local prefix="${1:-$DEFAULT_PREFIX}"
local destdir="${DESTDIR:-}"
require_root
local bindir="$destdir$prefix/$DEFAULT_BINDIR"
local target_dir
target_dir="$(get_target_dir true)"
# Ensure release build exists
if ! binaries_exist true; then
die "Release binaries not found. Run 'shepherd build --release' first."
fi
info "Installing binaries to $bindir..."
ensure_dir "$bindir" 0755
for binary in "${SHEPHERD_BINARIES[@]}"; do
local src="$target_dir/$binary"
local dst="$bindir/$binary"
info " Installing $binary..."
install -m 0755 "$src" "$dst"
done
success "Installed binaries to $bindir"
}
# Install the sway configuration
install_sway_config() {
local destdir="${DESTDIR:-}"
local repo_root
repo_root="$(get_repo_root)"
require_root
local src_config="$repo_root/sway.conf"
local dst_dir="$destdir$SWAY_CONFIG_DIR"
local dst_config="$dst_dir/$SHEPHERD_SWAY_CONFIG"
if [[ ! -f "$src_config" ]]; then
die "Source sway.conf not found at $src_config"
fi
info "Installing sway configuration to $dst_config..."
ensure_dir "$dst_dir" 0755
# Create a production version of the sway config
# Replace debug paths with installed paths
local prefix="${1:-$DEFAULT_PREFIX}"
local bindir="$prefix/$DEFAULT_BINDIR"
# Copy and modify the config for production use
sed \
-e "s|./target/debug/shepherd-launcher|$bindir/shepherd-launcher|g" \
-e "s|./target/debug/shepherd-hud|$bindir/shepherd-hud|g" \
-e "s|./target/debug/shepherdd|$bindir/shepherdd|g" \
-e "s|./config.example.toml|/etc/shepherd/config.toml|g" \
-e "s|-c ./sway.conf|-c $dst_config|g" \
"$src_config" > "$dst_config"
chmod 0644 "$dst_config"
success "Installed sway configuration"
}
# Install desktop entry for display manager
install_desktop_entry() {
local prefix="${1:-$DEFAULT_PREFIX}"
local destdir="${DESTDIR:-}"
require_root
local dst_dir="$destdir$prefix/$DESKTOP_ENTRY_DIR"
local dst_entry="$dst_dir/$DESKTOP_ENTRY_NAME"
info "Installing desktop entry to $dst_entry..."
ensure_dir "$dst_dir" 0755
cat > "$dst_entry" <<EOF
[Desktop Entry]
Name=Shepherd Kiosk
Comment=Shepherd game launcher kiosk mode
Exec=sway -c $SWAY_CONFIG_DIR/$SHEPHERD_SWAY_CONFIG
Type=Application
DesktopNames=shepherd
EOF
chmod 0644 "$dst_entry"
success "Installed desktop entry"
}
# Deploy user configuration
install_config() {
local user="${1:-}"
local source_config="${2:-}"
if [[ -z "$user" ]]; then
die "Usage: shepherd install config --user USER [--source CONFIG]"
fi
validate_user "$user"
local repo_root
repo_root="$(get_repo_root)"
# Default source is the example config
if [[ -z "$source_config" ]]; then
source_config="$repo_root/config.example.toml"
fi
if [[ ! -f "$source_config" ]]; then
die "Source config not found: $source_config"
fi
# Get user's config directory
local user_home
user_home="$(get_user_home "$user")"
local user_config_dir="$user_home/.config/shepherd"
local dst_config="$user_config_dir/config.toml"
info "Installing user config to $dst_config..."
# Create config directory owned by user
maybe_sudo mkdir -p "$user_config_dir"
maybe_sudo chown "$user:$user" "$user_config_dir"
maybe_sudo chmod 0755 "$user_config_dir"
# Copy config file
maybe_sudo cp "$source_config" "$dst_config"
maybe_sudo chown "$user:$user" "$dst_config"
maybe_sudo chmod 0644 "$dst_config"
success "Installed user configuration for $user"
}
# Install the system-wide shepherd config directory
install_system_config() {
local destdir="${DESTDIR:-}"
local repo_root
repo_root="$(get_repo_root)"
require_root
local src_config="$repo_root/config.example.toml"
local dst_dir="$destdir/etc/shepherd"
local dst_config="$dst_dir/config.toml"
if [[ ! -f "$src_config" ]]; then
die "Source config not found: $src_config"
fi
info "Installing system config to $dst_config..."
ensure_dir "$dst_dir" 0755
if [[ ! -f "$dst_config" ]]; then
install -m 0644 "$src_config" "$dst_config"
success "Installed system configuration"
else
info "System configuration already exists, not overwriting"
fi
}
# Install everything
install_all() {
local user="${1:-}"
local prefix="${2:-$DEFAULT_PREFIX}"
if [[ -z "$user" ]]; then
die "Usage: shepherd install all --user USER [--prefix PREFIX]"
fi
require_root
validate_user "$user"
info "Installing shepherd-launcher (prefix: $prefix)..."
install_bins "$prefix"
install_sway_config "$prefix"
install_system_config
install_desktop_entry "$prefix"
install_config "$user"
success "Installation complete!"
info ""
info "Next steps:"
info " 1. Edit /etc/shepherd/config.toml for your system"
info " 2. Edit user config at ~$user/.config/shepherd/config.toml"
info " 3. Select 'Shepherd Kiosk' session at login"
info " 4. Optionally run 'shepherd harden apply --user $user' for kiosk mode"
}
# Main install command dispatcher
install_main() {
local subcmd="${1:-}"
shift || true
local user=""
local prefix="$DEFAULT_PREFIX"
local source_config=""
# Parse remaining arguments
while [[ $# -gt 0 ]]; do
case "$1" in
--user)
user="$2"
shift 2
;;
--prefix)
prefix="$2"
shift 2
;;
--source)
source_config="$2"
shift 2
;;
*)
die "Unknown option: $1"
;;
esac
done
case "$subcmd" in
bins)
install_bins "$prefix"
;;
config)
install_config "$user" "$source_config"
;;
sway-config)
install_sway_config "$prefix"
;;
desktop-entry)
install_desktop_entry "$prefix"
;;
system-config)
install_system_config
;;
all)
install_all "$user" "$prefix"
;;
""|help|-h|--help)
cat <<EOF
Usage: shepherd install <command> [OPTIONS]
Commands:
bins Install release binaries
config Deploy user configuration
sway-config Install sway configuration
desktop-entry Install display manager desktop entry
system-config Install system-wide configuration
all Install everything
Options:
--user USER Target user for config deployment (required for config/all)
--prefix PREFIX Installation prefix (default: $DEFAULT_PREFIX)
--source CONFIG Source config file (default: config.example.toml)
Environment:
DESTDIR Installation root for packaging (default: empty)
Examples:
shepherd install bins --prefix /usr/local
shepherd install config --user kiosk
shepherd install all --user kiosk --prefix /usr
EOF
;;
*)
die "Unknown install command: $subcmd (try: shepherd install help)"
;;
esac
}

152
scripts/lib/sway.sh Executable file
View file

@ -0,0 +1,152 @@
#!/usr/bin/env bash
# Sway compositor helpers for shepherd-launcher
# Handles nested sway execution for development and production
# Get the directory containing this script
SWAY_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Source common utilities
# shellcheck source=common.sh
source "$SWAY_LIB_DIR/common.sh"
# Source build utilities for binary paths
# shellcheck source=build.sh
source "$SWAY_LIB_DIR/build.sh"
# PID of the running sway process (for cleanup)
SWAY_PID=""
# Default directories
DEFAULT_DEV_RUNTIME="./dev-runtime"
DEFAULT_DATA_DIR="$DEFAULT_DEV_RUNTIME/data"
DEFAULT_SOCKET_PATH="$DEFAULT_DEV_RUNTIME/shepherd.sock"
# Cleanup function for sway processes
sway_cleanup() {
info "Cleaning up sway session..."
# Kill the nested sway - this will clean up everything inside it
if [[ -n "${SWAY_PID:-}" ]]; then
kill "$SWAY_PID" 2>/dev/null || true
fi
# Explicitly kill any shepherd processes that might have escaped
kill_matching "shepherdd"
kill_matching "shepherd-launcher"
kill_matching "shepherd-hud"
# Remove socket
if [[ -n "${SHEPHERD_SOCKET:-}" ]]; then
rm -f "$SHEPHERD_SOCKET"
fi
}
# Kill any existing dev instances
sway_kill_existing() {
info "Cleaning up any existing dev instances..."
kill_matching "sway -c.*sway.conf"
kill_matching "shepherdd"
kill_matching "shepherd-launcher"
kill_matching "shepherd-hud"
# Remove stale socket if it exists
if [[ -n "${SHEPHERD_SOCKET:-}" ]] && [[ -e "$SHEPHERD_SOCKET" ]]; then
rm -f "$SHEPHERD_SOCKET"
fi
# Brief pause to allow cleanup
sleep 0.5
}
# Set up environment for shepherd binaries
sway_setup_env() {
local data_dir="${1:-$DEFAULT_DATA_DIR}"
local socket_path="${2:-$DEFAULT_SOCKET_PATH}"
# Create directories
mkdir -p "$data_dir"
# Export environment variables
export SHEPHERD_SOCKET="$socket_path"
export SHEPHERD_DATA_DIR="$data_dir"
}
# Generate a sway config for development
# Uses debug binaries and development paths
sway_generate_dev_config() {
local repo_root
repo_root="$(get_repo_root)"
# Use the existing sway.conf as template
local sway_config="$repo_root/sway.conf"
if [[ ! -f "$sway_config" ]]; then
die "sway.conf not found at $sway_config"
fi
# Return path to the sway config (we use the existing one for dev)
echo "$sway_config"
}
# Start a nested sway session for development
sway_start_nested() {
local sway_config="$1"
require_command sway
info "Starting nested sway session..."
# Set up cleanup trap
trap sway_cleanup EXIT
# Start sway with wayland backend (nested in current session)
WLR_BACKENDS=wayland WLR_LIBINPUT_NO_DEVICES=1 sway -c "$sway_config" &
SWAY_PID=$!
info "Sway started with PID $SWAY_PID"
# Wait for sway to exit
wait "$SWAY_PID"
}
# Run a development session (build + nested sway)
sway_dev_run() {
local repo_root
repo_root="$(get_repo_root)"
verify_repo
cd "$repo_root" || die "Failed to change directory to $repo_root"
# Set up environment
sway_setup_env "$DEFAULT_DATA_DIR" "$DEFAULT_SOCKET_PATH"
# Kill any existing instances
sway_kill_existing
# Build debug binaries
info "Building shepherd binaries..."
build_cargo false
# Get sway config
local sway_config
sway_config="$(sway_generate_dev_config)"
# Start nested sway (blocking)
sway_start_nested "$sway_config"
}
# Main sway command dispatcher (internal use)
sway_main() {
local subcmd="${1:-}"
shift || true
case "$subcmd" in
run)
sway_dev_run "$@"
;;
*)
# Default to run for backwards compatibility
sway_dev_run "$@"
;;
esac
}

128
scripts/shepherd Executable file
View file

@ -0,0 +1,128 @@
#!/usr/bin/env bash
# Shepherd CLI - Main entrypoint for shepherd-launcher tooling
# Provides unified interface for build, development, installation, and hardening
set -euo pipefail
# Get the directory containing this script
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LIB_DIR="$SCRIPT_DIR/lib"
# Source shared libraries
# shellcheck source=lib/common.sh
source "$LIB_DIR/common.sh"
# shellcheck source=lib/deps.sh
source "$LIB_DIR/deps.sh"
# shellcheck source=lib/build.sh
source "$LIB_DIR/build.sh"
# shellcheck source=lib/sway.sh
source "$LIB_DIR/sway.sh"
# shellcheck source=lib/install.sh
source "$LIB_DIR/install.sh"
# shellcheck source=lib/harden.sh
source "$LIB_DIR/harden.sh"
# Version
VERSION="0.1.0"
# Print usage information
usage() {
cat <<EOF
shepherd - Unified tooling for shepherd-launcher
Usage: shepherd <command> [subcommand] [options]
Commands:
deps Manage system dependencies
build Build shepherd binaries
dev Development commands
install Install shepherd to the system
harden User hardening for kiosk mode
Development:
shepherd deps install dev Install all development dependencies
shepherd build Build debug binaries
shepherd dev run Build and run in nested sway
CI/Build:
shepherd deps install build Install build-only dependencies
shepherd build --release Build release binaries
Runtime:
shepherd deps install run Install runtime dependencies only
Installation:
shepherd install all --user USER Full installation
shepherd install bins --prefix /usr Install binaries only
shepherd install config --user USER Deploy user config
Hardening:
shepherd harden apply --user USER Apply kiosk restrictions
shepherd harden revert --user USER Remove restrictions
Options:
-h, --help Show this help message
-V, --version Show version
Run 'shepherd <command> help' for detailed command information.
EOF
}
# Main entry point
main() {
local cmd="${1:-}"
shift || true
case "$cmd" in
deps)
deps_main "$@"
;;
build)
build_main "$@"
;;
dev)
local subcmd="${1:-}"
shift || true
case "$subcmd" in
run)
sway_dev_run "$@"
;;
""|help|-h|--help)
cat <<EOF
Usage: shepherd dev <command>
Commands:
run Build and run shepherd in a nested sway session
Examples:
shepherd dev run
EOF
;;
*)
die "Unknown dev command: $subcmd (try: shepherd dev help)"
;;
esac
;;
install)
install_main "$@"
;;
harden)
harden_main "$@"
;;
-h|--help|help|"")
usage
;;
-V|--version|version)
echo "shepherd $VERSION"
;;
*)
error "Unknown command: $cmd"
echo ""
usage
exit 1
;;
esac
}
# Run main with all arguments
main "$@"