Files
Linux_post_install/DEV.md
T
2026-07-28 10:28:11 -04:00

13 KiB

Development Guide

How this repo works, how to add features, and what to keep in mind when editing.


Concepts

Three-Phase Installation

The installer runs in three sequential phases:

                 install.sh
                     │
         ┌───────────┼───────────┐
         ▼           ▼           ▼
   preinstall.sh   bin/*    postinstall.sh
   (packages)   → /usr/local/bin  (config + services)
Phase Script Responsibility
Pre preinstall.sh System packages, apt repositories, global binaries (yt-dlp)
Install install.sh Copies everything in bin/ to /usr/local/bin with chmod 755
Post postinstall.sh User config (SSH, rclone), ~/.bashrc, systemd services

Each phase is independent and is only run if the corresponding file exists.

Script Categories

Directory Purpose Installed To
bin/ Daily-use tools and wrappers /usr/local/bin/
apps/<category>/ Optional desktop apps (by category) run on demand
lib/ Shared library (common.sh) sourced at build time
<<<<<<< HEAD
config/ Static config files + SSH authorized_keys ~/.config/<app>/ (via postinstall)

======= | config/ | Static config files (gitignored — user adds their own) | ~/.config/<app>/ (via postinstall) |

bba577c (Initial commit) | compose/ | ScaleTail templates (dev reference only) | cloned to /usr/local/share/mylinux/scale-tail on install | | systemd/ | Systemd service unit files | /etc/systemd/system/ (via postinstall) |

Key Files Added

File Purpose
<<<<<<< HEAD
.gitignore Prevents secrets (rclone tokens) and build artifacts from being committed
config/authorized_keys SSH public keys read by postinstall.sh (replaces hardcoded key)

======= | .gitignore | Prevents secrets (rclone tokens, SSH keys) and build artifacts from being committed |

bba577c (Initial commit) | ~/.config/mylinux/compose.env | Global Docker Compose defaults (TS_AUTHKEY, TZ, DNS_SERVER, SERVICES_BASE) — created by wr-compose config |


How to Add a New Tool

1. Create the script in bin/

#!/usr/bin/env bash

set -euo pipefail

# Use the shared library for colors and helpers (preferred)
source "$(dirname "$0")/../lib/common.sh"

usage() {
    cat <<EOF
Usage: my-tool <argument>
EOF
    exit 0
}

case "${1:-}" in
    -h|--help|"") usage ;;
esac

# --- script logic ---

Conventions to follow:

  • Shebang: #!/usr/bin/env bash (portable across distros)
  • Strict mode: set -euo pipefail at the top
  • --help flag: all tools must accept -h / --help — use the case ... esac pattern above
  • Shared library: source lib/common.sh from any script in bin/ or apps/ for consistent colors, logging (log, warn, err, ok), spinners (spawn), and dry-run support (run). Use spawn "message" command for long-running installs.
  • Fallback (no lib): if sourcing common.sh is not desired, inline:
    log()  { echo "[+] $*"; }
    warn() { echo "[!] $*"; }
    err()  { echo "ERROR: $*" >&2; exit 1; }
    
  • Exit codes: 0 for success, 1 for error

2. Add system dependencies (if any)

Open preinstall.sh and add the package name to the PACKAGES array:

PACKAGES=(
    ...
    your-package
)

3. Add runtime configuration (if any)

If the tool needs a config file:

  • Place the file in config/
  • Add copy logic in postinstall.sh

If the file contains secrets (tokens, keys):

  • Add it to .gitignore
  • Document in README how to create it manually

<<<<<<< HEAD

4. Add SSH keys (if needed)

Place public keys in config/authorized_keys (one per line). postinstall.sh reads from this file automatically.

=======

bba577c (Initial commit)

5. Update README.md

Add a section under Tools Reference following the existing format.

6. Test

# Syntax check
bash -n bin/your-tool

# ShellCheck linting
shellcheck bin/your-tool

# Run directly
./bin/your-tool --help

How to Edit an Existing Tool

  1. Find the script — all tools live in bin/
  2. Understand the contract — what args does it expect? What does it print? What exit codes?
  3. Make the change — keep it idempotent if possible (running twice = same result)
  4. Update README if usage, output, or behaviour changed
  5. Run shellcheck on the modified file:
    shellcheck bin/your-tool
    

How to Add a New App

App installers live in apps/<category>/ and follow a simple pattern. Each is a standalone script that can be run independently.

Template

#!/usr/bin/env bash
set -euo pipefail
source "$(dirname "$0")/../../lib/common.sh"

install_myapp() {
    command -v myapp &>/dev/null && { log "myapp already installed"; return 0; }

    spawn "Installing myapp" sudo apt install -y myapp
}

install_myapp

Note: app scripts are now in apps/<category>/, so the source path to common.sh is two levels up (../../lib/common.sh).

Conventions

  • Shebang: #!/usr/bin/env bash
  • Strict mode: set -euo pipefail
  • Shared library: always source lib/common.sh from the app directory
  • Idempotent: check command -v before installing; skip if present
  • Method: standardize on official repos/scripts over PPAs or third-party
  • APT packagessudo apt install -y <pkg> wrapped in spawn
  • Official scriptscurl ... | sh inside spawn
  • Flatpakflatpak install -y flathub <app-id> inside spawn
  • .deb files → download to temp and sudo apt install -y ./file.deb inside spawn
  • Groups: usermod commands print a re-login reminder (log "Log out and back in for group changes to take effect")

Adding to the picker

apps/install.sh auto-discovers all apps/<category>/*.sh files (excluding itself). Just create the script in the appropriate category subdirectory and it will appear in the interactive prompt under that category.

Categories: browsers, development, media, networking, remote-access, system, utilities.


Best Practices

Idempotency

Scripts should be safe to run multiple times:

  • Check if something exists before creating it
  • Use >> with checks (grep for existing content) instead of blindly appending
  • Don't overwrite configs that the user may have customized

Error Handling

# Fail fast
set -euo pipefail

# Check for required commands
if ! command -v docker &>/dev/null; then
    echo "docker not found"
    exit 1
fi

# Check arguments
if [[ -z "${1:-}" ]]; then
    echo "Usage: my-tool <argument>"
    exit 1
fi

Portability

This repo targets Debian and Ubuntu. Keep in mind:

  • Use apt not apt-get unless you need non-interactive guarantees
  • Assume bash is at /usr/bin/env bash
  • Prefer POSIX-safe patterns when possible
  • Check for command availability with command -v

Dry-run support

Scripts that make changes (install.sh, preinstall.sh) support --dry-run:

./install.sh --dry-run    # preview without executing

Use the run() helper pattern:

run() {
    if [ "$DRY_RUN" -eq 1 ]; then
        log "(dry-run) $*"
    else
        "$@"
    fi
}

run sudo apt install -y git

Security

  • Never hardcode secrets in scripts (SSH keys, API tokens, passwords) — put them in config/ files that are .gitignored
  • Use chmod 600 for sensitive files (SSH keys, rclone config)
  • Validate user input before using it in shell commands
  • Use sudo only where necessary; don't run the whole script as root if only one command needs elevation

Naming

  • Prefix personal wrappers with wr- (e.g., wr-ip, wr-docker)
  • Keep names lowercase, use hyphens for word separation
  • Name should hint at the tool's purpose (wr-scan-ping, wr-checkport)

Working with Systemd

Adding a new service

  1. Create systemd/<name>.service
  2. postinstall.sh automatically copies all *.service files to /etc/systemd/system/ and enables them

Service file template:

[Unit]
Description=My Service
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/usr/local/bin/your-script.sh
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target

Working with Config Files

  1. Place the file in config/
  2. Add a section to postinstall.sh:
if [ -f config/your-config.conf ]; then
    mkdir -p "$HOME/.config/your-app"
    cp config/your-config.conf "$HOME/.config/your-app/your-config.conf"
    chmod 600 "$HOME/.config/your-app/your-config.conf"
    echo "Installed your-config.conf"
fi

Working with Docker Compose

The installer clones ScaleTail templates to /usr/local/share/mylinux/scale-tail/ — a library of 119+ self-hosted services with a Tailscale sidecar pattern. Each service runs with network_mode: service:tailscale, gets a tail-xxxxx.ts.net URL, and optional automatic HTTPS via Tailscale Serve or Funnel.

Architecture (after install)

/usr/local/share/mylinux/scale-tail/   # ScaleTail templates (git repo)
└── services/<name>/
    ├── compose.yaml     # Service definition (Tailscale + app containers)
    └── .env             # Template variables (SERVICE, IMAGE_URL, TS_AUTHKEY, TZ, ...)

~/.config/mylinux/compose.env          # Global defaults — set via wr-compose config

<SERVICES_BASE>/<name>/                # Active deployments (default: /srv/<name>)
    ├── compose.yaml     # Copied from template (refreshed on wr-compose update)
    ├── .env             # Your real config — preserved across updates
    ├── config/          # Service configuration data
    └── data/            # Service persistent data

wr-compose commands

Command Behaviour
wr-compose up <service> Deploys service to $SERVICES_BASE/<service>/ (default: /srv), creates config/ + data/ dirs, generates .env from global config (prompts for TS_AUTHKEY if empty), runs docker compose up -d
wr-compose down <service> Runs docker compose down in the service directory
wr-compose update git pull in ScaleTail templates dir, then re-copies compose.yaml into all deployed directories — .env files are left untouched
wr-compose config set K=V Persists a value in ~/.config/mylinux/compose.env (e.g. TS_AUTHKEY, TZ, DNS_SERVER, SERVICES_BASE)

Portable .env design

  • Global: ~/.config/mylinux/compose.env — one place for TS_AUTHKEY, TZ, DNS_SERVER, SERVICES_BASE.
  • Per-service: <SERVICES_BASE>/<service>/.env — generated from the ScaleTail template on first deploy, with empty values filled from the global config.
  • On update: wr-compose update refreshes only compose.yaml from the templates; .env files are preserved.
  • Services path: set SERVICES_BASE to any directory (e.g. /srv) via wr-compose config set SERVICES_BASE=/srv. Defaults to /srv.

This means wr-compose works anywhere — no repo clone needed after install. Just set TS_AUTHKEY once and deploy.

Contributing upstream

ScaleTail provides a service template. To add a service:

  1. Fork ScaleTail and add your service under services/<name>/
  2. Submit a PR upstream
  3. Changes are picked up by wr-compose update

Commit Guidelines

  • Use conventional commit prefixes: feat:, fix:, docs:, refactor:, chore:
  • Explain why the change was made, not just what changed
  • Keep commits focused — one logical change per commit

Examples:

feat: add wr-mytool for monitoring disk usage
fix: wr-ip fails when no default route exists
docs: add example output for wr-scan-ping

Useful Commands

# Syntax-check a script without running it
bash -n bin/my-script
bash -n apps/utilities/myapp.sh

# ShellCheck linting
shellcheck bin/my-script
shellcheck apps/utilities/myapp.sh

# Quick syntax check all scripts
for f in bin/* apps/*/*.sh lib/common.sh install.sh preinstall.sh postinstall.sh; do
    bash -n "$f" || echo "FAIL: $f"
done

# Initialize submodule after clone
git submodule update --init

# Pull latest ScaleTail services
git submodule update --remote compose/scale-tail

# List available compose services
./bin/wr-compose ls

# Test install in Docker
docker run --rm -it -v $PWD:/repo ubuntu:22.04 bash
# inside container: cd /repo && ./install.sh

# Test app installation interactively
./apps/install.sh
./apps/install.sh --all        # install all apps
./apps/install.sh docker vscode # install specific apps