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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 34 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,28 +66,28 @@ RustifyMyClaw runs locally. Messages in -> directly to your Agent, responses out
curl -fsSL https://raw.githubusercontent.com/Escoto/RustifyMyClaw/main/scripts/install.sh | bash
```

**crates.io (any platform with Rust installed):**
**Specific version:**

```bash
cargo install rustifymyclaw
curl -fsSL https://raw.githubusercontent.com/Escoto/RustifyMyClaw/main/scripts/install.sh | bash -s -- v0.1.0
```

**Windows (Chocolatey):**
**Windows (PowerShell script):**

```powershell
choco install rustifymyclaw
irm https://raw.githubusercontent.com/Escoto/RustifyMyClaw/main/scripts/install.ps1 | iex
```

**Windows (PowerShell script):**
**Windows (Chocolatey):**

```powershell
irm https://raw.githubusercontent.com/Escoto/RustifyMyClaw/main/scripts/install.ps1 | iex
choco install rustifymyclaw
```

The installer downloads the binary, verifies its SHA256 checksum, creates a starter config, and adds it to your PATH. To install a specific version:
**crates.io (any platform with Rust installed):**

```bash
curl -fsSL https://raw.githubusercontent.com/Escoto/RustifyMyClaw/main/scripts/install.sh | bash -s -- v0.1.0
cargo install rustifymyclaw
```

Or [build from source](docs/building-from-source.md).
Expand Down Expand Up @@ -149,6 +149,32 @@ Validate your config without starting the daemon:
rustifymyclaw --validate
```

### 4. Run as a Linux daemon

> [!IMPORTANT]
> **Requires explicite workspace write permissions**

```bash
curl -fsSL https://raw.githubusercontent.com/Escoto/RustifyMyClaw/main/scripts/install.sh | sudo bash -s -- --system
```

This places the binary in `/usr/local/bin/`, config in `/etc/rustifymyclaw/`, and installs a hardened systemd unit. Then:

```bash
sudo nano /etc/rustifymyclaw/config.yaml # configure workspaces/channels
sudo nano /etc/rustifymyclaw/env # add API tokens
sudo systemctl enable --now rustifymyclaw
journalctl -u rustifymyclaw -f # check logs
```

To allow your CLI Agent to **write**, use the built-in command:

```bash
sudo rustifymyclaw config allow-path /home/user/projects/my-project
```

See [docs/configuration.md](docs/configuration.md#running-as-a-systemd-service-linux) for full setup details and security hardening options.

## Backends

RustifyMyClaw proxies to whichever AI CLI tool you have installed locally. Adding a new backend is one file and one trait implementation — see [How to Add a New Backend](CLAUDE.md).
Expand Down
86 changes: 84 additions & 2 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ RustifyMyClaw resolves the config path using this priority chain:
| 1 | `-f` / `--config-file` CLI flag | `rustifymyclaw -f ./my-config.yaml` |
| 2 | `RUSTIFYMYCLAW_CONFIG` environment variable | `export RUSTIFYMYCLAW_CONFIG=~/projects/config.yaml` |
| 3 | `./config.yaml` in the current working directory | `cd my-project && rustifymyclaw` |
| 4 | Platform default | `~/.rustifymyclaw/config.yaml` (Unix) or `%APPDATA%\RustifyMyClaw\config.yaml` (Windows) |
| 4 | `~/.rustifymyclaw/config.yaml` | Per-user config (Unix) or `%APPDATA%\RustifyMyClaw\config.yaml` (Windows) |
| 5 | `/etc/rustifymyclaw/config.yaml` | System-wide config (Unix only) |

The first match wins. Use `rustifymyclaw config path` to see which path would be used from your current directory.
The first **existing** file wins. Use `rustifymyclaw config path` to see which path would be used from your current directory.

## Full annotated example

Expand Down Expand Up @@ -187,3 +188,84 @@ RustifyMyClaw watches `config.yaml` for changes using the `notify` crate. Change
| `allowed_users` | Logged as changed. Requires restart to apply. |

Invalid configs (YAML errors, missing env vars, validation failures) are logged and the running config remains in effect.

## Running as a systemd service (Linux)

### System-wide install

```bash
sudo bash scripts/install.sh --system
```

This installs the binary to `/usr/local/bin/`, creates `/etc/rustifymyclaw/` with a starter config, env file, and systemd unit.

### Manual setup

1. Copy the binary to `/usr/local/bin/` and the unit file:

```bash
sudo cp rustifymyclaw /usr/local/bin/
sudo chmod 755 /usr/local/bin/rustifymyclaw
sudo cp systemd/rustifymyclaw.service /etc/systemd/system/
sudo systemctl daemon-reload
```

2. Create the config directory and files:

```bash
sudo mkdir -p /etc/rustifymyclaw
sudo cp examples/config.yaml /etc/rustifymyclaw/config.yaml
sudo cp systemd/env.example /etc/rustifymyclaw/env
sudo chmod 640 /etc/rustifymyclaw/config.yaml
sudo chmod 600 /etc/rustifymyclaw/env
```

3. Edit `config.yaml` with your workspaces and channels.

```bash
sudo nano /etc/rustifymyclaw/config.yaml
```

4. Add API tokens to `env`:

```bash
sudo nano /etc/rustifymyclaw/env
```

5. Enable and start:

```bash
sudo systemctl enable --now rustifymyclaw
```

### Enable daemon to access your workspaces / directory permissions

Because `DynamicUser=yes` runs the daemon as an ephemeral user, workspace directories are read-only by default. Without this step your CLI agent can still read the project and answer questions about it, but cannot write or edit files. If you expect your agent to have write permissions, you must explicitly allow each workspace path.

Use the built-in command:

```bash
sudo rustifymyclaw config allow-path /home/user/projects/my-project
```

Or, manually:

```ini
# /etc/systemd/system/rustifymyclaw.service.d/override.conf
[Service]
ReadWritePaths=/home/user/projects/my-project
```
Then reload: `sudo systemctl daemon-reload && sudo systemctl restart rustifymyclaw`

### Security hardening

The included systemd unit uses:

- **`DynamicUser=yes`** — allocates an ephemeral service user, no manual user creation.
- **`NoNewPrivileges=yes`** — the process cannot gain new privileges.
- **`ProtectSystem=strict`** — the filesystem is read-only except for allowed paths.
- **`ProtectHome=read-only`** — home directories are visible but not writable.
- **`PrivateTmp=yes`** — isolated `/tmp` namespace.
- **`EnvironmentFile`** — secrets loaded from `/etc/rustifymyclaw/env` (mode 600).


152 changes: 124 additions & 28 deletions scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,32 @@ set -euo pipefail

REPO="Escoto/RustifyMyClaw"
BINARY_NAME="rustifymyclaw"
GITHUB_API="https://api.github.com/repos/${REPO}"
SYSTEM_INSTALL=false

# Paths — overridden when --system is passed
INSTALL_DIR="${HOME}/.rustifymyclaw"
CONFIG_FILE="${INSTALL_DIR}/config.yaml"
GITHUB_API="https://api.github.com/repos/${REPO}"

# ---------------------------------------------------------------------------
# Argument parsing
# ---------------------------------------------------------------------------
for arg in "$@"; do
case "$arg" in
--system) SYSTEM_INSTALL=true ;;
esac
done

if $SYSTEM_INSTALL; then
if [ "$(id -u)" -ne 0 ]; then
echo "Error: --system install requires root. Run with sudo." >&2
exit 1
fi
INSTALL_DIR="/usr/local/bin"
CONFIG_DIR="/etc/rustifymyclaw"
CONFIG_FILE="${CONFIG_DIR}/config.yaml"
ENV_FILE="${CONFIG_DIR}/env"
fi

# ---------------------------------------------------------------------------
# Output helpers
Expand Down Expand Up @@ -164,15 +187,23 @@ download_and_verify() {
# Install binary
# ---------------------------------------------------------------------------
install_binary() {
mkdir -p "$INSTALL_DIR"

info "Extracting to ${INSTALL_DIR}..."
tar xzf "$ARTIFACT_PATH" -C "$INSTALL_DIR"
chmod +x "${INSTALL_DIR}/${BINARY_NAME}"

# macOS: remove quarantine attribute
if [ "$(uname -s)" = "Darwin" ]; then
xattr -d com.apple.quarantine "${INSTALL_DIR}/${BINARY_NAME}" 2>/dev/null || true
if $SYSTEM_INSTALL; then
info "Extracting binary to ${INSTALL_DIR}..."
local tmp_extract
tmp_extract=$(mktemp -d)
tar xzf "$ARTIFACT_PATH" -C "$tmp_extract"
install -m 755 "${tmp_extract}/${BINARY_NAME}" "${INSTALL_DIR}/${BINARY_NAME}"
rm -rf "$tmp_extract"
else
mkdir -p "$INSTALL_DIR"
info "Extracting to ${INSTALL_DIR}..."
tar xzf "$ARTIFACT_PATH" -C "$INSTALL_DIR"
chmod +x "${INSTALL_DIR}/${BINARY_NAME}"

# macOS: remove quarantine attribute
if [ "$(uname -s)" = "Darwin" ]; then
xattr -d com.apple.quarantine "${INSTALL_DIR}/${BINARY_NAME}" 2>/dev/null || true
fi
fi

info "Binary installed at ${INSTALL_DIR}/${BINARY_NAME}"
Expand All @@ -182,18 +213,57 @@ install_binary() {
# Config scaffold
# ---------------------------------------------------------------------------
write_config() {
if $SYSTEM_INSTALL; then
mkdir -p "$CONFIG_DIR"
chmod 755 "$CONFIG_DIR"
fi

if [ -f "$CONFIG_FILE" ]; then
info "Existing config preserved at ${CONFIG_FILE}"
else
local config_url="https://raw.githubusercontent.com/${REPO}/main/examples/config.yaml"
info "Downloading example config..."
download_file "$config_url" "$CONFIG_FILE" || \
die "Failed to download example config from ${config_url}"
if $SYSTEM_INSTALL; then
chmod 640 "$CONFIG_FILE"
else
chmod 600 "$CONFIG_FILE"
fi
info "Starter config created at ${CONFIG_FILE}"
fi

if $SYSTEM_INSTALL; then
if [ ! -f "$ENV_FILE" ]; then
local env_url="https://raw.githubusercontent.com/${REPO}/main/systemd/env.example"
info "Downloading env template..."
download_file "$env_url" "$ENV_FILE" || \
die "Failed to download env template from ${env_url}"
chmod 600 "$ENV_FILE"
info "Env file created at ${ENV_FILE} (chmod 600)"
else
info "Existing env file preserved at ${ENV_FILE}"
fi
fi
}

# ---------------------------------------------------------------------------
# Systemd unit installation (--system only)
# ---------------------------------------------------------------------------
install_systemd_unit() {
if ! $SYSTEM_INSTALL; then
return
fi

local config_url="https://raw.githubusercontent.com/${REPO}/main/examples/config.yaml"
info "Downloading example config..."
download_file "$config_url" "$CONFIG_FILE" || \
die "Failed to download example config from ${config_url}"
local unit_url="https://raw.githubusercontent.com/${REPO}/main/systemd/rustifymyclaw.service"
local unit_dest="/etc/systemd/system/rustifymyclaw.service"

chmod 600 "$CONFIG_FILE"
info "Starter config created at ${CONFIG_FILE}"
info "Installing systemd unit..."
download_file "$unit_url" "$unit_dest" || \
die "Failed to download systemd unit file"
chmod 644 "$unit_dest"
systemctl daemon-reload
info "Systemd unit installed at ${unit_dest}"
}

# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -247,30 +317,56 @@ main() {
printf "\n RustifyMyClaw Installer\n\n"

detect_platform
resolve_version "${1:-}"
# Pass first non-flag argument as version
local ver_arg=""
for arg in "$@"; do
case "$arg" in
--*) ;;
*) ver_arg="$arg"; break ;;
esac
done
resolve_version "${ver_arg}"
info "Installing RustifyMyClaw ${VERSION} for ${PLATFORM}"

download_and_verify
install_binary
write_config
update_path
install_systemd_unit
if ! $SYSTEM_INSTALL; then
update_path
fi

printf "\n"
info "RustifyMyClaw ${VERSION} installed successfully!"
printf "\n"
printf " Binary: %s/%s\n" "$INSTALL_DIR" "$BINARY_NAME"
printf " Config: %s\n" "$CONFIG_FILE"
printf "\n"
printf " Next steps:\n"
printf " 1. Edit %s\n" "$CONFIG_FILE"
printf " - Set your workspace directory\n"
printf " - Configure your channel (Telegram / WhatsApp / Slack)\n"
printf " - Set allowed_users\n"
printf " 2. Export required environment variables:\n"
printf " export TELEGRAM_BOT_TOKEN=your_token_here\n"
printf " 3. Start the daemon:\n"
printf " rustifymyclaw\n"
printf " 4. Open a new terminal or run: source ~/.bashrc\n"

if $SYSTEM_INSTALL; then
printf " Next steps:\n"
printf " 1. Edit %s\n" "$CONFIG_FILE"
printf " - Set your workspace directory\n"
printf " - Configure your channel (Telegram / WhatsApp / Slack)\n"
printf " - Set allowed_users\n"
printf " 2. Add API tokens to %s\n" "$ENV_FILE"
printf " 3. Enable and start the service:\n"
printf " sudo systemctl enable --now rustifymyclaw\n"
printf " 4. Check logs:\n"
printf " journalctl -u rustifymyclaw -f\n"
else
printf " Next steps:\n"
printf " 1. Edit %s\n" "$CONFIG_FILE"
printf " - Set your workspace directory\n"
printf " - Configure your channel (Telegram / WhatsApp / Slack)\n"
printf " - Set allowed_users\n"
printf " 2. Export required environment variables:\n"
printf " export TELEGRAM_BOT_TOKEN=your_token_here\n"
printf " 3. Start the daemon:\n"
printf " rustifymyclaw\n"
printf " 4. Open a new terminal or run: source ~/.bashrc\n"
fi

printf "\n"
printf " Full config reference:\n"
printf " https://github.com/%s/blob/main/docs/configuration.md\n\n" "$REPO"
Expand Down
Loading
Loading