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:
|
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
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@
|
||||||
|
|
||||||
### tl;dr
|
### 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
|
### Requirements
|
||||||
|
|
||||||
|
|
@ -15,14 +16,40 @@ You need a Wayland-capable Linux system, Rust, and a small set of system depende
|
||||||
|
|
||||||
2. **System dependencies**
|
2. **System dependencies**
|
||||||
|
|
||||||
* Platform-specific packages are required.
|
* Platform-specific packages are required for building and running.
|
||||||
* For Ubuntu, see the [`SYSTEM_DEPS` section in the CI configuration](./.github/workflows/ci.yml).
|
* 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
|
||||||
|
|
||||||
```sh
|
`shepherd-launcher` provides a unified script system for managing dependencies, building, and running:
|
||||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
|
||||||
```
|
```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
|
### Running in development
|
||||||
|
|
||||||
|
|
|
||||||
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
|
> [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
|
## Core concepts
|
||||||
|
|
||||||
* **Launcher-first**: only one foreground activity at a time
|
* **Launcher-first**: only one foreground activity at a time
|
||||||
|
|
@ -113,16 +90,12 @@ such as:
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
`shepherd-launcher` is pre-alpha and in active development. As such, end-user
|
`shepherd-launcher` is pre-alpha and in active development. The helper at
|
||||||
binaries and installation instructions are not yet available.
|
`./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.
|
Check out this repository and run `./scripts/shepherd --help` or see
|
||||||
|
[INSTALL.md](./docs/INSTALL.md) for more.
|
||||||
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))
|
|
||||||
|
|
||||||
## Example configuration
|
## 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
|
daily_quota_seconds = 3600 # 1 hour per day
|
||||||
cooldown_seconds = 600 # 10 minute cooldown
|
cooldown_seconds = 600 # 10 minute cooldown
|
||||||
|
|
||||||
[[entries.warnings]]
|
|
||||||
seconds_before = 600
|
|
||||||
severity = "info"
|
|
||||||
message = "10 minutes left - start wrapping up!"
|
|
||||||
|
|
||||||
[[entries.warnings]]
|
[[entries.warnings]]
|
||||||
seconds_before = 120
|
seconds_before = 120
|
||||||
severity = "warn"
|
severity = "warn"
|
||||||
|
|
@ -178,7 +146,12 @@ See [config.example.toml](./config.example.toml) for more.
|
||||||
|
|
||||||
## Development
|
## 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
|
## 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
|
#!/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
|
||||||
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