Skip to content
Draft
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
71 changes: 16 additions & 55 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -720,63 +720,24 @@ release-appimage: _confirm-release-action
$(call block_partial_release)


# Optional Linux Snap package surface.
# Use:
# Build a Snap package
# `make package-snap` # Build snap (requires snapcraft)
#
# Verify produced artifacts
# `make show-snap-artifacts`
#
# Publish to Snap Store (requires authentication)
# `make release-snap`
#
# Note: Snap building requires snapcraft tool and assumes snapcraft.yaml exists
# Legacy Linux Snap package surface: HARD-DISABLED.
# Snap is not part of the canonical 21-artifact release contract
# (docs/release/artifact-contract.md). The legacy targets are kept only as
# explicit refusal stubs so stale automation fails fast instead of silently
# producing a 22nd artifact. There is no snapcraft.yaml in this repository.
# ---------------------------

SNAP_VERSION ?= $(PACKAGE_VERSION)
SNAP_PKG_DIR ?= $(RELEASE_DIR)
SNAP_FILE ?= $(SNAP_PKG_DIR)/ecli_$(SNAP_VERSION)_linux_$(LINUX_ARCH).snap
SNAP_SHA_FILE ?= $(SNAP_FILE).sha256

.PHONY: package-snap
package-snap: clean validate-runtime-imports
@command -v snapcraft >/dev/null 2>&1 || (echo "snapcraft not found. Install: sudo snap install snapcraft --classic"; exit 1)
@test -f snapcraft.yaml || (echo "snapcraft.yaml not found in project root"; exit 1)
@echo "--> Building Snap..."
snapcraft
@mkdir -p $(RELEASE_DIR)
@set -- *.snap; \
if [ "$$1" = "*.snap" ]; then \
echo "Snap build did not produce a .snap artifact"; \
exit 1; \
fi; \
if [ "$$#" -ne 1 ]; then \
echo "Expected exactly one .snap artifact, found $$#"; \
printf '%s\n' "$$@"; \
exit 1; \
fi; \
mv "$$1" "$(SNAP_FILE)"
@cd "$(SNAP_PKG_DIR)" && sha256sum "$$(basename "$(SNAP_FILE)")" > "$$(basename "$(SNAP_SHA_FILE)")"
$(MAKE) package-snap-assert

.PHONY: package-snap-assert
package-snap-assert:
$(call assert_current_release_file,$(SNAP_FILE))
$(call verify_sha256,$(SNAP_FILE))
@echo "--> OK: $(SNAP_FILE)"
@echo "--> OK: $(SNAP_SHA_FILE)"

.PHONY: show-snap-artifacts
show-snap-artifacts:
@echo "Version: $(SNAP_VERSION)"
@ls -lh $(RELEASE_DIR)/*.snap 2>/dev/null || echo "(no snap artifacts yet)"

.PHONY: release-snap
release-snap: package-snap-assert
@echo "--> Publishing to Snap Store (requires authentication)..."
@echo " Run: snapcraft login"
@echo " Then: snapcraft upload --release=stable $(RELEASE_DIR)/ecli_*.snap"
define snap_targets_disabled
@echo "ERROR: Snap targets are disabled." ; \
echo "Snap is not part of the canonical 21-artifact release contract" ; \
echo "(docs/release/artifact-contract.md). No Snap artifact may be" ; \
echo "built, verified, or published from this repository." ; \
exit 1
endef

.PHONY: package-snap package-snap-assert show-snap-artifacts release-snap
package-snap package-snap-assert show-snap-artifacts release-snap:
$(call snap_targets_disabled)


# Linux archive package surface.
Expand Down
2 changes: 2 additions & 0 deletions docker/build-linux-deb.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
ca-certificates \
curl \
dpkg-dev \
file \
g++ \
gcc \
Expand All @@ -39,6 +40,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
libncursesw6 \
libtinfo6 \
libyaml-dev \
lintian \
make \
ncurses-bin \
ncurses-term \
Expand Down
6 changes: 5 additions & 1 deletion docs/architecture/ecli-f4-linter-microservices-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -996,7 +996,11 @@ Every entry must be tested according to the existing release contract. Do not de

Different artifact types may deliver linters differently, but every Full installer/provisioner path must first detect the OS/artifact context, then check for already-installed required tools before installing or bundling missing tools:

- Debian/Ubuntu `.deb`: package dependencies or bundled tools.
- Debian/Ubuntu `.deb`: two-stage model on Debian 13 — the standalone
linter installer (`scripts/install_ecli_linters.py`) provisions the
19-tool toolchain into `/opt/ecli/payload` first; the `.deb` installs
only ECLI itself and discovers tools through `PATH`
(see `docs/install/debian.md`).
- RPM/openSUSE: package dependencies or bundled tools.
- Arch: `depends`/`makedepends` or bundled tools.
- Slackware: bundled tools or documented package metadata.
Expand Down
15 changes: 15 additions & 0 deletions docs/contributor/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,21 @@ Exact artifact names include the version and architecture.

### Debian / Ubuntu

On Debian 13 (Trixie) amd64, the recommended full sequence is two stages:
first provision the 19-tool F4 linter toolchain with the dedicated
installer, then install the ECLI package (see `docs/install/debian.md`):

```bash
sudo python3 scripts/install_ecli_linters.py
sudo apt install ./releases/0.2.4/ecli_0.2.4_linux_x86_64.deb
```

The `.deb` installs ECLI itself and its direct runtime dependencies only;
it does not bundle or download linters. ECLI discovers installed linter
executables through `PATH`, and F4 diagnostics list any missing tools.

Installing only the package also works:

```bash
sudo apt install ./ecli_<version>_linux_x86_64.deb
ecli
Expand Down
7 changes: 7 additions & 0 deletions docs/extensions/f4-linter-manual-installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,13 @@ Chocolatey IDs are to be verified in packaging implementation.

## Debian / Ubuntu

On Debian 13 amd64 the supported path is the official interactive
installer, `sudo python3 scripts/install_ecli_linters.py`, which
provisions all 19 tools from the committed lock
`packaging/debian/ecli-linter-lock.json` into `/opt/ecli/payload`
(see `docs/install/debian.md`). The manual strategies below remain valid
for developer checkouts and custom environments.

Debian 13 testing used a valid mixed user-space strategy: Ruff already in the
venv; npm custom prefix under `~/.local/share/ecli-linters/npm-global` for
Biome and markdownlint-cli2; dedicated venvs for yamllint and Python-delivered
Expand Down
214 changes: 214 additions & 0 deletions docs/install/debian.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
<!--
SPDX-License-Identifier: GPL-2.0-only

Project: Ecli
File: docs/install/debian.md
Website: https://www.ecli.io
Repository: https://github.com/SSobol77/ecli

Copyright (c) 2026 Siergej Sobolewski

Licensed under the GNU General Public License version 2 only.
See the LICENSE file in the project root for full license text.
-->
# Installing on Debian 13 (Trixie)

ECLI installation on Debian 13 amd64 is a **two-stage** process:

1. **Stage 1 — ECLI Linter Installer** provisions the complete 19-tool F4
linter toolchain into the operating system and into the managed payload
tree `/opt/ecli/payload`.
2. **Stage 2 — ECLI Debian package** installs ECLI itself.

The recommended full sequence is:

```bash
sudo python3 scripts/install_ecli_linters.py
sudo apt install ./releases/0.2.4/ecli_0.2.4_linux_x86_64.deb
```

The `.deb` installs only ECLI and its direct runtime dependencies. It never
bundles, downloads, or installs the linter toolchains; ECLI discovers the
installed linter executables through `PATH`. If the linter payload is
absent, ECLI still installs and runs — F4 diagnostics list any missing
executables instead of failing.

## Obtaining the linter installer bundle

The linter installer is a standalone **four-file bundle**. Copying only
`install_ecli_linters.py` is **not sufficient** — it loads its production
lock and npm lock from paths next to its own location, so all four files
must be fetched together, preserving this exact flat layout:

```text
install_ecli_linters.py
ecli-linter-lock.json
markdownlint-cli2/
package.json
package-lock.json
```

(A full repository checkout already satisfies this layout automatically:
the script also looks one directory up, under
`packaging/debian/ecli-linter-lock.json` and
`packaging/debian/markdownlint-cli2/`.)

### Prerequisite: Python 3

A minimal or netinst Debian 13 install does not ship Python. Install it
before running the bundle:

```bash
sudo apt-get update && sudo apt-get install -y python3
```

### Option A — clone the repository

```bash
git clone --branch v0.2.4 --depth 1 https://github.com/SSobol77/ecli.git
cd ecli
sudo python3 scripts/install_ecli_linters.py
```

### Option B — fetch only the four bundle files

```bash
mkdir -p ecli-linter-bundle/markdownlint-cli2 && cd ecli-linter-bundle
curl -fsSLO https://raw.githubusercontent.com/SSobol77/ecli/v0.2.4/scripts/install_ecli_linters.py
curl -fsSLO https://raw.githubusercontent.com/SSobol77/ecli/v0.2.4/packaging/debian/ecli-linter-lock.json
curl -fsSL -o markdownlint-cli2/package.json \
https://raw.githubusercontent.com/SSobol77/ecli/v0.2.4/packaging/debian/markdownlint-cli2/package.json
curl -fsSL -o markdownlint-cli2/package-lock.json \
https://raw.githubusercontent.com/SSobol77/ecli/v0.2.4/packaging/debian/markdownlint-cli2/package-lock.json
sudo python3 install_ecli_linters.py
```

The installer itself is never executed by any downloaded/piped shell
command — always fetch the four files, inspect them if desired, then run
`python3` directly against the local copy.

## Stage 1 — ECLI Linter Installer

Run the dedicated interactive installer as root (from a repository
checkout or the bundle fetched above):

```bash
sudo python3 scripts/install_ecli_linters.py
```

The installer refuses to run unless all of the following hold: effective
UID 0, Linux, Debian, Debian major version 13, architecture `amd64`.

It presents a 19-entry menu:

```text
[ A ] - Install All Linters

1. Ruff
2. Biome
3. markdownlint-cli2
4. yamllint
5. shellcheck
6. Zig
7. Hadolint
8. Taplo
9. actionlint
10. clang-tidy
11. cppcheck
12. clang-format
13. Checkstyle
14. PMD
15. SpotBugs
16. cargo-clippy
17. golangci-lint
18. SQLFluff
19. TFLint
```

Enter `A` for all tools, or a comma-separated list of numbers (for example
`1,4,5`). Non-interactive automation can use
`--select A` or `--select 1,4,5`.

What the installer does:

- Resolves the complete APT package set for the selection (yamllint,
shellcheck, clang-tidy, cppcheck, clang-format, checkstyle, cargo,
rust-clippy, sqlfluff, plus runtime dependencies such as nodejs/npm,
default-jre-headless, golang-go) and installs it in **one**
`apt-get install --yes --no-install-recommends` transaction.
- Installs the standalone tools (Ruff, Biome, Zig, Hadolint, Taplo,
actionlint, PMD, SpotBugs, golangci-lint, TFLint, and the npm-locked
markdownlint-cli2) into `/opt/ecli/payload` from the committed
production lock `packaging/debian/ecli-linter-lock.json`: pinned
versions, HTTPS-only downloads, exact SHA-256 verification, safe
archive extraction, and atomic promotion. No `releases/latest` queries
and no Zig `master` builds.
- Configures `/etc/profile.d/ecli_payload.sh` so login shells put
`/opt/ecli/payload/bin` on `PATH` (idempotent — no duplicate entries).
- Verifies every selected tool with its version probe and prints a
per-tool `[OK] / [SKIPPED] / [FAILED]` report. The success message
`ECLI linter installation completed successfully: 19/19 tools verified.`
is printed only when all 19 tools pass.

The installer is safely rerunnable: tools already at the locked version
are verified and skipped; outdated or corrupted managed installations are
replaced atomically; unmanaged files are never overwritten. A root-owned
log is written to `/var/log/ecli/linter-installer.log`.

Managed payload layout:

```text
/opt/ecli/payload/bin executable entry points (PATH surface)
/opt/ecli/payload/packages versioned distributions (zig, pmd, spotbugs, nodejs)
/opt/ecli/payload/state managed installation state
/opt/ecli/payload/cache verified download cache
```

## Stage 2 — ECLI Debian package

After the linter installer completes, install ECLI:

```bash
sudo apt install ./releases/0.2.4/ecli_0.2.4_linux_x86_64.deb
```

Verify the artifact checksum first when installing a downloaded release:

```bash
sha256sum -c ecli_0.2.4_linux_x86_64.deb.sha256
```

The package's maintainer scripts are intentionally conservative:

- `remove` and `purge` never delete user configuration
(`~/.config/ecli`, including `/root/.config/ecli`).
- No maintainer script uses the network, invokes APT, or touches
`/opt/ecli/payload`.
- `postinst` only reports whether the optional linter payload is present;
its absence never fails the installation.

## Verify

```bash
ecli --version
```

Open a new login shell (or `source /etc/profile.d/ecli_payload.sh`) so
`/opt/ecli/payload/bin` is on `PATH`, then run the headless toolchain
verifier:

```bash
ecli --f4-check
```

This proves, from the installed runtime, that all 19 provisioned
toolchain executables resolve from their approved managed or system
location (11 managed, 8 Debian-packaged). It reports two distinct counts
that must never be conflated: up to 19 **provisioned/verified
executables**, and a fixed **14 registered diagnostic providers** — the
number of linters F4 currently runs live diagnostics through inside the
editor. The remaining five provisioned tools (SpotBugs, golangci-lint,
SQLFluff, TFLint, clang-format) are installed and on `PATH` but do not
yet have a diagnostic provider wired into F4. Missing executables are
listed in the editor log by the startup toolchain check instead of
causing errors.
1 change: 0 additions & 1 deletion docs/release/artifact-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,6 @@ Current builder output forms before release normalization:
- Slackware package: `ecli_<version>_slackware_<arch>.txz`
- AppImage: `ecli_<version>_linux_<arch>.AppImage`
- Linux tarball: `ecli_<version>_linux_<arch>.tar.gz`
- Snap: `ecli_<version>_linux_<arch>.snap`
- FreeBSD: `ecli_<version>_freebsd_<arch>.pkg`
- Windows portable EXE: `ecli_<version>_win_<arch>.exe`
- Windows NSIS installer EXE: `ecli_<version>_win_<arch>_setup.exe`
Expand Down
Loading
Loading