Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package ttl for openSUSE:Factory checked in 
at 2026-07-09 22:18:58
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/ttl (Old)
 and      /work/SRC/openSUSE:Factory/.ttl.new.1991 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "ttl"

Thu Jul  9 22:18:58 2026 rev:9 rq:1364590 version:0.21.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/ttl/ttl.changes  2026-07-07 21:07:31.003247220 
+0200
+++ /work/SRC/openSUSE:Factory/.ttl.new.1991/ttl.changes        2026-07-09 
22:19:30.150376648 +0200
@@ -1,0 +2,8 @@
+Wed Jul  8 06:41:35 UTC 2026 - Martin Hauke <[email protected]>
+
+- Update to version 0.21.0:
+  * feat(update): allow disabling the update check (#110) (#122)
+- Build with "--no-default-features" to disable the automatic
+  update check (update-informer).
+
+-------------------------------------------------------------------

Old:
----
  ttl-0.20.2.obscpio

New:
----
  ttl-0.21.0.obscpio

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ ttl.spec ++++++
--- /var/tmp/diff_new_pack.D8l3cM/_old  2026-07-09 22:19:31.554424346 +0200
+++ /var/tmp/diff_new_pack.D8l3cM/_new  2026-07-09 22:19:31.558424481 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           ttl
-Version:        0.20.2
+Version:        0.21.0
 Release:        0
 Summary:        Modern traceroute/mtr-style TUI
 License:        Apache-2.0 OR MIT
@@ -38,7 +38,7 @@
 %autosetup -p 1 -a 1
 
 %build
-%{cargo_build}
+%{cargo_build} --no-default-features
 
 %install
 %{cargo_install}

++++++ _service ++++++
--- /var/tmp/diff_new_pack.D8l3cM/_old  2026-07-09 22:19:31.638427201 +0200
+++ /var/tmp/diff_new_pack.D8l3cM/_new  2026-07-09 22:19:31.650427609 +0200
@@ -2,7 +2,7 @@
   <service name="obs_scm" mode="manual">
     <param name="url">https://github.com/lance0/ttl</param>
     <param name="scm">git</param>
-    <param name="revision">v0.20.2</param>
+    <param name="revision">v0.21.0</param>
     <param name="versionformat">@PARENT_TAG@</param>
     <param name="versionrewrite-pattern">v(\d+\.\d+\.\d+)</param>
     <param name="changesgenerate">enable</param>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.D8l3cM/_old  2026-07-09 22:19:31.698429241 +0200
+++ /var/tmp/diff_new_pack.D8l3cM/_new  2026-07-09 22:19:31.706429512 +0200
@@ -1,6 +1,6 @@
 <servicedata>
 <service name="tar_scm">
                 <param name="url">https://github.com/lance0/ttl</param>
-              <param 
name="changesrevision">11349e34e91844b2f6144ae4e6d412b3f9ae5220</param></service></servicedata>
+              <param 
name="changesrevision">46f1dc2cd8c055408a764d5674afa897ca7cd30c</param></service></servicedata>
 (No newline at EOF)
 

++++++ ttl-0.20.2.obscpio -> ttl-0.21.0.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ttl-0.20.2/.github/dependabot.yml 
new/ttl-0.21.0/.github/dependabot.yml
--- old/ttl-0.20.2/.github/dependabot.yml       2026-07-06 19:35:37.000000000 
+0200
+++ new/ttl-0.21.0/.github/dependabot.yml       2026-07-07 16:42:22.000000000 
+0200
@@ -4,7 +4,22 @@
     directory: "/"
     schedule:
       interval: "weekly"
+    groups:
+      cargo-dependencies:
+        patterns:
+          - "*"
   - package-ecosystem: "github-actions"
     directory: "/"
     schedule:
       interval: "weekly"
+    groups:
+      github-actions:
+        patterns:
+          - "*"
+    ignore:
+      # dtolnay/rust-toolchain is pinned by Rust *version*, not action version.
+      # @1.88 in the MSRV job = our declared MSRV (Cargo.toml rust-version);
+      # dependabot misreads it as a stale tag and tries to bump it (e.g. #121:
+      # 1.88 -> 1.100), which would silently defeat the MSRV check. Other uses
+      # are @stable. MSRV changes are made by hand.
+      - dependency-name: "dtolnay/rust-toolchain"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ttl-0.20.2/.github/workflows/audit.yml 
new/ttl-0.21.0/.github/workflows/audit.yml
--- old/ttl-0.20.2/.github/workflows/audit.yml  2026-07-06 19:35:37.000000000 
+0200
+++ new/ttl-0.21.0/.github/workflows/audit.yml  2026-07-07 16:42:22.000000000 
+0200
@@ -12,14 +12,21 @@
 jobs:
   audit:
     runs-on: ubuntu-latest
+    timeout-minutes: 15
     steps:
       - uses: actions/checkout@v7
 
       - name: Install Rust
         uses: dtolnay/rust-toolchain@stable
 
+      - name: Cache cargo-audit
+        uses: actions/cache@v6
+        with:
+          path: ~/.cargo/bin/cargo-audit
+          key: cargo-audit-0.21
+
       - name: Install cargo-audit
-        run: cargo install cargo-audit --locked
+        run: command -v cargo-audit || cargo install cargo-audit --locked
 
       - name: Run security audit
         run: cargo audit
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ttl-0.20.2/.github/workflows/ci.yml 
new/ttl-0.21.0/.github/workflows/ci.yml
--- old/ttl-0.20.2/.github/workflows/ci.yml     2026-07-06 19:35:37.000000000 
+0200
+++ new/ttl-0.21.0/.github/workflows/ci.yml     2026-07-07 16:42:22.000000000 
+0200
@@ -6,12 +6,17 @@
   pull_request:
     branches: [master]
 
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
 env:
   CARGO_TERM_COLOR: always
 
 jobs:
   build:
     runs-on: ubuntu-latest
+    timeout-minutes: 30
     steps:
       - uses: actions/checkout@v7
 
@@ -27,6 +32,11 @@
       - name: Run tests
         run: cargo test --verbose
 
+      - name: Check docs
+        run: cargo doc --locked --no-deps --document-private-items
+        env:
+          RUSTDOCFLAGS: -D warnings
+
       # Real-kernel validation of the IP_HDRINCL send path (issue #12 fix): 
the ignored
       # test crafts IPv4 ICMP/UDP/TCP packets and sends them via a raw 
IP_HDRINCL socket,
       # which only succeeds if this OS's ip_len/ip_off byte order is correct 
(BSD kernels
@@ -39,13 +49,14 @@
           sudo "$BIN" runtime_hdrincl --ignored --nocapture
 
       - name: Clippy
-        run: cargo clippy -- -D warnings
+        run: cargo clippy --all-targets -- -D warnings
 
       - name: Format check
         run: cargo fmt -- --check
 
   build-macos:
     runs-on: macos-latest
+    timeout-minutes: 30
     steps:
       - uses: actions/checkout@v7
 
@@ -77,6 +88,7 @@
 
   msrv:
     runs-on: ubuntu-latest
+    timeout-minutes: 20
     steps:
       - uses: actions/checkout@v7
 
@@ -87,10 +99,14 @@
         uses: Swatinem/rust-cache@v2
 
       - name: Check MSRV
-        run: cargo check --verbose
+        run: cargo check --all-targets --locked --verbose
+
+      - name: Test MSRV lib
+        run: cargo test --lib --locked --verbose
 
   build-freebsd:
     runs-on: ubuntu-latest
+    timeout-minutes: 60
     steps:
       - uses: actions/checkout@v7
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ttl-0.20.2/.gitignore new/ttl-0.21.0/.gitignore
--- old/ttl-0.20.2/.gitignore   2026-07-06 19:35:37.000000000 +0200
+++ new/ttl-0.21.0/.gitignore   2026-07-07 16:42:22.000000000 +0200
@@ -5,3 +5,6 @@
 .DS_Store
 *.rlib
 .claude/
+
+# Code review state
+.last_review_sha
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ttl-0.20.2/CHANGELOG.md new/ttl-0.21.0/CHANGELOG.md
--- old/ttl-0.20.2/CHANGELOG.md 2026-07-06 19:35:37.000000000 +0200
+++ new/ttl-0.21.0/CHANGELOG.md 2026-07-07 16:42:22.000000000 +0200
@@ -5,6 +5,14 @@
 The format is based on [Keep a 
Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic 
Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [0.21.0] - 2026-07-07
+
+### Added
+- **Opt out of the startup update check** (#110). The GitHub release check can 
now be disabled five ways: the `--no-update-check` flag, the `DO_NOT_TRACK=1` 
(cross-tool standard) or `TTL_NO_UPDATE_CHECK=1` environment variables, a 
`no_update_check = true` preference in `~/.config/ttl/config.toml`, the 
**Update Check** toggle in the TUI Settings modal, or a build with 
`--no-default-features`, which compiles the check out entirely and drops the 
`update-informer` dependency. Addresses package-repository, air-gapped, and 
privacy use cases where an unconditional phone-home is unexpected. An 
[`examples/config.toml`](examples/config.toml) now documents the available 
preferences.
+
+### Security
+- **`crossbeam-epoch` 0.9.18 → 0.9.20** clears RUSTSEC-2026-0204 (invalid 
pointer dereference in the `fmt::Pointer` impl for `Atomic`/`Shared`). The 
advisory reaches ttl transitively via `moka` → `hickory-resolver`; the bump 
keeps `cargo audit` clean.
+
 ## [0.20.2] - 2026-07-06
 
 ### Fixed
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ttl-0.20.2/CONTRIBUTING.md 
new/ttl-0.21.0/CONTRIBUTING.md
--- old/ttl-0.20.2/CONTRIBUTING.md      2026-07-06 19:35:37.000000000 +0200
+++ new/ttl-0.21.0/CONTRIBUTING.md      2026-07-07 16:42:22.000000000 +0200
@@ -69,6 +69,36 @@
 After install, hooks run automatically. To run them manually against staged
 files: `prek run` (or `pre-commit run`).
 
+## Minimum Supported Rust Version (MSRV)
+
+ttl targets a **modest MSRV** rather than tracking the latest stable release. 
The
+current floor is **Rust 1.88** (edition 2024), declared as `rust-version` in
+`Cargo.toml` and enforced by the `msrv` CI job, which pins
+`dtolnay/[email protected]` and runs `cargo check`.
+
+Policy:
+
+- **MSRV bumps are deliberate and manual — never automated.** Dependabot's
+  `github-actions` group is configured to `ignore` `dtolnay/rust-toolchain`,
+  because that action is tagged by *Rust version*: without the ignore, 
Dependabot
+  reads the `@1.88` MSRV pin as a stale tag and tries to bump it to the newest
+  Rust, silently defeating the check.
+- **Dependency updates that raise the required Rust are caught, not merged
+  blindly.** If a `cargo` update needs newer than the MSRV, the `msrv` job goes
+  red. That's the decision point: pin the older dependency, or bump the MSRV on
+  purpose. Edition 2024 also uses the MSRV-aware resolver, so `cargo update`
+  prefers versions compatible with `rust-version` when one exists.
+- **When to re-evaluate:** when the `msrv` job forces it, when you genuinely 
need
+  a language/std feature stabilized after the current floor, or on a loose 
cadence
+  (a quick glance each minor release). A reasonable "modest" target is the Rust
+  shipped by current Debian stable / Ubuntu LTS, or roughly stable minus ~4
+  releases — don't chase the newest.
+
+To bump the MSRV when warranted, update **both** `rust-version` in `Cargo.toml`
+and the pinned version in the `msrv` job in `.github/workflows/ci.yml` (and the
+`Rust 1.88+` line under Prerequisites above). The `msrv` job then verifies the
+new floor actually builds.
+
 ## Testing
 
 ```bash
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ttl-0.20.2/Cargo.lock new/ttl-0.21.0/Cargo.lock
--- old/ttl-0.20.2/Cargo.lock   2026-07-06 19:35:37.000000000 +0200
+++ new/ttl-0.21.0/Cargo.lock   2026-07-07 16:42:22.000000000 +0200
@@ -519,9 +519,9 @@
 
 [[package]]
 name = "crossbeam-epoch"
-version = "0.9.18"
+version = "0.9.20"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
 dependencies = [
  "crossbeam-utils",
 ]
@@ -3601,7 +3601,7 @@
 
 [[package]]
 name = "ttl"
-version = "0.20.2"
+version = "0.21.0"
 dependencies = [
  "anyhow",
  "chrono",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ttl-0.20.2/Cargo.toml new/ttl-0.21.0/Cargo.toml
--- old/ttl-0.20.2/Cargo.toml   2026-07-06 19:35:37.000000000 +0200
+++ new/ttl-0.21.0/Cargo.toml   2026-07-07 16:42:22.000000000 +0200
@@ -1,6 +1,6 @@
 [package]
 name = "ttl"
-version = "0.20.2"
+version = "0.21.0"
 edition = "2024"
 rust-version = "1.88"
 description = "Modern traceroute/mtr-style TUI with hop stats and optional 
ASN/geo enrichment"
@@ -9,6 +9,13 @@
 keywords = ["traceroute", "mtr", "network", "tui", "diagnostics"]
 categories = ["command-line-utilities", "network-programming"]
 
+[features]
+default = ["update-check"]
+# Bundles the startup version check (a network call to the GitHub releases 
API).
+# Packagers can drop it entirely with `--no-default-features`, which also drops
+# the update-informer dependency.
+update-check = ["dep:update-informer"]
+
 [dependencies]
 # Async runtime
 tokio = { version = "1", features = ["full"] }
@@ -58,7 +65,7 @@
 dirs = "6.0"
 
 # Update notifications
-update-informer = { version = "1", default-features = false, features = 
["github", "ureq", "rustls-tls"] }
+update-informer = { version = "1", default-features = false, features = 
["github", "ureq", "rustls-tls"], optional = true }
 
 # TTY detection
 is-terminal = "0.4"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ttl-0.20.2/README.md new/ttl-0.21.0/README.md
--- old/ttl-0.20.2/README.md    2026-07-06 19:35:37.000000000 +0200
+++ new/ttl-0.21.0/README.md    2026-07-07 16:42:22.000000000 +0200
@@ -6,7 +6,9 @@
 
 Network diagnostic tool that goes beyond traceroute: MTU discovery, NAT 
detection, route flap alerts, IX identification, and more.
 
-![ttl screenshot](ttlss.png)
+<p align="center">
+  <img src="docs/demo.gif" alt="ttl live traceroute TUI: hops with ASN and 
reverse-DNS enrichment, per-hop loss/latency/jitter stats, sparklines, and a 
hop detail view" width="900">
+</p>
 
 
[![Crates.io](https://img.shields.io/crates/v/ttl.svg)](https://crates.io/crates/ttl)
 
[![CI](https://github.com/lance0/ttl/actions/workflows/ci.yml/badge.svg)](https://github.com/lance0/ttl/actions/workflows/ci.yml)
@@ -43,7 +45,7 @@
 - **MPLS label detection** from ICMP extensions
 - **ICMP, UDP, TCP probing** with auto-detection
 - **Great TUI** with themes, sparklines, and session export
-- **Update notifications** - in-app banner when new versions are available
+- **Update notifications** - in-app banner when new versions are available 
(opt out via `--no-update-check`, `DO_NOT_TRACK`, config, or a 
`--no-default-features` build)
 - **Scriptable** - JSON, CSV, text report, and line-delimited JSON streaming 
output
 - **Trace diffing** - compare two saved sessions for path and latency changes
 - **Daemon mode + Prometheus exporter** - headless continuous monitoring with 
`/metrics` and `/healthz`
@@ -225,6 +227,7 @@
 ttl --size 1400 host           # Large packets for MTU testing
 ttl --dscp 46 host             # QoS marking (EF)
 ttl --wide host                # Wide mode for wider terminals
+ttl --no-update-check host     # Skip the startup release check (see also 
DO_NOT_TRACK)
 ```
 
 See [docs/FEATURES.md](docs/FEATURES.md) for full CLI reference.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ttl-0.20.2/ROADMAP.md new/ttl-0.21.0/ROADMAP.md
--- old/ttl-0.20.2/ROADMAP.md   2026-07-06 19:35:37.000000000 +0200
+++ new/ttl-0.21.0/ROADMAP.md   2026-07-07 16:42:22.000000000 +0200
@@ -99,6 +99,12 @@
 
 ---
 
+## Completed (v0.21.0)
+
+- [x] Opt out of the startup update check (#110): `--no-update-check` flag, 
`DO_NOT_TRACK`/`TTL_NO_UPDATE_CHECK` env vars, `no_update_check` config key, a 
TUI Settings toggle, and a `--no-default-features` build that compiles the 
check (and `update-informer`) out entirely
+
+---
+
 ## Planned Features
 
 ### Before next release — TTL send-path correctness (follow-ups to #12)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ttl-0.20.2/docs/FEATURES.md 
new/ttl-0.21.0/docs/FEATURES.md
--- old/ttl-0.20.2/docs/FEATURES.md     2026-07-06 19:35:37.000000000 +0200
+++ new/ttl-0.21.0/docs/FEATURES.md     2026-07-07 16:42:22.000000000 +0200
@@ -419,6 +419,7 @@
 - **Theme**: Select from 11 built-in themes with live preview
 - **Display Mode**: Control column widths (auto/compact/wide)
 - **PeeringDB**: Configure API key and view cache status (only shown when IX 
detection is enabled)
+- **Update Check**: Toggle the startup check for new releases on/off 
(persisted to config)
 
 ### Navigation
 
@@ -426,7 +427,7 @@
 |-----|--------|
 | `Tab` | Switch between sections |
 | `Up`/`Down` or `j`/`k` | Navigate within section |
-| `Enter` or `Space` | Cycle option (theme/display mode) |
+| `Enter` or `Space` | Cycle option (theme/display mode) or toggle update 
check |
 | `r` | Refresh PeeringDB cache (in PeeringDB section) |
 | `Esc` | Close and save |
 
@@ -483,6 +484,21 @@
   - Pre-built binary: link to GitHub releases
 - **Non-interactive mode**: Update notice is printed to stderr after the run 
completes (JSON/CSV/report modes)
 
+### Disabling the update check
+
+The check can be turned off — useful for packaged installs, air-gapped hosts, 
or
+privacy. Any of these opts out (checked in this order):
+
+- **Per run**: `ttl --no-update-check <target>`
+- **Environment**: `DO_NOT_TRACK=1` (the [cross-tool 
standard](https://consoledonottrack.com/))
+  or `TTL_NO_UPDATE_CHECK=1`. Any value other than empty/`0`/`false` counts as 
set.
+- **Persistent**: `no_update_check = true` in `~/.config/ttl/config.toml` (see
+  [`examples/config.toml`](../examples/config.toml)), or toggle **Update 
Check**
+  off in the Settings modal (`s`), which saves the preference.
+- **Compile-time**: build with `--no-default-features` to drop the check — and 
the
+  `update-informer` dependency — entirely. Package maintainers can ship a build
+  that never phones home.
+
 ## Output Formats
 
 ### JSON
Binary files old/ttl-0.20.2/docs/demo.gif and new/ttl-0.21.0/docs/demo.gif 
differ
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ttl-0.20.2/docs/demo.tape 
new/ttl-0.21.0/docs/demo.tape
--- old/ttl-0.20.2/docs/demo.tape       1970-01-01 01:00:00.000000000 +0100
+++ new/ttl-0.21.0/docs/demo.tape       2026-07-07 16:42:22.000000000 +0200
@@ -0,0 +1,47 @@
+# ttl — README demo cassette (live TUI traceroute)
+#
+# Rendered with VHS (https://github.com/charmbracelet/vhs):
+#   PATH="$HOME/go/bin:$HOME/.local/bin:$PATH" vhs docs/demo.tape
+#
+# Captures the REAL TUI on a live trace. Raw-socket privilege comes from a
+# throwaway Docker container (--cap-add=NET_RAW), so no host setcap/sudo is
+# needed; the freshly built ./target/release/ttl is mounted in. Reproducible:
+# `cargo build --release` then re-run the vhs command from the repo root.
+
+Output docs/demo.gif
+
+Set Width 1280
+Set Height 760
+Set FontSize 15
+Set Padding 16
+Set Theme "Catppuccin Mocha"
+Set CursorBlink false
+
+# --- clean prompt 
-------------------------------------------------------------
+Hide
+Type "clear"
+Enter
+Sleep 500ms
+Show
+
+# --- launch the real TUI on a live trace (raw sockets via Docker NET_RAW) 
------
+Type@35ms "docker run --rm -it --cap-add=NET_RAW -v $PWD:/w -w /w ubuntu:24.04 
./target/release/ttl 1.1.1.1"
+Enter
+Sleep 12s        # container start + live trace: hops appear, ASNs + rDNS 
resolve, sparklines build
+
+# --- open a hop's detail view (percentiles, jitter, routing symmetry, TTL) 
----
+Down
+Sleep 350ms
+Down
+Sleep 350ms
+Down
+Sleep 500ms
+Enter
+Sleep 4500ms
+Escape
+Sleep 3s          # hold on the fully populated table (final good frame)
+
+# --- quit off-camera so the last frame is the TUI, not the shell prompt 
-------
+Hide
+Type "q"
+Sleep 800ms
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ttl-0.20.2/examples/config.toml 
new/ttl-0.21.0/examples/config.toml
--- old/ttl-0.20.2/examples/config.toml 1970-01-01 01:00:00.000000000 +0100
+++ new/ttl-0.21.0/examples/config.toml 2026-07-07 16:42:22.000000000 +0200
@@ -0,0 +1,21 @@
+# ttl configuration file
+# Place at ~/.config/ttl/config.toml
+#
+# ttl writes this file itself when you change settings in the TUI (theme,
+# display mode, PeeringDB key). You can also edit it by hand. Every key is
+# optional; omitted keys use the built-in defaults.
+
+# Color theme: default, kawaii, cyber, dracula, monochrome, matrix, nord,
+# gruvbox, catppuccin, tokyo_night, solarized
+theme = "default"
+
+# Column width mode: auto, compact, or wide
+display_mode = "auto"
+
+# PeeringDB API key for higher-rate-limit IX detection lookups
+# peeringdb_api_key = "your-key-here"
+
+# Disable the background check for a newer ttl release. Also honored via the
+# DO_NOT_TRACK or TTL_NO_UPDATE_CHECK environment variables, the
+# --no-update-check flag, or the Update Check toggle in the TUI settings.
+no_update_check = false
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ttl-0.20.2/src/cli.rs new/ttl-0.21.0/src/cli.rs
--- old/ttl-0.20.2/src/cli.rs   2026-07-06 19:35:37.000000000 +0200
+++ new/ttl-0.21.0/src/cli.rs   2026-07-07 16:42:22.000000000 +0200
@@ -124,6 +124,11 @@
     #[arg(long = "no-tui")]
     pub no_tui: bool,
 
+    /// Skip the startup check for new releases (also honors DO_NOT_TRACK=1 or
+    /// TTL_NO_UPDATE_CHECK=1, or a saved `no_update_check = true` preference)
+    #[arg(long = "no-update-check")]
+    pub no_update_check: bool,
+
     /// Output JSON (batch mode, requires -c)
     #[arg(long = "json")]
     pub json: bool,
@@ -475,6 +480,7 @@
             no_ix: false,
             geoip_db: None,
             no_tui: false,
+            no_update_check: false,
             json: false,
             csv: false,
             report: false,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ttl-0.20.2/src/main.rs new/ttl-0.21.0/src/main.rs
--- old/ttl-0.20.2/src/main.rs  2026-07-06 19:35:37.000000000 +0200
+++ new/ttl-0.21.0/src/main.rs  2026-07-07 16:42:22.000000000 +0200
@@ -70,13 +70,26 @@
         return run_diff_mode(&diff_files[0], &diff_files[1], args.json);
     }
 
-    // Spawn background update check after early exits
-    // Uses channel for non-blocking result retrieval at exit
-    let (update_tx, update_rx) = std::sync::mpsc::channel();
-    std::thread::spawn(move || {
-        let result = update::check_for_update();
-        let _ = update_tx.send(result); // Ignore if receiver dropped
-    });
+    // Spawn background update check after early exits, unless opted out by any
+    // source. Precedence (higher wins): compiled-out > env (DO_NOT_TRACK /
+    // TTL_NO_UPDATE_CHECK) > --no-update-check flag > saved `no_update_check`
+    // preference. Uses a channel for non-blocking result retrieval at exit.
+    // ponytail: re-reads the tiny prefs TOML here (the TUI reloads it later); 
a
+    // full prefs-threading refactor isn't worth it for one small file read.
+    let update_disabled = update::check_disabled(
+        update::env_opt_out(),
+        args.no_update_check,
+        Prefs::load().no_update_check,
+    );
+    let update_rx = if update_disabled {
+        None
+    } else {
+        let (update_tx, update_rx) = std::sync::mpsc::channel();
+        std::thread::spawn(move || {
+            let _ = update_tx.send(update::check_for_update()); // Ignore if 
receiver dropped
+        });
+        Some(update_rx)
+    };
 
     // Check permissions early
     if let Err(e) = check_permissions() {
@@ -358,7 +371,8 @@
 
     // Check for update notification (only for non-interactive mode)
     // Use short timeout so we don't delay exit if check is slow
-    if is_terminal::is_terminal(std::io::stderr())
+    if let Some(update_rx) = update_rx
+        && is_terminal::is_terminal(std::io::stderr())
         && let Ok(Some(new_version)) = 
update_rx.recv_timeout(Duration::from_millis(100))
     {
         update::print_update_notice(&new_version);
@@ -783,7 +797,7 @@
     cancel: CancellationToken,
     interface: Option<InterfaceInfo>,
     resolve_info: Option<ResolveInfo>,
-    update_rx: std::sync::mpsc::Receiver<Option<String>>,
+    update_rx: Option<std::sync::mpsc::Receiver<Option<String>>>,
 ) -> Result<()> {
     // Shared pending map for probe correlation (engine writes, receiver reads)
     let pending = new_pending_map();
@@ -940,7 +954,7 @@
         prefs,
         resolve_info,
         ix_lookup.clone(),
-        Some(update_rx),
+        update_rx,
         None, // replay_state (live mode)
         Some(add_target_tx),
     )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ttl-0.20.2/src/prefs.rs new/ttl-0.21.0/src/prefs.rs
--- old/ttl-0.20.2/src/prefs.rs 2026-07-06 19:35:37.000000000 +0200
+++ new/ttl-0.21.0/src/prefs.rs 2026-07-07 16:42:22.000000000 +0200
@@ -49,6 +49,11 @@
     /// PeeringDB API key for higher rate limits on IX detection
     #[serde(default, skip_serializing_if = "Option::is_none")]
     pub peeringdb_api_key: Option<String>,
+    /// Disable the background check for a newer ttl release. Absent/`None` 
means
+    /// enabled (the default); `true` opts out. Also overridable per-run via
+    /// --no-update-check or the DO_NOT_TRACK / TTL_NO_UPDATE_CHECK env vars.
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub no_update_check: Option<bool>,
 }
 
 impl Prefs {
@@ -137,6 +142,24 @@
         assert!(prefs.theme.is_none());
         assert!(prefs.display_mode.is_none());
         assert!(prefs.peeringdb_api_key.is_none());
+        assert!(prefs.no_update_check.is_none());
+    }
+
+    #[test]
+    fn test_prefs_no_update_check_roundtrip() {
+        // Some(true) is persisted; None is omitted (default = enabled).
+        let opted_out = Prefs {
+            no_update_check: Some(true),
+            ..Default::default()
+        };
+        let toml_str = toml::to_string_pretty(&opted_out).unwrap();
+        assert!(toml_str.contains("no_update_check = true"));
+        let loaded: Prefs = toml::from_str(&toml_str).unwrap();
+        assert_eq!(loaded.no_update_check, Some(true));
+
+        let default = Prefs::default();
+        let toml_str = toml::to_string_pretty(&default).unwrap();
+        assert!(!toml_str.contains("no_update_check"));
     }
 
     #[test]
@@ -145,6 +168,7 @@
             theme: Some("dracula".to_string()),
             display_mode: Some(DisplayMode::Wide),
             peeringdb_api_key: Some("test_api_key_123".to_string()),
+            no_update_check: None,
         };
         let toml_str = toml::to_string_pretty(&prefs).unwrap();
         assert!(toml_str.contains("theme = \"dracula\""));
@@ -166,6 +190,7 @@
             theme: Some("default".to_string()),
             display_mode: None,
             peeringdb_api_key: None,
+            no_update_check: None,
         };
         let toml_str = toml::to_string_pretty(&prefs).unwrap();
         // peeringdb_api_key should be omitted when None
@@ -208,6 +233,7 @@
             theme: None,
             display_mode: Some(DisplayMode::Auto),
             peeringdb_api_key: None,
+            no_update_check: None,
         };
         let toml_str = toml::to_string_pretty(&prefs).unwrap();
         assert!(toml_str.contains("display_mode = \"auto\""));
@@ -219,6 +245,7 @@
             theme: None,
             display_mode: Some(DisplayMode::Compact),
             peeringdb_api_key: None,
+            no_update_check: None,
         };
         let toml_str = toml::to_string_pretty(&prefs).unwrap();
         assert!(toml_str.contains("display_mode = \"compact\""));
@@ -230,6 +257,7 @@
             theme: None,
             display_mode: Some(DisplayMode::Wide),
             peeringdb_api_key: None,
+            no_update_check: None,
         };
         let toml_str = toml::to_string_pretty(&prefs).unwrap();
         assert!(toml_str.contains("display_mode = \"wide\""));
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ttl-0.20.2/src/probe/icmp.rs 
new/ttl-0.21.0/src/probe/icmp.rs
--- old/ttl-0.20.2/src/probe/icmp.rs    2026-07-06 19:35:37.000000000 +0200
+++ new/ttl-0.21.0/src/probe/icmp.rs    2026-07-07 16:42:22.000000000 +0200
@@ -9,7 +9,7 @@
 pub const DEFAULT_PAYLOAD_SIZE: usize = 56;
 /// Minimum payload size (4 bytes ProbeId + 4 bytes timestamp)
 pub const MIN_PAYLOAD_SIZE: usize = 8;
-/// PMTUD marker byte written to payload[8] to distinguish PMTUD probes.
+/// PMTUD marker byte written to `payload[8]` to distinguish PMTUD probes.
 /// Echo Replies echo the payload verbatim, so the receiver can detect
 /// PMTUD success without a separate ICMP identifier.
 pub const PMTUD_MARKER: u8 = 0x50; // 'P'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ttl-0.20.2/src/tui/app.rs 
new/ttl-0.21.0/src/tui/app.rs
--- old/ttl-0.20.2/src/tui/app.rs       2026-07-06 19:35:37.000000000 +0200
+++ new/ttl-0.21.0/src/tui/app.rs       2026-07-07 16:42:22.000000000 +0200
@@ -165,10 +165,14 @@
     let display_mode = initial_prefs.display_mode.unwrap_or_default();
     let api_key = initial_prefs.peeringdb_api_key.clone();
 
+    let mut settings = SettingsState::new(initial_index, display_mode, 
api_key);
+    // Toggle shows "enabled"; the pref stores the negated "disabled" form.
+    settings.update_check = !initial_prefs.no_update_check.unwrap_or(false);
+
     let mut ui_state = UiState {
         theme_index: initial_index,
         display_mode,
-        settings: SettingsState::new(initial_index, display_mode, api_key),
+        settings,
         update_rx,
         replay_state,
         ..Default::default()
@@ -216,6 +220,8 @@
         theme: Some(theme_names[ui_state.theme_index].to_string()),
         display_mode: Some(ui_state.display_mode),
         peeringdb_api_key: final_api_key,
+        // Toggle holds "enabled"; persist the negated "disabled" form.
+        no_update_check: Some(!ui_state.settings.update_check),
     })
 }
 
@@ -747,9 +753,15 @@
                         ui_state.settings.next_section(ix_enabled);
                     }
                     KeyCode::Enter | KeyCode::Char(' ') => {
-                        ui_state.settings.select();
-                        // Live preview display mode changes
-                        ui_state.display_mode = ui_state.settings.display_mode;
+                        if ui_state.settings.selected_section
+                            == SettingsState::update_check_section(ix_enabled)
+                        {
+                            ui_state.settings.update_check = 
!ui_state.settings.update_check;
+                        } else {
+                            ui_state.settings.select();
+                            // Live preview display mode changes
+                            ui_state.display_mode = 
ui_state.settings.display_mode;
+                        }
                     }
                     _ => {}
                 }
@@ -952,17 +964,20 @@
                     ui_state.set_status(format!("Display: {}", 
ui_state.display_mode.label()));
                 }
                 KeyCode::Char('s') => {
-                    // Open settings modal - preserve existing API key
+                    // Open settings modal - preserve existing API key and the
+                    // update-check toggle across opens.
                     let current_api_key = if 
ui_state.settings.api_key.is_empty() {
                         None
                     } else {
                         Some(ui_state.settings.api_key.clone())
                     };
+                    let update_check = ui_state.settings.update_check;
                     ui_state.settings = SettingsState::new(
                         ui_state.theme_index,
                         ui_state.display_mode,
                         current_api_key,
                     );
+                    ui_state.settings.update_check = update_check;
                     ui_state.show_settings = true;
                 }
                 // Open target input modal (live mode only)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ttl-0.20.2/src/tui/views/settings.rs 
new/ttl-0.21.0/src/tui/views/settings.rs
--- old/ttl-0.20.2/src/tui/views/settings.rs    2026-07-06 19:35:37.000000000 
+0200
+++ new/ttl-0.21.0/src/tui/views/settings.rs    2026-07-07 16:42:22.000000000 
+0200
@@ -23,6 +23,8 @@
     pub api_key: String,
     /// Cursor position in API key input
     pub api_key_cursor: usize,
+    /// Whether the startup update check is enabled (persisted to prefs)
+    pub update_check: bool,
 }
 
 impl SettingsState {
@@ -36,9 +38,17 @@
             display_mode,
             api_key,
             api_key_cursor: cursor,
+            // Caller overrides from prefs; enabled is the default.
+            update_check: true,
         }
     }
 
+    /// Section index of the Update Check toggle. It sits after the optional
+    /// PeeringDB section, so its index depends on whether IX detection is on.
+    pub fn update_check_section(ix_enabled: bool) -> usize {
+        if ix_enabled { 3 } else { 2 }
+    }
+
     /// Insert a character at the cursor position
     pub fn handle_char(&mut self, c: char) {
         self.api_key.insert(self.api_key_cursor, c);
@@ -123,9 +133,10 @@
         // Display mode section has no up/down navigation
     }
 
-    /// Switch between sections (0=Theme, 1=Display Mode, 2=PeeringDB)
+    /// Switch between sections. Order: 0=Theme, 1=Display Mode,
+    /// 2=PeeringDB (only when IX detection is enabled), then Update Check 
last.
     pub fn next_section(&mut self, ix_enabled: bool) {
-        let num_sections = if ix_enabled { 3 } else { 2 };
+        let num_sections = if ix_enabled { 4 } else { 3 };
         self.selected_section = (self.selected_section + 1) % num_sections;
     }
 
@@ -187,9 +198,10 @@
 
 impl Widget for SettingsView<'_> {
     fn render(self, area: Rect, buf: &mut Buffer) {
-        // Calculate centered popup area (taller if IX is enabled)
+        // Calculate centered popup area (taller if IX is enabled). The extra 4
+        // rows hold the Update Check section (blank + header + toggle + hint).
         let popup_width = 44.min(area.width.saturating_sub(4));
-        let base_height = if self.ix_enabled { 23 } else { 17 };
+        let base_height = if self.ix_enabled { 27 } else { 21 };
         let popup_height = base_height.min(area.height.saturating_sub(4));
         let popup_x = (area.width - popup_width) / 2 + area.x;
         let popup_y = (area.height - popup_height) / 2 + area.y;
@@ -391,6 +403,39 @@
             }
         }
 
+        // Update Check section (always shown; sits after the optional 
PeeringDB
+        // section so its index shifts with ix_enabled).
+        lines.push(Line::from(""));
+        let uc_section = SettingsState::update_check_section(self.ix_enabled);
+        let uc_selected = self.state.selected_section == uc_section;
+        let uc_header_style = if uc_selected {
+            Style::default()
+                .fg(self.theme.header)
+                .add_modifier(Modifier::BOLD)
+        } else {
+            Style::default().fg(self.theme.text_dim)
+        };
+        lines.push(Line::from(vec![Span::styled(
+            "  Update Check",
+            uc_header_style,
+        )]));
+        let uc_state = if self.state.update_check { "on" } else { "off" };
+        let uc_style = if uc_selected {
+            Style::default()
+                .fg(self.theme.shortcut)
+                .add_modifier(Modifier::BOLD)
+        } else {
+            Style::default().fg(self.theme.text_dim)
+        };
+        lines.push(Line::from(vec![Span::styled(
+            format!("    [{}]  (Enter to toggle)", uc_state),
+            uc_style,
+        )]));
+        lines.push(Line::from(vec![Span::styled(
+            "    check GitHub for new releases at startup",
+            Style::default().fg(self.theme.text_dim),
+        )]));
+
         lines.push(Line::from(""));
 
         // Footer with keybindings
@@ -457,21 +502,29 @@
     fn test_settings_state_next_section() {
         let mut state = SettingsState::new(0, DisplayMode::Auto, None);
 
-        // Without IX enabled (2 sections)
+        // Without IX enabled: 3 sections (theme, display, update-check)
         assert_eq!(state.selected_section, 0);
         state.next_section(false);
         assert_eq!(state.selected_section, 1);
         state.next_section(false);
+        assert_eq!(state.selected_section, 2); // update-check
+        state.next_section(false);
         assert_eq!(state.selected_section, 0); // Wraps
 
-        // With IX enabled (3 sections)
+        // With IX enabled: 4 sections (theme, display, peeringdb, 
update-check)
         state.selected_section = 0;
         state.next_section(true);
         assert_eq!(state.selected_section, 1);
         state.next_section(true);
         assert_eq!(state.selected_section, 2);
         state.next_section(true);
+        assert_eq!(state.selected_section, 3); // update-check
+        state.next_section(true);
         assert_eq!(state.selected_section, 0); // Wraps
+
+        // Update-check section index tracks ix_enabled.
+        assert_eq!(SettingsState::update_check_section(false), 2);
+        assert_eq!(SettingsState::update_check_section(true), 3);
     }
 
     #[test]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ttl-0.20.2/src/update.rs new/ttl-0.21.0/src/update.rs
--- old/ttl-0.20.2/src/update.rs        2026-07-06 19:35:37.000000000 +0200
+++ new/ttl-0.21.0/src/update.rs        2026-07-07 16:42:22.000000000 +0200
@@ -2,8 +2,42 @@
 //!
 //! Checks GitHub releases for new versions (cached, 1h interval).
 //! Detects install method and shows appropriate update command.
+//!
+//! The check can be opted out of via `--no-update-check`, the `DO_NOT_TRACK`
+//! or `TTL_NO_UPDATE_CHECK` env vars, a `no_update_check = true` preference, 
or
+//! by building without the default `update-check` feature.
+
+/// Whether update checking was compiled in (the `update-check` feature, on by
+/// default). Package builds can drop it with `--no-default-features`, which 
also
+/// removes the `update-informer` dependency from the tree.
+pub const ENABLED: bool = cfg!(feature = "update-check");
+
+/// True when the user opted out of the update check via environment:
+/// `DO_NOT_TRACK` (the cross-tool standard, <https://consoledonottrack.com>) 
or
+/// `TTL_NO_UPDATE_CHECK`.
+pub fn env_opt_out() -> bool {
+    ["DO_NOT_TRACK", "TTL_NO_UPDATE_CHECK"]
+        .iter()
+        .any(|var| std::env::var(var).is_ok_and(|v| flag_is_truthy(&v)))
+}
 
-use update_informer::registry::GitHub;
+/// A set env var counts as opt-out unless it's explicitly falsey (empty, "0",
+/// or "false"). We err toward *not* phoning home when the value is ambiguous.
+fn flag_is_truthy(v: &str) -> bool {
+    !matches!(v.trim(), "" | "0" | "false")
+}
+
+/// Resolve whether the background update check should be skipped, honoring
+/// precedence (higher layer wins): compiled-out > env opt-out > CLI flag >
+/// saved `no_update_check` preference.
+///
+/// `pref` is tristate: `Some(true)` opts out, `Some(false)` explicitly keeps
+/// the check on, and `None` (unset) falls back to the compiled-in default
+/// (enabled). ttl stores its config and TUI-saved prefs in one `config.toml`,
+/// so there is a single preference layer here (xfr splits config vs. prefs).
+pub fn check_disabled(env_opt_out: bool, cli_flag: bool, pref: Option<bool>) 
-> bool {
+    !ENABLED || env_opt_out || cli_flag || pref.unwrap_or(false)
+}
 
 /// How ttl was installed (best guess based on binary path)
 #[derive(Debug, Clone, Copy)]
@@ -59,9 +93,10 @@
 /// Uses interval(ZERO) to always perform a network check — we only call this
 /// once per process lifetime (in a background thread), so cache-based rate
 /// limiting is unnecessary.
+#[cfg(feature = "update-check")]
 pub fn check_for_update() -> Option<String> {
     use std::time::Duration;
-    use update_informer::Check;
+    use update_informer::{Check, registry::GitHub};
 
     let informer = update_informer::new(GitHub, "lance0/ttl", 
env!("CARGO_PKG_VERSION"))
         .interval(Duration::ZERO);
@@ -73,6 +108,12 @@
         .map(|v| v.to_string())
 }
 
+/// No-op stub when built without the `update-check` feature.
+#[cfg(not(feature = "update-check"))]
+pub fn check_for_update() -> Option<String> {
+    None
+}
+
 /// Print update notification to stderr
 pub fn print_update_notice(new_version: &str) {
     let method = InstallMethod::detect();
@@ -105,6 +146,32 @@
     use super::*;
 
     #[test]
+    fn test_flag_is_truthy() {
+        for on in ["1", "true", "yes", "on", " 1 "] {
+            assert!(flag_is_truthy(on), "{on:?} should count as opt-out");
+        }
+        for off in ["", "  ", "0", "false"] {
+            assert!(!flag_is_truthy(off), "{off:?} should not count as 
opt-out");
+        }
+    }
+
+    #[test]
+    fn test_check_disabled_precedence() {
+        // The tristate resolution only matters when the check is compiled in.
+        if !ENABLED {
+            assert!(check_disabled(false, false, Some(false)));
+            return;
+        }
+        // Saved pref governs when env/CLI are quiet.
+        assert!(check_disabled(false, false, Some(true))); // opt out
+        assert!(!check_disabled(false, false, Some(false))); // explicitly on
+        assert!(!check_disabled(false, false, None)); // default on
+        // env or CLI opt-out wins over an explicit pref "on".
+        assert!(check_disabled(true, false, Some(false)));
+        assert!(check_disabled(false, true, Some(false)));
+    }
+
+    #[test]
     fn test_install_method_commands() {
         assert_eq!(InstallMethod::Homebrew.update_command(), "brew upgrade 
ttl");
         assert_eq!(InstallMethod::Cargo.update_command(), "cargo install ttl");
Binary files old/ttl-0.20.2/ttlss.png and new/ttl-0.21.0/ttlss.png differ

++++++ ttl.obsinfo ++++++
--- /var/tmp/diff_new_pack.D8l3cM/_old  2026-07-09 22:19:32.130443926 +0200
+++ /var/tmp/diff_new_pack.D8l3cM/_new  2026-07-09 22:19:32.138444198 +0200
@@ -1,5 +1,5 @@
 name: ttl
-version: 0.20.2
-mtime: 1783359337
-commit: 11349e34e91844b2f6144ae4e6d412b3f9ae5220
+version: 0.21.0
+mtime: 1783435342
+commit: 46f1dc2cd8c055408a764d5674afa897ca7cd30c
 

++++++ vendor.tar.zst ++++++
/work/SRC/openSUSE:Factory/ttl/vendor.tar.zst 
/work/SRC/openSUSE:Factory/.ttl.new.1991/vendor.tar.zst differ: char 7, line 1

Reply via email to