diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5990d9c6..50eb3ef0 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,11 +1,41 @@ -# To get started with Dependabot version updates, you'll need to specify which -# package ecosystems to update and where the package manifests are located. -# Please see the documentation for all configuration options: +# Weekly version updates for the manifests this repository actually ships. # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file version: 2 updates: - - package-ecosystem: "" # See documentation for possible values - directory: "/" # Location of package manifests + - package-ecosystem: "pip" + directory: "/" schedule: interval: "weekly" + exclude-paths: + - "pyproject.toml" + groups: + python: + patterns: + - "*" + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + groups: + actions: + patterns: + - "*" + + - package-ecosystem: "docker" + directories: + - "/docker/java" + - "/docker/manager" + - "/docker/quake-linux" + - "/docker/service-console" + - "/docker/simple-tcp" + - "/docker/steamcmd-linux" + - "/docker/wine-proton" + schedule: + interval: "weekly" + groups: + images: + patterns: + - "*" + group-by: "dependency-name" diff --git a/.github/workflows/unittest.yaml b/.github/workflows/unittest.yaml index bd7d8181..5949b2c0 100644 --- a/.github/workflows/unittest.yaml +++ b/.github/workflows/unittest.yaml @@ -63,6 +63,15 @@ jobs: run: | bash ./lint.sh + system-install-test: + runs-on: ubuntu-latest + name: system-install-test + steps: + - name: Checkout AlphaGSM code + uses: actions/checkout@v4 + - name: Run system-wide install test in container + run: bash ./system_install_tests/run_system_install_in_container.sh + unit-test: runs-on: ubuntu-latest needs: build diff --git a/DEVELOPERS.md b/DEVELOPERS.md index 073bc171..0de0668a 100644 --- a/DEVELOPERS.md +++ b/DEVELOPERS.md @@ -946,6 +946,9 @@ merge groups, manual dispatch and reusable release validation. Its current gates - representative Minecraft backend integration on Windows and macOS - four native Windows unit cases, with exact passing JUnit evidence required by the final summary; Linux unit and coverage jobs deselect those cases +- `system-install-test`, which runs + [system_install_tests/run_system_install_in_container.sh](system_install_tests/run_system_install_in_container.sh) + and checks the Linux system install layout without changing the runner host Smoke, integration and isolated recheck commands run through `scripts/run_ci_workshop_tests.py`. It enables every public anonymous Workshop @@ -997,6 +1000,28 @@ Important environment variables: - `ALPHAGSM_DEBUG` The smoke tests rely on temporary config files and `ALPHAGSM_CONFIG_LOCATION` to isolate state per run. + +Linux install layouts: + +- [docs/install/local-user.md](docs/install/local-user.md) +- [docs/install/system-wide.md](docs/install/system-wide.md) + +`src/utils/settings/_settings.py` selects the default config from the program +path. A frozen binary reads `alphagsm.conf` beside the executable. A source +tree whose file path starts with `/usr/` reads `/etc/alphagsm.conf`. Every +other source tree reads `alphagsm.conf` four directories above that settings +module, which is the repository or install root. `ALPHAGSM_CONFIG_LOCATION` +replaces that choice. + +[scripts/install-system-wide.sh](scripts/install-system-wide.sh) copies the +tree under `/usr/local/lib/alphagsm`, so the program uses `/etc/alphagsm.conf`. +On a new config it sets `[downloader] user` to the `alphagsm` account and +points `db_path` and `target_path` at `/home/alphagsm/downloads`. Server data +still follows each operator's `~/.alphagsm` user settings. The download helper +re-executes through `sudo -Hu alphagsm`, and +`/etc/sudoers.d/gameservers` allows only `alphagsm-downloads` for the +`alphagsm` group. + GitHub Actions supplies `ALPHAGSM_GITHUB_TOKEN` to smoke and integration jobs using the workflow's read-only `GITHUB_TOKEN`; local runs can omit it unless they encounter GitHub's anonymous API rate limit. diff --git a/README.md b/README.md index f8168ec7..7710841d 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,19 @@ Linux, macOS, or Windows host when that guidance is known, and recommend using the Docker runtime instead when you would rather avoid host-side dependency setup. +## Install AlphaGSM on Linux + +Two scripts copy this repository into place and link the `alphagsm` command. +They do not install Python packages. Install `requirements-runtime.txt` first. +Both scripts are Linux only. + +- One account: [Local User Install](docs/install/local-user.md) +- A shared machine: [System-Wide Install](docs/install/system-wide.md) + +A system-wide install shares the program and the download cache. Each +person's servers stay in that person's home. The Fast Start below is the same +setup when you run `./alphagsm` from a checkout instead of an installed copy. + ## Fast Start If you want the quickest first run, start with the Docker-manager path above. @@ -510,9 +523,14 @@ These scripts are useful because they show a full real flow: 4. check it 5. stop it cleanly +`bash ./system_install_tests/run_system_install_in_container.sh` checks the +shared Linux install inside a container. It does not change the host. + ## Step-By-Step Server Guides - [Documentation Index](docs/README.md) +- [Local User Install](docs/install/local-user.md) +- [System-Wide Install](docs/install/system-wide.md) - [Everyday Commands](docs/commands.md) - [Installing Mods](docs/installing-mods.md) - [Updating Servers And AlphaGSM](docs/updating.md) diff --git a/alphagsm b/alphagsm index 71eb928e..089615f0 100755 --- a/alphagsm +++ b/alphagsm @@ -2,7 +2,8 @@ import os import sys -sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "src")) +_LAUNCHER = os.path.realpath(os.path.abspath(__file__)) +sys.path.insert(0, os.path.join(os.path.dirname(_LAUNCHER), "src")) if len(sys.argv) == 3 and sys.argv[1] == "--_supervise-process": from screen.supervisor import serve diff --git a/changelog.txt b/changelog.txt index 29094711..3b43882e 100644 --- a/changelog.txt +++ b/changelog.txt @@ -7,6 +7,17 @@ server support, CI, packaging, or workflow defaults. Historical detail lives in git history. Keep this file focused on release-facing notes and high-level milestones. +2026-11-07 +- Install: Linux local and system-wide scripts copy AlphaGSM into place and + link `alphagsm`. The command link resolves to the real program so Python + can find `src`. A system install reads `/etc/alphagsm.conf`, shares the + download cache as the `alphagsm` account, and leaves each operator's + servers in that operator's home. A container test checks that layout. +- Dependencies: Dependabot checks pip, GitHub Actions, and the runtime + Dockerfiles every week. Each ecosystem is grouped into one update. + `pyproject.toml` stays out of that scan because it only holds tool + settings. + 2026-11-05 - ET: Legacy: setup finds the Linux x86_64 tar.gz on the current etlegacy.com download page. Release numbers are read from the version label. diff --git a/docs/README.md b/docs/README.md index d62d13eb..752be472 100644 --- a/docs/README.md +++ b/docs/README.md @@ -22,6 +22,8 @@ with `update`. | I want to… | Read | | --- | --- | | Get AlphaGSM installed | [Getting Started](../README.md) | +| Install AlphaGSM for one Linux account | [Local User Install](install/local-user.md) | +| Install AlphaGSM once on a shared Linux host | [System-Wide Install](install/system-wide.md) | | Take or skip the guided setup tour | [First-run setup](first-run-setup.md) | | Run or maintain AlphaGSM with an AI agent | [Agent Operations](agent-operations.md) | | See every command (`restore`, `kill`, `activate`, `send`, …) | [Everyday Commands](commands.md) | diff --git a/docs/install/local-user.md b/docs/install/local-user.md new file mode 100644 index 00000000..1060b192 --- /dev/null +++ b/docs/install/local-user.md @@ -0,0 +1,95 @@ +# Local User Install + +Use this path on Linux when one person runs AlphaGSM from their own account. + +The script copies the repository, writes a config file if you do not already +have one, and links `alphagsm` into `~/.local/bin`. It does not install Python +packages, system libraries, Java, or SteamCMD. It does not share downloads +with other accounts. + +Windows and macOS are not covered here. On those systems, run AlphaGSM from a +checkout or use a standalone binary. See +[Getting Started](../../README.md). + +## Before you run it + +Install Python 3 and the runtime packages from the repository: + +```bash +python3 -m pip install -r requirements-runtime.txt +``` + +`requirements.txt` is the wider set used for development and tests. A normal +server only needs `requirements-runtime.txt`. + +Game servers may also need Java, 32-bit libraries, or other host packages. +The [host install requirements](../../README.md#host-install-requirements) +list the common ones. AlphaGSM reports a missing host package when you start +a server that needs it. + +## Run it + +From the repository root: + +```bash +bash ./scripts/install-local-user.sh +``` + +The copy leaves out a local `alphagsm.conf`, `env`, `.venv`, and +`__pycache__`. It keeps `.git` when the checkout has one. Running the script +again refreshes the copied code and does not replace an existing +`~/.local/lib/alphagsm/alphagsm.conf`. + +## Paths + +| Path | Role | +| --- | --- | +| `~/.local/lib/alphagsm` | Copied program | +| `~/.local/bin/alphagsm` | Command link. It points at the copied program, and AlphaGSM follows that link to find `src`. | +| `~/.local/lib/alphagsm/alphagsm.conf` | Config read by this install | + +AlphaGSM chooses that config because the code does not live under `/usr`. A +copy installed under `/usr`, including `/usr/local`, reads `/etc/alphagsm.conf` +instead. See [System-Wide Install](system-wide.md). + +`ALPHAGSM_CONFIG_LOCATION` still overrides the config file for one command. + +If `~/.local/bin` is not already on your `PATH`, add it to your shell profile: + +```bash +export PATH="$HOME/.local/bin:$PATH" +``` + +Open a new shell, then check the command: + +```bash +alphagsm --help +``` + +## First server + +Follow the [Fast Start](../../README.md#fast-start) commands. Use `alphagsm` +instead of `./alphagsm` once the link is on your `PATH`. For example: + +```bash +alphagsm mymc create minecraft.vanilla +alphagsm mymc setup +alphagsm mymc start +alphagsm mymc query +alphagsm mymc stop +``` + +Servers, logs, and backups for this account stay under `~/.alphagsm` unless +the config changes `alphagsm_path`. Downloads stay in this account too. There +is no shared download user. + +## Update the copy + +When the install still contains `.git`: + +```bash +git -C "$HOME/.local/lib/alphagsm" pull +``` + +Otherwise run `bash ./scripts/install-local-user.sh` again from a newer +checkout. Neither path edits your existing config file. diff --git a/docs/install/system-wide.md b/docs/install/system-wide.md new file mode 100644 index 00000000..c30e6953 --- /dev/null +++ b/docs/install/system-wide.md @@ -0,0 +1,147 @@ +# System-Wide Install + +Use this path on a shared Linux machine. It installs the AlphaGSM program +once. People on the machine then run that same `alphagsm` command. + +The shared part is the program and the download cache. Each person's servers, +logs, and backups stay in that person's home directory, normally +`~/.alphagsm`. This install does not put everyone's game servers into one +account. + +Windows and macOS are not covered here. + +## What the installer prepares + +Run as root: + +```bash +sudo bash ./scripts/install-system-wide.sh +``` + +On a new machine it does all of the following. + +- Creates a system account named `alphagsm` when that account is missing. + The account gets a home directory and cannot log in. +- Copies the program to `/usr/local/lib/alphagsm`. +- Links `/usr/local/bin/alphagsm` at that program. +- Installs `/usr/local/sbin/gitalphagsm` for root. +- Creates `/etc/alphagsm.conf` from `alphagsm.conf-template` when that file + is missing. +- Turns on the shared download cache in a new config file. +- Writes `/etc/sudoers.d/gameservers` and checks it with `visudo` when + `visudo` is installed. +- Creates `/home/alphagsm/downloads` and `/home/alphagsm/gitlive`. + +The copy leaves out a local `alphagsm.conf`, `env`, `.venv`, and +`__pycache__`. It keeps `.git` when the checkout has one. Running the script +again refreshes the program and does not replace an existing +`/etc/alphagsm.conf`. + +Install the Python packages before the first real command. The script does +not install them: + +```bash +sudo python3 -m pip install --break-system-packages -r requirements-runtime.txt +``` + +Use the package names from +[host install requirements](../../README.md#host-install-requirements) when a +game asks for Java or system libraries. + +## Paths + +| Path | Role | +| --- | --- | +| `/usr/local/lib/alphagsm` | Copied program | +| `/usr/local/bin/alphagsm` | Command used by every operator. It points at the copied program, and AlphaGSM follows that link to find `src`. | +| `/etc/alphagsm.conf` | Config read by this install | +| `/home/alphagsm` | Home of the download account | +| `/home/alphagsm/downloads/downloads.txt` | Shared download database | +| `/home/alphagsm/downloads/downloads` | Shared download files | +| `/home/alphagsm/gitlive` | Empty directory for an operator checkout | +| `/usr/local/sbin/gitalphagsm` | Root helper that runs git in the program directory | +| `/etc/sudoers.d/gameservers` | Passwordless sudo for the shared downloader | + +AlphaGSM reads `/etc/alphagsm.conf` because the program file is under `/usr`. +The same build, copied anywhere else, reads `alphagsm.conf` beside the +program instead. `ALPHAGSM_CONFIG_LOCATION` overrides that choice for one +command. + +## Shared downloads + +A new `/etc/alphagsm.conf` contains: + +```ini +[downloader] +user = alphagsm +db_path = /home/alphagsm/downloads/downloads.txt +target_path = /home/alphagsm/downloads/downloads +``` + +With `user` set, a missing file is downloaded by the `alphagsm` account. +AlphaGSM runs: + +```bash +sudo -Hu alphagsm /usr/local/lib/alphagsm/alphagsm-downloads MODULE ARGS +``` + +The sudoers entry allows that one command with no password for members of +the `alphagsm` group: + +```text +%alphagsm ALL = (alphagsm) NOPASSWD:/usr/local/lib/alphagsm/alphagsm-downloads +``` + +Add each person who will run servers: + +```bash +sudo usermod -aG alphagsm USER +``` + +That person starts a new login before the group applies. People outside the +group can still run `alphagsm`, but the first download of a file asks for a +password through `sudo`. + +If `/etc/alphagsm.conf` already existed, the installer leaves it alone. Set +the three `downloader` keys yourself to use the shared cache. + +`/home/alphagsm/gitlive` is only a directory. AlphaGSM does not clone into it +or read it. Use it when you want a shared checkout that is separate from the +copy under `/usr/local/lib/alphagsm`. + +## Each person's servers + +Creating a server writes that server under the account that ran the command. +Two people on the same machine get two homes and two server lists. They share +downloaded archives through the `alphagsm` account, so the second setup can +reuse a file the first setup already fetched. + +Per-person settings still live in `~/.alphagsm`, including the server list. +Change `alphagsm_path` in the user settings only when an account should keep +servers somewhere else. + +## Update the program + +When the copy contains `.git`, root can update it: + +```bash +sudo gitalphagsm pull +``` + +`gitalphagsm` runs `git` in `/usr/local/lib/alphagsm` and then stops. It does +not restart game servers. If that directory has no `.git`, the helper exits +and tells you. Run `sudo bash ./scripts/install-system-wide.sh` from a newer +checkout instead. + +## Check the layout without touching the host + +From a machine with Docker: + +```bash +bash ./system_install_tests/run_system_install_in_container.sh +``` + +That starts Ubuntu 24.04, installs Python and the runtime packages, runs the +system installer, checks the paths and sudoers file, runs the installer a +second time to prove the existing config is kept, and runs `alphagsm --help`. +The host's `/etc` and `/usr/local` are not changed. diff --git a/scripts/gitalphagsm b/scripts/gitalphagsm new file mode 100755 index 00000000..ad9e9462 --- /dev/null +++ b/scripts/gitalphagsm @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -euo pipefail + +INSTALL_ROOT="${INSTALL_ROOT:-/usr/local/lib/alphagsm}" + +if [[ ! -d "$INSTALL_ROOT/.git" ]]; then + echo "No git repository found at $INSTALL_ROOT" >&2 + exit 1 +fi + +git -C "$INSTALL_ROOT" "$@" diff --git a/scripts/install-local-user.sh b/scripts/install-local-user.sh new file mode 100755 index 00000000..76a7984b --- /dev/null +++ b/scripts/install-local-user.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +# Copy AlphaGSM for one Linux account. The program reads alphagsm.conf from +# this install root because the code is not under /usr. + +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +INSTALL_ROOT="${INSTALL_ROOT:-$HOME/.local/lib/alphagsm}" +BIN_DIR="${BIN_DIR:-$HOME/.local/bin}" +CONFIG_PATH="$INSTALL_ROOT/alphagsm.conf" + +case "$INSTALL_ROOT" in + "$REPO_ROOT"|"$REPO_ROOT"/*) + echo "INSTALL_ROOT must be outside the repository: $INSTALL_ROOT" >&2 + exit 1 + ;; +esac + +if [[ ! -f "$REPO_ROOT/alphagsm.conf-template" ]]; then + echo "Missing $REPO_ROOT/alphagsm.conf-template" >&2 + exit 1 +fi + +mkdir -p "$INSTALL_ROOT" "$BIN_DIR" +tar -C "$REPO_ROOT" \ + --exclude=alphagsm.conf \ + --exclude=env \ + --exclude=.venv \ + --exclude=__pycache__ \ + -cf - . | tar -C "$INSTALL_ROOT" -xf - + +if [[ ! -f "$CONFIG_PATH" ]]; then + cp "$INSTALL_ROOT/alphagsm.conf-template" "$CONFIG_PATH" +fi + +ln -sfn "$INSTALL_ROOT/alphagsm" "$BIN_DIR/alphagsm" + +cat <&2 + exit 1 +fi + +if ! command -v python3 >/dev/null 2>&1; then + echo "python3 is required to write /etc/alphagsm.conf." >&2 + exit 1 +fi + +case "$INSTALL_ROOT" in + "$REPO_ROOT"|"$REPO_ROOT"/*) + echo "INSTALL_ROOT must be outside the repository: $INSTALL_ROOT" >&2 + exit 1 + ;; +esac + +if [[ ! -f "$REPO_ROOT/alphagsm.conf-template" ]]; then + echo "Missing $REPO_ROOT/alphagsm.conf-template" >&2 + exit 1 +fi + +if ! id "$ALPHAGSM_USER" >/dev/null 2>&1; then + useradd --system --create-home --home-dir "/home/$ALPHAGSM_USER" --shell /usr/sbin/nologin "$ALPHAGSM_USER" +fi + +ALPHAGSM_HOME="$(getent passwd "$ALPHAGSM_USER" | cut -d: -f6)" +ALPHAGSM_GROUP="$(id -gn "$ALPHAGSM_USER")" +if [[ -z "$ALPHAGSM_HOME" || "$ALPHAGSM_HOME" == "/" ]]; then + echo "Refusing empty or root home for $ALPHAGSM_USER" >&2 + exit 1 +fi + +mkdir -p \ + "$INSTALL_ROOT" \ + "$BIN_DIR" \ + "$SBIN_DIR" \ + "$ALPHAGSM_HOME/gitlive" \ + "$ALPHAGSM_HOME/downloads/downloads" + +tar -C "$REPO_ROOT" \ + --exclude=alphagsm.conf \ + --exclude=env \ + --exclude=.venv \ + --exclude=__pycache__ \ + -cf - . | tar -C "$INSTALL_ROOT" -xf - + +if [[ ! -f "$CONFIG_PATH" ]]; then + cp "$INSTALL_ROOT/alphagsm.conf-template" "$CONFIG_PATH" + CONFIG_PATH="$CONFIG_PATH" ALPHAGSM_USER="$ALPHAGSM_USER" ALPHAGSM_HOME="$ALPHAGSM_HOME" \ + python3 - <<'PY' +import os +from pathlib import Path + +path = Path(os.environ["CONFIG_PATH"]) +user = os.environ["ALPHAGSM_USER"] +home = os.environ["ALPHAGSM_HOME"] +text = path.read_text(encoding="utf-8") +header = "[downloader]\n" +start = text.find(header) +if start < 0: + raise SystemExit("alphagsm.conf has no [downloader] section") +body_start = start + len(header) +rest = text[body_start:] +end = rest.find("\n[") +body = rest if end < 0 else rest[:end] +if any(line.startswith("user ") or line.startswith("user=") for line in body.splitlines()): + raise SystemExit(0) +block = ( + f"user = {user}\n" + f"db_path = {home}/downloads/downloads.txt\n" + f"target_path = {home}/downloads/downloads\n" +) +path.write_text(text[:body_start] + block + text[body_start:], encoding="utf-8") +PY +fi + +ln -sfn "$INSTALL_ROOT/alphagsm" "$BIN_DIR/alphagsm" +install -m 0755 "$INSTALL_ROOT/scripts/gitalphagsm" "$SBIN_DIR/gitalphagsm" + +cat >"$SUDOERS_PATH" </dev/null 2>&1; then + visudo -cf "$SUDOERS_PATH" +fi + +chown -R "$ALPHAGSM_USER:$ALPHAGSM_GROUP" "$ALPHAGSM_HOME" + +cat <> /etc/alphagsm.conf +bash ./scripts/install-system-wide.sh +grep -q "install-test-marker" /etc/alphagsm.conf + +python3 /usr/local/bin/alphagsm --help >/tmp/alphagsm-help.txt +test -s /tmp/alphagsm-help.txt +'