#+OPTIONS: tags:nil -:nil ^:{} H:4
* swiss-army-knife :TOC_7_gh:
- [[#created-by][Created By]]
- [[#introduction][Introduction]]
- [[#installation][Installation]]
- [[#static-builds-from-hackage][Static Builds from Hackage]]
- [[#documentation][Documentation]]
- [[#privileges-sudo][Privileges (sudo)]]
- [[#sak---help][sak --help]]
- [[#sak-version][sak version]]
- [[#sak-extip][sak extip]]
- [[#sak-kernel][sak kernel]]
- [[#sak-sleep][sak sleep]]
- [[#sak-zfscheck][sak zfscheck]]
- [[#sak-wg][sak wg]]
- [[#sak-nm][sak nm]]
- [[#sak-sn][sak sn]]
- [[#sak-gitit][sak gitit]]
- [[#managing-targets][Managing targets]]
- [[#creating-a-repo][Creating a repo]]
- [[#local][Local]]
- [[#github][GitHub]]
- [[#gitlab][GitLab]]
- [[#bitbucket][Bitbucket]]
- [[#errors][Errors]]
- [[#examples][Examples]]
** Created By
Fred Mitchell & Atomlogik
** Introduction
Swiss-Army-Knife is a collection of useful
tools that you can do quickly.
This is now in Hackage and the AUR. So in theory,
it can run "anywhere", though I have not had the time to test this,
so I am open to comments and pull requests. In particular, it is
gauranteed to run on Arch, and I do have some support for other
distros, and that will grow. But I have not had the time to fully
test other distros.
So some knives will run "anywhere", but some that call out to
the system for system-specific information may have issues.
As I find out about them, I will fix them as quickly as I can.
** Installation
+ using cabal (recommended)
#+begin_src sh
cabal update
cabal install swiss-army-knife
#+end_src
+ On Arch from the AUR
| using aura | aura -A swiss-army-knife |
| using yay | yay -S swiss-army-knife |
| using paru | paru -S swiss-army-knife |
*** Static Builds from Hackage
To build a statically-linked binary, install:
+ =ghc-static= (Arch: =pacman -S ghc-static=)
+ a static gmp (Arch: AUR =libgmp-static=)
+ a static zlib — Arch's official package dropped =libz.a= as of 1.3.2;
build it yourself from https://zlib.net if needed.
Then: =cabal install swiss-army-knife -fstatic=
** Documentation
These are the current "knives". More to come; suggestions welcome.
Every knife is a sub-command of =sak=, and each has its own help:
#+begin_src fish
sak --help # list all knives
sak wg --help # options for one knife
#+end_src
*** Privileges (sudo)
You don't need to run =sak= itself with =sudo=. The knives that
change system services (=wg=, =nm=, =sn=) call =sudo systemctl=
themselves, so expect a password prompt unless your sudoers
configuration allows =systemctl= without one. The other knives
run as your normal user.
*** sak --help
Lists all the knives with a one-line description of each.
=-v= / =--verbose= is accepted as a global flag, but no knife
uses it yet.
*** sak version
Prints the installed version of Swiss Army Knife.
*** sak extip
Shows the external (public) IP address of your connection, which
is what the rest of the internet sees. Handy for confirming that a
VPN is actually in use. By default it also shows the approximate
location and time zone of that address.
| Flag | Meaning |
|------------------+-----------------------------------------------------|
| -n, --nolocation | Show only the IP address, without location lookup. |
| -i, --ipv6 | Show your IPv6 address instead of the IPv4 address. |
Example output:
#+begin_example
203.0.113.42
Location: Frankfurt am Main, Germany (DE)
Timezone: Europe/Berlin
#+end_example
The address comes from jsonip.com; the location from ip-api.com.
=-i= only works if your connection has IPv6. The location lookup
is done over plain HTTP and may reflect your IPv4 address even
with =-i=; use =-n= if you'd rather not send that request.
*** sak kernel
Shows the kernel you are running and the kernel packages that are
installed, so you can tell whether you need a reboot after an
upgrade.
| Flag | Meaning |
|-------------------+--------------------------------------------------------------|
| -j, --justversion | Print only the running kernel version (useful in scripts). |
Example output (Arch):
#+begin_example
Running kernel: 6.17.7-arch1-1
Installed kernels:
linux 6.17.8.arch1-1
linux-lts 6.12.57-1
#+end_example
The running kernel comes from =uname -r=. Installed kernels are
looked up with the distribution's package manager:
| Distribution family | How |
|--------------------------------------------------------+-----------------------------------------------|
| Arch | =pacman -Q= for linux, linux-lts, linux-zen, linux-hardened |
| Debian, Ubuntu, Mint, Pop!_OS, Kali, elementary, Zorin | =dpkg-query= for =linux-image-*= |
| RHEL, CentOS, Fedora, Rocky, AlmaLinux, Oracle | =rpm -qa kernel*= (headers, devel etc. excluded) |
Arch is the most tested. If the packages can't be determined,
=sak kernel= says so and still shows the running kernel.
*** sak sleep
Suspends the machine (=systemctl suspend=).
| Flag | Meaning |
|--------------------+------------------------------------------|
| -w, --wait SECONDS | Wait this many seconds before suspending. |
#+begin_src fish
sak sleep # suspend now
sak sleep -w 300 # suspend in 5 minutes
#+end_src
Suspend is requested with =-i=, so it goes ahead even if a program
(a video player, a download) is holding a suspend inhibitor. The
wait runs in the foreground; press Ctrl-C during it to cancel.
*** sak zfscheck
*Arch Linux with archzfs only.* ZFS on Arch is an out-of-tree
module, and archzfs sometimes lags behind the newest kernel. If
you upgrade the kernel before ZFS supports it, the ZFS module
won't load and your pools won't import. =zfscheck= tells you,
before you upgrade, whether the kernel currently in the repos is
supported. That lets you stay on the regular =linux= kernel (held
back when needed) instead of switching to =linux-lts=.
| Flag | Meaning |
|------------+-------------------------------------------------------------|
| -f, --full | Also show where the supported-kernel ceiling was read from. |
It compares:
+ the =linux= version in the repos, the one installed, and the
one running;
+ the OpenZFS version the repos would install (=zfs-dkms=, or
=zfs-utils=), and the newest kernel that OpenZFS release
supports (=Linux-Maximum= in its =META= file, read from
=/usr/src/zfs-VERSION/META= if present, otherwise fetched from
the OpenZFS release tag on GitHub with =curl=);
+ the exact kernel build that the prebuilt =zfs-linux= package
targets, if that package is available.
It then gives a verdict for both ways of installing ZFS:
#+begin_example
DKMS route: OK
Prebuilt route: NO
Upgrade possible: install linux, linux-headers and the ZFS packages in one transaction.
#+end_example
+ *DKMS route*: OK when the repo kernel's major.minor version is
no newer than what the OpenZFS release supports.
+ *Prebuilt route*: OK only when =zfs-linux= was built for exactly
the repo kernel.
+ If neither is OK it tells you not to upgrade the kernel yet.
Add =linux= (and =linux-headers=) to =IgnorePkg= in
=/etc/pacman.conf= to hold it back while upgrading everything
else, and run =zfscheck= again later.
The repo versions come from pacman's sync databases, so the result
is only as fresh as your last database sync. =curl= must be
installed for the GitHub fallback.
*** sak wg
Manages WireGuard VPNs set up for =wg-quick=: each
=/etc/wireguard/NAME.conf= is a VPN called =NAME=, run by the
systemd unit =wg-quick@NAME=.
| Flag | Meaning |
|--------------------+--------------------------------------------------------------------|
| -l, --list | List each VPN with whether it is active and whether it is enabled. |
| -a, --activate VPN | Start VPN for this session only. |
| -A, --enable VPN | Start VPN and enable it, so it also starts at boot. |
| -d, --deactivate | Stop every active VPN. They stay enabled for the next boot. |
| -D, --disable | Disable every enabled VPN, and stop them. |
| -r, --reactivate | Restart every active VPN (e.g. after editing a config). |
=-l= can be combined with any one other flag; the rest are
mutually exclusive. The list is printed before the action runs,
so it shows the state beforehand.
#+begin_src fish
sak wg -l # what's there and what's running
sak wg -d; sak wg -a de-berlin # switch VPNs (one action per call)
#+end_src
=sak= needs to be able to read the =/etc/wireguard= directory to
find the VPN names.
*** sak nm
Controls NetworkManager (=NetworkManager.service=). Give exactly one
flag:
| Flag | Meaning |
|------------------+--------------------------------------------|
| -a, --activate | Start it for this session. |
| -A, --enable | Start it and enable it at boot. |
| -d, --deactivate | Stop it; it still starts at the next boot. |
| -D, --disable | Stop it and disable it at boot. |
| -r, --reactivate | Restart it. |
Together with =sak sn= this makes switching between NetworkManager
and systemd-networkd a two-command job, e.g. =sak nm -D; sak sn -A=.
Don't leave both enabled: they will fight over the interfaces.
*** sak sn
Controls systemd-networkd. It acts on both
=systemd-networkd.socket= and =systemd-networkd.service=, so the
socket doesn't restart the service behind your back. Give exactly
one flag:
| Flag | Meaning |
|------------------+----------------------------------------------------------|
| -a, --activate | Start socket and service for this session. |
| -A, --enable | Start both, and enable the service at boot. |
| -d, --deactivate | Stop both; the service still starts at the next boot. |
| -D, --disable | Stop both, and disable the service at boot. |
| -r, --reactivate | Restart both (e.g. after editing a =.network= file). |
*** sak gitit
Creates the remote repository for a local Git (or jj) repo, on
your own filesystem or on GitHub, GitLab or Bitbucket, and can add
it as a remote of the local repo in the same step.
Destinations are called /targets/. Each target has a name, a kind
(=local=, =github=, =gitlab= or =bitbucket=) and a location. They
are stored in =~/.config/swiss-army-knife/config.yaml= under the
=gitit= key, and managed with the sub-commands below rather than
by editing the file.
**** Managing targets
| Command | Description |
|----------------------------------+-------------------------------------------------------------------|
| sak gitit list | List the configured targets and their kinds. |
| sak gitit add KIND NAME LOCATION | Add a target (KIND: local, github, gitlab or bitbucket). |
| sak gitit delete NAME | Remove a target. |
| sak gitit default NAME | Record a target as the default (not yet used by =create=). |
LOCATION is a directory for =local= targets, and the forge's SSH
prefix for the others.
For example:
#+begin_src fish
sak gitit add local origin ~/pCloud/repo
sak gitit add github github git@github.com:flajann2
sak gitit add gitlab gitlab git@gitlab.com:eltoro-maestro
sak gitit add bitbucket bitbucket git@bitbucket.org:eltoro-maestro
#+end_src
For the forges the LOCATION is informational: the account a repo
is created in comes from the credentials described below.
**** Creating a repo
#+begin_src fish
sak gitit create TARGET REPONAME [-r | -n NAME]
#+end_src
| Flag | Meaning |
|------------------------+-------------------------------------------------------------------------------|
| -r, --add-remote | Also add the new repo as a remote of the current repo, named after the target. |
| -n, --remote-name NAME | Same, but use NAME as the remote name (e.g. =origin=). Implies =-r=. |
Without either flag, gitit only creates the repo and prints the
=git remote add= command for you to run yourself.
With =-r= or =-n=, run gitit from inside the local repo. Before
anything is created, gitit checks that
+ the current directory is inside a git or jj repo, and
+ the local repo does not already have a remote with that name,
so a typo never leaves an orphaned repo on the forge. In a jj
repo (including a colocated one) the remote is added with
=jj git remote add=, otherwise with =git remote add=. If adding
the remote fails after the repo was created, gitit prints the
exact command to add it by hand.
Repos are currently created as public on all forges.
**** Local
Creates a bare repo at =LOCATION/REPONAME.git=, for example on a
cloud-backed filesystem such as pCloud. The path is made absolute,
so it works as a remote URL from any directory. Fails if that path
already exists.
**** GitHub
Needs a GitHub token, taken from the first of:
1. the =GITHUB_TOKEN= environment variable, or
2. the GitHub CLI's login (=gh auth token=), after =gh auth login=.
A classic token needs the =repo= scope (=public_repo= is enough for
public repos); a fine-grained token needs /Administration: write/.
The repo is created under the account the token belongs to.
**** GitLab
Needs a GitLab token, taken from the first of:
1. the =GITLAB_TOKEN= environment variable, or
2. glab's saved login (=glab config get token=), after
=glab auth login=. If glab keeps the token in the system
keyring, gitit cannot read it from there; use =GITLAB_TOKEN=.
The token is a personal access token with the =api= scope,
created under /Edit profile → Access tokens/
(https://gitlab.com/-/user_settings/personal_access_tokens).
For a self-hosted GitLab, set =GITLAB_HOST= (the same variable
glab uses); the default is =gitlab.com=. The project is created
in your user namespace.
**** Bitbucket
Bitbucket has no CLI login to borrow, so it is configured with
environment variables:
| Variable | Required | Meaning |
|-----------------------+----------+----------------------------------------------------------|
| =BITBUCKET_WORKSPACE= | yes | Workspace slug, as in =bitbucket.org/WORKSPACE/...= |
| =BITBUCKET_EMAIL= | yes | The email address you log in to Atlassian with. |
| =BITBUCKET_TOKEN= | yes | An Atlassian API token with Bitbucket scopes (see below). |
| =BITBUCKET_PROJECT= | no | Key of the project to create the repo in. |
*The token.* Use an Atlassian *API token*, not a repository,
project or workspace access token (those are tied to one
resource and cannot create new repos). Create it at
https://id.atlassian.com/manage-profile/security/api-tokens:
1. Choose *Create API token with scopes* (the plain
/Create API token/ button makes a token Bitbucket rejects with
"API Token provided has no Bitbucket scopes").
2. Pick *Bitbucket* as the app.
3. Grant =read:repository:bitbucket=, =write:repository:bitbucket=
and =admin:repository:bitbucket=. Nothing else is needed; avoid
the =delete:= scopes.
4. Copy the token; it is shown only once.
*The project.* Every Bitbucket repo belongs to a project. Without
=BITBUCKET_PROJECT=, Bitbucket uses the workspace's default
project, and if you can't create repos there it answers
"You can't create repositories under this project". Set it to
the key of a project you administer (shown under the workspace's
/Projects/ page).
In fish:
#+begin_src fish
set -Ux BITBUCKET_WORKSPACE eltoro-maestro
set -Ux BITBUCKET_EMAIL you@example.com
set -Ux BITBUCKET_PROJECT KEY
read -s -x -U BITBUCKET_TOKEN # paste the token; it is not echoed
#+end_src
To check the credentials without gitit (expect =200=; =401= means
the email/token pair is wrong):
#+begin_src fish
curl -s -o /dev/null -w '%{http_code}\n' -u "$BITBUCKET_EMAIL:$BITBUCKET_TOKEN" \
"https://api.bitbucket.org/2.0/repositories/$BITBUCKET_WORKSPACE?pagelen=1"
#+end_src
Bitbucket repo slugs are lowercase, so =MyRepo= becomes =myrepo=.
**** Errors
API failures are reported as one line with the HTTP status and the
forge's own message, for example:
#+begin_example
gitit: Bitbucket API error (HTTP 400): project: You can't create repositories under this project.
#+end_example
When the forge sends no message (Bitbucket answers a bad token with
an empty 401), gitit says whether the problem is authentication
(401) or permissions/scopes (403).
** Examples
| Command | Description |
|--------------------------------+--------------------------------------------------------------------------------------------------------------------|
| sak extip | Will list your external IP and location information |
| sak wg -l | (If you are using wg_quick) will list all of your available WG vpns, as well as their active and enabled statuses. |
| sak wg -d | Will deactivate (but not disable) all WireGuard VPNs. |
| sak wg -D | Will disable all enabled WireGuard VPNs, and stop them. |
| sak sn -A | Will enable and start systemd-networkd. |
| sak nm -D | Will disable and stop NetworkManager. |
| sak extip -n | Will print just your external IPv4 address. |
| sak kernel -j | Will print just the running kernel version. |
| sak sleep -w 300 | Will suspend the machine in 5 minutes. |
| sak zfscheck | Will tell you whether archzfs supports the kernel in the repos, before you upgrade. |
| sak gitit create origin myrepo | Will create a new remote repo named "myrepo" in the origin target. |
| sak gitit create github myrepo -r | Will create "myrepo" on GitHub and add it as remote "github" of the current repo. |
| sak gitit create gitlab myrepo -n origin | Will create "myrepo" on GitLab and add it as remote "origin" of the current repo. |
| | |
|--------------------------------+--------------------------------------------------------------------------------------------------------------------|