Compare commits
3 commits
1c69a5bc29
...
3b620ef7e3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b620ef7e3 | ||
|
|
745e52723c | ||
|
|
1abe925f65 |
81
.claude/skills/worktree-aware/SKILL.md
Normal file
81
.claude/skills/worktree-aware/SKILL.md
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
---
|
||||||
|
name: worktree-aware
|
||||||
|
description: >
|
||||||
|
Use when working in a git worktree checkout for a parallel agent task.
|
||||||
|
The presence of an .agent-task file in the current working directory indicates
|
||||||
|
a task worktree (NOT the main checkout). Encodes branch hygiene: commit only
|
||||||
|
to the assigned task branch, NEVER push origin master, NEVER touch the main
|
||||||
|
checkout at ~/homelab-codex-ws, NEVER manage worktrees yourself. On task
|
||||||
|
completion, report the branch name verbatim and stop — the human merges via
|
||||||
|
scripts/dev/agent.sh.
|
||||||
|
---
|
||||||
|
|
||||||
|
## When this applies
|
||||||
|
|
||||||
|
- `.agent-task` present in your `cwd` → you are in a task worktree. Apply all rules below.
|
||||||
|
- `.agent-task` absent → you are in the main checkout. Do NOT treat yourself as a task agent.
|
||||||
|
In the main checkout these rules do not apply.
|
||||||
|
|
||||||
|
## Reading the marker
|
||||||
|
|
||||||
|
`.agent-task` is a YAML file. Your assigned branch is the value of the `branch:` key, e.g.:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
task: my-feature
|
||||||
|
branch: task/my-feature
|
||||||
|
parent_commit: abc1234
|
||||||
|
created_utc: 2026-06-03T10:00:00Z
|
||||||
|
worktree_path: /home/oskar/homelab-codex-ws-my-feature
|
||||||
|
```
|
||||||
|
|
||||||
|
Always read this file first before taking any action.
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
1. **Commit only to your branch.**
|
||||||
|
Before any `git commit`, run `git status` and confirm it says `On branch task/<name>`.
|
||||||
|
If it does not, stop immediately and report the discrepancy.
|
||||||
|
|
||||||
|
2. **Push only to your branch.**
|
||||||
|
The only permitted push is `git push origin task/<name>`.
|
||||||
|
NEVER `git push origin master` or any other branch.
|
||||||
|
|
||||||
|
3. **Do not touch the main checkout.**
|
||||||
|
`~/homelab-codex-ws/` is the main checkout — deploy-only, owned by the human.
|
||||||
|
Do not read from, write to, or execute commands inside it.
|
||||||
|
|
||||||
|
4. **Stay scoped.**
|
||||||
|
Only change files directly related to your assigned task.
|
||||||
|
If you notice other problems, report them in your final summary as separate follow-up proposals.
|
||||||
|
Do not fix them in this worktree.
|
||||||
|
|
||||||
|
5. **Never `git add -A`.**
|
||||||
|
Always stage specific files by name: `git add path/to/file`.
|
||||||
|
|
||||||
|
6. **Do not manage worktrees.**
|
||||||
|
Never run `git worktree add/remove` or invoke `scripts/dev/agent.sh`.
|
||||||
|
Worktree lifecycle is the human's responsibility.
|
||||||
|
|
||||||
|
7. **Final report before stopping.**
|
||||||
|
When the task is done, provide a structured report containing:
|
||||||
|
- Files changed (path and one-line summary of change)
|
||||||
|
- Tests run and results
|
||||||
|
- All commit hashes on the task branch
|
||||||
|
- **Branch name verbatim** (copy-paste ready)
|
||||||
|
- Follow-up items as bulleted proposals for separate tasks
|
||||||
|
|
||||||
|
## Definition of Done
|
||||||
|
|
||||||
|
- All commits are on `task/<name>` (verify with `git log --oneline master..task/<name>`)
|
||||||
|
- Test suite passes
|
||||||
|
- Branch pushed: `git push origin task/<name>`
|
||||||
|
- Full report delivered in conversation
|
||||||
|
|
||||||
|
## What you do NOT do
|
||||||
|
|
||||||
|
- Merge branches
|
||||||
|
- Create or push tags
|
||||||
|
- Run deploys or healthchecks against production nodes
|
||||||
|
- Delete branches or worktrees
|
||||||
|
- Modify files in other worktrees
|
||||||
|
- Push to `origin master` under any circumstances
|
||||||
12
CLAUDE.md
12
CLAUDE.md
|
|
@ -180,3 +180,15 @@ Before any new or changed service is considered ready:
|
||||||
- Services: kebab-case (`stability-agent`, `zigbee2mqtt`)
|
- Services: kebab-case (`stability-agent`, `zigbee2mqtt`)
|
||||||
- Container names must match service names
|
- Container names must match service names
|
||||||
- Always `restart: unless-stopped` unless `service.yaml` says otherwise
|
- Always `restart: unless-stopped` unless `service.yaml` says otherwise
|
||||||
|
|
||||||
|
## Multi-agent worktree mode
|
||||||
|
|
||||||
|
`~/homelab-codex-ws` (main checkout) is **deploy-only** and belongs to the human operator.
|
||||||
|
Parallel agent tasks run in isolated git worktrees created by `scripts/dev/agent.sh new <name>`.
|
||||||
|
|
||||||
|
If `.agent-task` exists in your current working directory, you are in a task worktree.
|
||||||
|
**You must immediately read `.agent-task` and load `.claude/skills/worktree-aware/SKILL.md`
|
||||||
|
before taking any action.** That skill defines all branch-hygiene rules for task worktrees.
|
||||||
|
|
||||||
|
Worktree lifecycle commands: `agent.sh new | list | merge | clean`.
|
||||||
|
Agents never invoke these — only the human does.
|
||||||
|
|
|
||||||
359
scripts/dev/agent.sh
Executable file
359
scripts/dev/agent.sh
Executable file
|
|
@ -0,0 +1,359 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Multi-agent worktree manager.
|
||||||
|
# EXIT: 0 ok, 1 preflight, 2 operation failed.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
RESERVED_NAMES=(master main HEAD list merge clean new)
|
||||||
|
MAX_WORKTREES=4
|
||||||
|
|
||||||
|
die() { echo "ERROR: $*" >&2; exit "${2:-2}"; }
|
||||||
|
prefail(){ echo "PREFLIGHT: $*" >&2; exit 1; }
|
||||||
|
|
||||||
|
# ── helpers ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
is_main_checkout() {
|
||||||
|
local git_dir common_dir
|
||||||
|
git_dir=$(git rev-parse --git-dir 2>/dev/null) || return 1
|
||||||
|
common_dir=$(git rev-parse --git-common-dir 2>/dev/null) || return 1
|
||||||
|
[ "$git_dir" = "$common_dir" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
require_main_checkout() {
|
||||||
|
is_main_checkout || prefail "must run from the main checkout, not a worktree"
|
||||||
|
}
|
||||||
|
|
||||||
|
require_master_branch() {
|
||||||
|
local branch
|
||||||
|
branch=$(git rev-parse --abbrev-ref HEAD)
|
||||||
|
[ "$branch" = "master" ] || prefail "must be on master (currently on '$branch')"
|
||||||
|
}
|
||||||
|
|
||||||
|
require_clean_tree() {
|
||||||
|
local dirty
|
||||||
|
dirty=$(git status --porcelain)
|
||||||
|
[ -z "$dirty" ] || prefail "working tree is not clean — stash or commit first"
|
||||||
|
}
|
||||||
|
|
||||||
|
worktree_count() {
|
||||||
|
# count registered worktrees that are NOT the main checkout
|
||||||
|
local main_path
|
||||||
|
main_path=$(git rev-parse --show-toplevel)
|
||||||
|
git worktree list --porcelain \
|
||||||
|
| awk '/^worktree /{p=$2} /^$/{print p}' \
|
||||||
|
| grep -cv "^${main_path}$"
|
||||||
|
}
|
||||||
|
|
||||||
|
worktree_paths() {
|
||||||
|
# list worktree paths (excluding main)
|
||||||
|
local main_path
|
||||||
|
main_path=$(git rev-parse --show-toplevel)
|
||||||
|
git worktree list --porcelain \
|
||||||
|
| awk '/^worktree /{p=$2} /^$/{print p}' \
|
||||||
|
| grep -v "^${main_path}$"
|
||||||
|
}
|
||||||
|
|
||||||
|
branch_exists_local() { git show-ref --verify --quiet "refs/heads/$1"; }
|
||||||
|
branch_exists_remote() { git ls-remote --exit-code origin "$1" >/dev/null 2>&1; }
|
||||||
|
|
||||||
|
utc_now() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
|
||||||
|
|
||||||
|
age_str() {
|
||||||
|
local created_utc="$1"
|
||||||
|
local now_ts created_ts diff_s
|
||||||
|
now_ts=$(date -u +%s)
|
||||||
|
# strip Z, replace T with space for `date -d`
|
||||||
|
created_ts=$(date -u -d "${created_utc//T/ }" +%s 2>/dev/null) || { echo "?"; return; }
|
||||||
|
diff_s=$(( now_ts - created_ts ))
|
||||||
|
if (( diff_s < 60 )); then echo "${diff_s}s"
|
||||||
|
elif (( diff_s < 3600 )); then echo "$(( diff_s/60 ))m"
|
||||||
|
elif (( diff_s < 86400 )); then echo "$(( diff_s/3600 ))h"
|
||||||
|
else echo "$(( diff_s/86400 ))d"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_name() {
|
||||||
|
local name="$1"
|
||||||
|
[[ "$name" =~ ^[a-z][a-z0-9-]*$ ]] || prefail "name '$name' must match ^[a-z][a-z0-9-]*$"
|
||||||
|
for r in "${RESERVED_NAMES[@]}"; do
|
||||||
|
[ "$name" = "$r" ] && prefail "'$name' is a reserved word"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── subcommands ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
cmd_new() {
|
||||||
|
local name="${1:-}"
|
||||||
|
[ -n "$name" ] || { usage; exit 1; }
|
||||||
|
|
||||||
|
validate_name "$name"
|
||||||
|
require_main_checkout
|
||||||
|
require_master_branch
|
||||||
|
require_clean_tree
|
||||||
|
|
||||||
|
# worktree limit
|
||||||
|
local count
|
||||||
|
count=$(worktree_count)
|
||||||
|
if (( count >= MAX_WORKTREES )); then
|
||||||
|
echo "ERROR: already at maximum of $MAX_WORKTREES active worktrees:" >&2
|
||||||
|
cmd_list
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# branch collision
|
||||||
|
if branch_exists_local "task/$name"; then
|
||||||
|
prefail "branch task/$name already exists locally"
|
||||||
|
fi
|
||||||
|
git fetch origin master --quiet
|
||||||
|
if branch_exists_remote "refs/heads/task/$name"; then
|
||||||
|
prefail "branch task/$name already exists on origin"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# directory collision
|
||||||
|
local main_path wt_path
|
||||||
|
main_path=$(git rev-parse --show-toplevel)
|
||||||
|
wt_path="$(dirname "$main_path")/homelab-codex-ws-${name}"
|
||||||
|
[ ! -e "$wt_path" ] || prefail "directory $wt_path already exists"
|
||||||
|
|
||||||
|
# create worktree
|
||||||
|
git worktree add -b "task/$name" "$wt_path" origin/master \
|
||||||
|
|| die "git worktree add failed"
|
||||||
|
|
||||||
|
# write marker
|
||||||
|
local parent_commit
|
||||||
|
parent_commit=$(git rev-parse origin/master)
|
||||||
|
cat > "$wt_path/.agent-task" <<EOF
|
||||||
|
task: $name
|
||||||
|
branch: task/$name
|
||||||
|
parent_commit: $parent_commit
|
||||||
|
created_utc: $(utc_now)
|
||||||
|
worktree_path: $wt_path
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Worktree created: $wt_path"
|
||||||
|
echo "Branch: task/$name"
|
||||||
|
echo ""
|
||||||
|
echo "── Start Claude Code in this worktree ──────────────────────────────────────"
|
||||||
|
echo "cd ~/homelab-codex-ws-${name} && claude --dangerously-skip-permissions \"Jesteś w worktree task '${name}' (branch task/${name}). NAJPIERW przeczytaj .agent-task i .claude/skills/worktree-aware/SKILL.md, dopiero potem zacznij pracę. Commituj wyłącznie na swoją gałąź; nie pushuj origin master.\""
|
||||||
|
echo "─────────────────────────────────────────────────────────────────────────────"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_list() {
|
||||||
|
local main_path
|
||||||
|
main_path=$(git rev-parse --show-toplevel)
|
||||||
|
|
||||||
|
# fetch to get up-to-date ahead/behind
|
||||||
|
git fetch origin master --quiet 2>/dev/null || true
|
||||||
|
|
||||||
|
local paths
|
||||||
|
paths=$(worktree_paths)
|
||||||
|
|
||||||
|
if [ -z "$paths" ]; then
|
||||||
|
echo "(no active task worktrees)"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "%-20s %-25s %-10s %-8s %-8s %-7s %s\n" \
|
||||||
|
"NAME" "BRANCH" "CREATED" "AGE" "STATUS" "A/B" "PARENT"
|
||||||
|
|
||||||
|
while IFS= read -r wt_path; do
|
||||||
|
[ -z "$wt_path" ] && continue
|
||||||
|
|
||||||
|
local marker="$wt_path/.agent-task"
|
||||||
|
local task_name branch parent_commit created_utc
|
||||||
|
if [ -f "$marker" ]; then
|
||||||
|
task_name=$( grep '^task:' "$marker" | awk '{print $2}')
|
||||||
|
branch=$( grep '^branch:' "$marker" | awk '{print $2}')
|
||||||
|
parent_commit=$(grep '^parent_commit:' "$marker" | awk '{print $2}')
|
||||||
|
created_utc=$(grep '^created_utc:' "$marker" | awk '{print $2}')
|
||||||
|
else
|
||||||
|
task_name="(no marker)"
|
||||||
|
branch=$(git -C "$wt_path" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "?")
|
||||||
|
parent_commit="?"
|
||||||
|
created_utc=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
local status="clean"
|
||||||
|
local dirty
|
||||||
|
dirty=$(git -C "$wt_path" status --porcelain 2>/dev/null || echo "?")
|
||||||
|
[ -n "$dirty" ] && status="dirty"
|
||||||
|
|
||||||
|
local ahead behind ab
|
||||||
|
ahead=$(git -C "$wt_path" rev-list --count "origin/master..${branch}" 2>/dev/null || echo "?")
|
||||||
|
behind=$(git -C "$wt_path" rev-list --count "${branch}..origin/master" 2>/dev/null || echo "?")
|
||||||
|
ab="+${ahead}/-${behind}"
|
||||||
|
|
||||||
|
local age=""
|
||||||
|
[ -n "$created_utc" ] && age=$(age_str "$created_utc")
|
||||||
|
|
||||||
|
local short_parent="${parent_commit:0:7}"
|
||||||
|
local short_created="${created_utc:0:10}"
|
||||||
|
|
||||||
|
printf "%-20s %-25s %-10s %-8s %-8s %-7s %s\n" \
|
||||||
|
"$task_name" "$branch" "$short_created" "$age" "$status" "$ab" "$short_parent"
|
||||||
|
done <<< "$paths"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_merge() {
|
||||||
|
local name="${1:-}"
|
||||||
|
[ -n "$name" ] || { usage; exit 1; }
|
||||||
|
|
||||||
|
require_main_checkout
|
||||||
|
require_master_branch
|
||||||
|
require_clean_tree
|
||||||
|
|
||||||
|
git fetch origin --quiet
|
||||||
|
|
||||||
|
branch_exists_local "task/$name" || die "branch task/$name not found locally" 1
|
||||||
|
|
||||||
|
local main_path wt_path
|
||||||
|
main_path=$(git rev-parse --show-toplevel)
|
||||||
|
wt_path="$(dirname "$main_path")/homelab-codex-ws-${name}"
|
||||||
|
|
||||||
|
# attempt ff-only merge
|
||||||
|
local merge_failed=0
|
||||||
|
git merge --ff-only "task/$name" || merge_failed=1
|
||||||
|
|
||||||
|
if (( merge_failed )); then
|
||||||
|
# abort any partial merge state
|
||||||
|
git merge --abort 2>/dev/null || true
|
||||||
|
echo ""
|
||||||
|
echo "ERROR: task/$name cannot be fast-forwarded into master." >&2
|
||||||
|
echo " The branch has likely diverged from master." >&2
|
||||||
|
echo "" >&2
|
||||||
|
echo "Diagnose with:" >&2
|
||||||
|
echo " git log master..task/$name # commits only on task branch" >&2
|
||||||
|
echo " git log task/$name..master # commits master has that task doesn't" >&2
|
||||||
|
echo "" >&2
|
||||||
|
echo "Then decide: rebase task/$name onto master, or merge manually." >&2
|
||||||
|
echo "Worktree and branch are preserved — no changes made." >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Merged task/$name into master (fast-forward)."
|
||||||
|
|
||||||
|
git push origin master || die "git push origin master failed"
|
||||||
|
echo "Pushed master to origin."
|
||||||
|
|
||||||
|
if [ -d "$wt_path" ]; then
|
||||||
|
git worktree remove "$wt_path" || die "git worktree remove $wt_path failed"
|
||||||
|
echo "Removed worktree: $wt_path"
|
||||||
|
else
|
||||||
|
echo "(worktree directory $wt_path not found — skipping worktree remove)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
git branch -d "task/$name" || die "git branch -d task/$name failed"
|
||||||
|
echo "Deleted local branch task/$name."
|
||||||
|
|
||||||
|
git push origin --delete "task/$name" 2>/dev/null \
|
||||||
|
&& echo "Deleted remote branch task/$name." \
|
||||||
|
|| echo "(remote branch task/$name not found — nothing to delete)"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Done. task/$name merged and cleaned up."
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_clean() {
|
||||||
|
local main_path
|
||||||
|
main_path=$(git rev-parse --show-toplevel)
|
||||||
|
git fetch origin --quiet 2>/dev/null || true
|
||||||
|
|
||||||
|
local to_remove=()
|
||||||
|
|
||||||
|
# orphaned registered worktrees: branch deleted or fully merged into master
|
||||||
|
local paths
|
||||||
|
paths=$(worktree_paths)
|
||||||
|
while IFS= read -r wt_path; do
|
||||||
|
[ -z "$wt_path" ] && continue
|
||||||
|
local branch
|
||||||
|
branch=$(git -C "$wt_path" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
|
||||||
|
[ -z "$branch" ] && { to_remove+=("worktree:$wt_path (unreadable branch)"); continue; }
|
||||||
|
|
||||||
|
# branch gone locally?
|
||||||
|
if ! branch_exists_local "$branch"; then
|
||||||
|
to_remove+=("worktree:$wt_path (branch $branch no longer exists)")
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# branch fully merged into master?
|
||||||
|
local ahead
|
||||||
|
ahead=$(git rev-list --count "origin/master..${branch}" 2>/dev/null || echo "1")
|
||||||
|
if [ "$ahead" = "0" ]; then
|
||||||
|
to_remove+=("worktree:$wt_path (branch $branch fully merged into origin/master)")
|
||||||
|
fi
|
||||||
|
done <<< "$paths"
|
||||||
|
|
||||||
|
# dangling directories: ../homelab-codex-ws-* not registered
|
||||||
|
local registered_paths
|
||||||
|
registered_paths=$(git worktree list --porcelain | awk '/^worktree /{print $2}')
|
||||||
|
local parent_dir
|
||||||
|
parent_dir=$(dirname "$main_path")
|
||||||
|
while IFS= read -r candidate; do
|
||||||
|
[ -d "$candidate" ] || continue
|
||||||
|
if ! echo "$registered_paths" | grep -qF "$candidate"; then
|
||||||
|
to_remove+=("dangling:$candidate")
|
||||||
|
fi
|
||||||
|
done < <(find "$parent_dir" -maxdepth 1 -name "homelab-codex-ws-*" -type d 2>/dev/null)
|
||||||
|
|
||||||
|
if [ ${#to_remove[@]} -eq 0 ]; then
|
||||||
|
echo "Nothing to clean."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Found ${#to_remove[@]} item(s) to clean:"
|
||||||
|
for entry in "${to_remove[@]}"; do
|
||||||
|
echo " $entry"
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
local overall_rc=0
|
||||||
|
for entry in "${to_remove[@]}"; do
|
||||||
|
local kind="${entry%%:*}"
|
||||||
|
local path="${entry#*:}"
|
||||||
|
# strip trailing annotation in parens
|
||||||
|
local raw_path
|
||||||
|
raw_path="${path%% (*}"
|
||||||
|
|
||||||
|
local confirm
|
||||||
|
read -r -p "Remove $kind '$raw_path'? [y/N] " confirm
|
||||||
|
if [[ "$confirm" =~ ^[Yy]$ ]]; then
|
||||||
|
if [ "$kind" = "worktree" ]; then
|
||||||
|
git worktree remove --force "$raw_path" 2>/dev/null \
|
||||||
|
|| { echo " WARNING: git worktree remove failed, trying rm -rf"; rm -rf "$raw_path" || true; }
|
||||||
|
else
|
||||||
|
rm -rf "$raw_path"
|
||||||
|
fi
|
||||||
|
echo " Removed."
|
||||||
|
else
|
||||||
|
echo " Skipped."
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
return $overall_rc
|
||||||
|
}
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage: agent.sh <subcommand> [args]
|
||||||
|
|
||||||
|
agent.sh new <name> Create a new task worktree (branch task/<name>)
|
||||||
|
agent.sh list List active task worktrees with status
|
||||||
|
agent.sh merge <name> Fast-forward merge task/<name> into master and clean up
|
||||||
|
agent.sh clean Remove orphaned or dangling worktrees (interactive)
|
||||||
|
|
||||||
|
EXIT: 0 ok, 1 preflight, 2 operation failed.
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── dispatch ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
SUBCOMMAND="${1:-}"
|
||||||
|
shift || true
|
||||||
|
|
||||||
|
case "$SUBCOMMAND" in
|
||||||
|
new) cmd_new "$@" ;;
|
||||||
|
list) cmd_list "$@" ;;
|
||||||
|
merge) cmd_merge "$@" ;;
|
||||||
|
clean) cmd_clean "$@" ;;
|
||||||
|
*) usage; exit 1 ;;
|
||||||
|
esac
|
||||||
Loading…
Reference in a new issue