Skip to content

Commit bb32457

Browse files
authored
Merge pull request #3 from ExpTechTW/main
Release build
2 parents 505ca89 + 7c75370 commit bb32457

9 files changed

Lines changed: 125 additions & 15 deletions

File tree

.github/workflows/draft-release.yml

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ on:
99
permissions:
1010
contents: write
1111

12+
concurrency:
13+
group: release-artifacts-${{ github.ref }}
14+
cancel-in-progress: true
15+
1216
jobs:
1317
meta:
1418
runs-on: ubuntu-latest
@@ -18,22 +22,59 @@ jobs:
1822
steps:
1923
- name: Compute release version
2024
id: ver
25+
env:
26+
GH_TOKEN: ${{ github.token }}
27+
GH_REPO: ${{ github.repository }}
2128
run: |
22-
VERSION="$(date -u +%Y%m%d)+b${GITHUB_RUN_NUMBER}"
29+
VERSION="$(python3 - <<'PY'
30+
import json
31+
import os
32+
import re
33+
import urllib.request
34+
from datetime import datetime, timezone
35+
36+
token = os.environ.get("GH_TOKEN", "").strip()
37+
repo = os.environ.get("GH_REPO", "").strip()
38+
if not token or not repo:
39+
raise SystemExit("missing GH_TOKEN or GH_REPO")
40+
41+
day = datetime.now(timezone.utc).strftime("%Y.%m.%d")
42+
prefix = f"v{day}+build."
43+
max_n = 0
44+
page = 1
45+
pat = re.compile(rf"^v{re.escape(day)}\+build\.(\d+)$")
46+
47+
while page <= 5:
48+
url = f"https://api.github.com/repos/{repo}/releases?per_page=100&page={page}"
49+
req = urllib.request.Request(url)
50+
req.add_header("Accept", "application/vnd.github+json")
51+
req.add_header("Authorization", f"Bearer {token}")
52+
with urllib.request.urlopen(req, timeout=10) as resp:
53+
data = json.loads(resp.read().decode("utf-8"))
54+
if not data:
55+
break
56+
for rel in data:
57+
tag = (rel.get("tag_name") or "").strip()
58+
m = pat.match(tag)
59+
if not m:
60+
continue
61+
n = int(m.group(1))
62+
if n > max_n:
63+
max_n = n
64+
page += 1
65+
66+
print(f"{day}+build.{max_n + 1}")
67+
PY
68+
)"
2369
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
2470
2571
- name: Decide release mode by branch and commit message
2672
id: mode
2773
env:
28-
HEAD_MSG: ${{ github.event.head_commit.message }}
2974
BRANCH_NAME: ${{ github.ref_name }}
3075
run: |
3176
if [[ "$BRANCH_NAME" == "main" ]]; then
32-
if printf '%s\n' "$HEAD_MSG" | grep -qi 'build:'; then
33-
echo "mode=pre-release" >> "$GITHUB_OUTPUT"
34-
else
35-
echo "mode=none" >> "$GITHUB_OUTPUT"
36-
fi
77+
echo "mode=pre-release" >> "$GITHUB_OUTPUT"
3778
elif [[ "$BRANCH_NAME" == "release" ]]; then
3879
echo "mode=release" >> "$GITHUB_OUTPUT"
3980
else

COMMAND_ZH.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ kekkai status # 預設讀 /etc/kekkai/kekkai.yaml
2929
kekkai status /path/to/kekkai.yaml # 指定 config 路徑
3030
```
3131

32-
需要 root 才能讀 pinned eBPF maps
32+
一般情況不需要 root。若主機有額外 LSM/硬化策略擋住 bpffs 讀取,再改用 sudo
3333

3434
```bash
3535
sudo kekkai status
@@ -620,7 +620,7 @@ RPi 的 `macb` / `bcmgenet` driver 沒有 native XDP 支援。這是驅動層限
620620

621621
### 9.4 `kekkai status` 回報 `open pinned stats map`
622622

623-
代表 agent 沒在跑,或 bpffs 沒掛
623+
通常代表 agent 沒在跑、bpffs 沒掛,或主機策略限制了 bpffs 讀取權限
624624

625625
```bash
626626
systemctl is-active kekkai-agent # 確認 agent 跑著
@@ -629,6 +629,7 @@ ls /sys/fs/bpf/kekkai/ # 確認 pin 路徑有檔案
629629
```
630630

631631
bpffs 沒掛:`sudo mount -t bpf bpf /sys/fs/bpf`
632+
若一般使用者仍 permission denied:先試 `sudo kekkai status` 驗證是否純權限問題。
632633

633634
### 9.5 `kekkai.sh update` 中止且 rollback
634635

@@ -716,6 +717,7 @@ cat /var/run/kekkai/stats.txt > /tmp/stats-$(date +%s).txt
716717
| 指令 | 實作 | 說明 |
717718
|---|---|---|
718719
| `kekkai status [path]` || 互動式 TUI |
720+
| `kekkai config [path]` || 用 nano 編輯 config,退出後自動 reload |
719721
| `kekkai doctor` || 全系統健康檢查(read-only) |
720722
| `kekkai check [path]` || 驗證 config (read-only) |
721723
| `kekkai ports [path]` || 彩色列出 public/private port 與 SSH 暴露狀態 |

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ CLI_BIN := kekkai
33
AGENT_PKG := ./cmd/kekkai-agent
44
CLI_PKG := ./cmd/kekkai
55

6-
DATE_UTC := $(shell date -u +%Y%m%d)
6+
DATE_UTC := $(shell date -u +%Y.%m.%d)
77
GIT_SHA := $(shell git rev-parse --short HEAD 2>/dev/null || echo nosha)
88
VERSION ?= $(DATE_UTC)+$(GIT_SHA)
99
LDFLAGS := -ldflags "-s -w -X main.version=$(VERSION)"

cmd/kekkai-agent/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import (
2929
)
3030

3131
// version is injected at build time via -ldflags:
32-
// -X main.version=YYYYMMDD+bN
32+
// -X main.version=YYYY.MM.DD+build.N
3333
// Keep default empty so linker override works reliably.
3434
var version string
3535

cmd/kekkai/main.go

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import (
2626
)
2727

2828
// version is injected at build time via -ldflags:
29-
// -X main.version=YYYYMMDD+bN
29+
// -X main.version=YYYY.MM.DD+build.N
3030
// Keep default empty so linker override works reliably.
3131
var version string
3232

@@ -50,6 +50,8 @@ func main() {
5050
switch cmd {
5151
case "status":
5252
os.Exit(cmdStatus(args))
53+
case "config":
54+
os.Exit(cmdConfig(args))
5355
case "version", "-v", "--version":
5456
cmdVersion()
5557
case "check":
@@ -351,6 +353,47 @@ func cmdBypassSave(wantBypass bool, cfgPath string) int {
351353
return 0
352354
}
353355

356+
// cmdConfig opens config in nano and reloads the agent after editor exit.
357+
// Non-root users are transparently escalated with sudo when needed.
358+
func cmdConfig(args []string) int {
359+
cfgPath := firstArgOrDefault(args, defaultConfigPath)
360+
361+
nanoPath, err := exec.LookPath("nano")
362+
if err != nil {
363+
fmt.Fprintln(os.Stderr, "nano not found; install nano first")
364+
return 1
365+
}
366+
367+
editorArgs := []string{nanoPath, cfgPath}
368+
editorCmd := exec.Command(editorArgs[0], editorArgs[1:]...)
369+
if os.Geteuid() != 0 && !isWritableByCurrentUser(cfgPath) {
370+
editorCmd = exec.Command("sudo", editorArgs...)
371+
}
372+
if code := runCommand(editorCmd, "edit config with nano"); code != 0 {
373+
return code
374+
}
375+
376+
// Reuse existing reload flow (includes config check).
377+
if os.Geteuid() == 0 {
378+
return cmdReload([]string{cfgPath})
379+
}
380+
exe, err := os.Executable()
381+
if err != nil {
382+
fmt.Fprintf(os.Stderr, "resolve executable for reload: %v\n", err)
383+
return 1
384+
}
385+
return runCommand(exec.Command("sudo", exe, "reload", cfgPath), "sudo reload after config edit")
386+
}
387+
388+
func isWritableByCurrentUser(path string) bool {
389+
f, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND, 0)
390+
if err != nil {
391+
return false
392+
}
393+
_ = f.Close()
394+
return true
395+
}
396+
354397
func writeFileAtomic(path string, data []byte, mode os.FileMode) error {
355398
tmp := path + ".tmp"
356399
if err := os.WriteFile(tmp, data, mode); err != nil {
@@ -550,6 +593,7 @@ Usage:
550593
551594
Commands:
552595
status [config] launch the live TUI (default: /etc/kekkai/kekkai.yaml)
596+
config [config] open config in nano, then auto-reload kekkai-agent
553597
check [config] validate a config file (read-only; safe as non-root)
554598
ports [config] show colorized public/private port summary
555599
show [config] print the normalised config after migration

internal/loader/loader_linux.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,11 +213,19 @@ func (l *linuxImpl) close() error {
213213
// <root>/<map-name>. Called right after NewCollection so subsequent
214214
// LoadPinnedMap() calls from CLI / TUI processes succeed.
215215
func pinMaps(coll *ebpf.Collection, root string) error {
216+
// Best-effort: keep pin root traversable for non-root operator tooling
217+
// (kekkai status / doctor). Some hardened systems may still enforce
218+
// additional LSM checks, so permission failures are non-fatal here.
219+
_ = os.Chmod(root, 0o755)
220+
216221
for name, m := range coll.Maps {
217222
path := filepath.Join(root, name)
218223
if err := m.Pin(path); err != nil {
219224
return fmt.Errorf("pin %s → %s: %w", name, path, err)
220225
}
226+
// Make pinned map inode readable by non-root users so `kekkai status`
227+
// can open maps without sudo on standard Linux setups.
228+
_ = os.Chmod(path, 0o644)
221229
}
222230
return nil
223231
}

internal/tui/model.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"io"
77
"net/http"
8+
"regexp"
89
"strconv"
910
"strings"
1011
"time"
@@ -347,6 +348,14 @@ func normalizeSemver(v string) (string, bool) {
347348
if i := strings.IndexAny(core, "-+"); i >= 0 {
348349
core = core[:i]
349350
}
351+
352+
// Backward compatibility:
353+
// older builds used YYYYMMDD(+meta), e.g. 20260414+b8.
354+
// Convert to loose semver-like core: YYYY.MM.DD so update checks still work.
355+
if isEightDigits(core) {
356+
return fmt.Sprintf("%s.%s.%s", core[:4], core[4:6], core[6:8]), true
357+
}
358+
350359
parts := strings.Split(core, ".")
351360
if len(parts) != 3 {
352361
return "", false
@@ -362,6 +371,12 @@ func normalizeSemver(v string) (string, bool) {
362371
return core, true
363372
}
364373

374+
var eightDigitsRe = regexp.MustCompile(`^\d{8}$`)
375+
376+
func isEightDigits(s string) bool {
377+
return eightDigitsRe.MatchString(s)
378+
}
379+
365380
func compareSemver(a, b string) int {
366381
pa := strings.Split(a, ".")
367382
pb := strings.Split(b, ".")

kekkai.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -840,7 +840,7 @@ release_update() {
840840
old_sha=""; [[ -f "$AGENT_BIN" ]] && old_sha="$(sha256sum "$AGENT_BIN" | awk '{print $1}')"
841841
new_sha="$(sha256sum "$new_agent" | awk '{print $1}')"
842842
if [[ "$old_sha" == "$new_sha" ]]; then
843-
log "binary unchanged — nothing to restart"
843+
log "up-to-date (binary unchanged — nothing to restart)"
844844
print_version_transition "$old_ver" "$new_ver"
845845
rm -rf "$tmpdir"
846846
return 0
@@ -953,7 +953,7 @@ do_update() {
953953
old_sha=""; [[ -f "$AGENT_BIN" ]] && old_sha="$(sha256sum "$AGENT_BIN" | awk '{print $1}')"
954954
new_sha="$(sha256sum "$ROOT/bin/kekkai-agent" | awk '{print $1}')"
955955
if [[ "$old_sha" == "$new_sha" ]]; then
956-
log "binary unchanged — nothing to restart"
956+
log "up-to-date (binary unchanged — nothing to restart)"
957957
print_version_transition "$old_ver" "$new_ver"
958958
return 0
959959
fi

readme.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ bash ./kekkai.sh
5353
sudo nano /etc/kekkai/kekkai.yaml
5454
kekkai check
5555
sudo kekkai reload
56-
sudo kekkai status
56+
kekkai status
5757
```
5858

5959
> 注意:預設 `filter.ingress_allowlist` 會先放 `192.168.0.0/16` 避免初次啟動被 SSH 防呆擋住;請務必改成你的實際管理網段。

0 commit comments

Comments
 (0)