Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 24 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,34 @@ ci-self up

- `ci-self up` は `register + run-focus` を連続実行
- `verify.yml` / PRテンプレートが無ければ自動雛形を生成
- 雛形の生成はローカルファイル変更のみ(GitHub反映には commit/push が必要)
- 対象リポジトリに `flake.nix` がある場合、runner マシンに `nix` の事前インストールが必要
- `ci-self` / `verify.yml` は `nix-daemon.sh` を自動読み込みして `nix` を検出(毎回の手動 `source` は不要)
- 既存の `verify.yml` が古い場合は `bash ops/ci/scaffold_verify_workflow.sh --repo <target> --apply --force` で更新

## ネットワーク別の最短
## Mac mini ワンコマンド(推奨)

同一LANの Mac mini へ SSH:
MacBook から 1 コマンドで「鍵認証確認 -> 同期 -> Mac mini 実行 -> 結果回収」まで行う:

```bash
ci-self remote-up --host <mac-mini-host> --project-dir ~/dev/maakie-brainlab --repo mt4110/maakie-brainlab
ci-self remote-ci --host <user>@<mac-mini-ip-or-host> --project-dir '~/dev/maakie-brainlab' --repo mt4110/maakie-brainlab
```

外出先(SSHあり):
`remote-ci` の実行内容:

```bash
ci-self remote-up --host <mac-mini-remote-host> --project-dir ~/dev/maakie-brainlab --repo mt4110/maakie-brainlab
```
1. SSH 公開鍵認証(password禁止)を検証
2. ローカル作業ツリーを Mac mini へ `rsync` 同期
3. (repo指定時)runner bootstrap をベストエフォート実行
4. Mac mini で `ops/ci/run_verify_full.sh` を実行
5. `verify-full.status` と `out/logs` をローカル `out/remote/<host>/` に回収

外出先(SSHなし):
公開鍵未登録時は、`authorized_keys` 登録のヒントを出して停止します。

```bash
ci-self run-focus --repo mt4110/maakie-brainlab --ref main
```
補足:

- `--host` は `ssh` の接続先文字列(`user@host` / IP / `~/.ssh/config` のHost別名)
- `--project-dir` に `~` を使う場合は `--project-dir '~/<path>'` のようにクオート
- runner 初期化/復旧専用の旧導線は `ci-self remote-up`

## さらに短縮する設定ファイル

Expand All @@ -57,8 +65,8 @@ ci-self config-init
CI_SELF_REPO=mt4110/maakie-brainlab
CI_SELF_REF=main
CI_SELF_PROJECT_DIR=/Users/<you>/dev/maakie-brainlab
CI_SELF_REMOTE_HOST=mac-mini.local
CI_SELF_REMOTE_PROJECT_DIR=~/dev/maakie-brainlab
CI_SELF_REMOTE_HOST=<you>@mac-mini.local
CI_SELF_REMOTE_PROJECT_DIR=/Users/<you>/dev/maakie-brainlab
CI_SELF_PR_BASE=main
```

Expand All @@ -68,8 +76,10 @@ CI_SELF_PR_BASE=main

- `ci-self up`: ローカル最短(register + run-focus)
- `ci-self focus`: run-focus 後、PR未作成なら自動作成し checks を監視
- `ci-self remote-ci`: 鍵必須・同期・Mac mini実行・結果回収を1コマンドで実行
- `ci-self doctor --fix`: 依存/gh auth/colima/docker/runner_health を診断し可能な範囲で修復
- `ci-self remote-up`: SSH先で register + run-focus
- `ci-self doctor --repo-dir <path>`: `flake.nix` リポジトリの Nix 到達性も含めて診断
- `ci-self remote-up`: SSH先で register + run-focus(同期しない旧導線)
- `ci-self config-init`: `.ci-self.env` テンプレート生成

注: `doctor --fix` は `gh auth login` だけは自動化できないため、未ログイン時は手動ログインが必要です。
Expand Down
109 changes: 109 additions & 0 deletions cmd/runner_health/main.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package main

import (
"flag"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
Expand All @@ -15,6 +17,10 @@ type checkResult struct {
detail string
}

type options struct {
repoDir string
}

func main() {
defer func() {
if r := recover(); r != nil {
Expand All @@ -24,6 +30,14 @@ func main() {
}
}()

opts, err := parseOptions(os.Args[1:])
if err != nil {
writeStatus("ERROR", "invalid_args="+err.Error(), nil)
fmt.Printf("ERROR: runner_health invalid_args=%s\n", err.Error())
fmt.Println("STATUS: ERROR")
os.Exit(2)
}

fmt.Println("OK: runner_health start")

results := []checkResult{}
Expand All @@ -45,6 +59,7 @@ func main() {
results = append(results, checkDiskDir("out"))
results = append(results, checkDiskDir(".local"))
results = append(results, checkDiskDir("cache"))
results = append(results, checkNixForRepo(opts.repoDir))

// Print results and determine overall status
for _, r := range results {
Expand All @@ -58,6 +73,20 @@ func main() {
fmt.Printf("STATUS: %s\n", overallStatus)
}

func parseOptions(args []string) (options, error) {
opts := options{}
fs := flag.NewFlagSet("runner_health", flag.ContinueOnError)
fs.SetOutput(io.Discard)
fs.StringVar(&opts.repoDir, "repo-dir", "", "target repository directory for context-aware checks")
if err := fs.Parse(args); err != nil {
return options{}, err
}
if fs.NArg() > 0 {
return options{}, fmt.Errorf("unexpected_args=%s", strings.Join(fs.Args(), ","))
}
return opts, nil
}

func checkCommand(name, bin string, args ...string) checkResult {
cmd := exec.Command(bin, args...)
cmd.Stdout = nil
Expand Down Expand Up @@ -158,6 +187,86 @@ func checkDiskDir(dir string) checkResult {
}
}

func checkNixForRepo(repoDir string) checkResult {
if strings.TrimSpace(repoDir) == "" {
return checkResult{
name: "nix",
status: "SKIP",
detail: "reason=repo_dir_not_set",
}
}

flakePath := filepath.Join(repoDir, "flake.nix")
fi, err := os.Stat(flakePath)
if err != nil {
if os.IsNotExist(err) {
return checkResult{
name: "nix",
status: "SKIP",
detail: "reason=not_required(no_flake)",
}
}
return checkResult{
name: "nix",
status: "ERROR",
detail: "reason=flake_stat_failed(" + err.Error() + ")",
}
}
if fi.IsDir() {
return checkResult{
name: "nix",
status: "ERROR",
detail: "reason=flake_not_file",
}
}

// flake.nix exists => nix is required.
if p, err := exec.LookPath("nix"); err == nil {
return checkResult{
name: "nix",
status: "OK",
detail: "reason=available path=" + p,
}
}

defaultProfileBin := "/nix/var/nix/profiles/default/bin/nix"
if _, err := os.Stat(defaultProfileBin); err == nil {
return checkResult{
name: "nix",
status: "OK",
detail: "reason=available_via_default_profile path=" + defaultProfileBin,
}
}

user := strings.TrimSpace(os.Getenv("USER"))
if user == "" {
user = "unknown"
}
perUserBin := filepath.Join("/nix/var/nix/profiles/per-user", user, "profile/bin/nix")
if _, err := os.Stat(perUserBin); err == nil {
return checkResult{
name: "nix",
status: "OK",
detail: "reason=available_via_user_profile path=" + perUserBin,
}
}

daemonProfile := "/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh"
if _, err := os.Stat(daemonProfile); err == nil {
return checkResult{
name: "nix",
status: "ERROR",
detail: "reason=not_in_path profile=" + daemonProfile,
}
}

return checkResult{
name: "nix",
status: "ERROR",
detail: "reason=not_installed",
}
}

func writeStatus(st string, reason string, results []checkResult) {
outDir := "out"
_ = os.MkdirAll(outDir, 0o755)
Expand Down
26 changes: 21 additions & 5 deletions docs/ci/QUICKSTART.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,38 @@ ci-self config-init
CI_SELF_REPO=mt4110/maakie-brainlab
CI_SELF_REF=main
CI_SELF_PROJECT_DIR=/Users/<you>/dev/maakie-brainlab
CI_SELF_REMOTE_HOST=mac-mini.local
CI_SELF_REMOTE_PROJECT_DIR=~/dev/maakie-brainlab
CI_SELF_REMOTE_HOST=<you>@mac-mini.local
CI_SELF_REMOTE_PROJECT_DIR=/Users/<you>/dev/maakie-brainlab
CI_SELF_PR_BASE=main
```

## ネットワーク別ワンコマンド

同一LAN / 外出先(SSHあり):
同一LAN / 外出先(SSHあり, 推奨):

```bash
ci-self remote-up
ci-self remote-ci
```

`remote-up` は `.ci-self.env` の `CI_SELF_REMOTE_HOST` などが設定済みの場合の最短です。
`remote-ci` は以下を 1 コマンドで実行します:

1. SSH 鍵認証チェック(password不可)
2. ローカル変更を Mac mini に `rsync` 同期
3. Mac mini 側 verify 実行
4. `out/remote/<host>/` へ結果回収

未設定なら `--host --project-dir --repo` を明示してください。

```bash
ci-self remote-ci --host <user>@<mac-mini> --project-dir '~/dev/maakie-brainlab' --repo mt4110/maakie-brainlab
```

runner 初期化/復旧専用の旧導線:

```bash
ci-self remote-up
```

外出先(SSHなし):

```bash
Expand Down
Loading
Loading