Skip to content

Commit 8a253bd

Browse files
committed
(feat): bring pretty to 2026
1 parent c927638 commit 8a253bd

File tree

526 files changed

+3890
-213525
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

526 files changed

+3890
-213525
lines changed

.github/workflows/ci.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: Test and coverage
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
unit-tests:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- uses: actions/checkout@v6.0.2
10+
with:
11+
fetch-depth: 2
12+
- uses: actions/setup-go@v6.2.0
13+
with:
14+
go-version: '1.25'
15+
- name: Run coverage
16+
run: go env -w GOTOOLCHAIN=go1.25.0+auto && go test -coverpkg=./... ./... -race -coverprofile=coverage.out -covermode=atomic
17+
- name: Upload coverage to Codecov
18+
uses: codecov/codecov-action@v5
19+
with:
20+
verbose: true
21+
env:
22+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

.github/workflows/codeql.yml

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# For most projects, this workflow file will not need changing; you simply need
2+
# to commit it to your repository.
3+
#
4+
# You may wish to alter this file to override the set of languages analyzed,
5+
# or to provide custom queries or build logic.
6+
#
7+
# ******** NOTE ********
8+
# We have attempted to detect the languages in your repository. Please check
9+
# the `language` matrix defined below to confirm you have the correct set of
10+
# supported CodeQL languages.
11+
#
12+
name: "CodeQL"
13+
14+
on:
15+
push:
16+
branches: [ "main" ]
17+
pull_request:
18+
# The branches below must be a subset of the branches above
19+
branches: [ "main" ]
20+
schedule:
21+
- cron: '35 15 * * 3'
22+
23+
jobs:
24+
analyze:
25+
name: Analyze
26+
runs-on: ubuntu-latest
27+
permissions:
28+
actions: read
29+
contents: read
30+
security-events: write
31+
32+
strategy:
33+
fail-fast: false
34+
matrix:
35+
language: [ 'go' ]
36+
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
37+
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
38+
39+
steps:
40+
- name: Checkout repository
41+
uses: actions/checkout@v4
42+
43+
# Initializes the CodeQL tools for scanning.
44+
- name: Initialize CodeQL
45+
uses: github/codeql-action/init@v3
46+
with:
47+
languages: ${{ matrix.language }}
48+
# If you wish to specify custom queries, you can do so here or in a config file.
49+
# By default, queries listed here will override any specified in a config file.
50+
# Prefix the list here with "+" to use these queries and those in the config file.
51+
52+
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
53+
# queries: security-extended,security-and-quality
54+
55+
56+
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
57+
# If this step fails, then you should remove it and run the build manually (see below)
58+
- name: Autobuild
59+
uses: github/codeql-action/autobuild@v3
60+
61+
# ℹ️ Command-line programs to run using the OS shell.
62+
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
63+
64+
# If the Autobuild fails above, remove it and uncomment the following three lines.
65+
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
66+
67+
# - run: |
68+
# echo "Run, Build Application using script"
69+
# ./location_of_script_within_repo/buildscript.sh
70+
71+
- name: Perform CodeQL Analysis
72+
uses: github/codeql-action/analyze@v3
73+
with:
74+
category: "/language:${{matrix.language}}"

.github/workflows/go.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# This workflow will build a golang project
2+
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go
3+
4+
name: Go
5+
6+
on:
7+
push:
8+
branches: [ "main" ]
9+
pull_request:
10+
branches: [ "main" ]
11+
12+
jobs:
13+
14+
build:
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@v6.0.2
18+
19+
- name: Set up Go
20+
uses: actions/setup-go@v6.2.0
21+
with:
22+
go-version: '1.25'
23+
24+
- name: Build
25+
run: go build -v ./...
26+
27+
- name: Test
28+
run: go test -v ./...

README.md

Lines changed: 119 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,154 @@
1+
[![Go Report Card](https://goreportcard.com/badge/github.com/ncode/pretty)](https://goreportcard.com/report/github.com/ncode/pretty)
2+
[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
3+
[![codecov](https://codecov.io/gh/ncode/pretty/graph/badge.svg?token=BCUQ77HCLY)](https://codecov.io/gh/ncode/pretty)
4+
15
# pretty
2-
Parallel remote execution tty - (Yet another parallel ssh/shell)
36

4-
## Installation:
5-
go get -u github.com/ncode/pretty
7+
`Parallel remote execution tty` - (Yet another parallel ssh/shell)
8+
9+
- Run commands across many hosts with colored, prefixed output.
10+
- Keep an interactive prompt with a per-host shell session.
11+
- Run async jobs in separate SSH sessions and track status.
12+
- Load hosts from args, config groups, or a hosts file.
613

7-
## Config:
8-
By default it lives in ~/.pretty.yaml
14+
## Installation
15+
Requires Go 1.25.
916

1017
```
11-
username: ncode
12-
history_file: ~/.pretty.history
13-
ssh_private_key: ~/.ssh/id_rsa
14-
groups:
15-
hosts:
16-
- host1
17-
- host2
18-
- host3
19-
- host4
18+
go install github.com/ncode/pretty@latest
2019
```
2120

22-
## Usage:
21+
## Quick start
22+
```
23+
pretty host1 host2 host3
24+
pretty -G prod
25+
pretty -H /tmp/hosts.txt
2326
```
24-
Parallel remote execution tty - (Yet another parallel ssh/shell)
2527

26-
usage:
27-
pretty <host1> <host2> <host3>...
28+
## Configuration
29+
`pretty` looks for a config file named `.pretty` in your home directory with a supported extension:
30+
`$HOME/.pretty.yaml`, `$HOME/.pretty.yml`, `$HOME/.pretty.json`, or `$HOME/.pretty.toml`.
31+
Use `--config` to point at an explicit path.
2832

29-
Usage:
30-
pretty [flags]
33+
Optional keys:
34+
- `username`: SSH username override (falls back to SSH config, then current shell user).
35+
- `known_hosts`: path to a known_hosts file for host key verification.
36+
- `groups.<name>`: host groups as wrapper objects with `hosts` and optional `user`.
37+
- `prompt`: interactive prompt string (UTF-8 supported). `--prompt` overrides config.
3138

32-
Flags:
33-
--config string config file (default is $HOME/.pretty.yaml)
34-
-h, --help help for pretty
35-
-G, --hostGroup string group of hosts to be loaded from the config file
36-
-H, --hostsFile string hosts file to be used instead of the args via stdout (one host per line)
39+
Example:
3740
```
41+
known_hosts: /Users/me/.ssh/known_hosts
42+
prompt: "pretty> "
43+
groups:
44+
web:
45+
user: deploy
46+
hosts:
47+
- web1.example.com
48+
- web2.example.com:2222
49+
```
50+
51+
Host key verification:
52+
- If `known_hosts` is set and loads successfully, it is used.
53+
- Otherwise `~/.ssh/known_hosts` is used if it loads successfully.
54+
- If neither can be loaded, host keys are not verified.
55+
- A loaded known_hosts file must contain each host key or connections will fail.
3856

39-
Connecting to hosts:
57+
Notes:
58+
- Group entries must use the wrapper schema with a `hosts` list.
59+
- Auth uses your SSH agent (`SSH_AUTH_SOCK`) and IdentityFile entries from SSH config. Load keys with `ssh-add`.
60+
61+
## Host specs
62+
Accepted formats:
63+
- `host` (defaults to port 22)
64+
- `host:port`
65+
- `user@host`
66+
- `user@host:port`
67+
- `[ipv6]:port` (required to specify a port with IPv6)
68+
- `user@[ipv6]:port`
69+
70+
Hosts files (`-H`) accept one entry per line in the same formats. Blank lines are ignored.
71+
72+
## Flags
73+
- `--config <path>`: config file path.
74+
- `--prompt <string>`: prompt to display in the interactive shell.
75+
- `-G`, `--hostGroup <name>`: load `groups.<name>` from config.
76+
- `-H`, `--hostsFile <path>`: read hosts from a file (one host per line).
77+
- `-h`, `--help`: help for pretty.
78+
79+
Host selection behavior:
80+
- At least one of positional hosts, `--hostGroup`, or `--hostsFile` is required.
81+
- With no positional hosts, `--hostGroup` loads only the group.
82+
- With more than one positional host, `--hostGroup` appends the group.
83+
- With exactly one positional host, `--hostGroup` is currently ignored.
84+
- `--hostsFile` always appends its hosts.
85+
86+
## Interactive commands
4087
```
41-
pretty host1 host2 host3 host4
42-
pretty(2)>>
43-
Error connection to host host3: Failed to dial: dial tcp: lookup host3: no such host
44-
Error connection to host host4: Failed to dial: dial tcp: lookup host4: no such host
88+
:help
89+
:list
90+
:status [id]
91+
:async <command>
92+
:scroll
93+
:bye
94+
exit
4595
```
4696

47-
Connecting to hostGroups:
97+
Notes:
98+
- `:list` shows connection status per host.
99+
- `:status` shows the last normal job plus the last two async jobs; `:status <id>` targets a single job.
100+
- `:async` runs a command in a new SSH session per host and returns to the prompt immediately.
101+
- `:scroll` enters scroll mode for the output viewport (output scrolling is disabled otherwise); press `esc` to return to the prompt.
102+
- Use Up/Down arrows to navigate command history (persisted in `history_file`).
103+
- `Ctrl+C` forwards to remote sessions; press twice within 500ms to quit locally.
104+
- `Ctrl+Z` forwards to remote sessions (suspend).
105+
106+
## How it works
107+
- Starts one persistent SSH shell session per host for interactive commands.
108+
- Wraps each command with a sentinel to capture per-host exit codes.
109+
- Runs async commands in fresh SSH sessions and updates job status as they finish.
110+
- Prefixes output with `host:port` and assigns a stable color per host.
111+
- Keeps the last 10,000 output lines in the UI buffer.
112+
113+
## Local SSHD testbed
114+
Use the local SSHD testbed to exercise `pretty` against three localhost targets.
115+
116+
Generate keys, password, and a ready-to-use config file:
48117
```
49-
pretty -G hosts
50-
pretty(2)>>
51-
Error connection to host host3: Failed to dial: dial tcp: lookup host3: no such host
52-
Error connection to host host4: Failed to dial: dial tcp: lookup host4: no such host
118+
export PRETTY_AUTHORIZED_KEY="$(ssh-add -L | grep 'my-key' | head -n1)"
119+
./scripts/ssh-testbed-setup.sh
53120
```
54121

55-
Connecting to hostsFile:
122+
Start the testbed:
56123
```
57-
pretty -H /tmp/hosts.txt
58-
pretty(2)>>
59-
Error connection to host host3: Failed to dial: dial tcp: lookup host3: no such host
60-
Error connection to host host4: Failed to dial: dial tcp: lookup host4: no such host
124+
docker compose -f docker-compose.sshd.yml up -d --build
61125
```
62126

63-
List connection status:
127+
Re-run setup after the containers are running to populate `.pretty-test/known_hosts`:
64128
```
65-
pretty(2)>> :status
66-
Connected hosts (2)
67-
Failed hosts (2)
129+
./scripts/ssh-testbed-setup.sh
68130
```
69131

70-
List hosts:
132+
If you want to use the generated test key instead of an existing agent key:
71133
```
72-
pretty(2)>> :list
73-
host1: Connected(true)
74-
host2: Connected(true)
75-
host3: Connected(false)
76-
host4: Connected(false)
134+
ssh-add .pretty-test/id_ed25519
77135
```
78136

79-
Running commands:
137+
Example run:
80138
```
81-
pretty(2)>> whoami
82-
host1: ncode
83-
host2: ncode
139+
pretty --config .pretty-test/pretty.yaml -G testbed
84140
```
141+
Then run `whoami` to confirm each host responds as `pretty`.
142+
143+
The generated password is stored at `.pretty-test/password.txt` for manual `ssh` testing if needed.
85144

86-
## Why do I need it?
87-
pretty is a tool to control interactive shells across multiple hosts from
88-
a single point.
145+
## Why pretty?
146+
`pretty` is a tool to control interactive shells across multiple hosts from a single point.
89147

90148
### Motivation
91-
After using [polysh](http://guichaz.free.fr/polysh) for a long time. It came with
92-
the motivation to try to write my own parallel shell in GO. In the end the tool worked
149+
After using [polysh](http://guichaz.free.fr/polysh) for a long time, it came with
150+
the motivation to try to write my own parallel shell in Go. In the end the tool worked
93151
so well and I decided to open source the code.
94152

95-
### TODO:
96-
Forward Control+C and Control+Z requests to the destination terminal
97-
Support for encrypted ssh keys
153+
## Limitations
154+
- SSH authentication uses the local agent and SSH config IdentityFile entries; there is no keyfile flag.

0 commit comments

Comments
 (0)