Add setup scripts
This commit is contained in:
parent
3b1a2fb166
commit
fc0c1ce51d
15 changed files with 1847 additions and 89 deletions
74
.github/workflows/ci.yml
vendored
74
.github/workflows/ci.yml
vendored
|
|
@ -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
60
run-dev
|
|
@ -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
148
scripts/README.md
Normal 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
42
scripts/admin
Executable 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
28
scripts/deps/build.pkgs
Normal 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
11
scripts/deps/dev.pkgs
Normal 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
14
scripts/deps/run.pkgs
Normal 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
6
scripts/dev
Executable 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
143
scripts/lib/build.sh
Executable 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
193
scripts/lib/common.sh
Executable 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
206
scripts/lib/deps.sh
Executable 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
425
scripts/lib/harden.sh
Executable 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
306
scripts/lib/install.sh
Executable 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
152
scripts/lib/sway.sh
Executable 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
128
scripts/shepherd
Executable 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 "$@"
|
||||||
Loading…
Reference in a new issue