Merge pull request #12 from aarmea/u/aarmea/2/setup-scripts
Add setup scripts and documentation
This commit is contained in:
commit
73203b3458
19 changed files with 2192 additions and 134 deletions
74
.github/workflows/ci.yml
vendored
74
.github/workflows/ci.yml
vendored
|
|
@ -8,20 +8,6 @@ on:
|
|||
|
||||
env:
|
||||
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:
|
||||
build:
|
||||
|
|
@ -30,17 +16,18 @@ jobs:
|
|||
container:
|
||||
image: ubuntu:25.10
|
||||
steps:
|
||||
- name: Install git and dependencies
|
||||
- name: Install git
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y git curl ${{ env.SYSTEM_DEPS }}
|
||||
apt-get install -y git curl
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
run: |
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||
- name: Install build dependencies
|
||||
run: ./scripts/shepherd deps install build
|
||||
|
||||
- name: Add Rust to PATH
|
||||
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Cache cargo registry and build
|
||||
uses: actions/cache@v4
|
||||
|
|
@ -56,7 +43,7 @@ jobs:
|
|||
- name: Build
|
||||
run: |
|
||||
. "$HOME/.cargo/env"
|
||||
cargo build --all-targets
|
||||
./scripts/shepherd build
|
||||
|
||||
test:
|
||||
name: Test
|
||||
|
|
@ -64,17 +51,18 @@ jobs:
|
|||
container:
|
||||
image: ubuntu:25.10
|
||||
steps:
|
||||
- name: Install git and dependencies
|
||||
- name: Install git
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y git curl ${{ env.SYSTEM_DEPS }}
|
||||
apt-get install -y git curl
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
run: |
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||
- name: Install build dependencies
|
||||
run: ./scripts/shepherd deps install build
|
||||
|
||||
- name: Add Rust to PATH
|
||||
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Cache cargo registry and build
|
||||
uses: actions/cache@v4
|
||||
|
|
@ -98,19 +86,23 @@ jobs:
|
|||
container:
|
||||
image: ubuntu:25.10
|
||||
steps:
|
||||
- name: Install git and dependencies
|
||||
- name: Install git
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y git curl ${{ env.SYSTEM_DEPS }}
|
||||
apt-get install -y git curl
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
- name: Install build dependencies
|
||||
run: ./scripts/shepherd deps install build
|
||||
|
||||
- name: Add clippy component
|
||||
run: |
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||
. "$HOME/.cargo/env"
|
||||
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
|
||||
uses: actions/cache@v4
|
||||
|
|
@ -127,3 +119,21 @@ jobs:
|
|||
run: |
|
||||
. "$HOME/.cargo/env"
|
||||
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
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@
|
|||
|
||||
### tl;dr
|
||||
|
||||
You need a Wayland-capable Linux system, Rust, and a small set of system dependencies. Once installed, `./run-dev` will start a development instance.
|
||||
You need a Wayland-capable Linux system, Rust, and a small set of system
|
||||
dependencies. Once installed, `./run-dev` will start a development instance.
|
||||
|
||||
### Requirements
|
||||
|
||||
|
|
@ -15,15 +16,41 @@ You need a Wayland-capable Linux system, Rust, and a small set of system depende
|
|||
|
||||
2. **System dependencies**
|
||||
|
||||
* Platform-specific packages are required.
|
||||
* For Ubuntu, see the [`SYSTEM_DEPS` section in the CI configuration](./.github/workflows/ci.yml).
|
||||
* Platform-specific packages are required for building and running.
|
||||
* View packages with: `./scripts/shepherd deps print dev`
|
||||
* Install all dev dependencies: `./scripts/shepherd deps install dev`
|
||||
* **Note**: Rust is automatically installed via rustup when installing build or dev dependencies.
|
||||
|
||||
3. **Rust toolchain**
|
||||
### Unified script system
|
||||
|
||||
`shepherd-launcher` provides a unified script system for managing dependencies, building, and running:
|
||||
|
||||
```sh
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
# View and install dependencies
|
||||
./scripts/shepherd deps print dev # List all dev dependencies
|
||||
./scripts/shepherd deps install dev # Install all dev dependencies
|
||||
|
||||
# Build binaries
|
||||
./scripts/shepherd build # Debug build
|
||||
./scripts/shepherd build --release # Release build
|
||||
|
||||
# Development
|
||||
./scripts/shepherd dev run # Build and run in nested Sway
|
||||
```
|
||||
|
||||
For CI/build-only environments:
|
||||
```sh
|
||||
./scripts/shepherd deps install build # Build dependencies only
|
||||
./scripts/shepherd build --release # Production build
|
||||
```
|
||||
|
||||
For runtime-only systems:
|
||||
```sh
|
||||
./scripts/shepherd deps install run # Runtime dependencies only
|
||||
```
|
||||
|
||||
See `./scripts/shepherd --help` for all available commands.
|
||||
|
||||
### Running in development
|
||||
|
||||
Start a development instance:
|
||||
|
|
|
|||
49
README.md
49
README.md
|
|
@ -74,29 +74,6 @@ If it can run on Linux in *any way, shape, or form*, it can be supervised by
|
|||
|
||||
> [A Short Hike](https://ashorthike.com/) running via Steam
|
||||
|
||||
Contributions are welcome for improvements and not yet implemented backends,
|
||||
such as:
|
||||
* Content-aware media player with supervised libraries
|
||||
([#3](https://github.com/aarmea/shepherd-launcher/issues/3))
|
||||
* Pre-booted Steam to improve launch time
|
||||
([#4](https://github.com/aarmea/shepherd-launcher/issues/4))
|
||||
* Android apps via Waydroid, including pre-booting Android if necessary
|
||||
([#5](https://github.com/aarmea/shepherd-launcher/issues/5))
|
||||
* Legacy Win9x via DOSBox, QEMU, or PCem, including scripts to create a boot-to-app image
|
||||
([#6](https://github.com/aarmea/shepherd-launcher/issues/6))
|
||||
* Chrome
|
||||
([#7](https://github.com/aarmea/shepherd-launcher/issues/7)),
|
||||
including strict sandboxing and support for firewall rules
|
||||
([#8](https://github.com/aarmea/shepherd-launcher/issues/8))
|
||||
* Awareness of whether an Internet connection is available, and an availability
|
||||
rule that gates activities based on this
|
||||
([#9](https://github.com/aarmea/shepherd-launcher/issues/9))
|
||||
* Porting to other *host* platforms, such as a Microsoft Windows shell
|
||||
replacement or macOS kiosk via MDM. `shepherd-launcher` is architected such
|
||||
that `shepherdd`, the core enforcement service, does not render any UI and
|
||||
performs platform-specific functions like process management in
|
||||
platform-specific crates.
|
||||
|
||||
## Core concepts
|
||||
|
||||
* **Launcher-first**: only one foreground activity at a time
|
||||
|
|
@ -113,16 +90,12 @@ such as:
|
|||
|
||||
## Installation
|
||||
|
||||
`shepherd-launcher` is pre-alpha and in active development. As such, end-user
|
||||
binaries and installation instructions are not yet available.
|
||||
`shepherd-launcher` is pre-alpha and in active development. The helper at
|
||||
`./scripts/shepherd` can be used to build and install a *fully functional*
|
||||
local kiosk setup from source:
|
||||
|
||||
See [CONTRIBUTING.md](./CONTRIBUTING.md) for how to run in development.
|
||||
|
||||
Contributions are welcome for:
|
||||
* a CI step that generates production binaries
|
||||
([#1](https://github.com/aarmea/shepherd-launcher/issues/1))
|
||||
* an installation script
|
||||
([#2](https://github.com/aarmea/shepherd-launcher/issues/2))
|
||||
Check out this repository and run `./scripts/shepherd --help` or see
|
||||
[INSTALL.md](./docs/INSTALL.md) for more.
|
||||
|
||||
## Example configuration
|
||||
|
||||
|
|
@ -158,11 +131,6 @@ max_run_seconds = 1800 # 30 minutes (roughly 3 in-game days)
|
|||
daily_quota_seconds = 3600 # 1 hour per day
|
||||
cooldown_seconds = 600 # 10 minute cooldown
|
||||
|
||||
[[entries.warnings]]
|
||||
seconds_before = 600
|
||||
severity = "info"
|
||||
message = "10 minutes left - start wrapping up!"
|
||||
|
||||
[[entries.warnings]]
|
||||
seconds_before = 120
|
||||
severity = "warn"
|
||||
|
|
@ -178,7 +146,12 @@ See [config.example.toml](./config.example.toml) for more.
|
|||
|
||||
## Development
|
||||
|
||||
See [CONTRIBUTING.md](./CONTRIBUTING.md)
|
||||
Build instructions and contribution guidelines are described in
|
||||
[CONTRIBUTING.md](./CONTRIBUTING.md).
|
||||
|
||||
If you'd like to help out, look on
|
||||
[GitHub Issues](https://github.com/aarmea/shepherd-launcher/issues) for
|
||||
potential work items.
|
||||
|
||||
## Written in 2025, responsibly
|
||||
|
||||
|
|
|
|||
66
docs/INSTALL.md
Normal file
66
docs/INSTALL.md
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
# Installation
|
||||
|
||||
`shepherd-launcher` can be installed on Linux with a modern Wayland compositor.
|
||||
It is currently developed and tested on Ubuntu 25.10.
|
||||
|
||||
`shepherd-launcher` currently must be built from source. `./scripts/shepherd`
|
||||
can help set up your build environment and manage your installation.
|
||||
|
||||
## Basic setup
|
||||
|
||||
```sh
|
||||
# 0. Install build dependencies
|
||||
sudo ./scripts/shepherd deps build run
|
||||
|
||||
# 1. Install runtime dependencies
|
||||
sudo ./scripts/shepherd deps install run
|
||||
|
||||
# 2. Build release binaries
|
||||
./scripts/shepherd build --release
|
||||
|
||||
# 3. Install everything for a kiosk user
|
||||
sudo ./scripts/shepherd install all --user kiosk
|
||||
```
|
||||
|
||||
This installs:
|
||||
- Binaries to `/usr/local/bin/`
|
||||
- System Sway configuration to `/etc/sway/shepherd.conf`
|
||||
- Display manager desktop entry ("Shepherd Kiosk" session)
|
||||
- System config template to `/etc/shepherd/config.toml`
|
||||
- User config to `~kiosk/.config/shepherd/config.toml`
|
||||
|
||||
For custom installation paths:
|
||||
```sh
|
||||
# Install to /usr instead of /usr/local
|
||||
sudo ./scripts/shepherd install all --user kiosk --prefix /usr
|
||||
```
|
||||
|
||||
Individual installation steps can be run separately:
|
||||
```sh
|
||||
sudo ./scripts/shepherd install bins --prefix /usr/local
|
||||
sudo ./scripts/shepherd install config --user kiosk
|
||||
```
|
||||
|
||||
## Kiosk hardening (optional)
|
||||
|
||||
Kiosk hardening is optional and intended for devices primarily used by
|
||||
children, not developer machines.
|
||||
|
||||
```sh
|
||||
sudo ./scripts/shepherd harden apply --user kiosk
|
||||
```
|
||||
|
||||
This restricts the user to only access the Shepherd session by:
|
||||
- Denying SSH access
|
||||
- Restricting console (TTY) login
|
||||
- Denying sudo access
|
||||
- Restricting shell to Sway sessions only
|
||||
|
||||
To revert (all changes are reversible):
|
||||
```sh
|
||||
sudo ./scripts/shepherd harden revert --user kiosk
|
||||
```
|
||||
|
||||
## Complete documentation
|
||||
|
||||
See the scripts' [README](../scripts/README.md) for more.
|
||||
231
docs/ai/history/2025-12-31 001 scripts.md
Normal file
231
docs/ai/history/2025-12-31 001 scripts.md
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
This prompt created the setup script ecosystem, including CI setup, dev setup, and end-user installation and configuration. It was provided to Claude Opus 4.5 via VSCode Agent mode.
|
||||
|
||||
The first draft was written by ChatGPT after some back-and-forth [in this conversation](https://chatgpt.com/share/6955f5ee-46f4-800b-87bc-db0e3df16541).
|
||||
|
||||
The output was committed as fc0c1ce51d739aafcdce66f6958f008ef056a76a.
|
||||
|
||||
-----
|
||||
|
||||
# Task: Implement unified script system for dev, build, install, and user hardening
|
||||
|
||||
## Objective
|
||||
|
||||
Implement a **single, composable script system** that:
|
||||
|
||||
* Avoids duplication between CI, developer, and runtime workflows
|
||||
* Exposes clear, reversible primitives for build, install, and user hardening
|
||||
* Uses one authoritative entrypoint with subcommands
|
||||
* Allows CI, developers, and administrators to invoke the *same logic* with different scopes
|
||||
|
||||
The system must support:
|
||||
|
||||
* CI build-only environments
|
||||
* Runtime-only systems
|
||||
* Developer systems (union of build + runtime deps)
|
||||
* Production installation and kiosk-style user hardening
|
||||
|
||||
---
|
||||
|
||||
## High-level design constraints
|
||||
|
||||
* **One CLI entrypoint** (script-based), with subcommands
|
||||
* **Shared logic lives in libraries**, not duplicated scripts
|
||||
* **All tasks must be independently callable**
|
||||
* **All destructive actions must be reversible**
|
||||
* **Dependency sets must be defined once and derived programmatically**
|
||||
* Shell scripts are acceptable and preferred for system integration
|
||||
|
||||
---
|
||||
|
||||
## Required repository layout
|
||||
|
||||
```
|
||||
scripts/
|
||||
shepherd # main CLI dispatcher
|
||||
dev # thin wrapper → shepherd dev run
|
||||
admin # optional wrapper → shepherd admin ...
|
||||
|
||||
lib/
|
||||
common.sh # logging, error handling, sudo helpers
|
||||
deps.sh # dependency set logic
|
||||
build.sh # cargo build logic
|
||||
sway.sh # nested sway execution helpers
|
||||
install.sh # binary + config installation
|
||||
harden.sh # harden / unharden user logic
|
||||
|
||||
deps/
|
||||
build.pkgs # build-only system packages
|
||||
run.pkgs # runtime-only system packages
|
||||
dev.pkgs # optional dev-only extras
|
||||
```
|
||||
|
||||
No business logic should live in `Makefile` (if present).
|
||||
|
||||
---
|
||||
|
||||
## CLI interface (must be implemented)
|
||||
|
||||
### Dependency management
|
||||
|
||||
```
|
||||
shepherd deps print build|run|dev
|
||||
shepherd deps install build|run|dev
|
||||
```
|
||||
|
||||
Behavior:
|
||||
|
||||
* `build` = build.pkgs
|
||||
* `run` = run.pkgs
|
||||
* `dev` = union(build, run, dev.pkgs)
|
||||
* No duplicated lists anywhere
|
||||
|
||||
The build packages are currently listed in the CI definition (./.github/workflows/ci.yml). The CI definition should be updated to use this script for package management.
|
||||
You may leave a stub for the runtime packages that only includes `sway`.
|
||||
|
||||
You may assume Ubuntu 25.10 or higher. Warn but do not fail if the environment indicates otherwise.
|
||||
|
||||
---
|
||||
|
||||
### Build
|
||||
|
||||
```
|
||||
shepherd build # debug build
|
||||
shepherd build --release # production build
|
||||
```
|
||||
|
||||
Internals:
|
||||
|
||||
* Wrap `cargo build`
|
||||
* Centralize binary names and target paths
|
||||
* No logic duplication in dev/run/install paths
|
||||
|
||||
---
|
||||
|
||||
### Development run
|
||||
|
||||
```
|
||||
shepherd dev run
|
||||
```
|
||||
|
||||
This should be functionally equivalent to what is currently in `./run-dev` and will serve as its replacement.
|
||||
|
||||
Behavior:
|
||||
|
||||
* Builds (debug)
|
||||
* Launches **nested sway**
|
||||
* Executes built binaries with correct environment
|
||||
|
||||
This should reuse shared sway helpers (not inline logic).
|
||||
|
||||
---
|
||||
|
||||
### Installation (composable steps)
|
||||
|
||||
Each step must be callable independently.
|
||||
|
||||
```
|
||||
shepherd install bins [--prefix PREFIX]
|
||||
shepherd install config --user USER [--source CONFIG]
|
||||
shepherd install all --user USER [--prefix PREFIX]
|
||||
```
|
||||
|
||||
Requirements:
|
||||
|
||||
* Install release binaries to standard paths
|
||||
* Install our global Sway configuration to a standard path
|
||||
* Install desktop entry that tells the display manager to run shepherd-launcher via the Sway configuration (you will need to write this)
|
||||
* Support PREFIX and DESTDIR
|
||||
* Deploy example config to a specified user
|
||||
* Do not assume hardening is enabled
|
||||
|
||||
---
|
||||
|
||||
### User hardening (reversible)
|
||||
|
||||
```
|
||||
shepherd harden apply --user USER
|
||||
shepherd harden revert --user USER
|
||||
```
|
||||
|
||||
Requirements:
|
||||
|
||||
* Harden a user so it can only run shepherd-launcher
|
||||
* Support reverting to original system state
|
||||
* Persist rollback state under:
|
||||
|
||||
```
|
||||
/var/lib/shepherdd/hardening/<user>/
|
||||
```
|
||||
* Hardening and unharden must be idempotent
|
||||
|
||||
No assumptions about display manager; logic should be isolated.
|
||||
|
||||
---
|
||||
|
||||
## Shared library responsibilities
|
||||
|
||||
### `lib/common.sh`
|
||||
|
||||
* Logging (`info`, `warn`, `error`)
|
||||
* `require_root`, `maybe_sudo`
|
||||
* Safe command execution helpers
|
||||
|
||||
### `lib/deps.sh`
|
||||
|
||||
* Read package lists
|
||||
* Compute unions
|
||||
* Install or print packages
|
||||
|
||||
### `lib/build.sh`
|
||||
|
||||
* Cargo build abstraction
|
||||
* Debug vs release handling
|
||||
* Binary discovery
|
||||
|
||||
### `lib/sway.sh`
|
||||
|
||||
* Nested sway environment setup
|
||||
* Command execution inside sway
|
||||
* Shared by dev and production paths
|
||||
|
||||
### `lib/install.sh`
|
||||
|
||||
* Binary install logic
|
||||
* Config deployment logic
|
||||
|
||||
### `lib/harden.sh`
|
||||
|
||||
* Apply hardening
|
||||
* Revert hardening
|
||||
* Track system state changes
|
||||
|
||||
---
|
||||
|
||||
## Wrapper scripts
|
||||
|
||||
* `./run-dev` → `exec ./scripts/shepherd dev run`
|
||||
* Optional `Makefile` targets may call `shepherd`, but must contain no logic
|
||||
|
||||
---
|
||||
|
||||
## Non-goals
|
||||
|
||||
* No Docker/Nix/devcontainer required
|
||||
* No GUI tooling
|
||||
* No distro-specific packaging beyond apt-style package lists
|
||||
* No Rust rewrite of scripts unless strictly necessary
|
||||
|
||||
---
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
* CI can install **only build deps** and run `shepherd build`
|
||||
* Runtime system can install **only run deps**
|
||||
* Developer can install **dev deps** and run `./run-dev`
|
||||
* Admin can:
|
||||
|
||||
* install binaries
|
||||
* deploy config
|
||||
* harden and unharden a user safely
|
||||
* No duplicated dependency lists
|
||||
* No duplicated build or sway logic
|
||||
60
run-dev
60
run-dev
|
|
@ -1,59 +1,5 @@
|
|||
#!/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
|
||||
|
||||
# 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
|
||||
exec ./scripts/shepherd dev run "$@"
|
||||
|
|
|
|||
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
|
||||
31
scripts/deps/build.pkgs
Normal file
31
scripts/deps/build.pkgs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# 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
|
||||
|
||||
# Required for rustup
|
||||
curl
|
||||
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