6 Commits

Author SHA1 Message Date
Dict Xiong
aa1df92783 [dev] feat: README.md (#47)
feat: README.md
2026-08-17 18:33:33 +08:00
Dict Xiong
3bd0121786 [dev] GPG agent forwarding; improve riot, sagt, tmux and zsh (#46)
* feat(riot): support literal ipv6 addresses (with or without ports given)

feat(riot): -v, -4, and -6

fix(riot): do not call external binaries if possible

* feat(riot): command git

* build(riot-config): nasp.fit -> jump.nasp.fit

* feat: add bnd and nor and completions

* fix(zshrc): bnd exit when build error

* fix(tmux): press ^a twice to send ^a

* feat(riot): sshl supports specifying local port and unix sock

* feat(riot): sshr for ssh -R

* feat(riot): -o ServerAliveInterval=60

* feat(riot): conditionally RequestTTY

* feat: zsh completion for beam and beaml

* fix(frigg): ddns get ip

* fix(riot): tmux error with window number and spaces

The fix to spaces is just a workaround. Issue may still exist when
command contains spaces.

* feat(riot): ControlPersist=60s

* fix(ci): riot

* feat(tmux): prevent tmux from exiting copy mode after selection with mouse

see:
- https://www.reddit.com/r/tmux/comments/v73005/how_to_prevent_tmux_from_exiting_copy_mode_after/
- https://www.reddit.com/r/tmux/comments/1ltms6a/how_to_select_text_in_tmux_without_having_it_jump/

* feat(tmux): F12 to passthru

* fix(riot): ControlPersist 60s -> 5s

* feat: sne: search and edit

* feat(ssh): add key ip17/sep

* feat(tmux): press ESC to exit copy mode

* feat(ssh): add key sk1/piv/9a

* feat(ssh): add key sk1/fido2

* feat(ssh): add key openpgp:0x8774BF70 and remove key ip14/sep and ltp2 (asu127)

* feat(sagent): sagt gpg; improve code

* feat(sagent): check pinentry and give notes

* feat(riot): add GPG agent forwarding

Signed-off-by: Dict Xiong <me@beardic.cn>

* feat: gitconf add gpg signature; remove macos from ci

* feat(eid): add sk1 9a cert

* feat(riot): better help and fix option quite->quiet

* feat(riot): separate command options from riot options

* feat(sagt): gpg-pin and associated tests

* feat(riot): zsh completions

* fix: Apply suggestions from code review

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* fix(riot): simplify and secure

* fix: syntax

---------

Signed-off-by: Dict Xiong <me@beardic.cn>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-08-13 20:10:55 +08:00
891bbcacff [dev] refactor riot; use command; remove log; sagt op (#45)
* feat(riot-config): default port 12022

* fix(ci)

* feat(riot-config): remove tailing dot from .domain; remove j.remote and x.domain

* feat(riot): *.batch

* feat(riot): run command in tmux window(s)

note that spaces in ssh commands are still not supported

* feat(riot): -o RequestTTY=yes

* fix(riot): SSH_OPTIONS

* fix(riot): tmux use bash

* feat(zshrc): use()

* fix(riot): scp, and ci

note that riot still has problems with spaces. we should use array to
handle parameters.

* fix(ci): update macos image

* feat: update email

* feat: revert git email

* feat(ssh): update keys

* feat(sagent): sagt op

* fix(sagent): error note

* fix(ci): test of auto-dep

* feat(ssh): remove keys ltp1-bd and ltp1

* feat(riot): rm - remove host keys

* build(ci): update to ubuntu-latest and Yikun/hub-mirror-action@v1.5

* feat: remove frigg-client.log

* feat(riot): -t or --trust to set RIOT_TRUST_SERVER

* feat(zshrc/alias): add sc and t, remove cps and mvs

feat(zshrc/plugins): add man and web-search, remove ufw

* feat(riot): refactor argparse

feat(riot): add `--password` and `--`

feat(common.sh): argparse supports `--`

feat(riot): refactor ping to ping remote

* feat(riot): sshd can specify the local port

* feat(riot): print help when no argument or no remote

* fix(riot): ci

* feat(riot): better print_cmd with escape

* feat(riot-config): support sed*

* fix(frigg): hostname converted into lower case
2025-06-24 19:56:04 +08:00
055d44886b [debug] vim syntax highlighting; sagt file location; dxng.net (#44)
* feat(tmux): prefix + C-x to toggle synchronize-panes

* feat(riot-cfg): nasp domain username to root

* feat(vimrc): ^N to switch between nu/rnu/null

* feat(zshrc): VIRTUAL_ENV_DISABLE_PROMPT set

* fix(sagent): secure file location

* ci: remove schedule run for gitee_sync

* fix(vimrc): syntax highlighting

* feat(zshrc): move time style from l to ls

* fix(riot-config): ibd.ink -> dxng.net

* fix: ci

---------

Co-authored-by: xiongdian.me <xiongdian.me@bytedance.com>
2024-09-11 01:14:13 +08:00
92aa5fa920 [dev] refactor riot; improve tmux and vim; more plugins; more aliases (#43)
* riot: remove unknown domain warn

* install.sh: --no-ssh -> --no-auth-info

* doll: --restart=unless-stopped

* zshrc: alias cps and mvs

* riot: proxy -> ssh

* zshrc: not alias rm to trash

* (trial) riot devel: separate preset to config dir riot.d

* riot: support extra options and extra -o options

* (experimental) riot config in a single file

* riot config: add nasp remote and null domain

* gitconf: pull.ff = only

* fix ci

* riot: dynamic port forwarding

* riot: only one domain func will be exec

* to-install: update lemonbench

* to-install: alist

* [exp] riot inferred ssh: ping ping6

* sagt: fix nixos

* riot config: domain 42

* ubuntu.sh: DEBIAN_FRONTEND=noninteractive

* zshrc: ping -n

* zshrc: alias ping -n

* riot-config: jumpserver from sir0 to ssh.beardic.cn

* zshrc: alias ping before checking os type

* frigg: support api4.beardic.cn

* fix(install.sh): crontab fails on a new server

* fix(riot-conf): nasp.ob.ac.cn -> nasp.fit

* fix(install.sh): install crontab (exp)

* feat(test.zsh): test crontab

* fix(riot): secure control master

* fix(ci): riot control master

* fix(riot): not mkdir if dry-run

* feat(vimrc): set shiftwidth=4

* feat(ci): sync tmux-yank

* feat(tmux): set-clipboard on and mouse on (experimental)

* feat(zshrc): alias ping6

* build(ci): hub mirror 1.3->1.4

* fix(zshrc): tmux on msys; feat(common): better perf getting os type and linux dist

* fix(common.sh): get_os_type and get_linux_dist

* feat(zshrc): add plugin {magic-enter,per-directory-history,pip,podman,python,rsync,systemd,timer}

* feat(zshrc): journalctl alias

* feat(vimrc): tab=2 for c,cpp,nix,yaml

* build(ci): checkout v3 -> v4

---------

Co-authored-by: xiongdian.me <xiongdian.me@bytedance.com>
2024-05-05 12:02:55 +08:00
483f7fd7f3 [dev] nixos; riot multiple targets; install without ssh keys (#42)
* auth: add .eid/authorized_certificates for pam pkcs11 auth

* .zshrc: alias sl for sudo zsh -l

* to-install: nix

* zshrc: use gnu ls on mac

* zshrc: try to use gnu-ls

* try to fix ci for macos

* riot: add domain box[0-9]

* riot: shortcuts i,x,j

* .zshrc: warn if not in main channel

* sagt: reset agent so paths

* sagt: import ssh-agent -P paths

* common.sh: is_port_free and get_free_port

* riot use get_free_port to fix issue on windows

* riot: ssh support instant command

* riot: proxy delimiter from comma (,) to slash (/)

* riot: support multiple remotes, delimiter=comma (,)

* riot: fix ci; install.sh: --no-ssh

* riot: improve ci

---------

Co-authored-by: xiongdian.me <xiongdian.me@bytedance.com>
2023-11-06 19:13:22 +08:00
33 changed files with 1746 additions and 299 deletions

View File

@@ -0,0 +1,61 @@
-----BEGIN CERTIFICATE-----
MIIFIDCCAwigAwIBAgIUK1zXH5UosBim7i+kIZyPGpowU9cwDQYJKoZIhvcNAQEL
BQAwgZUxCzAJBgNVBAYTAkNOMRAwDgYDVQQIDAdCRUlKSU5HMRAwDgYDVQQHDAdC
RUlKSU5HMRIwEAYDVQQKDAlEaWN0IFRlY2gxEDAOBgNVBAsMB1Jvb3QgQ0ExHjAc
BgNVBAMMFURpY3QgVGVjaCBSb290IENBIC0gMDEcMBoGCSqGSIb3DQEJARYNbWVA
YmVhcmRpYy5jbjAeFw0yMjA3MjIxNzU5MjNaFw0yNTA3MjExNzU5MjNaMEkxEjAQ
BgNVBAoMCURpY3QgVGVjaDEQMA4GA1UECAwHQkVJSklORzELMAkGA1UEBhMCQ04x
FDASBgNVBAMMC3NrMC5pYmQuaW5rMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEAlDeyFxeJ5RXFX4B3rIIpNyyAIl5VTGCT5t8g9dxFWq0MeY6nQYJPA8IJ
IH+KzzujBabyKYpzshDvMzkuhx0Kwi2VL+ckxWg5FLge3kNBJqYnBm5pPWx6o9R5
DmcWqG5KRH0FJhSmL5KeBHsVcOurDtKn174x2eB+sggCSyuWlJyn4KxPLTg8z/Nr
FP5qSY1vj6t7mpzau+eIGh8IcQiFxteN/86fKswQHint/MVaqg6vX05BuIb8xb5E
XUvPIupdc/vtD959BxnTo7UoOz+3zu/u2cj2K9IDubwe9tDpv88aj6xnfXInFouD
/mlSmvtLo1h/6arn1hurgoylh5wbOwIDAQABo4GyMIGvMAkGA1UdEwQCMAAwCwYD
VR0PBAQDAgTwMB0GA1UdDgQWBBR9DxLqtR0+zUWoPyFoDRrnyJyDBTAfBgNVHSME
GDAWgBRsprmVI/Tjey+sw4qoZpLd7PT0gTBVBgNVHSUETjBMBggrBgEFBQcDAgYI
KwYBBQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYKKwYBBAGCN0MBAQYKKwYBBAGC
N0MBAgYKKwYBBAGCNxQCAjANBgkqhkiG9w0BAQsFAAOCAgEA7BJXiRg32shmZWyY
gPjq52ffN9RDNe58KadajJ1PnVP1yNJySgsOG3eSxONQX3ITr36Ihsfb3fZneBfX
/IcXgjyvy1CDcvBBAyee91hdR8orMCePqMpeOSNP9CUp9Ctd4V+an0DE8Z1yrezA
ieaYgBZCDxuZdloYSmj9Z/Rqn2dy4dcUAxKrEwW/6VPqQNrJETTtadS1SF0EsDxV
XZpDqNYaS/N7/ciSm7TaQJud62A4m+FWWHNnttTp9IjLESr3U80+qtRY8yDNpulb
/eNL8KK9aORiFUz9789l6M62HPi6vCsMflI0iHUeoi69GKX8mKf0WFR1KxoDsJiN
DG9/1gCfj6whFCrT6fJTKAGl8Hp5toWMOeDprbsE5gf4Ik3p+sKMV9t43zNm3gvA
1zNPtgQUffPU0kONlavIxUNfCmD8qVwK5ECT7wh0hrB8fRYfxTZHgN6Yvu7M9HNz
SNt5JZCLBT4nLt0uQp9O/xctdHElZw+/W8OfnP5vxnPdccIeVOxpGIyzErwjD0+E
9mNiqOa5JV9OMAJ+0I9cbOnuMaXm4mvHZadnetUzq2ZUw1Jba7z62zIJTciNRq9i
ZJc6v5e+iqbE1ECZLco5LjWqqfvFfYCrkqeOhCRsRkVPsXnGPo2QDDYdTm4EGCmg
dVZ6R452SZsrE4V+3LR011BxzEg=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFQDCCAyigAwIBAgIRAJmQ852eXApHBvrQujWaFAEwDQYJKoZIhvcNAQELBQAw
ODESMBAGA1UEChMJRGljdCBUZWNoMSIwIAYDVQQDExlEaWN0IFRlY2ggSXNzdWlu
ZyBDQSAyMDI2MB4XDTI2MDgxMjE1NTQyM1oXDTI5MDgyMTExMDAwMFowKzESMBAG
A1UEChMJRGljdCBUZWNoMRUwEwYDVQQDEwxzazEuZHhuZy5uZXQwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAkkF0Qc1YhhgmgZcujjfyn3liCxAAJSGl
Eme0eD6s908zm95mGd64fQJSoYVaFt5y9wrQLdWX16c+gvRyNaQdPfs5jxaP+G8V
r6PkkE79hwauEOQOTbr+q3bQJmfLe56cMV7oHrKVFffkJQTvp1mcK4+CYytsxJP5
v0m5TDyv4op6VcZ2m0gdrFEBV3DMMgsqF6/tSPkqbTTj9fMMSOFzXPq7qEQWgYmx
T+M0iQdgEiG7Z+INkofM56tBmQ7jSeO65/FTytB6ZMiI23NGmTmYvHPyxSDWXL0X
7tX42S0/A7QhxZHN0GUD0NGaSExLfgTawRLhARVHbUXC9iu5aCArAgMBAAGjggFQ
MIIBTDAOBgNVHQ8BAf8EBAMCBaAwKwYDVR0lBCQwIgYIKwYBBQUHAwIGCisGAQQB
gjcUAgIGCisGAQQBgjdDAQEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUOyThWF2A
BkoQv9xljyo3fFOclq0wHwYDVR0jBBgwFoAU9qXPjCjcil59akpPVISS8Yu/Jc0w
SgYIKwYBBQUHAQEEPjA8MDoGCCsGAQUFBzAChi5odHRwOi8vdG9vbHMuZHhuZy5j
bi9jZXJ0cy9pc3N1aW5nLWNhLTIwMjYuY2VyMCoGA1UdHwQjMCEwH6AdoBuGGWh0
dHA6Ly9jYS5pYmQuaW5rLzEuMC9jcmwwRwYMKwYBBAGCpGTGKEABBDcwNQIBAQQD
cGl2BCtaMnFWY05lMzlUaHZyc3RIbFVFWE5laXg1QXd2aFU3MjBlWEFFZWRlUG4w
MA0GCSqGSIb3DQEBCwUAA4ICAQCKSLjnMOlQiZ381IaBEl4iT7pCvY0QQxsVEMDD
RWYhIImx3JJDt7kiZXcJwlH70VG0opSz9eKrT3cw7OY6DnNm7Bm0JT2fJgyCSnWo
nG7J1soE74Rl2MiNnP1JTqhFlZKLrmIb6eP4U0eM+ZaJ7luyxizqiJAeRyJB+YCe
DM9uZ7rC33DYZeZ46V4f7DfJ23TjVJvaX/NFu0Ab/7ylF6X7frT8xf11XLlE2IyR
MmnLtQiREXXETaw9l5suMuH8fmVg01QrqnOHmz4r9Nuzh7vHxogEsJtgBLhv2Min
X6IfaKABhFXk1cVZGNgaC0Pao6cYbaWJwF2zcVHGq5EDKXhkKmCLeDo9em0rOJ2y
fU23yP3ByMGJ3Xi7nI8E5dfSw6Q39b3t1/kxj1y8T3PGdTN/3vFLVqf5A7XXdQL9
/uy2lYh3Fud/aZuthZyoL2rkC4O8N4seqfNIOBEHAmfbk5j6LoFFwSTrRGqzCAf4
4r6ckHjL3KBnR8EeMwwit324iRYd3ppUL3TINkeiHRNFVKU0flUvcG6T0v+hC2sX
yupYy0wylkonub6L0gUIINqx38nsDCR07eAfsDXJshIwTxUBFrVpKjPK7jVQOZ6+
9/yPu72BhyGMZs4bgYMEPwqKTbzUNitO2WfBIgXhzGe8YzMA1U2BMTRT0nIQ6oa9
8YbnQw==
-----END CERTIFICATE-----

View File

@@ -1,6 +1,7 @@
[user] [user]
email = me@beardic.cn email = me@dxng.cn
name = Dict Xiong name = Dict Xiong
signingkey = 3E241558655D7FE06C6711A5A5D6250D1806CAA8
[core] [core]
editor = vim editor = vim
# test this by `git update-index --test-untracked-cache` # test this by `git update-index --test-untracked-cache`
@@ -9,6 +10,8 @@
quotepath = false # chinese chars quotepath = false # chinese chars
[push] [push]
autoSetupRemote = true autoSetupRemote = true
[pull]
ff = only
[branch] [branch]
# Show most recently changed branches first. # Show most recently changed branches first.
sort = -committerdate sort = -committerdate

View File

@@ -1,15 +1,13 @@
name: sync repos to gitee name: sync repos to gitee
on: on:
push: ~ push: ~
schedule:
- cron: 10 4 * * *
workflow_dispatch: ~ workflow_dispatch: ~
jobs: jobs:
dotfiles: dotfiles:
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- name: DictXiong/dotfiles - name: DictXiong/dotfiles
uses: Yikun/hub-mirror-action@v1.3 uses: Yikun/hub-mirror-action@v1.5
with: with:
src: github/DictXiong src: github/DictXiong
dst: gitee/dictxiong dst: gitee/dictxiong

View File

@@ -5,10 +5,10 @@ on:
workflow_dispatch: ~ workflow_dispatch: ~
jobs: jobs:
ohmyzsh: ohmyzsh:
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- name: ohmyzsh repo - name: ohmyzsh repo
uses: Yikun/hub-mirror-action@v1.3 uses: Yikun/hub-mirror-action@v1.5
with: with:
src: github/ohmyzsh src: github/ohmyzsh
dst: gitee/dictxiong dst: gitee/dictxiong
@@ -22,10 +22,10 @@ jobs:
static_list: "ohmyzsh" static_list: "ohmyzsh"
timeout: '600s' timeout: '600s'
zsh-users: zsh-users:
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- name: zsh-users repos - name: zsh-users repos
uses: Yikun/hub-mirror-action@v1.3 uses: Yikun/hub-mirror-action@v1.5
with: with:
src: github/zsh-users src: github/zsh-users
dst: gitee/dictxiong dst: gitee/dictxiong
@@ -39,10 +39,10 @@ jobs:
static_list: "antigen,zsh-syntax-highlighting,zsh-autosuggestions,zsh-completions" static_list: "antigen,zsh-syntax-highlighting,zsh-autosuggestions,zsh-completions"
timeout: '600s' timeout: '600s'
zdharma-continuum: zdharma-continuum:
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- name: zdharma-continuum repos - name: zdharma-continuum repos
uses: Yikun/hub-mirror-action@v1.3 uses: Yikun/hub-mirror-action@v1.5
with: with:
src: github/zdharma-continuum src: github/zdharma-continuum
dst: gitee/dictxiong dst: gitee/dictxiong
@@ -56,10 +56,10 @@ jobs:
static_list: "fast-syntax-highlighting" static_list: "fast-syntax-highlighting"
timeout: '600s' timeout: '600s'
tmux-mem-cpu-load: tmux-mem-cpu-load:
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- name: thewtex/tmux-mem-cpu-load - name: thewtex/tmux-mem-cpu-load
uses: Yikun/hub-mirror-action@v1.3 uses: Yikun/hub-mirror-action@v1.5
with: with:
src: github/thewtex src: github/thewtex
dst: gitee/dictxiong dst: gitee/dictxiong
@@ -73,10 +73,10 @@ jobs:
static_list: "tmux-mem-cpu-load" static_list: "tmux-mem-cpu-load"
timeout: '600s' timeout: '600s'
tmux-plugins: tmux-plugins:
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- name: tmux-plugins - name: tmux-plugins
uses: Yikun/hub-mirror-action@v1.3 uses: Yikun/hub-mirror-action@v1.5
with: with:
src: github/tmux-plugins src: github/tmux-plugins
dst: gitee/dictxiong dst: gitee/dictxiong
@@ -87,13 +87,13 @@ jobs:
clone_style: "https" clone_style: "https"
debug: true debug: true
force_update: true force_update: true
static_list: "tpm,tmux-resurrect" static_list: "tpm,tmux-resurrect,tmux-yank"
timeout: '600s' timeout: '600s'
vundle-vim: vundle-vim:
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- name: VundleVim/Vundle-vim - name: VundleVim/Vundle-vim
uses: Yikun/hub-mirror-action@v1.3 uses: Yikun/hub-mirror-action@v1.5
with: with:
src: github/VundleVim src: github/VundleVim
dst: gitee/dictxiong dst: gitee/dictxiong
@@ -107,10 +107,10 @@ jobs:
static_list: "Vundle.vim" static_list: "Vundle.vim"
timeout: '600s' timeout: '600s'
onedark-vim: onedark-vim:
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- name: joshdick/onedark-vim - name: joshdick/onedark-vim
uses: Yikun/hub-mirror-action@v1.3 uses: Yikun/hub-mirror-action@v1.5
with: with:
src: github/joshdick src: github/joshdick
dst: gitee/dictxiong dst: gitee/dictxiong
@@ -124,10 +124,10 @@ jobs:
static_list: "onedark.vim" static_list: "onedark.vim"
timeout: '600s' timeout: '600s'
fzf: fzf:
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- name: junegunn/fzf - name: junegunn/fzf
uses: Yikun/hub-mirror-action@v1.3 uses: Yikun/hub-mirror-action@v1.5
with: with:
src: github/junegunn src: github/junegunn
dst: gitee/dictxiong dst: gitee/dictxiong

View File

@@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: checkout repo - name: checkout repo
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
@@ -44,57 +44,16 @@ jobs:
shell: /bin/zsh -ileo PIPE_FAIL {0} shell: /bin/zsh -ileo PIPE_FAIL {0}
run: source tools/test.zsh run: source tools/test.zsh
test-macos:
name: test on macos
runs-on: macos-12
steps:
- name: checkout repo
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: install dfs
run: |
rev=`git rev-parse HEAD`
pwd
set -x
DFS_NO_WALL=0 ./install.sh -adl
test `git rev-parse HEAD` = "$rev"
- name: antigen build
shell: /bin/zsh -ileo PIPE_FAIL {0}
run: |
echo $SHELL
antigen list
- name: run tests and reset
shell: /bin/zsh -ileo PIPE_FAIL {0}
run: |
source tools/test.zsh
antigen reset
rm -rf $ANTIGEN $HOME/.antigen $HOME/.config/dotfiles
./install.sh -dl
- name: antigen build with DFS_NO_WALL
shell: /bin/zsh -ileo PIPE_FAIL {0}
run: |
echo $SHELL
antigen list
- name: run tests
shell: /bin/zsh -ileo PIPE_FAIL {0}
run: source tools/test.zsh
test-autodep: test-autodep:
name: test of auto-install dependencies name: test of auto-install dependencies
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: remove dependencies - name: remove dependencies
run: | run: |
sudo apt-get -y remove curl vim python3 sudo apt-get -y remove curl vim
- name: checkout repo - name: checkout repo
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
@@ -116,7 +75,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: checkout repo - name: checkout repo
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
@@ -128,4 +87,4 @@ jobs:
shell: /bin/zsh -ileo PIPE_FAIL {0} shell: /bin/zsh -ileo PIPE_FAIL {0}
run: | run: |
dfs cd dfs cd
source tools/test.zsh source tools/test.zsh

View File

@@ -2,27 +2,22 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGakUcSuNhuH7tk+Nj9gDfRtxYfGPL9yb8toQ/EpSJAM openpgp:0xD9271B01 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGakUcSuNhuH7tk+Nj9gDfRtxYfGPL9yb8toQ/EpSJAM openpgp:0xD9271B01
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEJ4FeeBwPcUe25JsZQ7xDmGtFVeO1TQ9332IPjFY0Xm openpgp:0x8774BF70
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCUN7IXF4nlFcVfgHesgik3LIAiXlVMYJPm3yD13EVarQx5jqdBgk8Dwgkgf4rPO6MFpvIpinOyEO8zOS6HHQrCLZUv5yTFaDkUuB7eQ0EmpicGbmk9bHqj1HkOZxaobkpEfQUmFKYvkp4EexVw66sO0qfXvjHZ4H6yCAJLK5aUnKfgrE8tODzP82sU/mpJjW+Pq3uanNq754gaHwhxCIXG143/zp8qzBAeKe38xVqqDq9fTkG4hvzFvkRdS88i6l1z++0P3n0HGdOjtSg7P7fO7+7ZyPYr0gO5vB720Om/zxqPrGd9cicWi4P+aVKa+0ujWH/pqufWG6uCjKWHnBs7 sk0/piv/9a ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCUN7IXF4nlFcVfgHesgik3LIAiXlVMYJPm3yD13EVarQx5jqdBgk8Dwgkgf4rPO6MFpvIpinOyEO8zOS6HHQrCLZUv5yTFaDkUuB7eQ0EmpicGbmk9bHqj1HkOZxaobkpEfQUmFKYvkp4EexVw66sO0qfXvjHZ4H6yCAJLK5aUnKfgrE8tODzP82sU/mpJjW+Pq3uanNq754gaHwhxCIXG143/zp8qzBAeKe38xVqqDq9fTkG4hvzFvkRdS88i6l1z++0P3n0HGdOjtSg7P7fO7+7ZyPYr0gO5vB720Om/zxqPrGd9cicWi4P+aVKa+0ujWH/pqufWG6uCjKWHnBs7 sk0/piv/9a
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGV/6cLUGA5YWVI6uPhIBOBZvlNBlZLkehHQN0Gxnc+B ltp0/ubuntu ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAkkF0Qc1YhhgmgZcujjfyn3liCxAAJSGlEme0eD6s908zm95mGd64fQJSoYVaFt5y9wrQLdWX16c+gvRyNaQdPfs5jxaP+G8Vr6PkkE79hwauEOQOTbr+q3bQJmfLe56cMV7oHrKVFffkJQTvp1mcK4+CYytsxJP5v0m5TDyv4op6VcZ2m0gdrFEBV3DMMgsqF6/tSPkqbTTj9fMMSOFzXPq7qEQWgYmxT+M0iQdgEiG7Z+INkofM56tBmQ7jSeO65/FTytB6ZMiI23NGmTmYvHPyxSDWXL0X7tX42S0/A7QhxZHN0GUD0NGaSExLfgTawRLhARVHbUXC9iu5aCAr sk1/piv/9a
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILqRfdez8IAOf6m9Ysi3uSNKEG0Xi6GPuhA6R2fci2yB ltp0/windows sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIA71ukY9nzzeAGp9WYstPYiIwexzIANlPwMQ68QNQwqxAAAABHNzaDo= sk1/fido2
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFO4k0tJ+Bfu95Uavg/5P3EXMKNcq+bMqaTqzkvRZ7ji ltp1/ubuntu ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMyZILj+GxTUhdCgz2w1TxQ+aTcggnOJIb84qA4u271S ltp0
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIN8g34WHLEix6Qt0J/ClYbZeb9wh+p1IOcZkz/vNbZrv ltp1/windows ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGZFTyR+R86fUpZBDkDR9yYzJpmeNsIv2CMHTh6EuEeq pc0
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBN24pvN1RMN+iSZyHPdyExA1Rvt8pdr3e6ih6iX+KrQ ltp2 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHLYgVj+NPino6sOmahULN7SbAMaVAgzqPfDjz2S8zDv pc1
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGZFTyR+R86fUpZBDkDR9yYzJpmeNsIv2CMHTh6EuEeq pc0/windows
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHLYgVj+NPino6sOmahULN7SbAMaVAgzqPfDjz2S8zDv pc1/windows
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNvHya5i3GNniGww9Yx5ikos4Z5jevu8zz8hCQi05++SgejFPnbw9UjbHFjODK2M+ZHuhRF5mdf996r4/BxWyP8= pad0/sep
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBC5gu+U4picxVvCNYjLlmxGLpJl7svj3LklO7jSpwqdrNxlE+/xkx07PJx76AbA77dDM1Dxmm/VOvVQCKTRLTgk= ip14/sep
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIF52mPmbLEriZ+DfmrMHFtvg2kiO2JpRpitvs7PbJowh home0 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIF52mPmbLEriZ+DfmrMHFtvg2kiO2JpRpitvs7PbJowh home0
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKhS4voo3K/Dvzqckr0bouO1WkCI5XxswstHWnuuyKBz ltp1-bd ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNvHya5i3GNniGww9Yx5ikos4Z5jevu8zz8hCQi05++SgejFPnbw9UjbHFjODK2M+ZHuhRF5mdf996r4/BxWyP8= pad0/sep
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMXu7UND6N1EKcbeSeAj2CwzGA3sM5/bRtLwoY0euWdR4vzB0nwcwd6nct5JCEyozjujJfMt8d7oCzhSt1w/1cU= ip17/sep

View File

@@ -1,5 +1,7 @@
set -g prefix ^a set -g prefix ^a
set -g mouse off bind ^a send-prefix
set -g mouse on
set -g set-clipboard on
set -g default-terminal "xterm-256color" set -g default-terminal "xterm-256color"
set -g history-limit 10000 set -g history-limit 10000
@@ -23,8 +25,19 @@ bind - split-window -v -c "#{pane_current_path}"
bind | split-window -h -c "#{pane_current_path}" bind | split-window -h -c "#{pane_current_path}"
# other key bindings # other key bindings
## prefix r: reload conf
bind r source-file ~/.tmux.conf \; display-message "tmux.conf reloaded" bind r source-file ~/.tmux.conf \; display-message "tmux.conf reloaded"
## prefix m: toggle mouse mode
bind m run 'old=$(tmux show -gv mouse); new="off"; if [ "$old" = "off" ]; then new="on"; fi; tmux set -g mouse $new && tmux display-message toggle\ mouse\ mode\ $new' bind m run 'old=$(tmux show -gv mouse); new="off"; if [ "$old" = "off" ]; then new="on"; fi; tmux set -g mouse $new && tmux display-message toggle\ mouse\ mode\ $new'
## prefix C-x: pane sync
bind C-x set-window-option synchronize-panes\; display-message "synchronize-panes is now #{?pane_synchronized,on,off}"
## prevent jumping to end after mouse selection
bind -T copy-mode-vi MouseDragEnd1Pane send-keys -X copy-selection -x
## F12: passthru
bind -T root F12 set prefix None \; set key-table off \; set status-bg red \; refresh-client -S
bind -T off F12 set -u prefix \; set -u key-table \; set -u status-bg \; refresh-client -S
## ESC: exit copy mode
bind -T copy-mode-vi Escape send-keys -X cancel
# styles # styles
# status bar # status bar
@@ -50,4 +63,4 @@ set -g window-style fg=colour248
set -g window-active-style fg=white set -g window-active-style fg=white
# better mouse scrolling. see: https://superuser.com/questions/1622812/mouse-scrolling-in-mobaxterm-tmux # better mouse scrolling. see: https://superuser.com/questions/1622812/mouse-scrolling-in-mobaxterm-tmux
set -g terminal-overrides 'xterm*:smcup@:rmcup@' set -g terminal-overrides 'xterm*:smcup@:rmcup@'

View File

@@ -39,8 +39,7 @@ case $DFS_UPDATE_CHANNEL in
esac esac
if [[ ${#DFS_COMMIT} != 40 ]]; then if [[ ${#DFS_COMMIT} != 40 ]]; then
fmt_error "invalid commit id" fmt_error "invalid commit id"
apost_beacon "dfs.invalid-commit" apost_beacon "dfs.invalid-commit" "invalid commit id: ${DFS_COMMIT}"
apost_log "ERROR" "$THIS_FILE" "invalid commit id: ${DFS_COMMIT}"
exit exit
fi fi

View File

@@ -10,12 +10,13 @@ filetype plugin indent on
" end vundle " end vundle
colorscheme onedark colorscheme onedark
syntax on
set cursorline set cursorline
set syntax=on
set autoindent set autoindent
set smartindent set smartindent
set tabstop=4
set expandtab "spaces instead of tabs set expandtab "spaces instead of tabs
set tabstop=4 softtabstop=4 shiftwidth=4
autocmd FileType c,cpp,nix,yaml setlocal tabstop=2 softtabstop=2 shiftwidth=2
set backspace=indent,eol,start set backspace=indent,eol,start
set number "line numbers set number "line numbers
set history=1000 set history=1000
@@ -31,6 +32,7 @@ hi User3 ctermfg=15 ctermbg=2
au InsertEnter * hi User3 ctermfg=15 ctermbg=5 au InsertEnter * hi User3 ctermfg=15 ctermbg=5
au InsertLeave * hi User3 ctermfg=15 ctermbg=2 au InsertLeave * hi User3 ctermfg=15 ctermbg=2
set statusline=%3*\ %{mode()}\ %*[%n][%F](%{\"\".(&fenc==\"\"?&enc:&fenc).((exists(\"+bomb\")\ &&\ &bomb)?\"+\":\"\").\"\"},%{&ff},%Y)%2*%r%1*%m%*%<%=\'0x%B\'\ [Line:%l/%L,Col:%c][%p%%] set statusline=%3*\ %{mode()}\ %*[%n][%F](%{\"\".(&fenc==\"\"?&enc:&fenc).((exists(\"+bomb\")\ &&\ &bomb)?\"+\":\"\").\"\"},%{&ff},%Y)%2*%r%1*%m%*%<%=\'0x%B\'\ [Line:%l/%L,Col:%c][%p%%]
nnoremap <silent> <C-n> :let [&nu, &rnu] = [!&rnu, &nu+&rnu==1]<cr>
" When editing a file, always jump to the last known cursor position. " When editing a file, always jump to the last known cursor position.
" Don't do it when the position is invalid, when inside an event handler " Don't do it when the position is invalid, when inside an event handler

70
.zshrc2
View File

@@ -9,13 +9,16 @@ else
fi fi
export DOTFILES=$( cd "$( dirname "${BASH_SOURCE[0]:-${(%):-%x}}" )" && pwd ) export DOTFILES=$( cd "$( dirname "${BASH_SOURCE[0]:-${(%):-%x}}" )" && pwd )
export PATH="$PATH:$DOTFILES/scripts" export PATH="$PATH:$DOTFILES/scripts"
fpath=($DOTFILES/functions $fpath)
export EDITOR='vim' export EDITOR='vim'
# env for apps # env for apps
export PYTHONIOENCODING='UTF-8' export PYTHONIOENCODING='UTF-8'
export GPG_TTY=$(tty) export GPG_TTY=$(tty)
export LESS_TERMCAP_md=$'\E[01;33m' export LESS_TERMCAP_md=$'\E[01;33m'
export VIRTUAL_ENV_DISABLE_PROMPT=1
# env for dfs # env for dfs
if [[ -f ~/.config/dotfiles/env ]]; then set -a; source ~/.config/dotfiles/env; set +a; fi if [[ -f ~/.config/dotfiles/env ]]; then set -a; source ~/.config/dotfiles/env; set +a; fi
export DFS_OS_TYPE="$("$DOTFILES/tools/common.sh" get_os_type)"
# antigen # antigen
if [[ "$DFS_NO_WALL" == "1" ]]; then if [[ "$DFS_NO_WALL" == "1" ]]; then
@@ -66,9 +69,18 @@ ANTIGEN_PLUGINS+=(
"extract" "extract"
"fzf" "fzf"
"git" "git"
"magic-enter"
"man"
"per-directory-history"
"pip"
"podman"
"python"
"ripgrep" "ripgrep"
"rsync"
"systemd"
"timer"
"tmux" "tmux"
"ufw" "web-search"
"z" "z"
) )
for i in ${ANTIGEN_PLUGINS[*]}; do for i in ${ANTIGEN_PLUGINS[*]}; do
@@ -101,21 +113,51 @@ fi
# alias # alias
alias "pls"='sudo $(fc -ln -1)' alias "pls"='sudo $(fc -ln -1)'
alias "se"='sudo -sE' alias "se"='sudo -sE'
alias "sl"='sudo zsh -l'
if [[ "$DFS_OS_TYPE" == "linux" ]]; then alias "ping"='ping -n'; alias "ping6"='ping6 -n'; fi
if [[ "$DFS_OS_TYPE" == "msys" ]]; then alias "tmux"='script -qO /dev/null -c "tmux -u"'; fi
alias "pbd"='ping baidu.com' alias "pbd"='ping baidu.com'
alias "p114"='ping 114.114.114.114' alias "p114"='ping 114.114.114.114'
alias "p666"='ping6 2001:da8::666' alias "p666"='ping6 240c::6666'
alias "cbd"='curl http://www.baidu.com' alias "cbd"='curl http://www.baidu.com'
alias "cbds"='curl https://www.baidu.com' alias "cbds"='curl https://www.baidu.com'
alias "gdebug"='git add -A; git commit --allow-empty -m "bug fix ($(date))"' alias "gdebug"='git add -A; git commit --allow-empty -m "bug fix ($(date))"'
case $(bash "$DOTFILES/tools/common.sh" get_os_type) in alias "ls"='ls --color=tty --time-style="+%y-%m-%d %H:%M"'
macos ) alias l='ls -lAGh -D "%y-%m-%d %H:%M"' ;; alias "l"='ls -lAGh'
* ) alias l='ls -lAGh --time-style="+%y-%m-%d %H:%M"' ;; alias "jc"='journalctl'
esac alias "jce"='jc -e'
if [[ -x $(command -v trash) ]]; then alias "jceu"='jc -eu'
alias "rm"="echo use the full path i.e. '/bin/rm'\; consider using trash" alias "jcf"='jc -f'
fi alias "jcfu"='jc -fu'
gbes() { git for-each-ref --sort=-committerdate refs/heads refs/remotes --format="%(authordate:format:%y-%m-%d.%a %H:%M %z)|%(color:red)%(objectname:short)|%(color:yellow)%(refname:short)%(color:reset)|%(color:reset)%(authorname): %(color:green)%(subject)" --color=always | column -ts"|" | less -FX } alias "sc"='systemctl'
sagt() { eval "$($DOTFILES/tools/sagent.sh $@)" } alias "t"='tmux'
gbes() { git for-each-ref --sort=-committerdate refs/heads refs/remotes --format="%(authordate:format:%y-%m-%d.%a %H:%M %z)|%(color:red)%(objectname:short)|%(color:yellow)%(refname:short)%(color:reset)|%(color:reset)%(authorname): %(color:green)%(subject)" --color=always | column -ts"|" | less -FX ; }
sagt() {
local output
output="$("$DOTFILES/tools/sagent.sh" "$@")" || return $?
eval "$output"
}
## nixos
use() { nix --experimental-features nix-command --extra-experimental-features flakes shell "${(*)@/#%(#b)([^#]#)/nixpkgs#$match}" ; }
bnd() {
if [[ -n "$1" && -d "./hosts/$1" ]]; then
nixos-rebuild --flake .#$1 build || return $?
nix-diff /opt/NixOS-Config/results/hosts/$1 ./result --color=always | less
else
echo host $1 does not exist
fi
}
nor() {
test -n "$1" || { echo fatal: no verb given; return 1; }
nixos-rebuild "$1" --flake .${2:+#}$2
}
alias 'nors'='nor switch'
alias 'norbt'='nor boot'
alias 'nort'='nor test'
alias 'norb'='nor build'
alias 'nordb'='nor dry-build'
alias 'norda'='nor dry-activate'
# key bindings # key bindings
bindkey "^b" beginning-of-line # ctrl+b bindkey "^b" beginning-of-line # ctrl+b
@@ -171,12 +213,16 @@ dfs()
"$DOTFILES/install.sh" -i "$DOTFILES/install.sh" -i
echo 'Done. Please open a new shell to see the changes.' echo 'Done. Please open a new shell to see the changes.'
;; ;;
log ) "$DOTFILES/tools/common.sh" "post_log" "INFO" "dfs" "$2" ;;
beacon ) "$DOTFILES/tools/common.sh" "post_beacon" "$2" "$3" ;; beacon ) "$DOTFILES/tools/common.sh" "post_beacon" "$2" "$3" ;;
* ) echo "unknown command \"$1\". available: update, force-update, version, reset, cd, log" ;; * ) echo "unknown command \"$1\". available: update, force-update, version, reset, cd, log" ;;
esac esac
} }
# motd
if [[ "$DFS_INITED" != "1" && -n "$DFS_UPDATE_CHANNEL" && "$DFS_UPDATE_CHANNEL" != "main" ]]; then
echo dotfiles not in the main channel. use with caution.
fi
# clean # clean
unset i unset i
export DFS_INITED=1 export DFS_INITED=1

View File

@@ -1 +1,58 @@
# dotfiles # dotfiles
My personal shell environment and command-line tools, mainly for Linux and
NixOS systems.
It includes configurations for zsh, tmux, Vim, Git, and SSH, together with a
few utility scripts used across my machines.
## Highlights
- `riot`: an SSH wrapper with host presets, jump hosts, port forwarding, tmux
sessions, and optional GPG agent forwarding.
- `sagt`: an SSH/GPG agent manager with support for PIV, 1Password, and
OpenPGP smart cards.
- zsh aliases and completions for the bundled tools.
- tmux and Vim configurations with plugin setup.
- automatic updates with selectable `main`, `dev`, and `latest` channels.
## Installation
Quick install:
```sh
curl -fsSL https://dotfiles.cn | bash
```
Alternatively, clone the repository so the installer can be reviewed first:
```sh
git clone https://github.com/DictXiong/dotfiles.git ~/dotfiles
~/dotfiles/install.sh -a
```
The installer adds small source/include entries to the existing zsh, tmux,
Vim, and Git configuration files instead of replacing them entirely.
## Useful commands
```sh
riot --help # show the SSH wrapper manual
sagt # start or reuse an ssh-agent
sagt gpg # use gpg-agent as SSH agent
sagt gpg-pin # cache the smart-card signature PIN
dfs update # update dotfiles from the selected channel
dfs config # edit machine-specific settings
```
## Configuration
Machine-specific settings can be placed in:
```text
~/.config/dotfiles/env
~/.config/riot-config.sh
```
This is a personal dotfiles repository. Review the configuration, SSH keys,
and installation script before using it on another machine.

99
functions/_beam Normal file
View File

@@ -0,0 +1,99 @@
#compdef beam
# =============================================================================
# Beam-Go Zsh Completion Script
# =============================================================================
_beam() {
local context state state_descr line
typeset -A opt_args
_arguments -C \
'1: :_beam_commands' \
'*:: :->args'
case $state in
(args)
case $line[1] in
(serve) _beam_serve ;;
(add) _beam_add ;;
(list) _beam_list ;;
(del) _beam_del ;;
esac
;;
esac
}
# --- Subcommand Definitions ---
(( $+functions[_beam_commands] )) ||
_beam_commands() {
local -a commands
commands=(
'serve:Start the beam server daemon'
'add:Share a file or directory'
'list:List active shares'
'del:Delete a share by path or code'
)
_describe -t commands 'beam command' commands
}
_beam_serve() {
_arguments \
'(-d --database)'{-d,--database}'=[Database directory (Required)]:directory:_path_files -/' \
'(-p --port)'{-p,--port}'=[Port to listen on (default :8280)]:port:' \
'(-s --socket)'{-s,--socket}'=[Socket path]:socket path:_files' \
'--subpath=[Enable subpath sharing]'
}
_beam_add() {
_arguments \
'(-d --days)'{-d,--days}'=[Expiration days (default 7)]:days:' \
'(-s --socket)'{-s,--socket}'=[Socket path]:socket path:_files' \
'1:file to share:_files'
}
_beam_list() {
_arguments \
'(-s --socket)'{-s,--socket}'=[Socket path]:socket path:_files'
}
_beam_del() {
_arguments \
'(-s --socket)'{-s,--socket}'=[Socket path]:socket path:_files' \
'(-c --code)'{-c,--code}'=[Share code]:share code:_beam_active_codes' \
'1:shared path:_files'
}
# --- Helpers ---
# 动态获取当前的 Share Code
# 它会尝试读取用户当前输入的 -s 参数,去连接正确的 socket
_beam_active_codes() {
local socket_path=""
# 从 Zsh 解析的参数中提取 socket 路径
if [[ -n ${opt_args[-s]} ]]; then
socket_path=${opt_args[-s]}
elif [[ -n ${opt_args[--socket]} ]]; then
socket_path=${opt_args[--socket]}
fi
# 构造查询命令
local cmd_args=()
cmd_args+=("list")
[[ -n $socket_path ]] && cmd_args+=("-s" "$socket_path")
# 调用 beam list,跳过前两行表头,提取 Code($1) 和 Path($3)
# 2>/dev/null 防止服务未启动时报错打印到终端
local -a shares
shares=("${(@f)$(beam "${cmd_args[@]}" 2>/dev/null | awk 'NR>2 {print $1":"$2" "$3" "$4}')}")
if [[ ${#shares} -gt 0 ]]; then
_describe -t shares 'active share' shares
else
_message 'no active shares or server not running'
fi
}
_beam "$@"

35
functions/_beaml Normal file
View File

@@ -0,0 +1,35 @@
#compdef beaml
_beaml() {
local context state line
typeset -A opt_args
_arguments -C \
'1: :->cmds' \
'2: :->args'
case $state in
cmds)
local commands; commands=(
'add:Add a new item'
'del:Delete an item'
'list:List all items'
)
_describe -t commands 'beaml commands' commands
;;
args)
case $line[1] in
add)
_files
;;
del)
local -a items
items=( /pss/lite/*(N:t) )
_describe -t items 'item to delete' items
;;
esac
;;
esac
}
_beaml "$@"

View File

@@ -0,0 +1,3 @@
#compdef bnd nor
compadd -- /root/NixOS-Config/hosts/*(/:t)

105
functions/_riot Normal file
View File

@@ -0,0 +1,105 @@
#compdef riot
_riot() {
local context state state_descr line
typeset -A opt_args
_arguments -C -s \
'(-4 -6)-4[Force ssh to use IPv4 addresses only]' \
'(-4 -6)-6[Force ssh to use IPv6 addresses only]' \
'(-D --dry-run)'{-D,--dry-run}'[Print commands without executing them]' \
'(-d --dev)'{-d,--dev}'[Enable shell execution tracing]' \
'(-g --gpg)'{-g,--gpg}'[Forward the local GPG agent for an interactive login]' \
'(-h --help)'{-h,--help}'[Display help and exit]' \
'(-l --lite)'{-l,--lite}'[Enable dotfiles lite mode]' \
'*-o[Pass an option to ssh]:SSH option:' \
'(-p --password)'{-p,--password}'[Use password authentication]' \
'(-q --quiet)'{-q,--quiet}'[Enable dotfiles quiet mode]' \
'(-t --trust)'{-t,--trust}'[Trust the remote and enable forwarding]' \
'*-v[Enable verbose ssh output]' \
'--color[Force colored output]' \
'--tmux[Open SSH sessions in tmux windows]' \
'1:remote:_riot_remotes' \
'2:command:_riot_commands' \
'*::command argument:->command-args'
case $state in
command-args)
case $line[2] in
ssh|tmux|zssh)
_riot_complete_with _ssh ssh "$line[1]" "${line[@]:2}"
;;
git)
_riot_complete_with _git git "${line[@]:2}"
;;
sftp)
_riot_complete_with _sftp sftp "$line[1]" "${line[@]:2}"
;;
scp)
_riot_complete_with _scp scp "${line[@]:2}"
;;
sshl|sshr)
_message 'forwarding specification'
;;
sshd)
_guard '[0-9]#' 'local port'
;;
rm|ping|ping4|ping6)
_message 'no more arguments'
;;
*)
_default
;;
esac
;;
esac
}
_riot_commands() {
local -a commands=(
'ssh:Open an SSH session (default)'
'tmux:Open SSH sessions in tmux windows'
'git:Run git on the remote host'
'sshl:Create local port forwarding with ssh -L'
'sshr:Create remote port forwarding with ssh -R'
'sshd:Create dynamic port forwarding with ssh -D'
'zssh:Open a zssh session'
'sftp:Open an SFTP session'
'scp:Copy files to or from the remote host'
'rm:Remove the remote host key from known_hosts'
'ping:Ping the remote host'
'ping4:Ping the remote host over IPv4'
'ping6:Ping the remote host over IPv6'
)
_describe -t commands 'riot command' commands
}
_riot_presets() {
local -a config_files presets
local file
[[ -z $DOTFILES ]] || config_files+=("$DOTFILES/riot-config.sh")
config_files+=("$HOME/.config/riot-config.sh" "$PWD/riot-config.sh")
for file in "${config_files[@]}"; do
[[ -r $file ]] || continue
presets+=("${(@f)$(sed -nE 's/^([[:alnum:]_.-]+)\.(remote|batch)\(\).*/\1/p' "$file" 2>/dev/null)}")
done
presets=(${(u)presets})
_describe -t presets 'riot preset' presets
}
_riot_remotes() {
_alternative \
'presets:riot preset:_riot_presets' \
'hosts:host:_hosts'
}
_riot_complete_with() {
local completer=$1
shift
local -a words=("$@")
local CURRENT=$#words
"$completer"
}
_riot "$@"

3
functions/_sagt Normal file
View File

@@ -0,0 +1,3 @@
#compdef sagt
compadd -- gpg gpg-pin kill ls op piv reset

3
functions/_use Normal file
View File

@@ -0,0 +1,3 @@
#compdef use
compadd -- $(nix eval --impure --expr 'builtins.attrNames ( (import <nixpkgs>) {} )' 2>/dev/null | tr '[]"' ' ')

View File

@@ -26,6 +26,8 @@ declare -a HOME_SYMLINKS_SRC
declare -a HOME_SYMLINKS_DST declare -a HOME_SYMLINKS_DST
HOME_SYMLINKS_SRC[0]=".ssh/authorized_keys2" HOME_SYMLINKS_SRC[0]=".ssh/authorized_keys2"
HOME_SYMLINKS_DST[0]=".ssh/authorized_keys2" HOME_SYMLINKS_DST[0]=".ssh/authorized_keys2"
HOME_SYMLINKS_SRC[1]=".eid/authorized_certificates"
HOME_SYMLINKS_DST[1]=".eid/authorized_certificates"
install_dependencies() install_dependencies()
{ {
@@ -147,6 +149,9 @@ install_symlink()
{ {
fmt_note "installing symlinks ..." fmt_note "installing symlinks ..."
for ((i=0; i<${#HOME_SYMLINKS_SRC[@]}; i++)); do for ((i=0; i<${#HOME_SYMLINKS_SRC[@]}; i++)); do
if [[ -z "${HOME_SYMLINKS_SRC[$i]}" ]]; then
continue
fi
local src="$DOTFILES/${HOME_SYMLINKS_SRC[$i]}" local src="$DOTFILES/${HOME_SYMLINKS_SRC[$i]}"
local dst="$HOME/${HOME_SYMLINKS_DST[$i]}" local dst="$HOME/${HOME_SYMLINKS_DST[$i]}"
fmt_info "creating symlink \"$dst\" --> \"$src\" ..." fmt_info "creating symlink \"$dst\" --> \"$src\" ..."
@@ -197,10 +202,11 @@ install_crontab()
{ {
if [[ -x $(command -v crontab) ]]; then if [[ -x $(command -v crontab) ]]; then
fmt_note "installing \"$CRON_JOB\" to crontab ..." fmt_note "installing \"$CRON_JOB\" to crontab ..."
if ! crontab -l 1>/dev/null 2>&1; then if [[ -z "$(crontab -l 2>/dev/null || true)" ]]; then
echo -n | crontab - echo "$CRON_JOB" | crontab -
elif !( crontab -l | grep -qxF "${CRON_JOB}"); then
( crontab -l; echo "$CRON_JOB" ) | crontab -
fi fi
( crontab -l | grep -vxF "${CRON_JOB}" | grep -v "no crontab for"; echo "$CRON_JOB" ) | crontab -
else else
fmt_warning "crontab does not exist. skipping ..." fmt_warning "crontab does not exist. skipping ..."
fi fi
@@ -325,6 +331,7 @@ for i in ${GOT_OPTS[@]}; do
-a|--auto ) INSTALL_DEP=1 ;; -a|--auto ) INSTALL_DEP=1 ;;
-H|--hist|--history ) store_hist=1 ;; -H|--hist|--history ) store_hist=1 ;;
-x ) store_config=1 ;; -x ) store_config=1 ;;
--no-auth-info ) HOME_SYMLINKS_SRC=(); HOME_SYMLINKS_DST=() ;;
* ) fmt_fatal "unknown option \"$i\"" ;; * ) fmt_fatal "unknown option \"$i\"" ;;
esac esac
done done

69
riot-config.sh Normal file
View File

@@ -0,0 +1,69 @@
#!/bin/false
# batches
nasps.batch() {
remotes+=(
g1.nasp
g2.nasp
g3.nasp
g4.nasp
g5.nasp
g6.nasp
g7.nasp
g8.nasp
g9.nasp
g10.nasp
g11.nasp
g12.nasp
g13.nasp
g14.nasp
dictxiong@g15.nasp
dictxiong@g16.nasp
g17.nasp
)
}
# remotes
nasp.remote() {
remote=jump.nasp.fit
RET_PORT=${RET_PORT:-36022}
RET_USERNAME=${RET_USERNAME:-root}
RET_TRUST_SERVER=1
}
# domains
.domain() {
RET_USERNAME=${RET_USERNAME:-root}
RET_PORT=${RET_PORT:-12022}
RET_HOSTNAME=${remote%.}
}
dxng.domain() {
if [[ "$host" =~ ^sed([0-9]{1,2})$ ]]; then
RET_HOSTNAME=192.168.98.$((100+BASH_REMATCH[1]))
else
RET_HOSTNAME=$host.dxng.net
fi
RET_PORT=${RET_PORT:-12022}
RET_USERNAME=${RET_USERNAME:-root}
RET_TRUST_SERVER=1
}
42.domain() {
RET_HOSTNAME=$host.i.bd.dn42
RET_PORT=${RET_PORT:-12022}
RET_USERNAME=${RET_USERNAME:-root}
RET_TRUST_SERVER=1
}
nasp.domain() {
RET_HOSTNAME=$host
RET_PORT=${RET_PORT:-12022}
RET_USERNAME=${RET_USERNAME:-root}
RET_JUMP_SERVER="ssh@jump.nasp.fit:36022"
RET_TRUST_SERVER=1
}
default.domain() {
dxng.domain
}

View File

@@ -21,5 +21,5 @@ if [[ -z "$IMAGE" ]]; then
fmt_fatal "image not found" fmt_fatal "image not found"
else else
fmt_note "--> ${IMAGE_META[@]}" fmt_note "--> ${IMAGE_META[@]}"
$SUDO docker run ${2:+"--name"} $2 -itd $IMAGE sh $SUDO docker run ${2:+"--name"} $2 -itd --restart=unless-stopped $IMAGE sh
fi fi

View File

@@ -1,20 +1,39 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# connect to iot services # connect to iot services
THIS_DIR=$( cd "$( dirname "${BASH_SOURCE[0]:-${(%):-%x}}" )" && pwd ) THIS_DIR=$( cd "$( dirname "${BASH_SOURCE[0]:-${(%):-%x}}" )" && pwd )
DFS_SKIP_ARG_PARSE=1
source "$THIS_DIR/../tools/common.sh" source "$THIS_DIR/../tools/common.sh"
unset DFS_SKIP_ARG_PARSE
RIOT_TRUST_CLIENT=${RIOT_TRUST_CLIENT:-${DFS_TRUST:-0}} RIOT_TRUST_CLIENT=${RIOT_TRUST_CLIENT:-${DFS_TRUST:-0}}
RIOT_TRUST_SERVER=${RIOT_TRUST_SERVER:-0} RIOT_TRUST_SERVER=${RIOT_TRUST_SERVER:-0}
EXTRA_SSH_OPTIONS=()
GPG_FORWARD=0
# config
RIOT_CONFIG_FILES=(
"$DOTFILES/riot-config.sh"
"$HOME/.config/riot-config.sh"
"riot-config.sh"
)
load_riot_config() {
local file
for file in "${RIOT_CONFIG_FILES[@]}"; do
if [[ -f "$file" ]]; then
source "$file"
fi
done
}
# check if port number valid # check if port number valid
check_port() { check_port() {
( echo $1 | grep -qxE "[1-9][0-9]{0,4}" ) || return 1 [[ "$1" =~ ^[1-9][0-9]{0,4}$ ]] || return 1
test $1 -lt 65536 -a $1 -gt 0 || return 1 [[ $1 -lt 65536 && $1 -gt 0 ]] || return 1
return 0 return 0
} }
# check if username valid # check if username valid
check_username() { check_username() {
( echo $1 | grep -qxE "^[a-z][-a-z0-9_]*\$" ) || return 1 [[ "$1" =~ ^[a-z][-a-z0-9_]*$ ]] || return 1
return 0 return 0
} }
@@ -29,71 +48,75 @@ get_server_meta() {
RET_JUMP_SERVER="" # optional RET_JUMP_SERVER="" # optional
# body # body
local remote="$1" local remote="$1"
# if in the form user@... # extract username from user@...
if [[ "$remote" == *@* ]]; then if [[ "$remote" == *@* ]]; then
RET_USERNAME=${remote%%@*} RET_USERNAME=${remote%%@*}
remote=${remote#*@} remote=${remote#*@}
check_username $RET_USERNAME || fmt_warning \"$RET_USERNAME\" is not a valid unix username check_username $RET_USERNAME || fmt_warning \"$RET_USERNAME\" is not a valid unix username
fi fi
# if in the form ...:22 # extract port from ...:port
if [[ "$remote" == "["*"]":* || ( "$remote" != "["*"]" && "$remote" == *:* ) ]]; then if [[
"$remote" =~ ^[^:]+:[1-9][0-9]*$ # contains only one colon
|| "$remote" =~ ^\[.+\]:[1-9][0-9]*$ # in the form of [host]:port
|| "$remote" =~ :::[1-9][0-9]*$ # in the form of :::port
|| "$remote" =~ ^([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}:[1-9][0-9]*$ # full ipv6 address with port
|| "$remote" =~ ^[0-9A-Fa-f:]+%.+:[1-9][0-9]*$ # ipv6 address with scope and port
]]; then
RET_PORT=${remote##*:} RET_PORT=${remote##*:}
remote=${remote%:*} remote=${remote%:*}
check_port $RET_PORT || fmt_fatal invalid port number \"$RET_PORT\" check_port $RET_PORT || fmt_fatal invalid port number \"$RET_PORT\"
fi fi
# remove square brackets
if [[ "$remote" =~ ^\[.*\]$ ]]; then
remote=${remote:1:-1}
fi
# presets -- match remote
local remote_func="$remote.remote"
if is_function "$remote_func"; then
"$remote_func"
fi
# presets -- match domain # presets -- match domain
RET_HOSTNAME=${remote}
local domain=${remote##*.} local domain=${remote##*.}
local host=${remote%.*} local host=${remote%.*}
# if there's no dot # if it contains no dot and is not ipv6
if [[ "$host" == "$domain" && "$host" != "["*"]" ]]; then if [[ "$remote" != *.* && "$remote" != *:* ]]; then
domain="ibd" domain="default"
fi
local domain_func="$domain.domain"
if is_function "$domain_func"; then
"$domain_func"
elif is_function ".domain"; then
".domain"
fi fi
case $domain in
i|ibd )
RET_HOSTNAME=$host.ibd.ink
RET_PORT=${RET_PORT:-12022}
RET_USERNAME=${RET_USERNAME:-root}
RET_TRUST_SERVER=1
;;
nasp )
RET_HOSTNAME=$host
RET_PORT=${RET_PORT:-12022}
RET_USERNAME=${RET_USERNAME:-dictxiong}
RET_JUMP_SERVER="ssh@nasp.ob.ac.cn:36022"
RET_TRUST_SERVER=1
;;
x|proxied )
RET_HOSTNAME=proxy.beardic.cn
local tmp=$(sha256sum <<< "$host" | tr -cd "[:digit:]")
tmp=${tmp:0:4}
RET_PORT=$((10#$tmp+36000))
RET_USERNAME=root
RET_TRUST_SERVER=1
;;
* )
test -z "$domain" || fmt_warning "unknown domain: \"$domain\". will try as host name"
RET_HOSTNAME="$remote"
esac
} }
# remote setting, including jump servers
# will be called only once
# provides:
SERVER=""
TRUST_SERVER=1
PORT="" # optional
USERNAME="" # optional
SSH_OPTIONS="" # optional
if [[ "$RIOT_TRUST_CLIENT" == "1" ]]; then
SSH_OPTIONS='-o ControlMaster=auto -o ControlPath=/tmp/sshcm-%C -o PermitLocalCommand=yes'
fi
parse_remote() { parse_remote() {
# remote setting, including jump servers
# called for every remote
# provides:
SERVER=""
TRUST_SERVER=1
PORT="" # optional
USERNAME="" # optional
SSH_OPTIONS=("-o" "ServerAliveInterval=60")
if [[ -t 1 ]]; then
SSH_OPTIONS+=("-o" "RequestTTY=yes")
fi
if [[ "$RIOT_TRUST_CLIENT" == "1" ]]; then
SSH_OPTIONS+=("-o" "PermitLocalCommand=yes")
if [[ "$(get_os_type)" != "msys" ]]; then
test "$DFS_DRY_RUN" = "1" || mkdir -p ~/.ssh/master-socket
SSH_OPTIONS+=("-o" "ControlMaster=auto" "-o" "ControlPersist=5s" "-o" "ControlPath=~/.ssh/master-socket/%C")
fi
fi
# handle input
local remote="$1" local remote="$1"
local jump_servers="" local jump_servers=""
# loop for jump servers # loop for jump servers
while [[ -n $remote ]]; do while [[ -n $remote ]]; do
local server=${remote%%,*} local server=${remote%%/*}
remote=${remote#*,} remote=${remote#*/}
get_server_meta "$server" get_server_meta "$server"
if [[ -n "$RET_JUMP_SERVER" ]]; then if [[ -n "$RET_JUMP_SERVER" ]]; then
jump_servers="$jump_servers${jump_servers:+,}$RET_JUMP_SERVER" jump_servers="$jump_servers${jump_servers:+,}$RET_JUMP_SERVER"
@@ -111,18 +134,167 @@ parse_remote() {
done done
# construct cmd # construct cmd
if [[ "$RIOT_TRUST_SERVER" == "1" || "$TRUST_SERVER" == "1" ]]; then if [[ "$RIOT_TRUST_SERVER" == "1" || "$TRUST_SERVER" == "1" ]]; then
SSH_OPTIONS="$SSH_OPTIONS -o ForwardX11=yes -o ForwardAgent=yes" SSH_OPTIONS+=("-o" "ForwardX11=yes" "-o" "ForwardAgent=yes")
fi fi
if [[ -n "$jump_servers" ]]; then if [[ -n "$jump_servers" ]]; then
SSH_OPTIONS="$SSH_OPTIONS -o ProxyJump=$jump_servers" SSH_OPTIONS+=("-o" "ProxyJump=$jump_servers")
fi fi
} }
eval_or_echo() { check_local_gpg_agent() {
if [[ "$DFS_DRY_RUN" == "1" ]]; then LOCAL_GPG_EXTRA_SOCKET=""
echo $@ GPG_FORWARD_ERROR=""
if ! command -v gpgconf > /dev/null 2>&1 || ! command -v gpg-connect-agent > /dev/null 2>&1; then
GPG_FORWARD_ERROR="gpgconf or gpg-connect-agent is not available"
return 1
fi
local agent_info
agent_info=$(gpg-connect-agent --no-autostart 'GETINFO pid' /bye 2>/dev/null || true)
if ! grep -qE '^D [1-9][0-9]*$' <<< "$agent_info"; then
GPG_FORWARD_ERROR="local gpg-agent is not running"
return 1
fi
LOCAL_GPG_EXTRA_SOCKET=$(gpgconf --list-dirs agent-extra-socket 2>/dev/null || true)
if [[ -z "$LOCAL_GPG_EXTRA_SOCKET" || "$LOCAL_GPG_EXTRA_SOCKET" != /* \
|| "$LOCAL_GPG_EXTRA_SOCKET" == *:* || "$LOCAL_GPG_EXTRA_SOCKET" == *$'\r'* \
|| "$LOCAL_GPG_EXTRA_SOCKET" == *$'\n'* || ! -S "$LOCAL_GPG_EXTRA_SOCKET" ]]; then
GPG_FORWARD_ERROR="local gpg-agent extra socket is unavailable"
return 1
fi
local extra_info
extra_info=$(gpg-connect-agent --raw-socket "$LOCAL_GPG_EXTRA_SOCKET" 'GETINFO version' /bye 2>/dev/null || true)
if ! grep -qE '^D [^[:space:]]+' <<< "$extra_info"; then
GPG_FORWARD_ERROR="local gpg-agent extra socket exists but is not accepting connections"
return 1
fi
}
probe_remote_gpg_socket() {
REMOTE_GPG_SOCKET=""
# The probe must be an independent connection: reusing or creating a
# multiplex master here races with the immediately following login.
local query_cmd=(ssh "-S" "none" "-o" "ClearAllForwardings=yes")
if [[ -n "$PORT" ]]; then
query_cmd+=("-p" "$PORT")
fi
query_cmd+=(
"${SSH_OPTIONS[@]}"
"${EXTRA_SSH_OPTIONS[@]}"
"-T"
"$USERNAME${USERNAME:+@}$SERVER"
'socket=$(gpgconf --list-dirs agent-socket 2>/dev/null) || exit 10
case "$socket" in /*/S.gpg-agent) ;; *) exit 11;; esac
case "$socket" in *:*) exit 11;; esac
case "$socket" in *"
"*) exit 11;; esac
carriage_return=$(printf "\r")
case "$socket" in *"$carriage_return"*) exit 11;; esac
if [ -e "$socket" ] && [ ! -S "$socket" ]; then exit 12; fi
if [ -S "$socket" ] && command -v gpg-connect-agent >/dev/null 2>&1; then
agent_mode=$(gpg-connect-agent --raw-socket "$socket" "GETINFO restricted" /bye 2>/dev/null || true)
case "$agent_mode" in *"D 1"*) exit 13;; esac
fi
systemd_socket=0
if command -v systemctl >/dev/null 2>&1 && systemctl --user is-active --quiet gpg-agent.socket >/dev/null 2>&1; then
systemd_socket=1
fi
gpgconf --kill gpg-agent >/dev/null 2>&1 || exit 14
rm -f "$socket" || exit 15
printf "%s\n%s\n" "$socket" "$systemd_socket"'
)
local output status
if output=$("${query_cmd[@]}"); then
status=0
else else
eval $@ status=$?
fi
case "$status" in
0) ;;
10) GPG_FORWARD_ERROR="gpgconf is unavailable on the remote host" ;;
11) GPG_FORWARD_ERROR="remote gpgconf returned an invalid agent socket" ;;
12) GPG_FORWARD_ERROR="refusing to remove the non-socket remote gpg-agent path" ;;
13) GPG_FORWARD_ERROR="another forwarded gpg-agent is already using the remote socket; close that session first" ;;
14) GPG_FORWARD_ERROR="failed to stop the remote gpg-agent (the socket may belong to another forwarding session)" ;;
15) GPG_FORWARD_ERROR="failed to remove the stale remote gpg-agent socket" ;;
*) GPG_FORWARD_ERROR="failed to query or clean the remote gpg-agent socket (ssh status $status)" ;;
esac
if [[ "$status" != "0" ]]; then
return 1
fi
local remote_systemd_socket
REMOTE_GPG_SOCKET=${output%%$'\n'*}
remote_systemd_socket=${output#*$'\n'}
if [[ -z "$REMOTE_GPG_SOCKET" || "$REMOTE_GPG_SOCKET" == *$'\r'* || "$REMOTE_GPG_SOCKET" == *$'\n'* \
|| "$REMOTE_GPG_SOCKET" != /*/S.gpg-agent || "$REMOTE_GPG_SOCKET" == *:* \
|| "$remote_systemd_socket" != "0" && "$remote_systemd_socket" != "1" ]]; then
GPG_FORWARD_ERROR="remote gpgconf returned an invalid agent socket: $output"
return 1
fi
if [[ "$remote_systemd_socket" == "1" ]]; then
fmt_warning "remote gpg-agent.socket is active and may race with GPG forwarding; consider disabling its socket activation"
fi
}
prepare_gpg_forwarding() {
if [[ "$DFS_DRY_RUN" == "1" ]]; then
REMOTE_GPG_SOCKET="<remote-gpg-agent-socket>"
LOCAL_GPG_EXTRA_SOCKET="<local-gpg-agent-extra-socket>"
else
if ! check_local_gpg_agent; then
fmt_fatal "cannot forward gpg-agent: $GPG_FORWARD_ERROR"
fi
if ! probe_remote_gpg_socket; then
fmt_fatal "cannot forward gpg-agent: $GPG_FORWARD_ERROR"
fi
fi
SSH_OPTIONS+=(
# The probe already removed the old socket. Do not let the main SSH
# connection unlink a path recreated during the gap between them.
"-o" "StreamLocalBindUnlink=no"
"-o" "ExitOnForwardFailure=yes"
"-R" "$REMOTE_GPG_SOCKET:$LOCAL_GPG_EXTRA_SOCKET"
)
}
print_cmd() {
local output=""
for s in "${CMD[@]}"; do
if [[ "$s" =~ [\ \\\'\"] ]]; then # needs to be escaped
s="${s@Q}"
fi
output+="$s "
done
fmt_note "--> ${output% }"
}
eval_or_echo() {
local DO=""
tmux_win=${tmux_win:-0}
if [[ "$DFS_DRY_RUN" == "1" ]]; then
DO=echo
fi
if [[ "$USE_TMUX" == "1" ]]; then
if [[ -z "$TMUX_SESS" ]]; then
TMUX_SESS=riot-$(date +%s)
$DO tmux new-session -d -s $TMUX_SESS bash -l
else
tmux_win=$((tmux_win+1))
$DO tmux new-window -t $TMUX_SESS:$tmux_win -d bash -l
fi
local command
printf -v command '%q ' "${CMD[@]}"
$DO tmux send-keys -l -t "$TMUX_SESS:$tmux_win" "${command% }"
$DO tmux send-keys -t "$TMUX_SESS:$tmux_win" Enter
else
$DO "${CMD[@]}"
fi fi
} }
@@ -134,37 +306,83 @@ prepare_ssh_cmd() {
else else
local port_param='-p' local port_param='-p'
fi fi
echo "$ssh_bin ${PORT:+$port_param} $PORT $SSH_OPTIONS $SCP_SRC $USERNAME${USERNAME:+@}$SERVER $SCP_DST" CMD=(
"$ssh_bin"
"${PORT:+$port_param}" "$PORT"
"${SSH_OPTIONS[@]}"
"${EXTRA_SSH_OPTIONS[@]}"
"$SCP_SRC"
"$USERNAME${USERNAME:+@}$SERVER"
"$SCP_DST"
"${@:2}"
)
for i in ${!CMD[@]}; do if [[ -z "${CMD[i]}" ]]; then unset CMD[i]; fi; done
} }
# ssh # ssh
run_ssh() run_ssh()
{ {
local cmd="$(prepare_ssh_cmd $1)" prepare_ssh_cmd "$@"
fmt_note "-->" $cmd print_cmd
eval_or_echo $cmd eval_or_echo
} }
# sshl # sshl
run_sshl() run_sshl()
{ {
local arg="$1" local arg left right localsock access
if [[ "$arg" != *":"* ]]; then local res="${1//[^:]}"
# treat as a port number local lorr="-L"
arg=localhost:$arg if [[ "${FUNCNAME[1]}" == "run_sshr" ]]; then
lorr="-R"
fi fi
while if [[ ${#res} -eq 2 ]]; then
local port=$(shuf -n 1 -i 49152-65535) arg="$1"
netstat -atun | grep -q "$port" elif [[ ${#res} -eq 0 ]]; then
do if [[ "$lorr" == "-R" ]]; then
continue arg="$1"
done else
arg="$(get_free_port):localhost:$1"
fi
else
left=${1%%:*}
right=${1##*:}
if check_port "$left"; then
arg="$1"
elif check_port "$right"; then
arg="$(get_free_port):$1"
else
arg="$1"
localsock=1
fi
fi
if [[ "$localsock" == "1" ]]; then
access="unix://${arg%%:*}"
else
access="localhost:${arg%%:*}"
fi
SSH_OPTIONS+=("-NC" "$lorr" "$arg")
prepare_ssh_cmd ssh
print_cmd
fmt_note " > please access $access"
eval_or_echo
}
SSH_OPTIONS="$SSH_OPTIONS -NC -L $port:$arg" # sshr
local cmd="$(prepare_ssh_cmd ssh)" run_sshr()
fmt_note "-->" $cmd {
run_sshl "$1"
}
# sshd
run_sshd()
{
local port=${1:-$(get_free_port)}
SSH_OPTIONS+=("-NC" "-D" "$port")
prepare_ssh_cmd ssh
print_cmd
fmt_note " > please access localhost:$port" fmt_note " > please access localhost:$port"
eval_or_echo $cmd eval_or_echo
} }
# scp # scp
@@ -178,59 +396,327 @@ run_scp() {
dst_is_remote=0 dst_is_remote=0
fi fi
if [[ "$dst_is_remote" == "1" ]]; then if [[ "$dst_is_remote" == "1" ]]; then
SCP_SRC=\""$src"\" SCP_SRC="$src"
SERVER="$SERVER":\""$dst"\" SERVER="$SERVER":"$dst"
else else
SERVER="$SERVER":\""$src"\" SERVER="$SERVER":"$src"
SCP_DST=\""$dst"\" SCP_DST="$dst"
fi fi
SSH_OPTIONS="$SSH_OPTIONS -r" SSH_OPTIONS+=("-r")
local cmd="$(prepare_ssh_cmd scp)" prepare_ssh_cmd scp
fmt_note "-->" $cmd print_cmd
eval_or_echo $cmd eval_or_echo
}
# ping
run_ping() {
CMD=(ping)
if [[ "$1" == "ping4" ]]; then
CMD+=(-4)
elif [[ "$1" == "ping6" ]]; then
CMD+=(-6)
fi
CMD+=(-c 4 "$SERVER")
print_cmd
eval_or_echo
}
# remove host keys
remove_hostkey() {
local key
if [[ -z "$PORT" || "$PORT" == "22" ]]; then
key=$SERVER
else
key="[$SERVER]:$PORT"
fi
ssh-keygen -R "$key"
} }
# main # main
print_help() print_help()
{ {
fmt_info "usage: $0 <service> [command] [options]" local pager=(cat)
echo "available commands: ssh (default), sshl (ssh -L), zssh, sftp" if [[ -t 1 ]] && command -v less > /dev/null 2>&1; then
pager=(less -R)
fi
cat <<EOF | "${pager[@]}"
NAME
riot - connect to remote hosts using SSH presets
SYNOPSIS
${0##*/} [OPTION]... REMOTE [OPTION]... [COMMAND [COMMAND-ARG]...]
DESCRIPTION
Connect to REMOTE using the matching configuration from riot-config.sh.
COMMAND defaults to ssh. Separate multiple remotes with commas and jump
hosts with slashes. OPTIONs may appear before COMMAND. Once COMMAND is
found, all remaining arguments are passed to it without further parsing.
OPTIONS
-4
Force ssh to use IPv4 addresses only.
-6
Force ssh to use IPv6 addresses only.
-D, --dry-run
Print commands without executing them.
-d, --dev
Enable shell execution tracing.
-g, --gpg
Forward the local GPG agent during an interactive SSH login. Use only
with trusted remote hosts.
-h, --help
Display this help and exit.
-l, --lite
Enable dotfiles lite mode for loaded configuration.
-o SSH-OPTION
Pass an option to ssh. This option may be specified multiple times.
-p, --password
Use password authentication instead of public-key authentication.
-q, --quiet
Enable dotfiles quiet mode for loaded configuration.
-t, --trust
Trust the remote and enable X11 and SSH agent forwarding.
-v
Enable verbose ssh output.
--color
Force colored output.
--tmux
Open SSH sessions in tmux windows.
--
End option parsing.
COMMANDS
ssh [SSH-ARG]...
Open an SSH session. This is the default command.
tmux [SSH-ARG]...
Open SSH sessions in multiple tmux windows.
git [GIT-ARG]...
Run git on the remote host.
sshl [LOCAL-PORT:REMOTE-HOST:]REMOTE-PORT
Create local port forwarding with ssh -L.
sshr [REMOTE-HOST:]REMOTE-PORT
Create remote port forwarding with ssh -R.
sshd [LOCAL-PORT]
Create dynamic port forwarding with ssh -D.
zssh [SSH-ARG]...
Open a zssh session.
sftp [SFTP-ARG]...
Open an SFTP session.
scp SOURCE DESTINATION
Copy files to or from the remote host.
rm
Remove the remote host key from known_hosts.
ping, ping4, ping6
Ping the remote host.
EOF
} }
router() { router() {
if [[ -z "$1" || "$1" == "-h" || "$1" == "--help" ]]; then local positional=()
print_help local arg=""
exit local option=""
local remaining=""
local option_value=""
while [[ $# -gt 0 || -n "$arg" ]]; do
if [[ -z "$arg" ]]; then
arg=$1
shift
fi
# Normalize a long option or one item from a short-option group.
remaining=""
case "$arg" in
-- )
positional+=("$@")
break
;;
--* )
option=$arg
arg=""
;;
-?* )
option=${arg:0:2}
remaining=${arg:2}
arg=${remaining:+-$remaining}
;;
* )
positional+=("$arg")
arg=""
if [[ "${#positional[@]}" -ge 2 ]]; then
positional+=("$@")
break
fi
continue
;;
esac
case "$option" in
-h|--help )
print_help
exit 0
;;
-t|--trust )
RIOT_TRUST_SERVER=1
;;
-g|--gpg )
GPG_FORWARD=1
;;
--tmux )
USE_TMUX=1
;;
-p|--password )
EXTRA_SSH_OPTIONS+=("-o" "PasswordAuthentication=yes" "-o" "PubkeyAuthentication=no")
;;
-D|--dry-run )
export DFS_DRY_RUN=1
;;
-d|--dev )
export DFS_DEV=1
set -x
;;
-l|--lite )
export DFS_LITE=1
;;
-q|--quiet )
export DFS_QUIET=1
;;
--color )
export DFS_COLOR=1
setup_color
;;
-4|-6|-v )
EXTRA_SSH_OPTIONS+=("$option")
;;
-o )
if [[ -n "$remaining" ]]; then
option_value=$remaining
arg=""
else
[[ $# -gt 0 ]] || fmt_fatal "option '-o' requires an argument"
option_value=$1
shift
fi
EXTRA_SSH_OPTIONS+=("-o" "$option_value")
;;
* )
fmt_fatal "unknown option: '$option'"
;;
esac
done
if [[ "${positional[2]}" == "--" ]]; then
positional=("${positional[@]:0:2}" "${positional[@]:3}")
fi
IFS=',' read -ra remotes <<< "${positional[0]}"
for i in ${!remotes[@]}; do if [[ -z "${remotes[i]}" ]]; then unset remotes[i]; fi; done
if [[ "${#positional[@]}" == "0" || "${#remotes[@]}" == "0" ]]; then
print_help
exit 1
fi
load_riot_config
if [[ "$GPG_FORWARD" == "1" && ( \
( "${positional[1]}" != "" && "${positional[1]}" != "ssh" && "${positional[1]}" != "tmux" ) \
|| "${#positional[@]}" -gt 2 ) ]]; then
fmt_fatal "gpg-agent forwarding is only supported for interactive SSH login"
fi
for i in ${!remotes[@]}; do
remote="${remotes[i]}"
local batch_func="${remote}.batch"
if is_function "$batch_func"; then
"$batch_func"
continue
fi
parse_remote "$remote"
case "${positional[1]}" in
ssh|tmux|"" )
[[ "${positional[1]}" == tmux ]] && USE_TMUX=1
if [[ "$GPG_FORWARD" == "1" ]]; then
prepare_gpg_forwarding
fi
run_ssh ssh "${positional[@]:2}"
;;
git )
if printf '%s\0' "${positional[@]:2}" | grep -Fxqz -- '-C'; then
run_ssh ssh git "${positional[@]:2}"
else
run_ssh ssh git -C "$(pwd)" "${positional[@]:2}"
fi
;;
ping|ping4|ping6 )
test "${#positional[@]}" -eq 2 || fmt_fatal "ping requires no arguments"
run_ping "${positional[1]}"
;;
zssh )
run_ssh zssh "${positional[@]:2}"
;;
sftp )
run_ssh sftp "${positional[@]:2}"
;;
sshl )
test -n "${positional[2]}" || fmt_fatal "no target address provided"
test "${#positional[@]}" -eq 3 || fmt_fatal "sshl requires exactly one argument"
run_sshl "${positional[2]}"
;;
sshr )
test -n "${positional[2]}" || fmt_fatal "no target address provided"
test "${#positional[@]}" -eq 3 || fmt_fatal "sshr requires exactly one argument"
run_sshr "${positional[2]}"
;;
sshd )
test "${#positional[@]}" -le 3 || fmt_fatal "sshd requires one or no arguments"
if [[ "${#positional[@]}" -eq 3 ]]; then
check_port "${positional[2]}" || fmt_fatal "invalid port number: ${positional[2]}"
run_sshd "${positional[2]}"
else
run_sshd
fi
;;
scp )
test "${#positional[@]}" -eq 4 || fmt_fatal "scp requires exactly two arguments: source and destination"
test -n "${positional[2]}" || fmt_fatal "no source path specified"
test -n "${positional[3]}" || fmt_fatal "no destination path specified"
run_scp "${positional[2]}" "${positional[3]}"
;;
rm )
test "${#positional[@]}" -eq 2 || fmt_fatal "rm requires no arguments"
remove_hostkey
;;
* )
print_help
fmt_fatal "unknown command: ${positional[1]}"
;;
esac
done
if [[ -n "$TMUX_SESS" && "$DFS_DRY_RUN" != "1" ]]; then
tmux attach-session -t $TMUX_SESS
fi fi
parse_remote "$1"
case $2 in
-h|--help)
print_help
exit
;;
ssh|"" )
run_ssh
;;
zssh )
run_ssh zssh
;;
sftp )
run_ssh sftp
;;
sshl )
test -n "$3" || fmt_fatal "no target address provided"
run_sshl "$3"
;;
scp )
test -n "$3" || fmt_fatal "no source path specified"
test -n "$4" || fmt_fatal "no destination path specified"
run_scp "$3" "$4"
;;
* )
print_help
fmt_fatal "unknown command: $2"
;;
esac
} }
router "${GOT_OPTS[@]}" router "$@"

31
scripts/sne Executable file
View File

@@ -0,0 +1,31 @@
#!/usr/bin/env bash
if [ "$#" -eq 0 ]; then
echo "usage: sne <filename or regex pattern>"
echo "example: sne config.yaml"
exit 1
fi
SEARCH_PATTERN="$1"
files=()
while IFS= read -r -d $'\0' file; do
files+=("$file")
done < <(fd "$SEARCH_PATTERN" --type f --print0)
file_count=${#files[@]}
if [ "$file_count" -eq 0 ]; then
echo "no file match '$SEARCH_PATTERN'"
exit 0
fi
echo "🔎 found $file_count files:"
for file in "${files[@]}"; do
echo " 📄 $file"
done
echo "----------------------------------------"
read -r -p "edit? [Y/n] " response
response=${response:-Y}
if [[ "$response" =~ ^[Yy]$ ]]; then
vim "${files[@]}"
else
echo "❌ aborted"
fi

View File

@@ -6,16 +6,19 @@ if [[ -f ~/.config/dotfiles/env ]]; then set -a; source ~/.config/dotfiles/env;
if [[ "$DFS_DEV" == "1" ]]; then set -x; fi if [[ "$DFS_DEV" == "1" ]]; then set -x; fi
DFS_CURL_OPTIONS="--retry 2 --max-time 20" DFS_CURL_OPTIONS="--retry 2 --max-time 20"
# parse args and set env, when it is sourced # Parse args and set env when sourced, unless the caller handles its own
# todo: make this skipable # option boundary.
if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then if [[ "${BASH_SOURCE[0]}" != "${0}" && "$DFS_SKIP_ARG_PARSE" != "1" ]]; then
ORIGIN_ARGS=("$@") ORIGIN_ARGS=("$@")
ARG="" ARG=""
GOT_OPTS=() GOT_OPTS=()
while [[ $# > 0 || -n "$ARG" ]]; do while [[ $# -gt 0 || -n "$ARG" ]]; do
if [[ -z "$ARG" ]]; then ARG=$1; shift; fi if [[ -z "$ARG" ]]; then
if [[ "$1" == "--" ]]; then GOT_OPTS+=("$@"); break; fi
ARG="$1"; shift;
fi
case $ARG in case $ARG in
-q*|--quite ) export DFS_QUIET=1 ;; -q*|--quiet ) export DFS_QUIET=1 ;;
-l*|--lite ) export DFS_LITE=1 ;; -l*|--lite ) export DFS_LITE=1 ;;
-d*|--dev ) export DFS_DEV=1; set -x ;; -d*|--dev ) export DFS_DEV=1; set -x ;;
-D*|--dry-run ) export DFS_DRY_RUN=1 ;; -D*|--dry-run ) export DFS_DRY_RUN=1 ;;
@@ -38,15 +41,9 @@ fi
# Color settings # Color settings
# Source: https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh # Source: https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh
if [[ -t 1 || "$DFS_COLOR" == "1" ]]; then is_tty() {
is_tty() { [[ -t 1 || "$DFS_COLOR" == "1" ]]
true }
}
else
is_tty() {
false
}
fi
supports_truecolor() { supports_truecolor() {
case "$COLORTERM" in case "$COLORTERM" in
@@ -162,19 +159,6 @@ ask_for_Yn()
fi fi
} }
post_log()
{
if [[ $# != 3 || -z "$1" || -z "$2" || -z "$3" ]]; then
fmt_fatal "usage: post_log <level> <section> <content>"
fi
"${DOTFILES}/tools/frigg-client.sh" "log" "[$1][$2] $3"
}
apost_log()
{
post_log "$@" 1>/dev/null &
}
post_beacon() post_beacon()
{ {
if [[ $# < 1 || -z "$1" ]]; then if [[ $# < 1 || -z "$1" ]]; then
@@ -190,6 +174,7 @@ apost_beacon()
get_os_type() get_os_type()
{ {
test -z "$DFS_OS_TYPE" || { echo "$DFS_OS_TYPE"; return; }
local ans="unknown" local ans="unknown"
case "$(uname -s)" in case "$(uname -s)" in
Darwin*) ans="MacOS";; Darwin*) ans="MacOS";;
@@ -198,11 +183,13 @@ get_os_type()
Linux* ) ans="Linux";; Linux* ) ans="Linux";;
*) ans="unknown";; *) ans="unknown";;
esac esac
export DFS_OS_TYPE="$ans"
echo $ans | tr '[:upper:]' '[:lower:]' echo $ans | tr '[:upper:]' '[:lower:]'
} }
get_linux_dist() get_linux_dist()
{ {
test -z "$DFS_LINUX_DIST" || { echo "$DFS_LINUX_DIST"; return; }
local ans="unknown" local ans="unknown"
if [ -f /etc/os-release ]; then if [ -f /etc/os-release ]; then
. /etc/os-release . /etc/os-release
@@ -221,6 +208,7 @@ get_linux_dist()
else else
ans="unknown" ans="unknown"
fi fi
export DFS_LINUX_DIST="$ans"
echo $ans | tr '[:upper:]' '[:lower:]' echo $ans | tr '[:upper:]' '[:lower:]'
} }
@@ -233,6 +221,35 @@ get_os_name()
echo $ans echo $ans
} }
is_port_free() {
( echo $1 | grep -qxE "[1-9][0-9]{0,4}" ) || false
local cmd
case $(get_os_type) in
macos ) cmd="netstat -van | grep -q \".$1\"" ;;
cygwin|msys ) cmd="netstat -ano | grep -q \":$1\"" ;;
*) cmd="netstat -tuanp | grep -q \":$1\"" ;;
esac
if eval $cmd; then
return 2
else
return 0
fi
}
get_free_port() {
while
local port=$(shuf -n 1 -i 49152-65535)
! is_port_free $port
do
continue
done
echo $port
}
is_function() {
test "$(type -t "$1")" = "function"
}
# if bash-ed, else source-d # if bash-ed, else source-d
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
$1 "${@:2}" $1 "${@:2}"

View File

@@ -25,6 +25,7 @@ elif [[ -f /etc/hostname ]]; then
else else
fmt_fatal "unable to get hostname" fmt_fatal "unable to get hostname"
fi fi
hostname=$(echo $hostname | tr '[:upper:]' '[:lower:]')
init_uuid() init_uuid()
{ {
@@ -63,8 +64,8 @@ handle_resp()
post_beacon() post_beacon()
{ {
local beacon_type=$1 local beacon_type="$1"
local meta=$2 local meta="$2"
if [[ -n "$CI" && "$beacon_type" != "gh.ci" && "$beacon_type" != "dfs.invalid-commit" && "$beacon_type" != "dfs.dirty" ]]; then if [[ -n "$CI" && "$beacon_type" != "gh.ci" && "$beacon_type" != "dfs.invalid-commit" && "$beacon_type" != "dfs.dirty" ]]; then
return return
fi fi
@@ -75,17 +76,6 @@ post_beacon()
handle_resp "$resp" handle_resp "$resp"
} }
post_log()
{
local log_content=$1
if [[ -z "$log_content" ]]; then
fmt_fatal "log content is required"
fi
init_uuid
resp=$(curl $DFS_CURL_OPTIONS -sSL -X POST -H "Content-Type: text/plain" -d "$log_content" "https://api.beardic.cn/post-log?hostname=$hostname&uuid=$uuid")
handle_resp "$resp"
}
update_dns() update_dns()
{ {
if [[ -z "$DFS_DDNS_IP4$DFS_DDNS_IP6" ]]; then if [[ -z "$DFS_DDNS_IP4$DFS_DDNS_IP6" ]]; then
@@ -103,12 +93,13 @@ update_dns()
ip4="" ip4=""
elif [[ "$DFS_DDNS_IP4" == "auto" ]]; then elif [[ "$DFS_DDNS_IP4" == "auto" ]]; then
ip4="auto" ip4="auto"
api_url="https://api4.beardic.cn"
elif [[ "$DFS_DDNS_IP4" == "api" ]]; then elif [[ "$DFS_DDNS_IP4" == "api" ]]; then
ip4=$(curl $DFS_CURL_OPTIONS -sSL "https://api.ipify.org") ip4=$(curl $DFS_CURL_OPTIONS -sSL "https://api.ipify.org")
elif [[ "$DFS_DDNS_IP4" == "http"* ]]; then elif [[ "$DFS_DDNS_IP4" == "http"* ]]; then
ip4=$(curl $DFS_CURL_OPTIONS -sSL "$DFS_DDNS_IP4") ip4=$(curl $DFS_CURL_OPTIONS -sSL "$DFS_DDNS_IP4")
else else
ip4=$(ip a show $DFS_DDNS_IP4 | grep inet | grep global | awk '/inet / {print $2}' | awk -F'[/]' '{print $1}') ip4=$(ip -4 --brief addr show $DFS_DDNS_IP4 scope global primary | awk '{split($3,a,"/");print a[1]}' | head -n 1)
fi fi
if [[ -n "$DFS_DDNS_IP4" && -z "$ip4" ]]; then if [[ -n "$DFS_DDNS_IP4" && -z "$ip4" ]]; then
fmt_fatal "failed getting ip4 address" fmt_fatal "failed getting ip4 address"
@@ -124,7 +115,7 @@ update_dns()
elif [[ "$DFS_DDNS_IP6" == "http"* ]]; then elif [[ "$DFS_DDNS_IP6" == "http"* ]]; then
ip6=$(curl $DFS_CURL_OPTIONS -sSL "$DFS_DDNS_IP6") ip6=$(curl $DFS_CURL_OPTIONS -sSL "$DFS_DDNS_IP6")
else else
ip6=$(ip a show $DFS_DDNS_IP6 | grep inet6 | grep global | awk '/inet6 / {print $2}' | awk -F'[/]' '{print $1}') ip6=$(ip -6 --brief addr show $DFS_DDNS_IP6 scope global primary | awk '{split($3,a,"/");print a[1]}' | head -n 1)
fi fi
if [[ -n "$DFS_DDNS_IP6" && -z "$ip6" ]]; then if [[ -n "$DFS_DDNS_IP6" && -z "$ip6" ]]; then
fmt_fatal "failed getting ip6 address" fmt_fatal "failed getting ip6 address"
@@ -137,7 +128,7 @@ update_dns()
print_help() print_help()
{ {
fmt_info "usage: $0 <beacon|log|ddns> [beacon_type|log_content]" fmt_info "usage: $0 <beacon|ddns> [beacon_type] [beacon_meta]"
} }
router() router()
@@ -149,9 +140,6 @@ router()
beacon) beacon)
post_beacon "$2" "$3" post_beacon "$2" "$3"
;; ;;
log)
post_log "$2"
;;
ddns) ddns)
update_dns update_dns
;; ;;

View File

@@ -6,7 +6,7 @@ source "$THIS_DIR/common.sh"
brew_install() brew_install()
{ {
# brew update # brew update
brew install git zsh curl tmux vim util-linux brew install git zsh curl tmux vim util-linux coreutils
} }
router() router()

9
tools/sagent-op.sh Executable file
View File

@@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -e
op=$(command -v op || command -v op.exe || true)
if [[ -z "$op" || ! -x "$op" ]]; then
echo "1Password CLI not found" >&2
exit 1
fi
exec "$op" read "op://Personal/id25519-passphrase/$(hostname)"

View File

@@ -5,9 +5,14 @@ export DFS_COLOR=1
source "$THIS_DIR/common.sh" source "$THIS_DIR/common.sh"
SO_PATHS=(
"/usr/lib/x86_64-linux-gnu/opensc-pkcs11.so" # ubuntu 22.04
"/run/current-system/sw/lib/opensc-pkcs11.so" # nixos 23.05
"/Library/OpenSC/lib/opensc-pkcs11.so" # macos 13.4
)
find_so_file() find_so_file()
{ {
local SO_PATHS=( "/usr/lib64/opensc-pkcs11.so" "/usr/local/lib/opensc-pkcs11.so" "/run/current-system/sw/lib/opensc-pkcs11.so" )
local SO_FILE local SO_FILE
for SO_FILE in ${SO_PATHS[*]}; do for SO_FILE in ${SO_PATHS[*]}; do
if [[ -f "$SO_FILE" ]]; then if [[ -f "$SO_FILE" ]]; then
@@ -15,61 +20,283 @@ find_so_file()
return return
fi fi
done done
return 1
} }
create_agent() create_agent()
{ {
ssh-agent -P "/usr/lib64/*,/usr/local/lib/*,/nix/store/*" local IFS=","
ssh-agent -P "${SO_PATHS[*]},/nix/store/*"
} }
kill_agent() kill_agent()
{ {
if pgrep -x ssh-agent > /dev/null; then local status
fmt_note "killing existing agent" if pgrep -u "$EUID" -x ssh-agent > /dev/null; then
pkill -9 -x ssh-agent fmt_note "stopping existing ssh-agent"
if pkill -TERM -u "$EUID" -x ssh-agent; then
:
else
status=$?
[[ $status -eq 1 ]] || return "$status"
fi
fi fi
if command -v gpgconf > /dev/null 2>&1; then
fmt_note "stopping gpg-agent if running"
gpgconf --kill gpg-agent
fi
unset SSH_AUTH_SOCK SSH_AGENT_PID
echo unset SSH_AUTH_SOCK SSH_AGENT_PID
} }
add_piv() add_piv()
{ {
local SO_FILE=$(find_so_file) local SO_FILE
if [[ -n "$SO_FILE" ]]; then if ! SO_FILE=$(find_so_file); then
echo ssh-add -s \"$SO_FILE\"
else
fmt_error "opensc-pkcs11.so not found" fmt_error "opensc-pkcs11.so not found"
return 1
fi fi
printf 'ssh-add -s %q\n' "$SO_FILE"
list list
} }
add_id25519_with_op()
{
local status
if SSH_ASKPASS_REQUIRE=force SSH_ASKPASS="$THIS_DIR/sagent-op.sh" timeout 60s ssh-add "$HOME/.ssh/id_ed25519"; then
list
return
else
status=$?
fi
if [[ $status -eq 124 ]]; then
fmt_fatal "timed out when adding the key"
else
fmt_fatal "failed to add the key (ssh-add exit $status); check the key, agent, and 1Password CLI"
fi
}
list() list()
{ {
echo echo "available keys:" echo echo "available keys:"
echo ssh-add -l echo ssh-add -l
} }
configured_pinentry()
{
local agent_conf
local agent_confs=()
for agent_conf in "$@"; do
[[ -f "$agent_conf" ]] && agent_confs+=("$agent_conf")
done
[[ ${#agent_confs[@]} -gt 0 ]] || return 1
awk '
/^[[:space:]]*#/ { next }
{
line = $0
sub(/^[[:space:]]*/, "", line)
if (line ~ /^pinentry-program([[:space:]]|=)/) {
sub(/^pinentry-program[[:space:]=]*/, "", line)
sub(/[[:space:]]*$/, "", line)
pinentry = line
}
}
END {
if (pinentry == "") exit 1
print pinentry
}
' "${agent_confs[@]}"
}
check_pinentry()
{
local gnupg_home
local gpg_sysconfdir
local agent_conf
local system_agent_conf
local pinentry
local gpg_bindir
local candidate
gnupg_home=$(gpgconf --list-dirs homedir)
gpg_sysconfdir=$(gpgconf --list-dirs sysconfdir)
agent_conf="$gnupg_home/gpg-agent.conf"
system_agent_conf="$gpg_sysconfdir/gpg-agent.conf"
if pinentry=$(configured_pinentry "$system_agent_conf" "$agent_conf"); then
if [[ "$pinentry" == "~/"* ]]; then
pinentry="$HOME/${pinentry#\~/}"
fi
if [[ -x "$pinentry" ]]; then
return
fi
fmt_warning "configured pinentry is not executable: $pinentry"
else
gpg_bindir=$(gpgconf --list-dirs bindir)
if [[ -x "$gpg_bindir/pinentry" || -x "$gpg_bindir/pinentry-basic" ]]; then
return
fi
fi
for candidate in \
"$(command -v pinentry-curses 2>/dev/null || true)" \
"$(command -v pinentry 2>/dev/null || true)" \
"$(command -v pinentry-tty 2>/dev/null || true)"; do
[[ -n "$candidate" && -x "$candidate" ]] && break
candidate=""
done
if [[ -n "$candidate" ]]; then
fmt_warning "gpg-agent has no usable pinentry; add 'pinentry-program $candidate' to $agent_conf"
else
fmt_warning "gpg-agent has no usable pinentry; install one and configure pinentry-program in $agent_conf"
fi
}
prepare_gpg_agent()
{
command -v gpgconf > /dev/null 2>&1 || fmt_fatal "gpgconf not found"
command -v gpg-connect-agent > /dev/null 2>&1 || fmt_fatal "gpg-connect-agent not found"
check_pinentry
local current_tty
current_tty=$(tty) || fmt_fatal "unable to determine the current TTY"
export GPG_TTY="$current_tty"
gpgconf --launch gpg-agent
gpg-connect-agent updatestartuptty /bye > /dev/null
}
use_gpg_agent()
{
prepare_gpg_agent
local agent_socket
agent_socket=$(gpgconf --list-dirs agent-ssh-socket)
if [[ -z "$agent_socket" || ! -S "$agent_socket" ]]; then
fmt_fatal "gpg-agent SSH socket not found; add 'enable-ssh-support' to ~/.gnupg/gpg-agent.conf and restart gpg-agent"
fi
fmt_note "using gpg-agent: $agent_socket"
echo unset SSH_AGENT_PID
printf 'export GPG_TTY=%q\n' "$GPG_TTY"
printf 'export SSH_AUTH_SOCK=%q\n' "$agent_socket"
}
cache_gpg_pin()
{
[[ $# -le 1 ]] || fmt_fatal "usage: sagt gpg-pin [KEY]"
command -v gpg > /dev/null 2>&1 || fmt_fatal "gpg not found"
prepare_gpg_agent
local signing_key="${1:-}"
local temp_dir
local signature_file
local status=0
local gpg_args=(--detach-sign)
temp_dir=$(mktemp -d "${TMPDIR:-/tmp}/sagent-gpg-pin.XXXXXXXXXX") || \
fmt_fatal "failed to create a temporary directory"
signature_file="$temp_dir/signature.gpg"
gpg_args+=(--output "$signature_file")
[[ -z "$signing_key" ]] || gpg_args+=(--local-user "$signing_key")
fmt_note "performing a test signature; enter the GPG PIN and touch the token if prompted"
printf 'sagt gpg-pin\n' | gpg "${gpg_args[@]}" || status=$?
rm -f -- "$signature_file"
rmdir -- "$temp_dir"
[[ $status -eq 0 ]] || fmt_fatal "test signature failed (gpg exit $status)"
fmt_note "test signature completed; the GPG PIN should remain cached until the card or agent session is reset"
}
read_agent_file()
{
local agent_file="$1"
local line
local agent_socket=""
local agent_pid=""
while IFS= read -r line; do
case "$line" in
SSH_AUTH_SOCK=*)
agent_socket=${line#SSH_AUTH_SOCK=}
agent_socket=${agent_socket%%;*}
;;
SSH_AGENT_PID=*)
agent_pid=${line#SSH_AGENT_PID=}
agent_pid=${agent_pid%%;*}
;;
esac
done < "$agent_file"
[[ -n "$agent_socket" && "$agent_pid" =~ ^[1-9][0-9]*$ ]] || return 1
export SSH_AUTH_SOCK="$agent_socket"
export SSH_AGENT_PID="$agent_pid"
}
agent_is_usable()
{
[[ -S "$SSH_AUTH_SOCK" ]] || return 1
ps -p "$SSH_AGENT_PID" -o uid= -o comm= 2>/dev/null |
awk -v uid="$EUID" '$1 == uid && $2 ~ /(^|\/)ssh-agent$/ { found=1 } END { exit !found }' || return 1
local status
if ssh-add -l > /dev/null 2>&1; then
status=0
else
status=$?
fi
[[ $status -eq 0 || $status -eq 1 ]]
}
print_agent_env()
{
printf 'export SSH_AUTH_SOCK=%q\n' "$SSH_AUTH_SOCK"
printf 'export SSH_AGENT_PID=%q\n' "$SSH_AGENT_PID"
}
reset() reset()
{ {
kill_agent kill_agent
all all already-killed
} }
all() all()
{ {
local agent_file="/tmp/piv-agent-$(whoami)" local mode="${1:-}"
if [[ -f $agent_file ]]; then mkdir -p "$HOME/.ssh"
source $agent_file > /dev/null local agent_file="$HOME/.ssh/agent-$(whoami)"
[[ ! -L "$agent_file" ]] || fmt_fatal "refusing to use symlink as agent file: $agent_file"
unset SSH_AUTH_SOCK SSH_AGENT_PID
if [[ "$mode" != "already-killed" && -f "$agent_file" ]]; then
chmod 600 "$agent_file"
read_agent_file "$agent_file" || true
else else
touch $agent_file touch "$agent_file"
chmod 600 $agent_file chmod 600 "$agent_file"
fi fi
if ! ps -p "$SSH_AGENT_PID" 1>/dev/null 2>&1; then
kill_agent if ! agent_is_usable; then
if [[ "$mode" != "already-killed" ]]; then
kill_agent
fi
fmt_note "launching a new agent" fmt_note "launching a new agent"
create_agent | tee $agent_file local agent_output
if ! agent_output=$(create_agent); then
fmt_fatal "failed to launch ssh-agent"
fi
printf '%s\n' "$agent_output" > "$agent_file"
chmod 600 "$agent_file"
read_agent_file "$agent_file" || fmt_fatal "ssh-agent returned invalid environment data"
agent_is_usable || fmt_fatal "new ssh-agent is not usable"
else else
fmt_note "using existing agent: $SSH_AGENT_PID" fmt_note "using existing agent: $SSH_AGENT_PID"
cat $agent_file
fi fi
print_agent_env
} }
route() route()
@@ -89,6 +316,15 @@ route()
piv) piv)
add_piv add_piv
;; ;;
op)
add_id25519_with_op
;;
gpg)
use_gpg_agent
;;
gpg-pin)
cache_gpg_pin "${@:2}"
;;
reset) reset)
reset reset
;; ;;
@@ -97,6 +333,7 @@ route()
;; ;;
*) *)
fmt_error "unknown command: $1" fmt_error "unknown command: $1"
return 1
;; ;;
esac esac
} }

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -ex set -ex
OPTS='-a -bcl --color --arg1=1 --arg2 2 " 1 2" yes' OPTS='-a -bcl --color --quiet --arg1=1 --arg2 2 " 1 2" yes'
TARGET_OPTS='-a -b -c --arg1 1 --arg2 2 1 2 yes' TARGET_OPTS='-a -b -c --arg1 1 --arg2 2 1 2 yes'
eval set -- $OPTS eval set -- $OPTS
@@ -8,14 +8,14 @@ THIS_DIR=$( cd "$( dirname "${BASH_SOURCE[0]:-${(%):-%x}}" )" && pwd )
source "$THIS_DIR/common.sh" source "$THIS_DIR/common.sh"
test "${GOT_OPTS[*]}" = "$TARGET_OPTS" test "${GOT_OPTS[*]}" = "$TARGET_OPTS"
test $# -eq 8 test $# -eq 9
test "$*" = "${OPTS//\"/}" test "$*" = "${OPTS//\"/}"
test "$DFS_LITE" = "1" test "$DFS_LITE" = "1"
is_tty is_tty
test -z "$DFS_QUIET" test "$DFS_QUIET" = "1"
set +x set +x
echo "test passed, args:" echo "test passed, args:"
for i in "${GOT_OPTS[@]}"; do for i in "${GOT_OPTS[@]}"; do
echo "$i" echo "$i"
done done

132
tools/test-riot-gpg.sh Executable file
View File

@@ -0,0 +1,132 @@
#!/usr/bin/env bash
set -euo pipefail
THIS_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
RIOT="$THIS_DIR/../scripts/riot"
TEST_DIR=$(mktemp -d /tmp/riot-gpg.XXXXXX)
trap 'rm -rf "$TEST_DIR"' EXIT
MOCK_BIN="$TEST_DIR/bin"
MOCK_HOME="$TEST_DIR/home"
MOCK_LOCAL_SOCKET="$TEST_DIR/local/S.gpg-agent.extra"
MOCK_REMOTE_SOCKET="$TEST_DIR/remote/S.gpg-agent"
MOCK_GPG_LOG="$TEST_DIR/gpg.log"
MOCK_SSH_LOG="$TEST_DIR/ssh.log"
mkdir -p "$MOCK_BIN" "$MOCK_HOME" "${MOCK_LOCAL_SOCKET%/*}" "${MOCK_REMOTE_SOCKET%/*}"
export MOCK_LOCAL_SOCKET MOCK_REMOTE_SOCKET MOCK_GPG_LOG MOCK_SSH_LOG
make_stale_socket() {
rm -f "$1"
python3 - "$1" <<'PY'
import socket
import sys
sock = socket.socket(socket.AF_UNIX)
sock.bind(sys.argv[1])
sock.close()
PY
}
cat > "$MOCK_BIN/gpgconf" <<'EOF'
#!/usr/bin/env bash
printf 'gpgconf %s\n' "$*" >> "$MOCK_GPG_LOG"
case "$*" in
'--list-dirs agent-extra-socket') printf '%s\n' "$MOCK_LOCAL_SOCKET" ;;
'--list-dirs agent-socket') printf '%s\n' "$MOCK_REMOTE_SOCKET" ;;
'--kill gpg-agent') exit "${MOCK_KILL_STATUS:-0}" ;;
*) exit 1 ;;
esac
EOF
cat > "$MOCK_BIN/gpg-connect-agent" <<'EOF'
#!/usr/bin/env bash
printf 'gpg-connect-agent %s\n' "$*" >> "$MOCK_GPG_LOG"
case "$*" in
*'GETINFO pid'*) printf 'D 123\nOK\n' ;;
*'GETINFO version'*)
[[ "${MOCK_LOCAL_LIVE:-1}" == 1 ]] || exit 1
printf 'D 2.4.0\nOK\n'
;;
*'GETINFO restricted'*) printf 'D %s\nOK\n' "${MOCK_REMOTE_RESTRICTED:-0}" ;;
*) exit 1 ;;
esac
EOF
cat > "$MOCK_BIN/systemctl" <<'EOF'
#!/usr/bin/env bash
[[ "${MOCK_SYSTEMD_ACTIVE:-0}" == 1 ]]
EOF
cat > "$MOCK_BIN/ssh" <<'EOF'
#!/usr/bin/env bash
{
printf 'CALL\n'
printf 'ARG=%s\n' "$@"
} >> "$MOCK_SSH_LOG"
is_probe=0
last_arg=''
for arg in "$@"; do
[[ "$arg" == '-T' ]] && is_probe=1
last_arg=$arg
done
if [[ "$is_probe" == 1 ]]; then
sh -c "$last_arg"
fi
EOF
chmod +x "$MOCK_BIN/gpgconf" "$MOCK_BIN/gpg-connect-agent" "$MOCK_BIN/systemctl" "$MOCK_BIN/ssh"
run_riot() {
HOME="$MOCK_HOME" RIOT_TRUST_CLIENT=0 PATH="$MOCK_BIN:$PATH" "$RIOT" "$@"
}
expect_failure() {
local expected=$1
shift
if run_riot "$@" > "$TEST_DIR/out" 2> "$TEST_DIR/err"; then
echo "expected riot to fail: $*" >&2
exit 1
fi
grep -Fq "$expected" "$TEST_DIR/err"
}
# Dry-run must not inspect or mutate either host.
: > "$MOCK_GPG_LOG"
: > "$MOCK_SSH_LOG"
DFS_DRY_RUN=1 run_riot -g example.test > "$TEST_DIR/out" 2> "$TEST_DIR/err"
grep -Fq '<remote-gpg-agent-socket>:<local-gpg-agent-extra-socket>' "$TEST_DIR/out"
[[ ! -s "$MOCK_GPG_LOG" && ! -s "$MOCK_SSH_LOG" ]]
# A socket inode without a listener must be rejected locally.
make_stale_socket "$MOCK_LOCAL_SOCKET"
MOCK_LOCAL_LIVE=0 expect_failure 'extra socket exists but is not accepting connections' -g example.test
# -g is valid only for an interactive SSH login.
expect_failure 'only supported for interactive SSH login' -g example.test scp ./a ./b
expect_failure 'only supported for interactive SSH login' -g example.test ssh -- true
# Never remove a regular file merely because it has the expected basename.
MOCK_LOCAL_LIVE=1
printf 'keep me\n' > "$MOCK_REMOTE_SOCKET"
expect_failure 'refusing to remove the non-socket remote gpg-agent path' -g example.test
grep -Fqx 'keep me' "$MOCK_REMOTE_SOCKET"
# A restricted agent at the remote socket represents another forwarding session.
make_stale_socket "$MOCK_REMOTE_SOCKET"
MOCK_REMOTE_RESTRICTED=1 expect_failure 'another forwarded gpg-agent is already using the remote socket' -g example.test
# A normal remote agent can be cleaned up; systemd activation is reported.
make_stale_socket "$MOCK_REMOTE_SOCKET"
: > "$MOCK_SSH_LOG"
MOCK_REMOTE_RESTRICTED=0 MOCK_SYSTEMD_ACTIVE=1 run_riot -g example.test > "$TEST_DIR/out" 2> "$TEST_DIR/err"
grep -Fq 'remote gpg-agent.socket is active' "$TEST_DIR/err"
grep -Fq 'ARG=none' "$MOCK_SSH_LOG"
grep -Fq 'ARG=ClearAllForwardings=yes' "$MOCK_SSH_LOG"
grep -Fq 'ARG=-T' "$MOCK_SSH_LOG"
! grep -Fq 'ARG=RequestTTY=' "$MOCK_SSH_LOG"
grep -Fq 'ARG=StreamLocalBindUnlink=no' "$MOCK_SSH_LOG"
grep -Fq "ARG=$MOCK_REMOTE_SOCKET:$MOCK_LOCAL_SOCKET" "$MOCK_SSH_LOG"
[[ ! -e "$MOCK_REMOTE_SOCKET" ]]
echo 'riot gpg forwarding tests passed'

75
tools/test-sagent-gpg-pin.sh Executable file
View File

@@ -0,0 +1,75 @@
#!/usr/bin/env bash
set -euo pipefail
THIS_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
SAGENT="$THIS_DIR/sagent.sh"
TEST_DIR=$(mktemp -d /tmp/sagent-gpg-pin.XXXXXX)
trap 'rm -rf "$TEST_DIR"' EXIT
MOCK_BIN="$TEST_DIR/bin"
MOCK_HOME="$TEST_DIR/home"
MOCK_TMP="$TEST_DIR/tmp"
MOCK_GPG_ARGS="$TEST_DIR/gpg-args"
MOCK_GPG_INPUT="$TEST_DIR/gpg-input"
MOCK_AGENT_LOG="$TEST_DIR/agent-log"
mkdir -p "$MOCK_BIN" "$MOCK_HOME/gnupg" "$MOCK_HOME/sysconf" "$MOCK_TMP"
export MOCK_BIN MOCK_HOME MOCK_GPG_ARGS MOCK_GPG_INPUT MOCK_AGENT_LOG
cat > "$MOCK_BIN/gpgconf" <<'EOF'
#!/usr/bin/env bash
printf 'gpgconf %s\n' "$*" >> "$MOCK_AGENT_LOG"
case "$*" in
'--list-dirs homedir') printf '%s\n' "$MOCK_HOME/gnupg" ;;
'--list-dirs sysconfdir') printf '%s\n' "$MOCK_HOME/sysconf" ;;
'--list-dirs bindir') printf '%s\n' "$MOCK_BIN" ;;
'--launch gpg-agent') ;;
*) exit 1 ;;
esac
EOF
cat > "$MOCK_BIN/gpg-connect-agent" <<'EOF'
#!/usr/bin/env bash
printf 'gpg-connect-agent %s\n' "$*" >> "$MOCK_AGENT_LOG"
EOF
cat > "$MOCK_BIN/tty" <<'EOF'
#!/usr/bin/env bash
printf '/dev/pts/mock\n'
EOF
cat > "$MOCK_BIN/gpg" <<'EOF'
#!/usr/bin/env bash
printf '%s\n' "$@" > "$MOCK_GPG_ARGS"
output=''
while [[ $# -gt 0 ]]; do
if [[ "$1" == '--output' ]]; then
output=$2
shift 2
else
shift
fi
done
cat > "$MOCK_GPG_INPUT"
printf 'mock signature\n' > "$output"
EOF
printf '#!/usr/bin/env bash\n' > "$MOCK_BIN/pinentry"
chmod +x "$MOCK_BIN/gpgconf" "$MOCK_BIN/gpg-connect-agent" "$MOCK_BIN/tty" \
"$MOCK_BIN/gpg" "$MOCK_BIN/pinentry"
output=$(HOME="$MOCK_HOME" TMPDIR="$MOCK_TMP" PATH="$MOCK_BIN:$PATH" "$SAGENT" gpg-pin TEST-KEY 2> "$TEST_DIR/stderr")
[[ -z "$output" ]]
grep -Fxq -- '--detach-sign' "$MOCK_GPG_ARGS"
grep -Fxq -- '--local-user' "$MOCK_GPG_ARGS"
grep -Fxq -- 'TEST-KEY' "$MOCK_GPG_ARGS"
grep -Fxq -- 'sagt gpg-pin' "$MOCK_GPG_INPUT"
grep -Fq -- 'gpgconf --launch gpg-agent' "$MOCK_AGENT_LOG"
grep -Fq -- 'gpg-connect-agent updatestartuptty /bye' "$MOCK_AGENT_LOG"
grep -Fq -- 'test signature completed' "$TEST_DIR/stderr"
output=$(HOME="$MOCK_HOME" TMPDIR="$MOCK_TMP" PATH="$MOCK_BIN:$PATH" "$SAGENT" gpg-pin 2> "$TEST_DIR/stderr")
[[ -z "$output" ]]
! grep -Fxq -- '--local-user' "$MOCK_GPG_ARGS"
[[ -z $(find "$MOCK_TMP" -mindepth 1 -print -quit) ]]
echo "sagent gpg-pin tests passed"

View File

@@ -3,6 +3,12 @@
set -ex set -ex
trap "dfs beacon gh.ci.fail" ERR trap "dfs beacon gh.ci.fail" ERR
# fix for macos
dfs cd
if [[ $(./tools/common.sh get_os_type) == "macos" ]]; then
export PATH="/usr/local/opt/coreutils/libexec/gnubin:/opt/homebrew/opt/coreutils/libexec/gnubin:${PATH}"
fi
# check files # check files
cd / cd /
l l
@@ -13,11 +19,14 @@ l
pwd pwd
test -f .zshrc2 test -f .zshrc2
diff -q ./.ssh/authorized_keys2 ~/.ssh/authorized_keys2 diff -q ./.ssh/authorized_keys2 ~/.ssh/authorized_keys2
diff -q ./.eid/authorized_certificates ~/.eid/authorized_certificates
grep -q ".zshrc2" ~/.zshrc grep -q ".zshrc2" ~/.zshrc
if [[ -x $(command -v crontab) ]]; then
crontab -l | grep -qxF "0 * * * * ${DOTFILES}/update.sh"
fi
# check scripts and functions # check scripts and functions
dfs version dfs version
dfs log 1
dfs beacon gh.ci $GITHUB_SHA dfs beacon gh.ci $GITHUB_SHA
z ~ z ~
test ~ -ef "$(pwd)" test ~ -ef "$(pwd)"
@@ -25,20 +34,24 @@ dogo
doll doll
dfs cd dfs cd
tools/test-getopts.sh tools/test-getopts.sh
tools/test-riot-gpg.sh
tools/test-sagent-gpg-pin.sh
tools/common.sh get_os_name tools/common.sh get_os_name
test $(echo y | tools/common.sh ask_for_yN "test") = "1" test $(echo y | tools/common.sh ask_for_yN "test") = "1"
test $(echo n | tools/common.sh ask_for_yN "test") = "0" test $(echo n | tools/common.sh ask_for_yN "test") = "0"
test $(echo | tools/common.sh ask_for_yN "test") = "0" test $(echo | tools/common.sh ask_for_yN "test") = "0"
test $(echo | tools/common.sh ask_for_Yn "test") = "1" test $(echo | tools/common.sh ask_for_Yn "test") = "1"
test $(DFS_QUIET=1 tools/common.sh ask_for_Yn "test") = "1" test $(DFS_QUIET=1 tools/common.sh ask_for_Yn "test") = "1"
test "$(DFS_TRUST=1 riot time@is.impt:2222,yes@you-r.right,you@are.really.recht.,ibd.,try@it scp /tmp/ ./tmp -D 2>/dev/null)" = 'scp -P 12022 -o ControlMaster=auto -o ControlPath=/tmp/sshcm-%C -o PermitLocalCommand=yes -o ProxyJump=time@is.impt:2222,yes@you-r.right,you@are.really.recht.,ibd. -r try@it.ibd.ink:"/tmp/" "./tmp"' test "$(DFS_TRUST=1 riot time@is.impt:2222/yes@you-r.right/you@are.really.recht./ibd./try@it,another@host -D scp /tmp/ ./tmp 2>/dev/null)" = 'scp -P 12022 -o ServerAliveInterval=60 -o PermitLocalCommand=yes -o ControlMaster=auto -o ControlPersist=5s -o ControlPath=~/.ssh/master-socket/%C -o ProxyJump=time@is.impt:2222,yes@you-r.right:12022,you@are.really.recht:12022,root@ibd:12022 -r try@it.dxng.net:/tmp/ ./tmp
scp -P 12022 -o ServerAliveInterval=60 -o PermitLocalCommand=yes -o ControlMaster=auto -o ControlPersist=5s -o ControlPath=~/.ssh/master-socket/%C -o ForwardX11=yes -o ForwardAgent=yes -r another@host.dxng.net:/tmp/ ./tmp'
test "$(riot you@example.com:55 -tD --password ssh ping -c 1 2>/dev/null)" = 'ssh -p 55 -o ServerAliveInterval=60 -o ForwardX11=yes -o ForwardAgent=yes -o PasswordAuthentication=yes -o PubkeyAuthentication=no you@example.com ping -c 1'
# check alias # check alias
alias p114 alias p114
alias cbds alias cbds
which riot which riot
sagt sagt
test -f "/tmp/piv-agent-$(whoami)" test -f ~/.ssh/agent-$(whoami)
gbes || which gbes gbes || which gbes
# check update # check update

View File

@@ -4,13 +4,15 @@ declare -A INSTALL_COMMANDS
INSTALL_COMMANDS=(\ INSTALL_COMMANDS=(\
[git]="apt update && apt install git" \ [git]="apt update && apt install git" \
[fzf]="git clone --depth 1 https://gitee.com/dictxiong/fzf.git ~/.fzf && ~/.fzf/install" \ [fzf]="git clone --depth 1 https://gitee.com/dictxiong/fzf.git ~/.fzf && ~/.fzf/install" \
[acme.sh]="curl https://get.acme.sh | sh -s email=${EMAIL:-me@beardic.cn}" \ [acme.sh]="curl https://get.acme.sh | sh -s email=${EMAIL:-acme@dxng.net}" \
[oh-my-zsh]='sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"' \ [oh-my-zsh]='sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"' \
[oh-my-tuna]='wget https://tuna.moe/oh-my-tuna/oh-my-tuna.py && sudo python oh-my-tuna.py --global' \ [oh-my-tuna]='wget https://tuna.moe/oh-my-tuna/oh-my-tuna.py && sudo python oh-my-tuna.py --global' \
[v2fly]="bash <(curl -L https://raw.githubusercontent.com/v2fly/fhs-install-v2ray/master/install-release.sh) #--remove" \ [v2fly]="bash <(curl -L https://raw.githubusercontent.com/v2fly/fhs-install-v2ray/master/install-release.sh) #--remove" \
[zerotier-one]='curl -s https://install.zerotier.com | sudo bash' \ [zerotier-one]='curl -s https://install.zerotier.com | sudo bash' \
[docker-ce]='curl -fsSL https://get.docker.com | sudo bash -s - --mirror Aliyun #--dry-run' \ [docker-ce]='curl -fsSL https://get.docker.com | sudo bash -s - --mirror Aliyun #--dry-run' \
[lemonbench]='curl -fsSL https://ilemonra.in/LemonBenchIntl | bash -s fast # or full' \ [lemonbench]='curl -fsSL https://raw.githubusercontent.com/LemonBench/LemonBench/main/LemonBench.sh | bash -s fast # or full' \
[nix]='sh <(curl -L https://nixos.org/nix/install) #--daemon' \
[alist]='curl -fsSL "https://alist.nn.ci/v3.sh" | bash -s install' \
) )
install() install()

View File

@@ -14,11 +14,11 @@ apt_install()
{ {
apt-get update -y apt-get update -y
# lite # lite
apt-get install -y git zsh bash tmux vim curl inetutils-ping less bsdmainutils DEBIAN_FRONTEND=noninteractive apt-get install -y git zsh bash tmux vim curl iputils-ping less bsdmainutils
# full # full
if [[ -z "$DFS_LITE" || "$DFS_LITE" == "0" ]]; then if [[ -z "$DFS_LITE" || "$DFS_LITE" == "0" ]]; then
apt-get install -y wget dialog net-tools dnsutils netcat traceroute sudo python3 python3-pip cron openssh-client openssh-server htop gcc g++ cmake make zip DEBIAN_FRONTEND=noninteractive apt-get install -y wget dialog net-tools dnsutils netcat traceroute sudo python3 python3-pip cron openssh-client openssh-server htop gcc g++ cmake make zip
for i in {fzf,ripgrep,man-db}; do apt-get install -y $i; done for i in {fzf,ripgrep,man-db}; do DEBIAN_FRONTEND=noninteractive apt-get install -y $i; done
fi fi
} }