docs: add CLAUDE.md, move backup guide into installer, drop linux-to-sync
- CLAUDE.md: full contributor guide — service template, all helpers, globals, DRY_RUN convention, Caddy wiring, non-Docker patterns - services/backup.sh: print backup strategy guide (Kopia/Borg/rsync/ rsnapshot + when to use each) at the start of install_backup() - README.md: remove standalone backup section, fix broken backup row, inline base package list, add CLAUDE.md to layout - services/linux-to-sync.sh: deleted (never worked) - setup.sh: remove linux-to-sync from is_installed() https://claude.ai/code/session_019XgsQ13XKm4Zj3cNsDNwHj
This commit is contained in:
@@ -0,0 +1,236 @@
|
|||||||
|
# CLAUDE.md — ubuntu-post-install contributor guide
|
||||||
|
|
||||||
|
Context for adding or modifying services. Read this before touching any
|
||||||
|
service file so the result matches what's already here.
|
||||||
|
|
||||||
|
## How the system works
|
||||||
|
|
||||||
|
`setup.sh` sources `lib/common.sh` then globs every `services/*.sh` file.
|
||||||
|
Each service file self-registers and defines its install function. Nothing
|
||||||
|
in `setup.sh` needs to change when you add a service — just add the file.
|
||||||
|
|
||||||
|
The wizard groups services by category (from `register_service`), shows a
|
||||||
|
checklist per group, and calls `install_<name>()` for each selected item.
|
||||||
|
`--list`, `--dry-run`, and `--unattended` all work automatically.
|
||||||
|
|
||||||
|
## Adding a service — the three-step rule
|
||||||
|
|
||||||
|
1. Create `services/<name>.sh` (kebab-case filename)
|
||||||
|
2. Call `register_service` at the top of the file
|
||||||
|
3. Define `install_<name>()` (hyphens → underscores in function name)
|
||||||
|
|
||||||
|
That's it. The menu picks it up on the next run.
|
||||||
|
|
||||||
|
## Minimal Docker service template
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# services/my-tool.sh — One-line description.
|
||||||
|
# Part of the modular post-install system (sourced by setup.sh).
|
||||||
|
|
||||||
|
register_service my-tool utilities "What it does (My Tool)" 8080
|
||||||
|
|
||||||
|
install_my_tool() {
|
||||||
|
require_docker || return 1
|
||||||
|
|
||||||
|
local DIR="$DOCKER_DIR/my-tool"
|
||||||
|
|
||||||
|
if [ "$DRY_RUN" = true ]; then
|
||||||
|
echo "[DRY-RUN] Would create $DIR with docker-compose.yml"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$DIR"
|
||||||
|
ensure_docker_dir_ownership "$DIR"
|
||||||
|
cd "$DIR" || return 1
|
||||||
|
|
||||||
|
cat > docker-compose.yml << 'EOF'
|
||||||
|
name: my-tool
|
||||||
|
services:
|
||||||
|
my-tool:
|
||||||
|
image: vendor/my-tool:latest
|
||||||
|
container_name: my-tool
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
volumes:
|
||||||
|
- ./data:/data
|
||||||
|
EOF
|
||||||
|
|
||||||
|
configure_caddy_for_service "My Tool" "8080" "my-tool"
|
||||||
|
|
||||||
|
write_readme "$DIR" << 'MD'
|
||||||
|
# My Tool
|
||||||
|
Brief description.
|
||||||
|
|
||||||
|
## Manage
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
docker compose down
|
||||||
|
docker compose logs -f
|
||||||
|
docker compose pull && docker compose up -d
|
||||||
|
```
|
||||||
|
MD
|
||||||
|
|
||||||
|
local START=""
|
||||||
|
prompt_yn "Start My Tool now? (y/n):" "y" START
|
||||||
|
if [ "$START" = "y" ] || [ "$START" = "Y" ]; then
|
||||||
|
docker compose up -d \
|
||||||
|
&& log_success "My Tool started" \
|
||||||
|
|| log_warning "Start failed — check: docker compose logs"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## register_service signature
|
||||||
|
|
||||||
|
```bash
|
||||||
|
register_service <name> <group> "<description>" [port]
|
||||||
|
```
|
||||||
|
|
||||||
|
- `name` — kebab-case, matches the filename and the `install_` function
|
||||||
|
- `group` — one of the categories below; determines which menu it appears in
|
||||||
|
- `description` — shown in `--list` and the menu checklist
|
||||||
|
- `port` — optional; informational only (not used by the framework)
|
||||||
|
|
||||||
|
## Available globals
|
||||||
|
|
||||||
|
| Variable | Value |
|
||||||
|
|----------|-------|
|
||||||
|
| `DOCKER_DIR` | `~/docker` — parent for all Docker service directories |
|
||||||
|
| `ACTUAL_USER` | The non-root user that invoked sudo |
|
||||||
|
| `ACTUAL_HOME` | Home directory of `ACTUAL_USER` |
|
||||||
|
| `SITE_TZ` | Timezone from site config, e.g. `America/New_York` |
|
||||||
|
| `SITE_DOMAIN` | Base domain from site config, e.g. `example.com` |
|
||||||
|
| `SITE_CADDY_NET` | Docker network name for Caddy (default: `caddy_net`) |
|
||||||
|
| `DRY_RUN` | `true`/`false` — set by `--dry-run` flag |
|
||||||
|
| `UNATTENDED` | `true`/`false` — set by `--unattended` flag |
|
||||||
|
|
||||||
|
## Available helpers (lib/common.sh)
|
||||||
|
|
||||||
|
### Logging
|
||||||
|
|
||||||
|
```bash
|
||||||
|
log_info "message" # blue [INFO]
|
||||||
|
log_success "message" # green [OK]
|
||||||
|
log_warning "message" # yellow [WARN]
|
||||||
|
log_error "message" # red [ERROR]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Prompts — honor `UNATTENDED` automatically
|
||||||
|
|
||||||
|
```bash
|
||||||
|
prompt_yn "Question? (y/n):" "default_y_or_n" VARNAME
|
||||||
|
prompt_text "Question? [default]:" "default" VARNAME
|
||||||
|
```
|
||||||
|
|
||||||
|
When `UNATTENDED=true` both functions skip the prompt and use the default.
|
||||||
|
|
||||||
|
### Pre-flight
|
||||||
|
|
||||||
|
```bash
|
||||||
|
require_root # exits with an error if not running as root
|
||||||
|
require_docker # installs Docker CE + Compose plugin if missing, then returns
|
||||||
|
```
|
||||||
|
|
||||||
|
### Execution and ownership
|
||||||
|
|
||||||
|
```bash
|
||||||
|
run_cmd COMMAND [args...] # no-ops in DRY_RUN, executes otherwise
|
||||||
|
ensure_docker_dir_ownership DIR... # chown -R ACTUAL_USER:ACTUAL_USER (skips in DRY_RUN)
|
||||||
|
generate_password [length] # alphanumeric random string, default 32 chars
|
||||||
|
pip_user_install PACKAGE... # pip3 --user with --break-system-packages on 24.04+
|
||||||
|
```
|
||||||
|
|
||||||
|
### Caddy reverse proxy
|
||||||
|
|
||||||
|
```bash
|
||||||
|
configure_caddy_for_service "Display Name" "PORT" "default-subdomain" ["extra-block"]
|
||||||
|
```
|
||||||
|
|
||||||
|
Prompts the user for a domain, appends a site block to the Caddyfile, and
|
||||||
|
reloads Caddy. No-ops silently if Caddy isn't installed. The fourth argument
|
||||||
|
is an optional string inserted verbatim inside the Caddy site block (use it
|
||||||
|
for `import authelia` or custom matchers).
|
||||||
|
|
||||||
|
### README generation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
write_readme "$DIR" << 'MD'
|
||||||
|
# Title
|
||||||
|
Content
|
||||||
|
MD
|
||||||
|
```
|
||||||
|
|
||||||
|
Writes `$DIR/README.md` (creates the directory if needed). No-ops in DRY_RUN.
|
||||||
|
Every Docker service should call this so `~/docker/<name>/README.md` is
|
||||||
|
self-documenting on the deployed box.
|
||||||
|
|
||||||
|
## Categories
|
||||||
|
|
||||||
|
| Group | Purpose |
|
||||||
|
|-------|---------|
|
||||||
|
| `base` | CLI packages installed on every box |
|
||||||
|
| `homelab` | Core infrastructure — reverse proxy, auth, intrusion prevention |
|
||||||
|
| `utilities` | Self-hosted web apps — budget, DNS, files, monitoring, VPN, etc. |
|
||||||
|
| `media` | Media servers, photo backup, disc ripping |
|
||||||
|
| `cameras` | NVR and camera tooling (Frigate) |
|
||||||
|
| `gaming` | Game servers, cloud gaming (Wolf), emulation |
|
||||||
|
| `extras` | Non-Docker tools and scripts |
|
||||||
|
| `backup` | Backup solutions |
|
||||||
|
|
||||||
|
## Non-Docker services
|
||||||
|
|
||||||
|
Not everything is a container. For apt-based or git-clone–based services,
|
||||||
|
skip `require_docker` and the Docker helpers. See `services/base.sh` (apt
|
||||||
|
packages + Charm repo) and `services/crowdsec.sh` (official apt repo) as
|
||||||
|
reference patterns.
|
||||||
|
|
||||||
|
For non-Docker services the default `is_installed` check in `setup.sh`
|
||||||
|
looks for `$DOCKER_DIR/$name`, which won't exist. Add a case to the
|
||||||
|
`is_installed()` function in `setup.sh` so the `[installed]` marker appears
|
||||||
|
correctly in the menu:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# In setup.sh → is_installed()
|
||||||
|
my-tool) command -v my-tool >/dev/null 2>&1 ;;
|
||||||
|
```
|
||||||
|
|
||||||
|
Docker services use the default case and don't need an entry.
|
||||||
|
|
||||||
|
## DRY_RUN convention
|
||||||
|
|
||||||
|
Every `install_*` function must check `$DRY_RUN` before touching the
|
||||||
|
filesystem, installing packages, or starting containers. The pattern is:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
if [ "$DRY_RUN" = true ]; then
|
||||||
|
echo "[DRY-RUN] Would do X"
|
||||||
|
echo "[DRY-RUN] Would do Y"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
Put the check early — after any pure-display output (banners, info text)
|
||||||
|
but before the first write.
|
||||||
|
|
||||||
|
## .env files and secrets
|
||||||
|
|
||||||
|
Generate passwords with `generate_password` (never hardcode them).
|
||||||
|
Write secrets to `.env` files in the service directory, owned by
|
||||||
|
`ACTUAL_USER`, permissions 600. Document every variable with a comment
|
||||||
|
in the `.env` heredoc so the user knows what to change later.
|
||||||
|
|
||||||
|
## Caddy network wiring
|
||||||
|
|
||||||
|
Services that need to reach Caddy (or each other) over Docker networking
|
||||||
|
should join the `$SITE_CADDY_NET` network. Add to `docker-compose.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
networks:
|
||||||
|
caddy_net:
|
||||||
|
external: true
|
||||||
|
name: ${CADDY_NET:-caddy_net}
|
||||||
|
```
|
||||||
|
|
||||||
|
And read the network name from `.env` using `CADDY_NET=$SITE_CADDY_NET`.
|
||||||
@@ -49,7 +49,7 @@ sudo ./setup.sh --unattended base # non-interactive, use defaults
|
|||||||
## What the wizard does
|
## What the wizard does
|
||||||
|
|
||||||
**First run:**
|
**First run:**
|
||||||
1. Installs essential CLI packages (see [Base packages](#base-packages) below)
|
1. Installs essential CLI packages (`net-tools`, `ncdu`, `git`, `curl`, `wget`, `htop`, `tree`, `zip`/`unzip`, `ca-certificates`, `gnupg`, `jq`, `rsync`, `glow`)
|
||||||
2. Checks for Docker CE + Compose plugin; prompts to install if missing
|
2. Checks for Docker CE + Compose plugin; prompts to install if missing
|
||||||
3. Offers to set **site defaults** — timezone, base domain, Caddy Docker network —
|
3. Offers to set **site defaults** — timezone, base domain, Caddy Docker network —
|
||||||
so every service picks them up automatically instead of asking each time
|
so every service picks them up automatically instead of asking each time
|
||||||
@@ -72,181 +72,10 @@ Update them any time with `sudo ./setup.sh configure`.
|
|||||||
| `cameras` | `frigate`, `frigate-audio`, `frigate-notify`, `sky-cam` |
|
| `cameras` | `frigate`, `frigate-audio`, `frigate-notify`, `sky-cam` |
|
||||||
| `gaming` | `js99er`, `minecraft`, `wolf`, `wolf-pair` |
|
| `gaming` | `js99er`, `minecraft`, `wolf`, `wolf-pair` |
|
||||||
| `extras` | `silent-send`, `sync-cc` |
|
| `extras` | `silent-send`, `sync-cc` |
|
||||||
| `backup` | 'Kopia, Borg (encrypted, dedup, scheduled)', rsync (plain or with --link-dest)' rsnapshot |
|
| `backup` | `backup` — Kopia (built-in, encrypted, dedup, scheduled); Borg, rsync, rsnapshot also supported (see installer) |
|
||||||
|
|
||||||
Run `./setup.sh --list` to see descriptions.
|
Run `./setup.sh --list` to see descriptions.
|
||||||
|
|
||||||
## Backup
|
|
||||||
|
|
||||||
The `backup` service (under the `backup` group) sets up **Kopia** — but you are
|
|
||||||
not limited to it. Below is a guide to every backup strategy in this repo,
|
|
||||||
with advice on when to reach for each one.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Kopia — the built-in `backup` service
|
|
||||||
|
|
||||||
**What it does:** block-level deduplication + zstd compression + AES-256
|
|
||||||
encryption, scheduled automatically via a systemd timer (cron fallback).
|
|
||||||
Each run snapshots configured paths and retains versions according to a
|
|
||||||
policy (latest N, daily, weekly, monthly). An optional `sync-to` step
|
|
||||||
mirrors the whole encrypted repository to another computer or a cloud
|
|
||||||
bucket (SFTP, Backblaze B2, S3, rclone).
|
|
||||||
|
|
||||||
**Install:** `sudo ./setup.sh backup`
|
|
||||||
|
|
||||||
**When to use:** files that change *constantly* — Minecraft region files,
|
|
||||||
game saves, Steam Proton prefixes, Docker config volumes. Every changed
|
|
||||||
block is stored once; identical blocks across snapshots share storage.
|
|
||||||
Kopia is the right default for homelab data that changes on every write.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Borg Backup
|
|
||||||
|
|
||||||
**What it is:** `borgbackup` — chunk-based deduplication + lz4/zstd/zlib
|
|
||||||
compression + AES-CTR encryption. Mature, battle-tested, wide ecosystem
|
|
||||||
(Borgmatic for YAML-driven automation, Vorta for a GUI).
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo apt install borgbackup
|
|
||||||
|
|
||||||
# initialise a repo
|
|
||||||
borg init --encryption=repokey /backups/borg-repo
|
|
||||||
|
|
||||||
# take a snapshot
|
|
||||||
borg create --stats --progress /backups/borg-repo::'{hostname}-{now}' ~/docker
|
|
||||||
|
|
||||||
# list snapshots
|
|
||||||
borg list /backups/borg-repo
|
|
||||||
|
|
||||||
# restore
|
|
||||||
borg extract /backups/borg-repo::snapshot-name
|
|
||||||
```
|
|
||||||
|
|
||||||
**When to use:** same class of problem as Kopia — frequently-changing files
|
|
||||||
where deduplication pays off. Choose Borg over Kopia if you prefer its
|
|
||||||
mature ecosystem, Borgmatic config files, or need to share a repo across
|
|
||||||
multiple machines. Both are excellent; pick the one whose tooling you
|
|
||||||
prefer.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### rsync — plain mirror (no versioning)
|
|
||||||
|
|
||||||
**What it is:** `rsync -av --delete SOURCE/ DEST/` — fast one-way mirror.
|
|
||||||
`--delete` removes files in `DEST` that no longer exist in `SOURCE`.
|
|
||||||
The destination is a plain readable copy of the source; no special tool
|
|
||||||
needed to browse or restore.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
rsync -av --delete /source/media/ /backups/media/
|
|
||||||
```
|
|
||||||
|
|
||||||
**When to use:** files that rarely or never change — media libraries,
|
|
||||||
ROM collections, game installs you could re-download but prefer to keep
|
|
||||||
locally. You just need *a copy*, not versioning. rsync is lightweight,
|
|
||||||
transparent, and the destination needs no special format.
|
|
||||||
|
|
||||||
**Not suitable for:** files that change often, because one bad `--delete`
|
|
||||||
run (source corruption, accidental deletion) immediately destroys the
|
|
||||||
only copy in the destination.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### rsync `--link-dest` — versioned snapshots with original structure
|
|
||||||
|
|
||||||
**What it is:** each backup run creates a new dated directory. Unchanged
|
|
||||||
files are **hard-linked** from the previous backup rather than copied, so
|
|
||||||
unchanged files cost no extra disk space. Every dated directory looks like
|
|
||||||
a complete independent snapshot of the source — original folder structure
|
|
||||||
preserved, no rsnapshot-style naming convention.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
#!/bin/bash
|
|
||||||
DEST=/backups/snapshots
|
|
||||||
PREV="$DEST/$(ls -1 "$DEST" | tail -1)" # most recent snapshot
|
|
||||||
TODAY="$DEST/$(date +%F)"
|
|
||||||
|
|
||||||
rsync -av --delete --link-dest="$PREV" /source/ "$TODAY/"
|
|
||||||
```
|
|
||||||
|
|
||||||
Run this daily (via cron or a systemd timer) and you get:
|
|
||||||
|
|
||||||
```
|
|
||||||
/backups/snapshots/
|
|
||||||
2024-01-13/ ← full copy (first run)
|
|
||||||
2024-01-14/ ← only changed files stored; rest are hard links
|
|
||||||
2024-01-15/ ← same
|
|
||||||
```
|
|
||||||
|
|
||||||
If yesterday's run with `--delete` removed everything from the source
|
|
||||||
(accidental wipe, filesystem corruption), today's snapshot will also be
|
|
||||||
empty — but `2024-01-14/` and `2024-01-13/` are untouched and fully
|
|
||||||
restorable with a plain `cp -al` or `rsync`.
|
|
||||||
|
|
||||||
**When to use:** general files where you want versioned point-in-time
|
|
||||||
backups **and** need the original folder structure preserved. No extra
|
|
||||||
tool required to restore — every dated folder is browsable with `ls` and
|
|
||||||
copyable with standard Unix tools. Simpler than Borg/Kopia; no encryption
|
|
||||||
or deduplication across snapshot boundaries.
|
|
||||||
|
|
||||||
**Compared to rsnapshot:** rsync `--link-dest` keeps your own naming and
|
|
||||||
structure; rsnapshot imposes `daily.0/`, `daily.1/`, etc. and manages
|
|
||||||
rotation automatically. `--link-dest` gives you more control; rsnapshot
|
|
||||||
gives you easier automation.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### rsnapshot — automated versioned snapshots
|
|
||||||
|
|
||||||
**What it is:** a wrapper around rsync that manages hard-link snapshots
|
|
||||||
automatically using a retention scheme (`hourly.0`, `daily.0`, `weekly.0`,
|
|
||||||
…). Configure sources and retention in `/etc/rsnapshot.conf`, then run on
|
|
||||||
a schedule.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo apt install rsnapshot
|
|
||||||
# edit /etc/rsnapshot.conf — set snapshot_root, backup sources, retain counts
|
|
||||||
rsnapshot daily # run manually or via cron
|
|
||||||
rsnapshot -t daily # dry-run / test config
|
|
||||||
```
|
|
||||||
|
|
||||||
The resulting layout looks like:
|
|
||||||
|
|
||||||
```
|
|
||||||
/backups/rsnapshot/
|
|
||||||
daily.0/ ← most recent
|
|
||||||
daily.1/
|
|
||||||
daily.2/
|
|
||||||
weekly.0/
|
|
||||||
```
|
|
||||||
|
|
||||||
Each interval directory contains a full view of the source, hard-linked
|
|
||||||
where files are unchanged.
|
|
||||||
|
|
||||||
**When to use:** you want automated versioned backups without writing your
|
|
||||||
own `--link-dest` rotation script, and you don't mind that the destination
|
|
||||||
uses rsnapshot's own directory naming rather than your original structure.
|
|
||||||
Good for simple setups (home directories, config files) where the rotation
|
|
||||||
automation saves time.
|
|
||||||
|
|
||||||
**Not ideal for:** frequently-changing large files (game saves, databases)
|
|
||||||
— use Kopia or Borg instead for efficient deduplication across many
|
|
||||||
changed blocks.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Choosing the right tool
|
|
||||||
|
|
||||||
| Scenario | Recommended tool |
|
|
||||||
|----------|-----------------|
|
|
||||||
| Minecraft worlds, game saves, Steam prefixes — change on every write | **Kopia** (built-in) or **Borg** |
|
|
||||||
| Media library, ROMs — rarely change, just need a copy | **rsync plain** |
|
|
||||||
| General files — want versioning, want original folder structure | **rsync `--link-dest`** |
|
|
||||||
| Want automated rotation without scripting `--link-dest` yourself | **rsnapshot** |
|
|
||||||
| Multi-machine deduplicated repo, YAML-driven config (Borgmatic) | **Borg** |
|
|
||||||
|
|
||||||
## Layout
|
## Layout
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -254,6 +83,7 @@ setup.sh dispatcher — wizard, direct install, --list, --dry-run
|
|||||||
lib/common.sh shared helpers: logging, prompts, site config, OS detection
|
lib/common.sh shared helpers: logging, prompts, site config, OS detection
|
||||||
services/ one file per service (self-registering)
|
services/ one file per service (self-registering)
|
||||||
extras/ non-Docker assets bundled with the repo (e.g. sync_cc.py)
|
extras/ non-Docker assets bundled with the repo (e.g. sync_cc.py)
|
||||||
|
CLAUDE.md contributor guide — how to add services, available helpers
|
||||||
```
|
```
|
||||||
|
|
||||||
## Managing installed services
|
## Managing installed services
|
||||||
|
|||||||
@@ -25,6 +25,68 @@ register_service backup backup "Automatic encrypted backups (Kopia)"
|
|||||||
install_backup() {
|
install_backup() {
|
||||||
log_info "Setting up automatic encrypted backups (Kopia)..."
|
log_info "Setting up automatic encrypted backups (Kopia)..."
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "╔═══════════════════════════════════════════════════════════════════╗"
|
||||||
|
echo "║ BACKUP STRATEGIES — choose the right tool for your data ║"
|
||||||
|
echo "╚═══════════════════════════════════════════════════════════════════╝"
|
||||||
|
echo ""
|
||||||
|
echo " This installer sets up Kopia (the recommended default), but here"
|
||||||
|
echo " is a quick guide to all available options so you can pick the"
|
||||||
|
echo " right tool for each type of data."
|
||||||
|
echo ""
|
||||||
|
echo " ┌─────────────────────────────────────────────────────────────────┐"
|
||||||
|
echo " │ KOPIA (installed here) — block-level dedup + zstd + encryption │"
|
||||||
|
echo " │ Use for: files that change constantly — Minecraft worlds, │"
|
||||||
|
echo " │ game saves, Steam prefixes, Docker config volumes. │"
|
||||||
|
echo " │ Changed blocks are stored once; old blocks are shared. │"
|
||||||
|
echo " │ Restores via: kopia snapshot list / kopia restore │"
|
||||||
|
echo " └─────────────────────────────────────────────────────────────────┘"
|
||||||
|
echo ""
|
||||||
|
echo " ┌─────────────────────────────────────────────────────────────────┐"
|
||||||
|
echo " │ BORG (sudo apt install borgbackup) — chunk dedup + encryption │"
|
||||||
|
echo " │ Use for: same as Kopia. Choose Borg if you prefer Borgmatic │"
|
||||||
|
echo " │ (YAML config), Vorta (GUI), or multi-machine repos. │"
|
||||||
|
echo " │ borg init / borg create / borg list / borg extract │"
|
||||||
|
echo " └─────────────────────────────────────────────────────────────────┘"
|
||||||
|
echo ""
|
||||||
|
echo " ┌─────────────────────────────────────────────────────────────────┐"
|
||||||
|
echo " │ RSYNC plain rsync -av --delete /src/ /dest/ │"
|
||||||
|
echo " │ Use for: media, ROMs, files that rarely change and you just │"
|
||||||
|
echo " │ need a copy. Fast, transparent — no special restore tool. │"
|
||||||
|
echo " │ Not suitable for files that change often (one bad --delete │"
|
||||||
|
echo " │ run immediately destroys the only copy in the destination). │"
|
||||||
|
echo " └─────────────────────────────────────────────────────────────────┘"
|
||||||
|
echo ""
|
||||||
|
echo " ┌─────────────────────────────────────────────────────────────────┐"
|
||||||
|
echo " │ RSYNC --link-dest versioned snapshots, original folder layout │"
|
||||||
|
echo " │ Creates dated dirs (2024-01-15/, 2024-01-16/, …). │"
|
||||||
|
echo " │ Unchanged files are hard-linked — cost no extra disk space. │"
|
||||||
|
echo " │ Each dated dir is a complete, browsable snapshot of the │"
|
||||||
|
echo " │ source. Original folder structure preserved (unlike │"
|
||||||
|
echo " │ rsnapshot). If today's --delete wiped something, yesterday's │"
|
||||||
|
echo " │ dated dir is untouched. │"
|
||||||
|
echo " │ Use for: general files where you want versioning + readable │"
|
||||||
|
echo " │ snapshot dirs without a special restore tool. │"
|
||||||
|
echo " └─────────────────────────────────────────────────────────────────┘"
|
||||||
|
echo ""
|
||||||
|
echo " ┌─────────────────────────────────────────────────────────────────┐"
|
||||||
|
echo " │ RSNAPSHOT (sudo apt install rsnapshot) — automated rotation │"
|
||||||
|
echo " │ Wraps rsync with a retention scheme (daily.0, weekly.0, …). │"
|
||||||
|
echo " │ Hard-links unchanged files like --link-dest, but dirs are │"
|
||||||
|
echo " │ named by rsnapshot (not your original structure). │"
|
||||||
|
echo " │ Use for: automated versioning without scripting --link-dest, │"
|
||||||
|
echo " │ when the rsnapshot naming convention doesn't bother you. │"
|
||||||
|
echo " └─────────────────────────────────────────────────────────────────┘"
|
||||||
|
echo ""
|
||||||
|
echo " Quick reference:"
|
||||||
|
echo " Constantly-changing data (saves, worlds, configs) → Kopia or Borg"
|
||||||
|
echo " Media / ROMs (rarely changes, just need a copy) → rsync plain"
|
||||||
|
echo " Versioned snapshots, keep original folder layout → rsync --link-dest"
|
||||||
|
echo " Versioned snapshots, want auto rotation scripted → rsnapshot"
|
||||||
|
echo ""
|
||||||
|
echo " Continuing with Kopia setup..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
# ── Repo-conventional paths ──────────────────────────────────────────────
|
# ── Repo-conventional paths ──────────────────────────────────────────────
|
||||||
local BACKUP_DIR="$DOCKER_DIR/backup"
|
local BACKUP_DIR="$DOCKER_DIR/backup"
|
||||||
local CONF_FILE="$BACKUP_DIR/backup.conf" # editable settings
|
local CONF_FILE="$BACKUP_DIR/backup.conf" # editable settings
|
||||||
|
|||||||
@@ -1,130 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# services/linux-to-sync.sh — Clone the private linux-to-sync repository.
|
|
||||||
# Part of the modular post-install system (sourced by setup.sh).
|
|
||||||
#
|
|
||||||
# Ported from ubuntu-post-install-24.04-crowdsec.sh (# ---- LINUX-TO-SYNC ----).
|
|
||||||
# Clones outis1one/linux-to-sync to ~/linux-to-sync via SSH or HTTPS+PAT.
|
|
||||||
# No server/container — this is a personal sync/config repo.
|
|
||||||
|
|
||||||
register_service linux-to-sync extras "Personal sync & config scripts (linux-to-sync private repo)"
|
|
||||||
|
|
||||||
install_linux-to-sync() {
|
|
||||||
local SYNC_DIR="$ACTUAL_HOME/linux-to-sync"
|
|
||||||
|
|
||||||
if [ "$DRY_RUN" = true ]; then
|
|
||||||
echo "[DRY-RUN] linux-to-sync would:"
|
|
||||||
echo " - Clone outis1one/linux-to-sync to $SYNC_DIR"
|
|
||||||
echo " - Authenticate via SSH key or GitHub Personal Access Token"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ── Re-run: already cloned → offer pull ──────────────────────────────────
|
|
||||||
if [ -d "$SYNC_DIR/.git" ]; then
|
|
||||||
log_info "linux-to-sync already cloned at $SYNC_DIR"
|
|
||||||
local DO_PULL=""
|
|
||||||
prompt_yn "Pull latest changes? (y/n) [y]:" "y" DO_PULL
|
|
||||||
if [[ ${DO_PULL:-y} =~ ^[Yy]$ ]]; then
|
|
||||||
if sudo -u "$ACTUAL_USER" git -C "$SYNC_DIR" pull; then
|
|
||||||
log_success "linux-to-sync updated"
|
|
||||||
else
|
|
||||||
log_warning "git pull failed — check connectivity and credentials"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo " Requires access to github.com/outis1one/linux-to-sync"
|
|
||||||
echo " Authenticate with ONE of:"
|
|
||||||
echo " [1] SSH key already added to your GitHub account"
|
|
||||||
echo " [2] GitHub Personal Access Token (PAT)"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
local AUTH_METHOD=""
|
|
||||||
prompt_text "Authentication method [1=SSH, 2=PAT, default: 1]:" "1" AUTH_METHOD
|
|
||||||
AUTH_METHOD="${AUTH_METHOD:-1}"
|
|
||||||
|
|
||||||
if [ "$AUTH_METHOD" = "2" ]; then
|
|
||||||
echo ""
|
|
||||||
echo " Create a PAT at: https://github.com/settings/tokens/new"
|
|
||||||
echo " Select the 'repo' scope for full repository access."
|
|
||||||
echo ""
|
|
||||||
local GH_TOKEN=""
|
|
||||||
prompt_text "GitHub Personal Access Token:" "" GH_TOKEN
|
|
||||||
if [ -z "$GH_TOKEN" ]; then
|
|
||||||
log_warning "No token provided — skipping."
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
log_info "Cloning via HTTPS + PAT..."
|
|
||||||
if sudo -u "$ACTUAL_USER" \
|
|
||||||
git clone "https://$GH_TOKEN@github.com/outis1one/linux-to-sync.git" "$SYNC_DIR"; then
|
|
||||||
# Remove token from remote URL so it isn't stored in plain text
|
|
||||||
sudo -u "$ACTUAL_USER" git -C "$SYNC_DIR" remote set-url origin \
|
|
||||||
"https://github.com/outis1one/linux-to-sync.git"
|
|
||||||
log_success "linux-to-sync cloned to $SYNC_DIR"
|
|
||||||
echo " Token stripped from remote URL. For future pulls use:"
|
|
||||||
echo " git -C $SYNC_DIR pull (will prompt for credentials)"
|
|
||||||
echo " Or set up a credential helper:"
|
|
||||||
echo " git config --global credential.helper store"
|
|
||||||
else
|
|
||||||
log_error "Clone failed — check your PAT and network, then retry."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
# SSH auth — git must run as the actual user to use their SSH keys.
|
|
||||||
echo ""
|
|
||||||
echo " Checking for SSH key in $ACTUAL_HOME/.ssh/ ..."
|
|
||||||
local SSH_KEY_FOUND=false
|
|
||||||
for _k in id_ed25519 id_rsa id_ecdsa; do
|
|
||||||
if [ -f "$ACTUAL_HOME/.ssh/$_k" ]; then
|
|
||||||
log_info " Found: $ACTUAL_HOME/.ssh/$_k"
|
|
||||||
SSH_KEY_FOUND=true
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
if [ "$SSH_KEY_FOUND" = false ]; then
|
|
||||||
log_warning "No SSH key found in $ACTUAL_HOME/.ssh/"
|
|
||||||
echo ""
|
|
||||||
echo " To generate one:"
|
|
||||||
echo " ssh-keygen -t ed25519 -C 'your@email.com'"
|
|
||||||
echo " cat $ACTUAL_HOME/.ssh/id_ed25519.pub"
|
|
||||||
echo " → Add the public key at: github.com/settings/keys"
|
|
||||||
echo ""
|
|
||||||
local CONTINUE=""
|
|
||||||
prompt_yn "Continue anyway (will fail if no key on GitHub)? (y/n) [n]:" "n" CONTINUE
|
|
||||||
[[ ${CONTINUE:-n} =~ ^[Yy]$ ]] || return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
log_info "Cloning via SSH (running as $ACTUAL_USER)..."
|
|
||||||
if sudo -u "$ACTUAL_USER" \
|
|
||||||
git clone git@github.com:outis1one/linux-to-sync.git "$SYNC_DIR"; then
|
|
||||||
log_success "linux-to-sync cloned to $SYNC_DIR"
|
|
||||||
else
|
|
||||||
log_error "SSH clone failed."
|
|
||||||
echo ""
|
|
||||||
echo " Common causes:"
|
|
||||||
echo " • SSH key not added to GitHub — go to github.com/settings/keys"
|
|
||||||
echo " • Key not accepted by ssh-agent — try: ssh-add $ACTUAL_HOME/.ssh/id_ed25519"
|
|
||||||
echo " • Test with: sudo -u $ACTUAL_USER ssh -T git@github.com"
|
|
||||||
echo " Then retry: sudo ./setup.sh linux-to-sync"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
write_readme "$SYNC_DIR" << MD
|
|
||||||
# linux-to-sync
|
|
||||||
|
|
||||||
Private personal sync and config repository cloned from outis1one/linux-to-sync.
|
|
||||||
|
|
||||||
## Update
|
|
||||||
\`\`\`bash
|
|
||||||
cd $SYNC_DIR
|
|
||||||
git pull
|
|
||||||
\`\`\`
|
|
||||||
MD
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo " Cloned to: $SYNC_DIR"
|
|
||||||
echo ""
|
|
||||||
}
|
|
||||||
@@ -80,7 +80,6 @@ is_installed() {
|
|||||||
glow) command -v glow >/dev/null 2>&1 ;;
|
glow) command -v glow >/dev/null 2>&1 ;;
|
||||||
crowdsec) command -v cscli >/dev/null 2>&1 ;;
|
crowdsec) command -v cscli >/dev/null 2>&1 ;;
|
||||||
silent-send) [ -d "$ACTUAL_HOME/silent-send/.git" ] ;;
|
silent-send) [ -d "$ACTUAL_HOME/silent-send/.git" ] ;;
|
||||||
linux-to-sync) [ -d "$ACTUAL_HOME/linux-to-sync/.git" ] ;;
|
|
||||||
sync-cc) [ -f "$ACTUAL_HOME/sync-cc/sync_cc.py" ] ;;
|
sync-cc) [ -f "$ACTUAL_HOME/sync-cc/sync_cc.py" ] ;;
|
||||||
sky-cam) [ -d "$ACTUAL_HOME/sky-cam/.git" ] ;;
|
sky-cam) [ -d "$ACTUAL_HOME/sky-cam/.git" ] ;;
|
||||||
*) [ -e "$DOCKER_DIR/$1" ] ;;
|
*) [ -e "$DOCKER_DIR/$1" ] ;;
|
||||||
|
|||||||
Reference in New Issue
Block a user