Compare commits
41 Commits
658910097b
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| dbf11c8336 | |||
| e484bb4f4f | |||
| 31e5d8a830 | |||
| 4287cbf56a | |||
| 5e7d5ca941 | |||
| f4dd4e3896 | |||
| 7879a36b28 | |||
| 750c571ca7 | |||
| ee1dbf7d82 | |||
| ddf1e4dc40 | |||
| 464a9fad42 | |||
| f9ae3c5c38 | |||
| b869120287 | |||
| ae0fc05e6d | |||
| 8aac9b5cbc | |||
| 9f6a98728c | |||
| 03c9699d97 | |||
| e03073122b | |||
| 20064ae546 | |||
| 0b5a454ae0 | |||
| 09b0f9c68e | |||
| 9789d546f6 | |||
| b0235e5e5a | |||
| 0fac7610c7 | |||
| 66d49f160a | |||
| bed4cac7d5 | |||
| 85ba75bd63 | |||
| ea4ed1c6bc | |||
| 969f477e19 | |||
| 5a8ee4faa4 | |||
| aee7fca84a | |||
| ce477f9503 | |||
| ebc0a38d2b | |||
| be467e6c4e | |||
| df2bce8913 | |||
| 153bd77227 | |||
| 77892cff01 | |||
| 39e8a220d8 | |||
| 6a8c7f3fd0 | |||
| 0bef55e40a | |||
| ff73a24bab |
+17
-5
@@ -1,11 +1,23 @@
|
|||||||
keys:
|
keys:
|
||||||
- &primary age1nlta6ek2fsre42g38ytwg3fxtra4h444psd7g986md0gzmvv6d5qqlwwjy
|
- &user age1nlta6ek2fsre42g38ytwg3fxtra4h444psd7g986md0gzmvv6d5qqlwwjy
|
||||||
|
- &oparic-local-dev age1mgxmpfpy6qg374kyq66cc5yw68qfw0mlk89rcdy3lkzw9q93jvwqg73395
|
||||||
|
- &o4dev age1e80f6ea37ha7udmcctaggak9l44khtcn6e966fhcldrzjjxwzals65yasf
|
||||||
|
# 按 GPT 的说法, 要想不踩坑, 就绝对不要用宽泛的匹配规则
|
||||||
creation_rules:
|
creation_rules:
|
||||||
- path_regex: secrets/ssh-config.yaml$
|
- path_regex: ^secrets/ssh-config\.yaml$
|
||||||
key_groups:
|
key_groups:
|
||||||
- age:
|
- age:
|
||||||
- *primary
|
- *user
|
||||||
- path_regex: secrets/ssh-private.yaml$
|
- path_regex: ^secrets/ssh-private\.yaml$
|
||||||
key_groups:
|
key_groups:
|
||||||
- age:
|
- age:
|
||||||
- *primary
|
- *user
|
||||||
|
- path_regex: ^secrets/hosts/oparic-local-dev\.yaml$
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- *user
|
||||||
|
- *oparic-local-dev
|
||||||
|
- path_regex: ^hosts/oparic-local-dev/containers/o4dev/secrets\.yaml$
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- *o4dev
|
||||||
|
|||||||
@@ -0,0 +1,538 @@
|
|||||||
|
# 多机器秘密管理
|
||||||
|
|
||||||
|
> ChatGPT 回答摘录
|
||||||
|
|
||||||
|
> 用 sops, 给我一个适合于个人 homelab 的方案. 有一把用户级的密钥, 可以解密和加密任意秘密. 然后每个机器都有一个自己的密钥可以加密解密我在配置文件里想要它们加解密的内容. 我要知道如何为新机器生成密钥, 如何删除机器的密钥, 如何轮转特定机器的密钥, 如何为特定机器生成秘密. 我希望不同机器的秘密是分开多个 yaml 的.
|
||||||
|
|
||||||
|
## 推荐模型
|
||||||
|
|
||||||
|
使用:
|
||||||
|
|
||||||
|
```text
|
||||||
|
SOPS + age + sops-nix
|
||||||
|
```
|
||||||
|
|
||||||
|
权限模型:
|
||||||
|
|
||||||
|
```text
|
||||||
|
你的用户 age key
|
||||||
|
├─ 能解密 / 编辑所有 secret
|
||||||
|
└─ 只放在你的管理机 / 笔记本上
|
||||||
|
|
||||||
|
每台机器自己的 age key
|
||||||
|
├─ 只放在该机器上
|
||||||
|
├─ 只被加入该机器对应的 YAML
|
||||||
|
└─ 只能解密自己的 host secrets
|
||||||
|
```
|
||||||
|
|
||||||
|
SOPS 支持 YAML/JSON/ENV/INI/BINARY,并且支持 `age`;`age` recipient 可以写进 `.sops.yaml` 的 `creation_rules`。SOPS 官方文档也明确说明,`creation_rules` 中的 `age` 字段是 age public key 列表。([SOPS: Secrets OPerationS][1])
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 目录结构
|
||||||
|
|
||||||
|
建议这样放:
|
||||||
|
|
||||||
|
```text
|
||||||
|
infra/
|
||||||
|
.sops.yaml
|
||||||
|
|
||||||
|
secrets/
|
||||||
|
hosts/
|
||||||
|
nas.yaml
|
||||||
|
pve.yaml
|
||||||
|
nuc.yaml
|
||||||
|
|
||||||
|
shared/
|
||||||
|
wireguard.yaml
|
||||||
|
tailscale.yaml
|
||||||
|
|
||||||
|
nixos/
|
||||||
|
hosts/
|
||||||
|
nas/
|
||||||
|
configuration.nix
|
||||||
|
secrets.nix
|
||||||
|
pve/
|
||||||
|
configuration.nix
|
||||||
|
secrets.nix
|
||||||
|
nuc/
|
||||||
|
configuration.nix
|
||||||
|
secrets.nix
|
||||||
|
```
|
||||||
|
|
||||||
|
原则:
|
||||||
|
|
||||||
|
```text
|
||||||
|
secrets/hosts/nas.yaml 只给 admin + nas 加密
|
||||||
|
secrets/hosts/pve.yaml 只给 admin + pve 加密
|
||||||
|
secrets/hosts/nuc.yaml 只给 admin + nuc 加密
|
||||||
|
secrets/shared/*.yaml 只在确实需要多机器共享时使用
|
||||||
|
```
|
||||||
|
|
||||||
|
不要把所有机器的 secret 塞进一个大 `secrets.yaml`,否则任意一台机器只要能解这个文件,就能看到别的机器的秘密。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 生成你的用户级 admin key
|
||||||
|
|
||||||
|
在你的管理机上:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p ~/.config/sops/age
|
||||||
|
age-keygen -o ~/.config/sops/age/keys.txt
|
||||||
|
chmod 600 ~/.config/sops/age/keys.txt
|
||||||
|
age-keygen -y ~/.config/sops/age/keys.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
输出类似:
|
||||||
|
|
||||||
|
```text
|
||||||
|
age1adminxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
```
|
||||||
|
|
||||||
|
这个 public key 记为:
|
||||||
|
|
||||||
|
```text
|
||||||
|
ADMIN_AGE_PUB
|
||||||
|
```
|
||||||
|
|
||||||
|
SOPS 默认会在 Linux 的 `$XDG_CONFIG_HOME/sops/age/keys.txt` 或 `~/.config/sops/age/keys.txt` 找 age private key;也可以用 `SOPS_AGE_KEY_FILE` 显式指定。([SOPS: Secrets OPerationS][1])
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 为新机器生成机器 key
|
||||||
|
|
||||||
|
以 `nas` 为例,在 `nas` 机器上执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo install -d -m 0700 -o root -g root /var/lib/sops-nix
|
||||||
|
sudo age-keygen -o /var/lib/sops-nix/key.txt
|
||||||
|
sudo chmod 0400 /var/lib/sops-nix/key.txt
|
||||||
|
sudo age-keygen -y /var/lib/sops-nix/key.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
得到:
|
||||||
|
|
||||||
|
```text
|
||||||
|
age1nasxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
```
|
||||||
|
|
||||||
|
这个 public key 记为:
|
||||||
|
|
||||||
|
```text
|
||||||
|
NAS_AGE_PUB
|
||||||
|
```
|
||||||
|
|
||||||
|
`sops-nix` 支持 `sops.age.keyFile = "/var/lib/sops-nix/key.txt"`,也可以设置 `sops.age.generateKey = true` 自动生成 key;不过个人 homelab 我更建议手动生成一次,然后把 private key 持久化。sops-nix 文档里也提示,如果用 Impermanence,这个 key 必须放在持久化目录并且启动早期可用。([GitHub][2])
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 写 `.sops.yaml`
|
||||||
|
|
||||||
|
示例:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
keys:
|
||||||
|
- &admin age1adminxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
|
||||||
|
- &nas age1nasxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
- &pve age1pvexxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
- &nuc age1nucxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
|
||||||
|
creation_rules:
|
||||||
|
- path_regex: ^secrets/hosts/nas\.yaml$
|
||||||
|
age:
|
||||||
|
- *admin
|
||||||
|
- *nas
|
||||||
|
|
||||||
|
- path_regex: ^secrets/hosts/pve\.yaml$
|
||||||
|
age:
|
||||||
|
- *admin
|
||||||
|
- *pve
|
||||||
|
|
||||||
|
- path_regex: ^secrets/hosts/nuc\.yaml$
|
||||||
|
age:
|
||||||
|
- *admin
|
||||||
|
- *nuc
|
||||||
|
|
||||||
|
- path_regex: ^secrets/shared/wireguard\.yaml$
|
||||||
|
age:
|
||||||
|
- *admin
|
||||||
|
- *nas
|
||||||
|
- *pve
|
||||||
|
- *nuc
|
||||||
|
```
|
||||||
|
|
||||||
|
注意:
|
||||||
|
|
||||||
|
```text
|
||||||
|
creation_rules 是顺序匹配;
|
||||||
|
建议 path_regex 写精确;
|
||||||
|
不要用一个泛泛的 ^secrets/.*\.yaml$ 把所有 host 都打通。
|
||||||
|
```
|
||||||
|
|
||||||
|
SOPS 文档里的例子也说明了 `creation_rules` 可以按 `path_regex` 匹配不同文件,并给不同文件配置不同的 age recipients。([SOPS: Secrets OPerationS][1])
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 为特定机器生成 secret
|
||||||
|
|
||||||
|
以 `nas` 为例:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sops secrets/hosts/nas.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
编辑内容:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
restic_password: "example-password"
|
||||||
|
minio_root_user: "minio"
|
||||||
|
minio_root_password: "example-password"
|
||||||
|
```
|
||||||
|
|
||||||
|
保存后,文件会变成加密形式。SOPS 会保留 YAML 结构,但加密具体值;这正是 SOPS 的主要设计之一。([SOPS: Secrets OPerationS][3])
|
||||||
|
|
||||||
|
检查谁能解:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sops -d secrets/hosts/nas.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
你的 admin key 能解。`nas` 机器的 `/var/lib/sops-nix/key.txt` 也能解。其他机器不能解。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 在 NixOS 里接入 sops-nix
|
||||||
|
|
||||||
|
`nixos/hosts/nas/secrets.nix`:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{ config, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
sops.age.keyFile = "/var/lib/sops-nix/key.txt";
|
||||||
|
|
||||||
|
sops.secrets."restic_password" = {
|
||||||
|
sopsFile = ../../../secrets/hosts/nas.yaml;
|
||||||
|
owner = "root";
|
||||||
|
group = "root";
|
||||||
|
mode = "0400";
|
||||||
|
};
|
||||||
|
|
||||||
|
sops.secrets."minio_root_user" = {
|
||||||
|
sopsFile = ../../../secrets/hosts/nas.yaml;
|
||||||
|
owner = "minio";
|
||||||
|
group = "minio";
|
||||||
|
mode = "0400";
|
||||||
|
};
|
||||||
|
|
||||||
|
sops.secrets."minio_root_password" = {
|
||||||
|
sopsFile = ../../../secrets/hosts/nas.yaml;
|
||||||
|
owner = "minio";
|
||||||
|
group = "minio";
|
||||||
|
mode = "0400";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
在 `configuration.nix` 里:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./secrets.nix
|
||||||
|
];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
服务引用 secret:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
services.restic.backups.main = {
|
||||||
|
passwordFile = config.sops.secrets."restic_password".path;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`sops-nix` 会在激活阶段解密 SOPS 文件,并把 secret 以单独文件形式放到 `/run/secrets/...`;权限、owner、group 可由 Nix 配置声明。([GitHub][2])
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 新增一台机器
|
||||||
|
|
||||||
|
假设新增 `router`。
|
||||||
|
|
||||||
|
### 6.1 在新机器上生成 key
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo install -d -m 0700 -o root -g root /var/lib/sops-nix
|
||||||
|
sudo age-keygen -o /var/lib/sops-nix/key.txt
|
||||||
|
sudo chmod 0400 /var/lib/sops-nix/key.txt
|
||||||
|
sudo age-keygen -y /var/lib/sops-nix/key.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
得到:
|
||||||
|
|
||||||
|
```text
|
||||||
|
age1routerxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 修改 `.sops.yaml`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
keys:
|
||||||
|
- &admin age1adminxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
|
||||||
|
- &nas age1nasxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
- &pve age1pvexxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
- &nuc age1nucxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
- &router age1routerxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
|
||||||
|
creation_rules:
|
||||||
|
- path_regex: ^secrets/hosts/router\.yaml$
|
||||||
|
age:
|
||||||
|
- *admin
|
||||||
|
- *router
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.3 创建该机器的 secret 文件
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sops secrets/hosts/router.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.4 写 Nix 配置
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
sops.age.keyFile = "/var/lib/sops-nix/key.txt";
|
||||||
|
|
||||||
|
sops.secrets."some_secret" = {
|
||||||
|
sopsFile = ../../../secrets/hosts/router.yaml;
|
||||||
|
owner = "root";
|
||||||
|
group = "root";
|
||||||
|
mode = "0400";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 删除一台机器的密钥
|
||||||
|
|
||||||
|
假设要删除 `nuc`。
|
||||||
|
|
||||||
|
### 7.1 从 `.sops.yaml` 删除 `nuc`
|
||||||
|
|
||||||
|
删除:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- &nuc age1nucxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
```
|
||||||
|
|
||||||
|
以及所有引用:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- *nuc
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 更新所有受影响文件
|
||||||
|
|
||||||
|
例如:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sops updatekeys -y secrets/hosts/nuc.yaml
|
||||||
|
sops updatekeys -y secrets/shared/wireguard.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
`updatekeys` 会根据 `.sops.yaml` 更新文件中的 recipients;官方文档说明它就是用 `.sops.yaml` 来添加或移除对应的 key。([sops.pages.dev][4])
|
||||||
|
|
||||||
|
### 7.3 删除 host 专属文件
|
||||||
|
|
||||||
|
如果这台机器永久下线:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git rm secrets/hosts/nuc.yaml
|
||||||
|
git rm -r nixos/hosts/nuc
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.4 对共享 secret 做 rotate
|
||||||
|
|
||||||
|
如果 `nuc` 曾经能解某些共享文件,例如 `secrets/shared/wireguard.yaml`,删除 recipient 后建议 rotate data key:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sops rotate -i secrets/shared/wireguard.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
SOPS 官方文档说明,`rotate` 会生成新的 data encryption key,并用当前 master keys 重新加密;文档也明确建议移除 key 时轮转 data key。([sops.pages.dev][4])
|
||||||
|
|
||||||
|
注意:这不能让对方“忘记”历史 Git 版本里已经能解出的旧 secret。真正撤销访问时,应该同时轮转 secret 本身,比如数据库密码、API token、WireGuard private key。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 轮转某台机器的 key
|
||||||
|
|
||||||
|
假设轮转 `nas` 的机器 key。
|
||||||
|
|
||||||
|
### 8.1 在 nas 上备份旧 key
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo cp /var/lib/sops-nix/key.txt /var/lib/sops-nix/key.txt.old
|
||||||
|
sudo chmod 0400 /var/lib/sops-nix/key.txt.old
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 生成新 key
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo age-keygen -o /var/lib/sops-nix/key.txt.new
|
||||||
|
sudo chmod 0400 /var/lib/sops-nix/key.txt.new
|
||||||
|
sudo age-keygen -y /var/lib/sops-nix/key.txt.new
|
||||||
|
```
|
||||||
|
|
||||||
|
得到:
|
||||||
|
|
||||||
|
```text
|
||||||
|
age1nasnewxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 修改 `.sops.yaml`
|
||||||
|
|
||||||
|
把旧的:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- &nas age1nasoldxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
```
|
||||||
|
|
||||||
|
换成新的:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- &nas age1nasnewxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.4 更新该机器相关 secret
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sops updatekeys -y secrets/hosts/nas.yaml
|
||||||
|
sops rotate -i secrets/hosts/nas.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
如果 `nas` 也在某些 shared secret 里:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sops updatekeys -y secrets/shared/wireguard.yaml
|
||||||
|
sops rotate -i secrets/shared/wireguard.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.5 在 nas 上替换 key
|
||||||
|
|
||||||
|
确认 repo 里的文件已经更新后:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo mv /var/lib/sops-nix/key.txt.new /var/lib/sops-nix/key.txt
|
||||||
|
sudo chmod 0400 /var/lib/sops-nix/key.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
然后:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo nixos-rebuild switch
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.6 成功后删除旧 key
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo shred -u /var/lib/sops-nix/key.txt.old
|
||||||
|
```
|
||||||
|
|
||||||
|
如果文件系统不保证 `shred` 有意义,例如 CoW 文件系统,直接删除即可,但不要保留旧 key 的备份。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 常用操作速查
|
||||||
|
|
||||||
|
### 编辑某台机器的 secret
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sops secrets/hosts/nas.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
### 查看明文,不落盘
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sops -d secrets/hosts/nas.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
### 修改 `.sops.yaml` 后同步 recipients
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sops updatekeys -y secrets/hosts/nas.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
### 移除 key 后轮转 data key
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sops rotate -i secrets/hosts/nas.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
### 批量更新所有 host secrets
|
||||||
|
|
||||||
|
```bash
|
||||||
|
find secrets/hosts -name '*.yaml' -print0 |
|
||||||
|
xargs -0 -n1 sops updatekeys -y
|
||||||
|
```
|
||||||
|
|
||||||
|
### 批量 rotate 所有 host secrets
|
||||||
|
|
||||||
|
```bash
|
||||||
|
find secrets/hosts -name '*.yaml' -print0 |
|
||||||
|
xargs -0 -n1 sops rotate -i
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 实际安全边界
|
||||||
|
|
||||||
|
这个方案能保证:
|
||||||
|
|
||||||
|
```text
|
||||||
|
nas 不能解 pve.yaml
|
||||||
|
pve 不能解 nas.yaml
|
||||||
|
你的 admin key 能解所有
|
||||||
|
Git 里只存加密文件
|
||||||
|
每台机器只需要持有自己的 private key
|
||||||
|
```
|
||||||
|
|
||||||
|
这个方案不能保证:
|
||||||
|
|
||||||
|
```text
|
||||||
|
删除某机器 key 后,它无法解历史 Git 版本
|
||||||
|
机器被攻破后,攻击者无法读取该机器当前可访问的 secret
|
||||||
|
admin key 泄露后其他 secret 仍然安全
|
||||||
|
```
|
||||||
|
|
||||||
|
所以额外建议:
|
||||||
|
|
||||||
|
```text
|
||||||
|
admin age private key 备份到密码管理器或离线介质;
|
||||||
|
机器 key 不进 Git;
|
||||||
|
机器 key 不复用;
|
||||||
|
shared secrets 少用;
|
||||||
|
删除机器时同时轮转真实业务 secret;
|
||||||
|
不要给 CI 一个能解所有 secret 的 key,除非 CI 本身就是高信任环境。
|
||||||
|
```
|
||||||
|
|
||||||
|
最终模型可以概括为:
|
||||||
|
|
||||||
|
```text
|
||||||
|
每个 secret 文件 = admin key + 最小机器 key 集合
|
||||||
|
每台机器 = 只能解自己声明需要的 YAML
|
||||||
|
所有权限变化 = 改 .sops.yaml + updatekeys
|
||||||
|
撤销访问 = updatekeys + rotate + 轮转真实业务凭证
|
||||||
|
```
|
||||||
|
|
||||||
|
[1]: https://getsops.io/docs/ "SOPS: Secrets OPerationS | SOPS: Secrets OPerationS"
|
||||||
|
[2]: https://github.com/Mic92/sops-nix/blob/master/README.md "sops-nix/README.md at master · Mic92/sops-nix · GitHub"
|
||||||
|
[3]: https://getsops.io/?utm_source=chatgpt.com "SOPS: Secrets OPerationS"
|
||||||
|
[4]: https://sops.pages.dev/ "SOPS Documentation"
|
||||||
Generated
+576
-6
@@ -21,6 +21,168 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1731533236,
|
||||||
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils_2": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems_2"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1731533236,
|
||||||
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils_3": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems_3"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1731533236,
|
||||||
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils_4": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems_4"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1731533236,
|
||||||
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils_5": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems_5"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1731533236,
|
||||||
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils_6": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems_6"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1731533236,
|
||||||
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils_7": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems_7"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1731533236,
|
||||||
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils_8": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems_8"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1731533236,
|
||||||
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils_9": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems_9"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1731533236,
|
||||||
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"home-manager": {
|
"home-manager": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
@@ -65,16 +227,16 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1775888245,
|
"lastModified": 1778443072,
|
||||||
"narHash": "sha256-nwASzrRDD1JBEu/o8ekKYEXm/oJW6EMCzCRdrwcLe90=",
|
"narHash": "sha256-zi7/fsqM/kFdNuED//4WOCUtezGtKKqRNORjMvfwjnA=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "13043924aaa7375ce482ebe2494338e058282925",
|
"rev": "da5ad661ba4e5ef59ba743f0d112cbc30e474f32",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"ref": "nixpkgs-unstable",
|
"ref": "nixos-unstable",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
@@ -111,6 +273,185 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"nixpkgs_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1775888245,
|
||||||
|
"narHash": "sha256-nwASzrRDD1JBEu/o8ekKYEXm/oJW6EMCzCRdrwcLe90=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "13043924aaa7375ce482ebe2494338e058282925",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixpkgs-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"o4dev-admin": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": [
|
||||||
|
"oparic-flakes",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1779025142,
|
||||||
|
"narHash": "sha256-K1BJ3qlxfYQUrGgn965NAo1w0ltl/OXNQnxUNE3c3O8=",
|
||||||
|
"ref": "refs/heads/main",
|
||||||
|
"rev": "8794956f942027ae9c70d7a83312cf7f64eb24c7",
|
||||||
|
"revCount": 10,
|
||||||
|
"type": "git",
|
||||||
|
"url": "ssh://gitea@git.inclyc.cn:20122/oparic/admin"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"ref": "refs/heads/main",
|
||||||
|
"type": "git",
|
||||||
|
"url": "ssh://gitea@git.inclyc.cn:20122/oparic/admin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"o4dev-backend": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils_2",
|
||||||
|
"nixpkgs": [
|
||||||
|
"oparic-flakes",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1779021375,
|
||||||
|
"narHash": "sha256-CXMq9jzeAWeTWkQ+zY01GZeSQ1kjl/LuiQvxz4Sfdvo=",
|
||||||
|
"ref": "refs/heads/main",
|
||||||
|
"rev": "d71b82129ecf9dd73743d22e89cb4b5a48bc1bd8",
|
||||||
|
"revCount": 220,
|
||||||
|
"type": "git",
|
||||||
|
"url": "ssh://gitea@git.inclyc.cn:20122/oparic/backend"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"ref": "refs/heads/main",
|
||||||
|
"type": "git",
|
||||||
|
"url": "ssh://gitea@git.inclyc.cn:20122/oparic/backend"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"o4dev-workflows": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils_3",
|
||||||
|
"nixpkgs": [
|
||||||
|
"oparic-flakes",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1778945408,
|
||||||
|
"narHash": "sha256-9yx4Ya7X/MOeHSIeUKHe1p08RmGuekHN/m9SQIjHS8c=",
|
||||||
|
"ref": "refs/heads/main",
|
||||||
|
"rev": "7458a1db6621fe4cc1df57e8841975dff2aad866",
|
||||||
|
"revCount": 114,
|
||||||
|
"type": "git",
|
||||||
|
"url": "ssh://gitea@git.inclyc.cn:20122/oparic/workflows"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"ref": "refs/heads/main",
|
||||||
|
"type": "git",
|
||||||
|
"url": "ssh://gitea@git.inclyc.cn:20122/oparic/workflows"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"oparic-flakes": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs",
|
||||||
|
"production-admin": "production-admin",
|
||||||
|
"production-backend": "production-backend",
|
||||||
|
"production-workflows": "production-workflows",
|
||||||
|
"sops-nix": "sops-nix",
|
||||||
|
"staging-admin": "staging-admin",
|
||||||
|
"staging-backend": "staging-backend",
|
||||||
|
"staging-workflows": "staging-workflows"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1779032883,
|
||||||
|
"narHash": "sha256-gMtQWHa7FQl/r592oPMxT1welkrtNa3xxSBvlpdV1RQ=",
|
||||||
|
"ref": "refs/heads/main",
|
||||||
|
"rev": "b0a076dd4b6085210bbda35b223d69273ec6ebab",
|
||||||
|
"revCount": 34,
|
||||||
|
"type": "git",
|
||||||
|
"url": "ssh://gitea@git.inclyc.cn:20122/oparic/container-flake"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "ssh://gitea@git.inclyc.cn:20122/oparic/container-flake"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"production-admin": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils_4",
|
||||||
|
"nixpkgs": [
|
||||||
|
"oparic-flakes",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1779025142,
|
||||||
|
"narHash": "sha256-K1BJ3qlxfYQUrGgn965NAo1w0ltl/OXNQnxUNE3c3O8=",
|
||||||
|
"ref": "refs/heads/main",
|
||||||
|
"rev": "8794956f942027ae9c70d7a83312cf7f64eb24c7",
|
||||||
|
"revCount": 10,
|
||||||
|
"type": "git",
|
||||||
|
"url": "ssh://gitea@git.inclyc.cn:20122/oparic/admin"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"ref": "refs/heads/main",
|
||||||
|
"type": "git",
|
||||||
|
"url": "ssh://gitea@git.inclyc.cn:20122/oparic/admin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"production-backend": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils_5",
|
||||||
|
"nixpkgs": [
|
||||||
|
"oparic-flakes",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1779021375,
|
||||||
|
"narHash": "sha256-CXMq9jzeAWeTWkQ+zY01GZeSQ1kjl/LuiQvxz4Sfdvo=",
|
||||||
|
"ref": "refs/heads/main",
|
||||||
|
"rev": "d71b82129ecf9dd73743d22e89cb4b5a48bc1bd8",
|
||||||
|
"revCount": 220,
|
||||||
|
"type": "git",
|
||||||
|
"url": "ssh://gitea@git.inclyc.cn:20122/oparic/backend"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"ref": "refs/heads/main",
|
||||||
|
"type": "git",
|
||||||
|
"url": "ssh://gitea@git.inclyc.cn:20122/oparic/backend"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"production-workflows": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils_6",
|
||||||
|
"nixpkgs": [
|
||||||
|
"oparic-flakes",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1778945408,
|
||||||
|
"narHash": "sha256-9yx4Ya7X/MOeHSIeUKHe1p08RmGuekHN/m9SQIjHS8c=",
|
||||||
|
"ref": "refs/heads/main",
|
||||||
|
"rev": "7458a1db6621fe4cc1df57e8841975dff2aad866",
|
||||||
|
"revCount": 114,
|
||||||
|
"type": "git",
|
||||||
|
"url": "ssh://gitea@git.inclyc.cn:20122/oparic/workflows"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"ref": "refs/heads/main",
|
||||||
|
"type": "git",
|
||||||
|
"url": "ssh://gitea@git.inclyc.cn:20122/oparic/workflows"
|
||||||
|
}
|
||||||
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"darwin": "darwin",
|
"darwin": "darwin",
|
||||||
@@ -118,12 +459,19 @@
|
|||||||
"home-manager-darwin": "home-manager-darwin",
|
"home-manager-darwin": "home-manager-darwin",
|
||||||
"nixpkgs-darwin": "nixpkgs-darwin",
|
"nixpkgs-darwin": "nixpkgs-darwin",
|
||||||
"nixpkgs-linux": "nixpkgs-linux",
|
"nixpkgs-linux": "nixpkgs-linux",
|
||||||
"sops-nix": "sops-nix"
|
"o4dev-admin": "o4dev-admin",
|
||||||
|
"o4dev-backend": "o4dev-backend",
|
||||||
|
"o4dev-workflows": "o4dev-workflows",
|
||||||
|
"oparic-flakes": "oparic-flakes",
|
||||||
|
"sops-nix": "sops-nix_2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sops-nix": {
|
"sops-nix": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": [
|
||||||
|
"oparic-flakes",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1777944972,
|
"lastModified": 1777944972,
|
||||||
@@ -138,6 +486,228 @@
|
|||||||
"repo": "sops-nix",
|
"repo": "sops-nix",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"sops-nix_2": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs_2"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1777944972,
|
||||||
|
"narHash": "sha256-VfGRo1qTBKOe3s2gOv8LSoA6Fk19PvBlwQ1ECN0Evn8=",
|
||||||
|
"owner": "Mic92",
|
||||||
|
"repo": "sops-nix",
|
||||||
|
"rev": "c591bf665727040c6cc5cb409079acb22dcce33c",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "Mic92",
|
||||||
|
"repo": "sops-nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"staging-admin": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils_7",
|
||||||
|
"nixpkgs": [
|
||||||
|
"oparic-flakes",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1779025142,
|
||||||
|
"narHash": "sha256-K1BJ3qlxfYQUrGgn965NAo1w0ltl/OXNQnxUNE3c3O8=",
|
||||||
|
"ref": "refs/heads/main",
|
||||||
|
"rev": "8794956f942027ae9c70d7a83312cf7f64eb24c7",
|
||||||
|
"revCount": 10,
|
||||||
|
"type": "git",
|
||||||
|
"url": "ssh://gitea@git.inclyc.cn:20122/oparic/admin"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"ref": "refs/heads/main",
|
||||||
|
"type": "git",
|
||||||
|
"url": "ssh://gitea@git.inclyc.cn:20122/oparic/admin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"staging-backend": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils_8",
|
||||||
|
"nixpkgs": [
|
||||||
|
"oparic-flakes",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1779021375,
|
||||||
|
"narHash": "sha256-CXMq9jzeAWeTWkQ+zY01GZeSQ1kjl/LuiQvxz4Sfdvo=",
|
||||||
|
"ref": "refs/heads/main",
|
||||||
|
"rev": "d71b82129ecf9dd73743d22e89cb4b5a48bc1bd8",
|
||||||
|
"revCount": 220,
|
||||||
|
"type": "git",
|
||||||
|
"url": "ssh://gitea@git.inclyc.cn:20122/oparic/backend"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"ref": "refs/heads/main",
|
||||||
|
"type": "git",
|
||||||
|
"url": "ssh://gitea@git.inclyc.cn:20122/oparic/backend"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"staging-workflows": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils_9",
|
||||||
|
"nixpkgs": [
|
||||||
|
"oparic-flakes",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1778945408,
|
||||||
|
"narHash": "sha256-9yx4Ya7X/MOeHSIeUKHe1p08RmGuekHN/m9SQIjHS8c=",
|
||||||
|
"ref": "refs/heads/main",
|
||||||
|
"rev": "7458a1db6621fe4cc1df57e8841975dff2aad866",
|
||||||
|
"revCount": 114,
|
||||||
|
"type": "git",
|
||||||
|
"url": "ssh://gitea@git.inclyc.cn:20122/oparic/workflows"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"ref": "refs/heads/main",
|
||||||
|
"type": "git",
|
||||||
|
"url": "ssh://gitea@git.inclyc.cn:20122/oparic/workflows"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems_3": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems_4": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems_5": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems_6": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems_7": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems_8": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems_9": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": "root",
|
"root": "root",
|
||||||
|
|||||||
@@ -17,9 +17,32 @@
|
|||||||
inputs.nixpkgs.follows = "nixpkgs-darwin";
|
inputs.nixpkgs.follows = "nixpkgs-darwin";
|
||||||
};
|
};
|
||||||
sops-nix.url = "github:Mic92/sops-nix";
|
sops-nix.url = "github:Mic92/sops-nix";
|
||||||
|
oparic-flakes.url = "git+ssh://gitea@git.inclyc.cn:20122/oparic/container-flake";
|
||||||
|
o4dev-backend = {
|
||||||
|
url = "git+ssh://gitea@git.inclyc.cn:20122/oparic/backend?ref=refs/heads/main";
|
||||||
|
inputs.nixpkgs.follows = "oparic-flakes/nixpkgs";
|
||||||
|
};
|
||||||
|
o4dev-admin = {
|
||||||
|
url = "git+ssh://gitea@git.inclyc.cn:20122/oparic/admin?ref=refs/heads/main";
|
||||||
|
inputs.nixpkgs.follows = "oparic-flakes/nixpkgs";
|
||||||
|
};
|
||||||
|
o4dev-workflows = {
|
||||||
|
url = "git+ssh://gitea@git.inclyc.cn:20122/oparic/workflows?ref=refs/heads/main";
|
||||||
|
inputs.nixpkgs.follows = "oparic-flakes/nixpkgs";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { nixpkgs-linux, nixpkgs-darwin, home-manager, home-manager-darwin, ... }@inputs: {
|
outputs = { nixpkgs-linux, nixpkgs-darwin, home-manager, home-manager-darwin, ... }@inputs:
|
||||||
|
let
|
||||||
|
o4devContainer = inputs.oparic-flakes.lib.mkOparicContainer {
|
||||||
|
name = "o4dev";
|
||||||
|
containerConfig = import ./hosts/oparic-local-dev/containers/o4dev/config.nix;
|
||||||
|
secretsFile = ./hosts/oparic-local-dev/containers/o4dev/secrets.yaml;
|
||||||
|
backendInput = inputs.o4dev-backend;
|
||||||
|
adminInput = inputs.o4dev-admin;
|
||||||
|
workflowsInput = inputs.o4dev-workflows;
|
||||||
|
};
|
||||||
|
in {
|
||||||
nixosConfigurations = {
|
nixosConfigurations = {
|
||||||
"eris" = nixpkgs-linux.lib.nixosSystem {
|
"eris" = nixpkgs-linux.lib.nixosSystem {
|
||||||
system = "x86_64-linux";
|
system = "x86_64-linux";
|
||||||
@@ -30,7 +53,7 @@
|
|||||||
};
|
};
|
||||||
"oparic-local-dev" = nixpkgs-linux.lib.nixosSystem {
|
"oparic-local-dev" = nixpkgs-linux.lib.nixosSystem {
|
||||||
system = "x86_64-linux";
|
system = "x86_64-linux";
|
||||||
specialArgs = { inherit inputs; };
|
specialArgs = { inherit inputs o4devContainer; };
|
||||||
modules = [
|
modules = [
|
||||||
./hosts/oparic-local-dev/configuration.nix
|
./hosts/oparic-local-dev/configuration.nix
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -8,6 +8,9 @@
|
|||||||
../modules/shell.nix
|
../modules/shell.nix
|
||||||
../modules/zsh.nix
|
../modules/zsh.nix
|
||||||
../modules/shell-develop.nix
|
../modules/shell-develop.nix
|
||||||
../modules/ssh.nix
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
programs.zsh.initContent = ''
|
||||||
|
zstyle ':prompt:grml:*:items:user' pre '%F{green}'
|
||||||
|
'';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{ lib, pkgs, ... }:
|
{ lib, pkgs, ... }:
|
||||||
{
|
{
|
||||||
programs.zsh.enable = true;
|
programs.zsh.enable = true;
|
||||||
|
programs.zsh.enableCompletion = false;
|
||||||
home.shell.enableZshIntegration = true;
|
home.shell.enableZshIntegration = true;
|
||||||
|
|
||||||
home.file =
|
home.file =
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#!/usr/bin/env zsh
|
#!/usr/bin/env zsh
|
||||||
|
|
||||||
|
export O4_FLAKES="$HOME/flakes"
|
||||||
|
|
||||||
if command -v direnv &> /dev/null; then
|
if command -v direnv &> /dev/null; then
|
||||||
eval "$(direnv hook zsh)"
|
eval "$(direnv hook zsh)"
|
||||||
fi
|
fi
|
||||||
@@ -16,7 +18,7 @@ o4-home-switch() {
|
|||||||
home-manager switch --flake ~/flakes#$(whoami)@$(hostname)
|
home-manager switch --flake ~/flakes#$(whoami)@$(hostname)
|
||||||
}
|
}
|
||||||
|
|
||||||
sops-update-file() {
|
o4-sops-update-file() {
|
||||||
local src_file="$1"
|
local src_file="$1"
|
||||||
local yaml_file="$2"
|
local yaml_file="$2"
|
||||||
local age_key_file="$HOME/.config/sops/age/keys.txt"
|
local age_key_file="$HOME/.config/sops/age/keys.txt"
|
||||||
@@ -53,12 +55,12 @@ sops-update-file() {
|
|||||||
return $rc
|
return $rc
|
||||||
}
|
}
|
||||||
|
|
||||||
sops-update-ssh-config () {( set -e
|
o4-sops-update-ssh-config () {( set -e
|
||||||
local SSH_CONFIG=$HOME/.ssh/config
|
local SSH_CONFIG=$HOME/.ssh/config
|
||||||
local FLAKES=$HOME/flakes
|
local FLAKES=$O4_FLAKES
|
||||||
|
|
||||||
$EDITOR $SSH_CONFIG
|
$EDITOR $SSH_CONFIG
|
||||||
sops-update-file $SSH_CONFIG $FLAKES/secrets/ssh-config.yaml
|
o4-sops-update-file $SSH_CONFIG $FLAKES/secrets/ssh-config.yaml
|
||||||
pushd $FLAKES
|
pushd $FLAKES
|
||||||
if [[ -z "$(git status --porcelain)" ]]; then
|
if [[ -z "$(git status --porcelain)" ]]; then
|
||||||
git add .
|
git add .
|
||||||
@@ -66,3 +68,40 @@ sops-update-ssh-config () {( set -e
|
|||||||
fi
|
fi
|
||||||
popd
|
popd
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
O4_SOPS_MACHINE_KEY_DIR="/var/lib/sops-nix"
|
||||||
|
O4_SOPS_MACHINE_KEY_FILE="$O4_SOPS_MACHINE_KEY_DIR/key.txt"
|
||||||
|
|
||||||
|
o4-sops-machine-key-init () {
|
||||||
|
# use sudo echo "require sudo" to get sudo
|
||||||
|
sudo echo "require sudo" > /dev/null
|
||||||
|
|
||||||
|
# check folder and file
|
||||||
|
local key_dir="$O4_SOPS_MACHINE_KEY_DIR"
|
||||||
|
local key_file="$O4_SOPS_MACHINE_KEY_FILE"
|
||||||
|
sudo mkdir -p $key_dir
|
||||||
|
if [[ -f "$key_file" ]]; then
|
||||||
|
echo "key exists: $key_file" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# keygen
|
||||||
|
sudo install -d -m 0700 -o root -g root $key_dir
|
||||||
|
sudo age-keygen -o $key_file
|
||||||
|
sudo chmod 0400 $key_file
|
||||||
|
sudo age-keygen -y $key_file
|
||||||
|
|
||||||
|
# print pub key
|
||||||
|
sudo grep "^# public key: " $key_file | cut -d ' ' -f 4
|
||||||
|
}
|
||||||
|
|
||||||
|
o4-sops-machine-key-print-pubkey () {
|
||||||
|
local key_file="$O4_SOPS_MACHINE_KEY_FILE"
|
||||||
|
if [[ ! -f "$key_file" ]]; then
|
||||||
|
echo "key file not found: $key_file" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
sudo grep "^# public key: " $key_file | cut -d ' ' -f 4
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1644,7 +1644,7 @@ bind2maps emacs viins vicmd -- -s '\e'${key[Left]} backward-word
|
|||||||
|
|
||||||
zrcautoload zmv
|
zrcautoload zmv
|
||||||
# zrcautoload zed # o4: 禁用 zed, 和 zed editor 的 cli binary name 撞车了
|
# zrcautoload zed # o4: 禁用 zed, 和 zed editor 的 cli binary name 撞车了
|
||||||
unset -f zed
|
# unset -f zed
|
||||||
|
|
||||||
# we don't want to quote/espace URLs on our own...
|
# we don't want to quote/espace URLs on our own...
|
||||||
# if autoload -U url-quote-magic ; then
|
# if autoload -U url-quote-magic ; then
|
||||||
@@ -0,0 +1,867 @@
|
|||||||
|
# Fish-like fast/unobtrusive autosuggestions for zsh.
|
||||||
|
# https://github.com/zsh-users/zsh-autosuggestions
|
||||||
|
# v0.7.1
|
||||||
|
# Copyright (c) 2013 Thiago de Arruda
|
||||||
|
# Copyright (c) 2016-2021 Eric Freese
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person
|
||||||
|
# obtaining a copy of this software and associated documentation
|
||||||
|
# files (the "Software"), to deal in the Software without
|
||||||
|
# restriction, including without limitation the rights to use,
|
||||||
|
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the
|
||||||
|
# Software is furnished to do so, subject to the following
|
||||||
|
# conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be
|
||||||
|
# included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
# OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
#--------------------------------------------------------------------#
|
||||||
|
# Global Configuration Variables #
|
||||||
|
#--------------------------------------------------------------------#
|
||||||
|
|
||||||
|
# Color to use when highlighting suggestion
|
||||||
|
# Uses format of `region_highlight`
|
||||||
|
# More info: http://zsh.sourceforge.net/Doc/Release/Zsh-Line-Editor.html#Zle-Widgets
|
||||||
|
(( ! ${+ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE} )) &&
|
||||||
|
typeset -g ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8'
|
||||||
|
|
||||||
|
# Prefix to use when saving original versions of bound widgets
|
||||||
|
(( ! ${+ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX} )) &&
|
||||||
|
typeset -g ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX=autosuggest-orig-
|
||||||
|
|
||||||
|
# Strategies to use to fetch a suggestion
|
||||||
|
# Will try each strategy in order until a suggestion is returned
|
||||||
|
(( ! ${+ZSH_AUTOSUGGEST_STRATEGY} )) && {
|
||||||
|
typeset -ga ZSH_AUTOSUGGEST_STRATEGY
|
||||||
|
ZSH_AUTOSUGGEST_STRATEGY=(history)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Widgets that clear the suggestion
|
||||||
|
(( ! ${+ZSH_AUTOSUGGEST_CLEAR_WIDGETS} )) && {
|
||||||
|
typeset -ga ZSH_AUTOSUGGEST_CLEAR_WIDGETS
|
||||||
|
ZSH_AUTOSUGGEST_CLEAR_WIDGETS=(
|
||||||
|
history-search-forward
|
||||||
|
history-search-backward
|
||||||
|
history-beginning-search-forward
|
||||||
|
history-beginning-search-backward
|
||||||
|
history-beginning-search-forward-end
|
||||||
|
history-beginning-search-backward-end
|
||||||
|
history-substring-search-up
|
||||||
|
history-substring-search-down
|
||||||
|
up-line-or-beginning-search
|
||||||
|
down-line-or-beginning-search
|
||||||
|
up-line-or-history
|
||||||
|
down-line-or-history
|
||||||
|
accept-line
|
||||||
|
copy-earlier-word
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Widgets that accept the entire suggestion
|
||||||
|
(( ! ${+ZSH_AUTOSUGGEST_ACCEPT_WIDGETS} )) && {
|
||||||
|
typeset -ga ZSH_AUTOSUGGEST_ACCEPT_WIDGETS
|
||||||
|
ZSH_AUTOSUGGEST_ACCEPT_WIDGETS=(
|
||||||
|
forward-char
|
||||||
|
end-of-line
|
||||||
|
vi-forward-char
|
||||||
|
vi-end-of-line
|
||||||
|
vi-add-eol
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Widgets that accept the entire suggestion and execute it
|
||||||
|
(( ! ${+ZSH_AUTOSUGGEST_EXECUTE_WIDGETS} )) && {
|
||||||
|
typeset -ga ZSH_AUTOSUGGEST_EXECUTE_WIDGETS
|
||||||
|
ZSH_AUTOSUGGEST_EXECUTE_WIDGETS=(
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Widgets that accept the suggestion as far as the cursor moves
|
||||||
|
(( ! ${+ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS} )) && {
|
||||||
|
typeset -ga ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS
|
||||||
|
ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS=(
|
||||||
|
forward-word
|
||||||
|
emacs-forward-word
|
||||||
|
vi-forward-word
|
||||||
|
vi-forward-word-end
|
||||||
|
vi-forward-blank-word
|
||||||
|
vi-forward-blank-word-end
|
||||||
|
vi-find-next-char
|
||||||
|
vi-find-next-char-skip
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Widgets that should be ignored (globbing supported but must be escaped)
|
||||||
|
(( ! ${+ZSH_AUTOSUGGEST_IGNORE_WIDGETS} )) && {
|
||||||
|
typeset -ga ZSH_AUTOSUGGEST_IGNORE_WIDGETS
|
||||||
|
ZSH_AUTOSUGGEST_IGNORE_WIDGETS=(
|
||||||
|
orig-\*
|
||||||
|
beep
|
||||||
|
run-help
|
||||||
|
set-local-history
|
||||||
|
which-command
|
||||||
|
yank
|
||||||
|
yank-pop
|
||||||
|
zle-\*
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Pty name for capturing completions for completion suggestion strategy
|
||||||
|
(( ! ${+ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME} )) &&
|
||||||
|
typeset -g ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME=zsh_autosuggest_completion_pty
|
||||||
|
|
||||||
|
#--------------------------------------------------------------------#
|
||||||
|
# Utility Functions #
|
||||||
|
#--------------------------------------------------------------------#
|
||||||
|
|
||||||
|
_zsh_autosuggest_escape_command() {
|
||||||
|
setopt localoptions EXTENDED_GLOB
|
||||||
|
|
||||||
|
# Escape special chars in the string (requires EXTENDED_GLOB)
|
||||||
|
echo -E "${1//(#m)[\"\'\\()\[\]|*?~]/\\$MATCH}"
|
||||||
|
}
|
||||||
|
|
||||||
|
#--------------------------------------------------------------------#
|
||||||
|
# Widget Helpers #
|
||||||
|
#--------------------------------------------------------------------#
|
||||||
|
|
||||||
|
_zsh_autosuggest_incr_bind_count() {
|
||||||
|
typeset -gi bind_count=$((_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]+1))
|
||||||
|
_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]=$bind_count
|
||||||
|
}
|
||||||
|
|
||||||
|
# Bind a single widget to an autosuggest widget, saving a reference to the original widget
|
||||||
|
_zsh_autosuggest_bind_widget() {
|
||||||
|
typeset -gA _ZSH_AUTOSUGGEST_BIND_COUNTS
|
||||||
|
|
||||||
|
local widget=$1
|
||||||
|
local autosuggest_action=$2
|
||||||
|
local prefix=$ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX
|
||||||
|
|
||||||
|
local -i bind_count
|
||||||
|
|
||||||
|
# Save a reference to the original widget
|
||||||
|
case $widgets[$widget] in
|
||||||
|
# Already bound
|
||||||
|
user:_zsh_autosuggest_(bound|orig)_*)
|
||||||
|
bind_count=$((_ZSH_AUTOSUGGEST_BIND_COUNTS[$widget]))
|
||||||
|
;;
|
||||||
|
|
||||||
|
# User-defined widget
|
||||||
|
user:*)
|
||||||
|
_zsh_autosuggest_incr_bind_count $widget
|
||||||
|
zle -N $prefix$bind_count-$widget ${widgets[$widget]#*:}
|
||||||
|
;;
|
||||||
|
|
||||||
|
# Built-in widget
|
||||||
|
builtin)
|
||||||
|
_zsh_autosuggest_incr_bind_count $widget
|
||||||
|
eval "_zsh_autosuggest_orig_${(q)widget}() { zle .${(q)widget} }"
|
||||||
|
zle -N $prefix$bind_count-$widget _zsh_autosuggest_orig_$widget
|
||||||
|
;;
|
||||||
|
|
||||||
|
# Completion widget
|
||||||
|
completion:*)
|
||||||
|
_zsh_autosuggest_incr_bind_count $widget
|
||||||
|
eval "zle -C $prefix$bind_count-${(q)widget} ${${(s.:.)widgets[$widget]}[2,3]}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Pass the original widget's name explicitly into the autosuggest
|
||||||
|
# function. Use this passed in widget name to call the original
|
||||||
|
# widget instead of relying on the $WIDGET variable being set
|
||||||
|
# correctly. $WIDGET cannot be trusted because other plugins call
|
||||||
|
# zle without the `-w` flag (e.g. `zle self-insert` instead of
|
||||||
|
# `zle self-insert -w`).
|
||||||
|
eval "_zsh_autosuggest_bound_${bind_count}_${(q)widget}() {
|
||||||
|
_zsh_autosuggest_widget_$autosuggest_action $prefix$bind_count-${(q)widget} \$@
|
||||||
|
}"
|
||||||
|
|
||||||
|
# Create the bound widget
|
||||||
|
zle -N -- $widget _zsh_autosuggest_bound_${bind_count}_$widget
|
||||||
|
}
|
||||||
|
|
||||||
|
# Map all configured widgets to the right autosuggest widgets
|
||||||
|
_zsh_autosuggest_bind_widgets() {
|
||||||
|
emulate -L zsh
|
||||||
|
|
||||||
|
local widget
|
||||||
|
local ignore_widgets
|
||||||
|
|
||||||
|
ignore_widgets=(
|
||||||
|
.\*
|
||||||
|
_\*
|
||||||
|
${_ZSH_AUTOSUGGEST_BUILTIN_ACTIONS/#/autosuggest-}
|
||||||
|
$ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX\*
|
||||||
|
$ZSH_AUTOSUGGEST_IGNORE_WIDGETS
|
||||||
|
)
|
||||||
|
|
||||||
|
# Find every widget we might want to bind and bind it appropriately
|
||||||
|
for widget in ${${(f)"$(builtin zle -la)"}:#${(j:|:)~ignore_widgets}}; do
|
||||||
|
if [[ -n ${ZSH_AUTOSUGGEST_CLEAR_WIDGETS[(r)$widget]} ]]; then
|
||||||
|
_zsh_autosuggest_bind_widget $widget clear
|
||||||
|
elif [[ -n ${ZSH_AUTOSUGGEST_ACCEPT_WIDGETS[(r)$widget]} ]]; then
|
||||||
|
_zsh_autosuggest_bind_widget $widget accept
|
||||||
|
elif [[ -n ${ZSH_AUTOSUGGEST_EXECUTE_WIDGETS[(r)$widget]} ]]; then
|
||||||
|
_zsh_autosuggest_bind_widget $widget execute
|
||||||
|
elif [[ -n ${ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS[(r)$widget]} ]]; then
|
||||||
|
_zsh_autosuggest_bind_widget $widget partial_accept
|
||||||
|
else
|
||||||
|
# Assume any unspecified widget might modify the buffer
|
||||||
|
_zsh_autosuggest_bind_widget $widget modify
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Given the name of an original widget and args, invoke it, if it exists
|
||||||
|
_zsh_autosuggest_invoke_original_widget() {
|
||||||
|
# Do nothing unless called with at least one arg
|
||||||
|
(( $# )) || return 0
|
||||||
|
|
||||||
|
local original_widget_name="$1"
|
||||||
|
|
||||||
|
shift
|
||||||
|
|
||||||
|
if (( ${+widgets[$original_widget_name]} )); then
|
||||||
|
zle $original_widget_name -- $@
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
#--------------------------------------------------------------------#
|
||||||
|
# Highlighting #
|
||||||
|
#--------------------------------------------------------------------#
|
||||||
|
|
||||||
|
# If there was a highlight, remove it
|
||||||
|
_zsh_autosuggest_highlight_reset() {
|
||||||
|
typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT
|
||||||
|
|
||||||
|
if [[ -n "$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT" ]]; then
|
||||||
|
region_highlight=("${(@)region_highlight:#$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT}")
|
||||||
|
unset _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# If there's a suggestion, highlight it
|
||||||
|
_zsh_autosuggest_highlight_apply() {
|
||||||
|
typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT
|
||||||
|
|
||||||
|
if (( $#POSTDISPLAY )); then
|
||||||
|
typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT="$#BUFFER $(($#BUFFER + $#POSTDISPLAY)) $ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE"
|
||||||
|
region_highlight+=("$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT")
|
||||||
|
else
|
||||||
|
unset _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
#--------------------------------------------------------------------#
|
||||||
|
# Autosuggest Widget Implementations #
|
||||||
|
#--------------------------------------------------------------------#
|
||||||
|
|
||||||
|
# Disable suggestions
|
||||||
|
_zsh_autosuggest_disable() {
|
||||||
|
typeset -g _ZSH_AUTOSUGGEST_DISABLED
|
||||||
|
_zsh_autosuggest_clear
|
||||||
|
}
|
||||||
|
|
||||||
|
# Enable suggestions
|
||||||
|
_zsh_autosuggest_enable() {
|
||||||
|
unset _ZSH_AUTOSUGGEST_DISABLED
|
||||||
|
|
||||||
|
if (( $#BUFFER )); then
|
||||||
|
_zsh_autosuggest_fetch
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Toggle suggestions (enable/disable)
|
||||||
|
_zsh_autosuggest_toggle() {
|
||||||
|
if (( ${+_ZSH_AUTOSUGGEST_DISABLED} )); then
|
||||||
|
_zsh_autosuggest_enable
|
||||||
|
else
|
||||||
|
_zsh_autosuggest_disable
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Clear the suggestion
|
||||||
|
_zsh_autosuggest_clear() {
|
||||||
|
# Remove the suggestion
|
||||||
|
POSTDISPLAY=
|
||||||
|
|
||||||
|
_zsh_autosuggest_invoke_original_widget $@
|
||||||
|
}
|
||||||
|
|
||||||
|
# Modify the buffer and get a new suggestion
|
||||||
|
_zsh_autosuggest_modify() {
|
||||||
|
local -i retval
|
||||||
|
|
||||||
|
# Only available in zsh >= 5.4
|
||||||
|
local -i KEYS_QUEUED_COUNT
|
||||||
|
|
||||||
|
# Save the contents of the buffer/postdisplay
|
||||||
|
local orig_buffer="$BUFFER"
|
||||||
|
local orig_postdisplay="$POSTDISPLAY"
|
||||||
|
|
||||||
|
# Clear suggestion while waiting for next one
|
||||||
|
POSTDISPLAY=
|
||||||
|
|
||||||
|
# Original widget may modify the buffer
|
||||||
|
_zsh_autosuggest_invoke_original_widget $@
|
||||||
|
retval=$?
|
||||||
|
|
||||||
|
emulate -L zsh
|
||||||
|
|
||||||
|
# Don't fetch a new suggestion if there's more input to be read immediately
|
||||||
|
if (( $PENDING > 0 || $KEYS_QUEUED_COUNT > 0 )); then
|
||||||
|
POSTDISPLAY="$orig_postdisplay"
|
||||||
|
return $retval
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Optimize if manually typing in the suggestion or if buffer hasn't changed
|
||||||
|
if [[ "$BUFFER" = "$orig_buffer"* && "$orig_postdisplay" = "${BUFFER:$#orig_buffer}"* ]]; then
|
||||||
|
POSTDISPLAY="${orig_postdisplay:$(($#BUFFER - $#orig_buffer))}"
|
||||||
|
return $retval
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Bail out if suggestions are disabled
|
||||||
|
if (( ${+_ZSH_AUTOSUGGEST_DISABLED} )); then
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get a new suggestion if the buffer is not empty after modification
|
||||||
|
if (( $#BUFFER > 0 )); then
|
||||||
|
if [[ -z "$ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" ]] || (( $#BUFFER <= $ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE )); then
|
||||||
|
_zsh_autosuggest_fetch
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
return $retval
|
||||||
|
}
|
||||||
|
|
||||||
|
# Fetch a new suggestion based on what's currently in the buffer
|
||||||
|
_zsh_autosuggest_fetch() {
|
||||||
|
if (( ${+ZSH_AUTOSUGGEST_USE_ASYNC} )); then
|
||||||
|
_zsh_autosuggest_async_request "$BUFFER"
|
||||||
|
else
|
||||||
|
local suggestion
|
||||||
|
_zsh_autosuggest_fetch_suggestion "$BUFFER"
|
||||||
|
_zsh_autosuggest_suggest "$suggestion"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Offer a suggestion
|
||||||
|
_zsh_autosuggest_suggest() {
|
||||||
|
emulate -L zsh
|
||||||
|
|
||||||
|
local suggestion="$1"
|
||||||
|
|
||||||
|
if [[ -n "$suggestion" ]] && (( $#BUFFER )); then
|
||||||
|
POSTDISPLAY="${suggestion#$BUFFER}"
|
||||||
|
else
|
||||||
|
POSTDISPLAY=
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Accept the entire suggestion
|
||||||
|
_zsh_autosuggest_accept() {
|
||||||
|
local -i retval max_cursor_pos=$#BUFFER
|
||||||
|
|
||||||
|
# When vicmd keymap is active, the cursor can't move all the way
|
||||||
|
# to the end of the buffer
|
||||||
|
if [[ "$KEYMAP" = "vicmd" ]]; then
|
||||||
|
max_cursor_pos=$((max_cursor_pos - 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If we're not in a valid state to accept a suggestion, just run the
|
||||||
|
# original widget and bail out
|
||||||
|
if (( $CURSOR != $max_cursor_pos || !$#POSTDISPLAY )); then
|
||||||
|
_zsh_autosuggest_invoke_original_widget $@
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Only accept if the cursor is at the end of the buffer
|
||||||
|
# Add the suggestion to the buffer
|
||||||
|
BUFFER="$BUFFER$POSTDISPLAY"
|
||||||
|
|
||||||
|
# Remove the suggestion
|
||||||
|
POSTDISPLAY=
|
||||||
|
|
||||||
|
# Run the original widget before manually moving the cursor so that the
|
||||||
|
# cursor movement doesn't make the widget do something unexpected
|
||||||
|
_zsh_autosuggest_invoke_original_widget $@
|
||||||
|
retval=$?
|
||||||
|
|
||||||
|
# Move the cursor to the end of the buffer
|
||||||
|
if [[ "$KEYMAP" = "vicmd" ]]; then
|
||||||
|
CURSOR=$(($#BUFFER - 1))
|
||||||
|
else
|
||||||
|
CURSOR=$#BUFFER
|
||||||
|
fi
|
||||||
|
|
||||||
|
return $retval
|
||||||
|
}
|
||||||
|
|
||||||
|
# Accept the entire suggestion and execute it
|
||||||
|
_zsh_autosuggest_execute() {
|
||||||
|
# Add the suggestion to the buffer
|
||||||
|
BUFFER="$BUFFER$POSTDISPLAY"
|
||||||
|
|
||||||
|
# Remove the suggestion
|
||||||
|
POSTDISPLAY=
|
||||||
|
|
||||||
|
# Call the original `accept-line` to handle syntax highlighting or
|
||||||
|
# other potential custom behavior
|
||||||
|
_zsh_autosuggest_invoke_original_widget "accept-line"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Partially accept the suggestion
|
||||||
|
_zsh_autosuggest_partial_accept() {
|
||||||
|
local -i retval cursor_loc
|
||||||
|
|
||||||
|
# Save the contents of the buffer so we can restore later if needed
|
||||||
|
local original_buffer="$BUFFER"
|
||||||
|
|
||||||
|
# Temporarily accept the suggestion.
|
||||||
|
BUFFER="$BUFFER$POSTDISPLAY"
|
||||||
|
|
||||||
|
# Original widget moves the cursor
|
||||||
|
_zsh_autosuggest_invoke_original_widget $@
|
||||||
|
retval=$?
|
||||||
|
|
||||||
|
# Normalize cursor location across vi/emacs modes
|
||||||
|
cursor_loc=$CURSOR
|
||||||
|
if [[ "$KEYMAP" = "vicmd" ]]; then
|
||||||
|
cursor_loc=$((cursor_loc + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If we've moved past the end of the original buffer
|
||||||
|
if (( $cursor_loc > $#original_buffer )); then
|
||||||
|
# Set POSTDISPLAY to text right of the cursor
|
||||||
|
POSTDISPLAY="${BUFFER[$(($cursor_loc + 1)),$#BUFFER]}"
|
||||||
|
|
||||||
|
# Clip the buffer at the cursor
|
||||||
|
BUFFER="${BUFFER[1,$cursor_loc]}"
|
||||||
|
else
|
||||||
|
# Restore the original buffer
|
||||||
|
BUFFER="$original_buffer"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return $retval
|
||||||
|
}
|
||||||
|
|
||||||
|
() {
|
||||||
|
typeset -ga _ZSH_AUTOSUGGEST_BUILTIN_ACTIONS
|
||||||
|
|
||||||
|
_ZSH_AUTOSUGGEST_BUILTIN_ACTIONS=(
|
||||||
|
clear
|
||||||
|
fetch
|
||||||
|
suggest
|
||||||
|
accept
|
||||||
|
execute
|
||||||
|
enable
|
||||||
|
disable
|
||||||
|
toggle
|
||||||
|
)
|
||||||
|
|
||||||
|
local action
|
||||||
|
for action in $_ZSH_AUTOSUGGEST_BUILTIN_ACTIONS modify partial_accept; do
|
||||||
|
eval "_zsh_autosuggest_widget_$action() {
|
||||||
|
local -i retval
|
||||||
|
|
||||||
|
_zsh_autosuggest_highlight_reset
|
||||||
|
|
||||||
|
_zsh_autosuggest_$action \$@
|
||||||
|
retval=\$?
|
||||||
|
|
||||||
|
_zsh_autosuggest_highlight_apply
|
||||||
|
|
||||||
|
zle -R
|
||||||
|
|
||||||
|
return \$retval
|
||||||
|
}"
|
||||||
|
done
|
||||||
|
|
||||||
|
for action in $_ZSH_AUTOSUGGEST_BUILTIN_ACTIONS; do
|
||||||
|
zle -N autosuggest-$action _zsh_autosuggest_widget_$action
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
#--------------------------------------------------------------------#
|
||||||
|
# Completion Suggestion Strategy #
|
||||||
|
#--------------------------------------------------------------------#
|
||||||
|
# Fetches a suggestion from the completion engine
|
||||||
|
#
|
||||||
|
|
||||||
|
_zsh_autosuggest_capture_postcompletion() {
|
||||||
|
# Always insert the first completion into the buffer
|
||||||
|
compstate[insert]=1
|
||||||
|
|
||||||
|
# Don't list completions
|
||||||
|
unset 'compstate[list]'
|
||||||
|
}
|
||||||
|
|
||||||
|
_zsh_autosuggest_capture_completion_widget() {
|
||||||
|
# Add a post-completion hook to be called after all completions have been
|
||||||
|
# gathered. The hook can modify compstate to affect what is done with the
|
||||||
|
# gathered completions.
|
||||||
|
local -a +h comppostfuncs
|
||||||
|
comppostfuncs=(_zsh_autosuggest_capture_postcompletion)
|
||||||
|
|
||||||
|
# Only capture completions at the end of the buffer
|
||||||
|
CURSOR=$#BUFFER
|
||||||
|
|
||||||
|
# Run the original widget wrapping `.complete-word` so we don't
|
||||||
|
# recursively try to fetch suggestions, since our pty is forked
|
||||||
|
# after autosuggestions is initialized.
|
||||||
|
zle -- ${(k)widgets[(r)completion:.complete-word:_main_complete]}
|
||||||
|
|
||||||
|
if is-at-least 5.0.3; then
|
||||||
|
# Don't do any cr/lf transformations. We need to do this immediately before
|
||||||
|
# output because if we do it in setup, onlcr will be re-enabled when we enter
|
||||||
|
# vared in the async code path. There is a bug in zpty module in older versions
|
||||||
|
# where the tty is not properly attached to the pty slave, resulting in stty
|
||||||
|
# getting stopped with a SIGTTOU. See zsh-workers thread 31660 and upstream
|
||||||
|
# commit f75904a38
|
||||||
|
stty -onlcr -ocrnl -F /dev/tty
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The completion has been added, print the buffer as the suggestion
|
||||||
|
echo -nE - $'\0'$BUFFER$'\0'
|
||||||
|
}
|
||||||
|
|
||||||
|
zle -N autosuggest-capture-completion _zsh_autosuggest_capture_completion_widget
|
||||||
|
|
||||||
|
_zsh_autosuggest_capture_setup() {
|
||||||
|
# There is a bug in zpty module in older zsh versions by which a
|
||||||
|
# zpty that exits will kill all zpty processes that were forked
|
||||||
|
# before it. Here we set up a zsh exit hook to SIGKILL the zpty
|
||||||
|
# process immediately, before it has a chance to kill any other
|
||||||
|
# zpty processes.
|
||||||
|
if ! is-at-least 5.4; then
|
||||||
|
zshexit() {
|
||||||
|
# The zsh builtin `kill` fails sometimes in older versions
|
||||||
|
# https://unix.stackexchange.com/a/477647/156673
|
||||||
|
kill -KILL $$ 2>&- || command kill -KILL $$
|
||||||
|
|
||||||
|
# Block for long enough for the signal to come through
|
||||||
|
sleep 1
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Try to avoid any suggestions that wouldn't match the prefix
|
||||||
|
zstyle ':completion:*' matcher-list ''
|
||||||
|
zstyle ':completion:*' path-completion false
|
||||||
|
zstyle ':completion:*' max-errors 0 not-numeric
|
||||||
|
|
||||||
|
bindkey '^I' autosuggest-capture-completion
|
||||||
|
}
|
||||||
|
|
||||||
|
_zsh_autosuggest_capture_completion_sync() {
|
||||||
|
_zsh_autosuggest_capture_setup
|
||||||
|
|
||||||
|
zle autosuggest-capture-completion
|
||||||
|
}
|
||||||
|
|
||||||
|
_zsh_autosuggest_capture_completion_async() {
|
||||||
|
_zsh_autosuggest_capture_setup
|
||||||
|
|
||||||
|
zmodload zsh/parameter 2>/dev/null || return # For `$functions`
|
||||||
|
|
||||||
|
# Make vared completion work as if for a normal command line
|
||||||
|
# https://stackoverflow.com/a/7057118/154703
|
||||||
|
autoload +X _complete
|
||||||
|
functions[_original_complete]=$functions[_complete]
|
||||||
|
function _complete() {
|
||||||
|
unset 'compstate[vared]'
|
||||||
|
_original_complete "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Open zle with buffer set so we can capture completions for it
|
||||||
|
vared 1
|
||||||
|
}
|
||||||
|
|
||||||
|
_zsh_autosuggest_strategy_completion() {
|
||||||
|
# Reset options to defaults and enable LOCAL_OPTIONS
|
||||||
|
emulate -L zsh
|
||||||
|
|
||||||
|
# Enable extended glob for completion ignore pattern
|
||||||
|
setopt EXTENDED_GLOB
|
||||||
|
|
||||||
|
typeset -g suggestion
|
||||||
|
local line REPLY
|
||||||
|
|
||||||
|
# Exit if we don't have completions
|
||||||
|
whence compdef >/dev/null || return
|
||||||
|
|
||||||
|
# Exit if we don't have zpty
|
||||||
|
zmodload zsh/zpty 2>/dev/null || return
|
||||||
|
|
||||||
|
# Exit if our search string matches the ignore pattern
|
||||||
|
[[ -n "$ZSH_AUTOSUGGEST_COMPLETION_IGNORE" ]] && [[ "$1" == $~ZSH_AUTOSUGGEST_COMPLETION_IGNORE ]] && return
|
||||||
|
|
||||||
|
# Zle will be inactive if we are in async mode
|
||||||
|
if zle; then
|
||||||
|
zpty $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME _zsh_autosuggest_capture_completion_sync
|
||||||
|
else
|
||||||
|
zpty $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME _zsh_autosuggest_capture_completion_async "\$1"
|
||||||
|
zpty -w $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME $'\t'
|
||||||
|
fi
|
||||||
|
|
||||||
|
{
|
||||||
|
# The completion result is surrounded by null bytes, so read the
|
||||||
|
# content between the first two null bytes.
|
||||||
|
zpty -r $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME line '*'$'\0''*'$'\0'
|
||||||
|
|
||||||
|
# Extract the suggestion from between the null bytes. On older
|
||||||
|
# versions of zsh (older than 5.3), we sometimes get extra bytes after
|
||||||
|
# the second null byte, so trim those off the end.
|
||||||
|
# See http://www.zsh.org/mla/workers/2015/msg03290.html
|
||||||
|
suggestion="${${(@0)line}[2]}"
|
||||||
|
} always {
|
||||||
|
# Destroy the pty
|
||||||
|
zpty -d $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#--------------------------------------------------------------------#
|
||||||
|
# History Suggestion Strategy #
|
||||||
|
#--------------------------------------------------------------------#
|
||||||
|
# Suggests the most recent history item that matches the given
|
||||||
|
# prefix.
|
||||||
|
#
|
||||||
|
|
||||||
|
_zsh_autosuggest_strategy_history() {
|
||||||
|
# Reset options to defaults and enable LOCAL_OPTIONS
|
||||||
|
emulate -L zsh
|
||||||
|
|
||||||
|
# Enable globbing flags so that we can use (#m) and (x~y) glob operator
|
||||||
|
setopt EXTENDED_GLOB
|
||||||
|
|
||||||
|
# Escape backslashes and all of the glob operators so we can use
|
||||||
|
# this string as a pattern to search the $history associative array.
|
||||||
|
# - (#m) globbing flag enables setting references for match data
|
||||||
|
# TODO: Use (b) flag when we can drop support for zsh older than v5.0.8
|
||||||
|
local prefix="${1//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}"
|
||||||
|
|
||||||
|
# Get the history items that match the prefix, excluding those that match
|
||||||
|
# the ignore pattern
|
||||||
|
local pattern="$prefix*"
|
||||||
|
if [[ -n $ZSH_AUTOSUGGEST_HISTORY_IGNORE ]]; then
|
||||||
|
pattern="($pattern)~($ZSH_AUTOSUGGEST_HISTORY_IGNORE)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Give the first history item matching the pattern as the suggestion
|
||||||
|
# - (r) subscript flag makes the pattern match on values
|
||||||
|
typeset -g suggestion="${history[(r)$pattern]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
#--------------------------------------------------------------------#
|
||||||
|
# Match Previous Command Suggestion Strategy #
|
||||||
|
#--------------------------------------------------------------------#
|
||||||
|
# Suggests the most recent history item that matches the given
|
||||||
|
# prefix and whose preceding history item also matches the most
|
||||||
|
# recently executed command.
|
||||||
|
#
|
||||||
|
# For example, suppose your history has the following entries:
|
||||||
|
# - pwd
|
||||||
|
# - ls foo
|
||||||
|
# - ls bar
|
||||||
|
# - pwd
|
||||||
|
#
|
||||||
|
# Given the history list above, when you type 'ls', the suggestion
|
||||||
|
# will be 'ls foo' rather than 'ls bar' because your most recently
|
||||||
|
# executed command (pwd) was previously followed by 'ls foo'.
|
||||||
|
#
|
||||||
|
# Note that this strategy won't work as expected with ZSH options that don't
|
||||||
|
# preserve the history order such as `HIST_IGNORE_ALL_DUPS` or
|
||||||
|
# `HIST_EXPIRE_DUPS_FIRST`.
|
||||||
|
|
||||||
|
_zsh_autosuggest_strategy_match_prev_cmd() {
|
||||||
|
# Reset options to defaults and enable LOCAL_OPTIONS
|
||||||
|
emulate -L zsh
|
||||||
|
|
||||||
|
# Enable globbing flags so that we can use (#m) and (x~y) glob operator
|
||||||
|
setopt EXTENDED_GLOB
|
||||||
|
|
||||||
|
# TODO: Use (b) flag when we can drop support for zsh older than v5.0.8
|
||||||
|
local prefix="${1//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}"
|
||||||
|
|
||||||
|
# Get the history items that match the prefix, excluding those that match
|
||||||
|
# the ignore pattern
|
||||||
|
local pattern="$prefix*"
|
||||||
|
if [[ -n $ZSH_AUTOSUGGEST_HISTORY_IGNORE ]]; then
|
||||||
|
pattern="($pattern)~($ZSH_AUTOSUGGEST_HISTORY_IGNORE)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get all history event numbers that correspond to history
|
||||||
|
# entries that match the pattern
|
||||||
|
local history_match_keys
|
||||||
|
history_match_keys=(${(k)history[(R)$~pattern]})
|
||||||
|
|
||||||
|
# By default we use the first history number (most recent history entry)
|
||||||
|
local histkey="${history_match_keys[1]}"
|
||||||
|
|
||||||
|
# Get the previously executed command
|
||||||
|
local prev_cmd="$(_zsh_autosuggest_escape_command "${history[$((HISTCMD-1))]}")"
|
||||||
|
|
||||||
|
# Iterate up to the first 200 history event numbers that match $prefix
|
||||||
|
for key in "${(@)history_match_keys[1,200]}"; do
|
||||||
|
# Stop if we ran out of history
|
||||||
|
[[ $key -gt 1 ]] || break
|
||||||
|
|
||||||
|
# See if the history entry preceding the suggestion matches the
|
||||||
|
# previous command, and use it if it does
|
||||||
|
if [[ "${history[$((key - 1))]}" == "$prev_cmd" ]]; then
|
||||||
|
histkey="$key"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Give back the matched history entry
|
||||||
|
typeset -g suggestion="$history[$histkey]"
|
||||||
|
}
|
||||||
|
|
||||||
|
#--------------------------------------------------------------------#
|
||||||
|
# Fetch Suggestion #
|
||||||
|
#--------------------------------------------------------------------#
|
||||||
|
# Loops through all specified strategies and returns a suggestion
|
||||||
|
# from the first strategy to provide one.
|
||||||
|
#
|
||||||
|
|
||||||
|
_zsh_autosuggest_fetch_suggestion() {
|
||||||
|
typeset -g suggestion
|
||||||
|
local -a strategies
|
||||||
|
local strategy
|
||||||
|
|
||||||
|
# Ensure we are working with an array
|
||||||
|
strategies=(${=ZSH_AUTOSUGGEST_STRATEGY})
|
||||||
|
|
||||||
|
for strategy in $strategies; do
|
||||||
|
# Try to get a suggestion from this strategy
|
||||||
|
_zsh_autosuggest_strategy_$strategy "$1"
|
||||||
|
|
||||||
|
# Ensure the suggestion matches the prefix
|
||||||
|
[[ "$suggestion" != "$1"* ]] && unset suggestion
|
||||||
|
|
||||||
|
# Break once we've found a valid suggestion
|
||||||
|
[[ -n "$suggestion" ]] && break
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
#--------------------------------------------------------------------#
|
||||||
|
# Async #
|
||||||
|
#--------------------------------------------------------------------#
|
||||||
|
|
||||||
|
_zsh_autosuggest_async_request() {
|
||||||
|
zmodload zsh/system 2>/dev/null # For `$sysparams`
|
||||||
|
|
||||||
|
typeset -g _ZSH_AUTOSUGGEST_ASYNC_FD _ZSH_AUTOSUGGEST_CHILD_PID
|
||||||
|
|
||||||
|
# If we've got a pending request, cancel it
|
||||||
|
if [[ -n "$_ZSH_AUTOSUGGEST_ASYNC_FD" ]] && { true <&$_ZSH_AUTOSUGGEST_ASYNC_FD } 2>/dev/null; then
|
||||||
|
# Close the file descriptor and remove the handler
|
||||||
|
builtin exec {_ZSH_AUTOSUGGEST_ASYNC_FD}<&-
|
||||||
|
zle -F $_ZSH_AUTOSUGGEST_ASYNC_FD
|
||||||
|
|
||||||
|
# We won't know the pid unless the user has zsh/system module installed
|
||||||
|
if [[ -n "$_ZSH_AUTOSUGGEST_CHILD_PID" ]]; then
|
||||||
|
# Zsh will make a new process group for the child process only if job
|
||||||
|
# control is enabled (MONITOR option)
|
||||||
|
if [[ -o MONITOR ]]; then
|
||||||
|
# Send the signal to the process group to kill any processes that may
|
||||||
|
# have been forked by the suggestion strategy
|
||||||
|
kill -TERM -$_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null
|
||||||
|
else
|
||||||
|
# Kill just the child process since it wasn't placed in a new process
|
||||||
|
# group. If the suggestion strategy forked any child processes they may
|
||||||
|
# be orphaned and left behind.
|
||||||
|
kill -TERM $_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fork a process to fetch a suggestion and open a pipe to read from it
|
||||||
|
builtin exec {_ZSH_AUTOSUGGEST_ASYNC_FD}< <(
|
||||||
|
# Tell parent process our pid
|
||||||
|
echo $sysparams[pid]
|
||||||
|
|
||||||
|
# Fetch and print the suggestion
|
||||||
|
local suggestion
|
||||||
|
_zsh_autosuggest_fetch_suggestion "$1"
|
||||||
|
echo -nE "$suggestion"
|
||||||
|
)
|
||||||
|
|
||||||
|
# There's a weird bug here where ^C stops working unless we force a fork
|
||||||
|
# See https://github.com/zsh-users/zsh-autosuggestions/issues/364
|
||||||
|
autoload -Uz is-at-least
|
||||||
|
is-at-least 5.8 || command true
|
||||||
|
|
||||||
|
# Read the pid from the child process
|
||||||
|
read _ZSH_AUTOSUGGEST_CHILD_PID <&$_ZSH_AUTOSUGGEST_ASYNC_FD
|
||||||
|
|
||||||
|
# When the fd is readable, call the response handler
|
||||||
|
zle -F "$_ZSH_AUTOSUGGEST_ASYNC_FD" _zsh_autosuggest_async_response
|
||||||
|
}
|
||||||
|
|
||||||
|
# Called when new data is ready to be read from the pipe
|
||||||
|
# First arg will be fd ready for reading
|
||||||
|
# Second arg will be passed in case of error
|
||||||
|
_zsh_autosuggest_async_response() {
|
||||||
|
emulate -L zsh
|
||||||
|
|
||||||
|
local suggestion
|
||||||
|
|
||||||
|
if [[ -z "$2" || "$2" == "hup" ]]; then
|
||||||
|
# Read everything from the fd and give it as a suggestion
|
||||||
|
IFS='' read -rd '' -u $1 suggestion
|
||||||
|
zle autosuggest-suggest -- "$suggestion"
|
||||||
|
|
||||||
|
# Close the fd
|
||||||
|
builtin exec {1}<&-
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Always remove the handler
|
||||||
|
zle -F "$1"
|
||||||
|
_ZSH_AUTOSUGGEST_ASYNC_FD=
|
||||||
|
}
|
||||||
|
|
||||||
|
#--------------------------------------------------------------------#
|
||||||
|
# Start #
|
||||||
|
#--------------------------------------------------------------------#
|
||||||
|
|
||||||
|
# Start the autosuggestion widgets
|
||||||
|
_zsh_autosuggest_start() {
|
||||||
|
# By default we re-bind widgets on every precmd to ensure we wrap other
|
||||||
|
# wrappers. Specifically, highlighting breaks if our widgets are wrapped by
|
||||||
|
# zsh-syntax-highlighting widgets. This also allows modifications to the
|
||||||
|
# widget list variables to take effect on the next precmd. However this has
|
||||||
|
# a decent performance hit, so users can set ZSH_AUTOSUGGEST_MANUAL_REBIND
|
||||||
|
# to disable the automatic re-binding.
|
||||||
|
if (( ${+ZSH_AUTOSUGGEST_MANUAL_REBIND} )); then
|
||||||
|
add-zsh-hook -d precmd _zsh_autosuggest_start
|
||||||
|
fi
|
||||||
|
|
||||||
|
_zsh_autosuggest_bind_widgets
|
||||||
|
}
|
||||||
|
|
||||||
|
# Mark for auto-loading the functions that we use
|
||||||
|
autoload -Uz add-zsh-hook is-at-least
|
||||||
|
|
||||||
|
# Automatically enable asynchronous mode in newer versions of zsh. Disable for
|
||||||
|
# older versions because there is a bug when using async mode where ^C does not
|
||||||
|
# work immediately after fetching a suggestion.
|
||||||
|
# See https://github.com/zsh-users/zsh-autosuggestions/issues/364
|
||||||
|
if is-at-least 5.0.8; then
|
||||||
|
typeset -g ZSH_AUTOSUGGEST_USE_ASYNC=
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Start the autosuggestion widgets on the next precmd
|
||||||
|
add-zsh-hook precmd _zsh_autosuggest_start
|
||||||
@@ -5,8 +5,9 @@ ZSHD="$HOME/.zsh.d"
|
|||||||
source "$ZSHD/normalize-fpath.zsh"
|
source "$ZSHD/normalize-fpath.zsh"
|
||||||
|
|
||||||
if [[ ! -v ZSH_GRML_SOURCED ]]; then
|
if [[ ! -v ZSH_GRML_SOURCED ]]; then
|
||||||
source $ZSHD/grml-zshrc
|
source $ZSHD/thirdparty/grml-zshrc
|
||||||
fi
|
fi
|
||||||
|
source "$ZSHD/thirdparty/zsh-autosuggestion.zsh"
|
||||||
|
|
||||||
source "$ZSHD/rsync.zsh"
|
source "$ZSHD/rsync.zsh"
|
||||||
source "$ZSHD/misc.zsh"
|
source "$ZSHD/misc.zsh"
|
||||||
|
|||||||
@@ -9,9 +9,9 @@
|
|||||||
[
|
[
|
||||||
# Include the results of the hardware scan.
|
# Include the results of the hardware scan.
|
||||||
./hardware-configuration.nix
|
./hardware-configuration.nix
|
||||||
inputs.sops-nix.nixosModules.sops
|
|
||||||
../modules/nix-cache.nix
|
../modules/nix-cache.nix
|
||||||
../modules/users/origami.nix
|
../modules/users/origami.nix
|
||||||
|
../modules/sops.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
boot.loader = {
|
boot.loader = {
|
||||||
@@ -150,13 +150,14 @@
|
|||||||
users.users.origami.extraGroups = [ "wheel" "networkmanager" ];
|
users.users.origami.extraGroups = [ "wheel" "networkmanager" ];
|
||||||
security.sudo.wheelNeedsPassword = false;
|
security.sudo.wheelNeedsPassword = false;
|
||||||
|
|
||||||
sops.defaultSopsFile = ../../secrets/ssh-private.yaml;
|
|
||||||
sops.defaultSopsFormat = "yaml";
|
|
||||||
sops.age.keyFile = "/home/origami/.config/sops/age/keys.txt";
|
|
||||||
|
|
||||||
# List services that you want to enable:
|
# List services that you want to enable:
|
||||||
|
|
||||||
# Enable the OpenSSH daemon.
|
# Enable the OpenSSH daemon.
|
||||||
services.openssh.enable = true;
|
services.openssh.enable = true;
|
||||||
|
|
||||||
|
# 密钥管理: 这是日用机器
|
||||||
|
sops.defaultSopsFile = ../../secrets/ssh-private.yaml;
|
||||||
|
sops.age.keyFile = "/home/origami/.config/sops/age/keys.txt";
|
||||||
|
|
||||||
system.stateVersion = "23.11";
|
system.stateVersion = "23.11";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,22 @@
|
|||||||
|
|
||||||
nix.settings.experimental-features = [ "nix-command" "flakes" ];
|
nix.settings.experimental-features = [ "nix-command" "flakes" ];
|
||||||
|
|
||||||
|
nix.buildMachines = [{
|
||||||
|
hostName = "192.168.2.22";
|
||||||
|
systems = [ "x86_64-linux" ];
|
||||||
|
sshUser = "origami";
|
||||||
|
sshKey = "/Users/origami/.config/sops-nix/secrets/ssh-private";
|
||||||
|
protocol = "ssh-ng";
|
||||||
|
maxJobs = 4;
|
||||||
|
supportedFeatures = [ "nixos-test" "big-parallel" "kvm" ];
|
||||||
|
}];
|
||||||
|
nix.distributedBuilds = true;
|
||||||
|
|
||||||
networking.hostName = "hypnos";
|
networking.hostName = "hypnos";
|
||||||
time.timeZone = "Asia/Shanghai";
|
time.timeZone = "Asia/Shanghai";
|
||||||
|
|
||||||
programs.zsh.enable = true;
|
programs.zsh.enable = true;
|
||||||
|
programs.zsh.enableGlobalCompInit = false;
|
||||||
|
|
||||||
users.users.origami = {
|
users.users.origami = {
|
||||||
home = "/Users/origami";
|
home = "/Users/origami";
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{ inputs, pkgs, ... }:
|
||||||
|
{
|
||||||
|
imports = [ inputs.sops-nix.nixosModules.sops ];
|
||||||
|
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
sops
|
||||||
|
age
|
||||||
|
];
|
||||||
|
|
||||||
|
sops.defaultSopsFormat = "yaml";
|
||||||
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
{ config, lib, pkgs, inputs, ... }:
|
{ config, lib, pkgs, inputs, o4devContainer, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
imports =
|
imports =
|
||||||
[
|
[
|
||||||
./hardware-configuration.nix
|
./hardware-configuration.nix
|
||||||
inputs.sops-nix.nixosModules.sops
|
|
||||||
../modules/nix-cache.nix
|
../modules/nix-cache.nix
|
||||||
../modules/users/origami.nix
|
../modules/users/origami.nix
|
||||||
|
../modules/sops.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
# BIOS + GRUB, 安装到 MBR
|
# BIOS + GRUB, 安装到 MBR
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
prefixLength = 24;
|
prefixLength = 24;
|
||||||
}];
|
}];
|
||||||
networking.defaultGateway = "192.168.2.1";
|
networking.defaultGateway = "192.168.2.1";
|
||||||
networking.nameservers = [ "119.29.29.29" ];
|
networking.nameservers = [ "119.29.29.29" "223.5.5.5" "114.114.114.114" ];
|
||||||
|
|
||||||
time.timeZone = "Asia/Shanghai";
|
time.timeZone = "Asia/Shanghai";
|
||||||
|
|
||||||
@@ -83,11 +83,74 @@
|
|||||||
users.users.origami.extraGroups = [ "wheel" ];
|
users.users.origami.extraGroups = [ "wheel" ];
|
||||||
security.sudo.wheelNeedsPassword = false;
|
security.sudo.wheelNeedsPassword = false;
|
||||||
|
|
||||||
sops.defaultSopsFile = ../../secrets/ssh-private.yaml;
|
|
||||||
sops.defaultSopsFormat = "yaml";
|
|
||||||
sops.age.keyFile = "/home/origami/.config/sops/age/keys.txt";
|
|
||||||
|
|
||||||
services.openssh.enable = true;
|
services.openssh.enable = true;
|
||||||
|
|
||||||
|
# Mihomo 代理
|
||||||
|
services.mihomo = {
|
||||||
|
enable = true;
|
||||||
|
configFile = "/etc/mihomo/config.yaml";
|
||||||
|
tunMode = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# 密钥管理: 这是对外机器
|
||||||
|
sops.defaultSopsFile = ../../secrets/hosts/oparic-local-dev.yaml;
|
||||||
|
sops.age.keyFile = "/var/lib/sops-nix/key.txt";
|
||||||
|
|
||||||
|
sops.secrets.caddy_cloudflare_api_key = {
|
||||||
|
owner = "caddy";
|
||||||
|
};
|
||||||
|
# Caddy 反向代理 + Cloudflare DNS challenge 通配符证书
|
||||||
|
services.caddy = {
|
||||||
|
enable = true;
|
||||||
|
package = pkgs.caddy.withPlugins {
|
||||||
|
plugins = [ "github.com/caddy-dns/cloudflare@v0.2.4" ];
|
||||||
|
hash = "sha256-VHm9POg2KixGsMsAcfFFDMK9x6niRJ1iJV9kkSwkSjc=";
|
||||||
|
};
|
||||||
|
virtualHosts."oparic.luo.ee, *.oparic.luo.ee" = {
|
||||||
|
extraConfig = ''
|
||||||
|
tls {
|
||||||
|
dns cloudflare {file.${config.sops.secrets.caddy_cloudflare_api_key.path}}
|
||||||
|
propagation_timeout -1
|
||||||
|
propagation_delay 30s
|
||||||
|
}
|
||||||
|
reverse_proxy 192.168.2.22:40000
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
networking.firewall.allowedTCPPorts = [ 80 443 40000 ];
|
||||||
|
|
||||||
|
networking.nat = {
|
||||||
|
enable = true;
|
||||||
|
internalInterfaces = [ "ve-o4dev" ];
|
||||||
|
externalInterface = "ens18";
|
||||||
|
forwardPorts = [{
|
||||||
|
sourcePort = 40000;
|
||||||
|
destination = "10.233.1.2:80";
|
||||||
|
proto = "tcp";
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
|
||||||
|
# NixOS container: o4dev
|
||||||
|
containers.o4dev = {
|
||||||
|
autoStart = true;
|
||||||
|
privateNetwork = true;
|
||||||
|
hostAddress = "10.233.1.1";
|
||||||
|
localAddress = "10.233.1.2";
|
||||||
|
bindMounts."/data" = {
|
||||||
|
hostPath = "/home/origami/data";
|
||||||
|
isReadOnly = false;
|
||||||
|
};
|
||||||
|
bindMounts."/var/lib/sops-nix/key.txt" = {
|
||||||
|
hostPath = "/home/origami/data/sops-age-key.txt";
|
||||||
|
isReadOnly = true;
|
||||||
|
};
|
||||||
|
forwardPorts = [{
|
||||||
|
containerPort = 80;
|
||||||
|
hostPort = 40000;
|
||||||
|
protocol = "tcp";
|
||||||
|
}];
|
||||||
|
path = o4devContainer.config.system.build.toplevel;
|
||||||
|
};
|
||||||
|
|
||||||
system.stateVersion = "23.11";
|
system.stateVersion = "23.11";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
domainSuffix = "oparic.luo.ee";
|
||||||
|
dataDir = "/data";
|
||||||
|
|
||||||
|
backend = {
|
||||||
|
port = 54289;
|
||||||
|
chatApiBaseUrl = "https://api.deepseek.com/v1";
|
||||||
|
suggestTitleModel = "deepseek-chat";
|
||||||
|
};
|
||||||
|
|
||||||
|
applogGateway = {
|
||||||
|
port = 9100;
|
||||||
|
};
|
||||||
|
|
||||||
|
clickhouse = {
|
||||||
|
httpPort = 8123;
|
||||||
|
database = "oparic_applog";
|
||||||
|
};
|
||||||
|
|
||||||
|
admin = {
|
||||||
|
basicauthUser = "admin";
|
||||||
|
basicauthHash = "$2a$14$oeoymHjMd5cPY3jWtbLax.Uyo4nvNLf/CbnbbjwclfYDhEzfBmc1C";
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
oparic:
|
||||||
|
backend: ENC[AES256_GCM,data:GSyPaHF56jqubIDxhbhJJuFfoFuWWudWQqJHFZtftHnG1gC02kBurGTMdS+zLKhRueWWwPYp9RjWoP1I+nzUjrtGzy9muAm4cECwpMup/7w14wRy+7SpfmqmcMY2n5zljV/Cg856RTHauQUyGYLChlLG3PtXwgxr6eDn5q7yO46qv43uPdkzr8QO5U/Q0V2lNsW4r00n4rpaB/oIgoWIx8SVLL9tCOlOGzpudlmIHqj0Sotqc/na+OSFkLLQvsg4O4jd+Leuze65B7RvtwesYcxje+uiGoYrn2YJWPyfA8fdIQwSLBjEz1fQUNPhIY/fpDt2+3/VxV/K1UmS2loYpTYjOsCT6UYJWLXNzeNp/KLu5oGMvt846U5oGIBETgzPm7ExgiVtWvG5Kp444JgT+DhGaiCzxV3fFgoSDALP7FguKWNKd0MuTYJbmFyunM4g+Y3vo5NrF/zVJmMnXlJ7KBQrJw==,iv:M/ORauW8DWc5/c+3pLyeOXdT+i7jpLcyrathDKS1V2o=,tag:wGqKUkxOLOC5ttcJcyBuRw==,type:str]
|
||||||
|
sops:
|
||||||
|
age:
|
||||||
|
- enc: |
|
||||||
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSArOEw2TiswN25nWEZaSEFo
|
||||||
|
czFUWmV0dVJKQ3JIRnF3TzV2YXZ5L0ROQ1ZnCnYwZUFmMzIwWWJEUWlkTVpOa3A2
|
||||||
|
cHA4UWg3SGdHNkFPOHIxMks4OWV0TUEKLS0tIEF4OUt4NHk1QThnM0dwL3RtMitj
|
||||||
|
UkxzbDhJejA4a0V1Lzd6d0JQSzAyUWMKqhUWjNPpsBrrALq7un53Qxhbp/RfKx67
|
||||||
|
tp3NWjMly9FI3RR+clQ82ZYaIK0Bm3YAlRI+wdzM7OiAbhNZdwh3Hw==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
recipient: age1e80f6ea37ha7udmcctaggak9l44khtcn6e966fhcldrzjjxwzals65yasf
|
||||||
|
lastmodified: "2026-05-17T11:17:24Z"
|
||||||
|
mac: ENC[AES256_GCM,data:DGTgnEl+ehkA3zLDueXdcfaTgCzAsa1fp7ZAuUl8DOsltF0rCSf4Nc5mGqSsaIyZphkzibU1e/ZD259s3Uw12ZojBG/AWPhVMgr6SxpKpKjQSl+a1dg+8nZuAR7OXoLZUSMWO3H8xkyusA2UrQXMKEmDruZTdhB7abP6vwySUS0=,iv:JBMiqCayLCSVMcbPWWJUsVCNjrXTvKmf8nTAipib+3Y=,tag:aPD0ddGNONtrW7kbzj/vkw==,type:str]
|
||||||
|
unencrypted_suffix: _unencrypted
|
||||||
|
version: 3.13.0
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
caddy_cloudflare_api_key: ENC[AES256_GCM,data:AXil/BHboMREDOXfgV/F0BIWYE9+Rq/kdpQhtOkh+yTOLsF9mmP89HQV7VFiOWjXGMvvAlU=,iv:CmqMVBLv55lwt371FJ/1qXY2On1Ilhdm3mzM6cKCw/o=,tag:36poStmxIS9cXk5a7tsNSw==,type:str]
|
||||||
|
sops:
|
||||||
|
age:
|
||||||
|
- enc: |
|
||||||
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAzSjk0cDFsY1JFRFR5WE1Q
|
||||||
|
ZXlJRTJCNjIyT29XYXkranJqbjAySVA0aEE0CmVEWGo5bVhvd1c5MEVDTjZ2NWpp
|
||||||
|
VEtTd2Z4bGZLd3R4YnhVKzZLS3pnckkKLS0tIEorcW5aejFtMnlEZjhTNmhZWXdt
|
||||||
|
RFM1MEFjT29BOUptT0lNazdQK1BNeUEK29JlPkRvbz7HRyB0s+0JHv7fd3i9uMKF
|
||||||
|
SBEoPrIXWuoNUKmCuZlqJVNIWPEV2v3/tpFWbL9sXN/6qoGpt30csw==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
recipient: age1nlta6ek2fsre42g38ytwg3fxtra4h444psd7g986md0gzmvv6d5qqlwwjy
|
||||||
|
- enc: |
|
||||||
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBsVmhxemI1S05zTW9sMzFB
|
||||||
|
WHVsaEpuNFU5cFZuWWI0Vnd2NFFQb1pvWWwwCmtHU2Y2ZFY2TTVIL1oxckVHbWxt
|
||||||
|
aVFTVW4wa3JWa2hNcjVmYm51VGZYNlkKLS0tIG44OGJWL0tab2diWkt4M1hEUmFS
|
||||||
|
ZXlBbjUramhvMXYxQlVSMEh4OEh5eDgKTJMgvoo2Wgn/FsoXsA0mCweUhmqhAGp/
|
||||||
|
nIvGJsDz88QS+nVGybLkekl6LM+UR+sRy7fttDzX49Oxre7ovkSVyw==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
recipient: age1mgxmpfpy6qg374kyq66cc5yw68qfw0mlk89rcdy3lkzw9q93jvwqg73395
|
||||||
|
lastmodified: "2026-05-17T04:26:09Z"
|
||||||
|
mac: ENC[AES256_GCM,data:XVocfoUXqh7ntKfM64sveo36xHgIX/4zM1dN1ML7Iu4qEsOB1YvHfS80z8KtJxY6ZTWl/XjqeT8YzQ2TSgpPAnND6DQ5dUiXz3G5jqZ8Foa6SQGdfqD5Yk6yBW+GRPHQUIhxBbJLeNtacRQRAC3vBuMdTqX7W/C9leLEt73B1WQ=,iv:XhwXnIXLqBfGRk9lymCGGby+SzYY0dDByPZL8AWk+xg=,tag:ryfQteA2Ul2rP8Y1NgdaOQ==,type:str]
|
||||||
|
unencrypted_suffix: _unencrypted
|
||||||
|
version: 3.13.0
|
||||||
+11
-11
@@ -1,16 +1,16 @@
|
|||||||
ssh-config: ENC[AES256_GCM,data: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,iv:fEmaF1T7BxT+wDYfZR+u1+0Oh4dR02ITExpHjWdiah4=,tag:EzJl8epBZyPc58Bi6PR1mw==,type:str]
|
ssh-config: ENC[AES256_GCM,data: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,iv:KlRODFs7u3WGzHKQJpMkjiNf+rqBBW1oMx02APGcuyg=,tag:472ZgRVE6dkHBnvEcMOWHQ==,type:str]
|
||||||
sops:
|
sops:
|
||||||
age:
|
age:
|
||||||
- recipient: age1nlta6ek2fsre42g38ytwg3fxtra4h444psd7g986md0gzmvv6d5qqlwwjy
|
- enc: |
|
||||||
enc: |
|
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA2TVNCWjduWXVGU2EwWFlu
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA2U0daVmk4YXoxMUtxNkhL
|
||||||
b2pKN29BbE1xcmNKc3R6WVJ3SG45OEM5T1FvCnZXQWJrdnNwZ1NyU0w4K1BDQXp3
|
ODc5a2p4aGcwbjZha3BsM3MwZmdLZTFaZXdzCkM2cE90QlpJNmQrTzlFbnA2dzdw
|
||||||
bS80NllZODhBcDVnUXYwRUtJUWhTaE0KLS0tIElCaE16c0J6dW1WaTJrUzZpczFT
|
MkJhM2JqNUlpcW5VTklMeFVDSGVwTTAKLS0tIDN3MFFBcGNlLzUxd01tK0V5YzVP
|
||||||
MFJINjBUMTBQVklkTG9KYVVvZXczZG8KH56Vo0jc8nLp+nAnVKSPfDSFb+Mw/L0k
|
VzFxL1hkdklLWUV5TFFrekt6dXBHa28K00ChSpcAKizcqoORF4rYQUvmblgIQ2sB
|
||||||
O1/X5MeF4Z2O80noGH2LaP5WKvy+h/kMW9c0hjBnayJBunf3VbhH3A==
|
NzkzR86t2mEKdI2NU4CmYayOZrNN9sjd+mMaJmFwaTAeERaw0UuM8w==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
lastmodified: "2026-05-16T17:02:16Z"
|
recipient: age1nlta6ek2fsre42g38ytwg3fxtra4h444psd7g986md0gzmvv6d5qqlwwjy
|
||||||
mac: ENC[AES256_GCM,data:6QDpFeS+42KTeklQZ+lt7fpGTIQmVU/NjlmX6SUxQEgNkJ4c5lMEP49SIR7MxnYo3zm6X6v2OM8O5kj3fFiOA49GdK5Bnuy/+Y2146rb0fa5aNNhVJwLs/L9CFVXzCVlCLqjsEPQkzq0EluwMAvW7HzkhvHb/IpeJkEct8NgqRU=,iv:URy/A9NkO/xTWGbToIhN61gC1FaexNO5RMRE00Y1VGA=,tag:jr634ex3sxg8khfWWYVCxw==,type:str]
|
lastmodified: "2026-05-17T11:25:42Z"
|
||||||
|
mac: ENC[AES256_GCM,data:bqNgucTbqPlbDgkrPjagVlXpeTIOev43gKAhkborN7HGi72aAoXpUirGIBOsZTvCnu4IrhkH9Kj5GRppjMmcaJ+b5FzpzLXEjJjbQU7PeKeuPFOdy8GsY6RafKLFosUwyDw0ZGBOosNzcc3Srd6af67Dutd2Kr0ke8pEVX4tKV8=,iv:jBGiuotm4wHUA/cQxY9MEgzHVn2giwQvP0aWvSbJl3w=,tag:P/cAyeowoexR0r2Lepp6hA==,type:str]
|
||||||
unencrypted_suffix: _unencrypted
|
unencrypted_suffix: _unencrypted
|
||||||
version: 3.11.0
|
version: 3.13.0
|
||||||
|
|||||||
Reference in New Issue
Block a user