Files
MultiPhysicsVault/scripts/allocate-address.sh
T
김경종 72dad72703
Tests / Hermetic test suite (push) Has been cancelled
Tests / Skill frontmatter validation (push) Has been cancelled
add claude-obsidian
2026-05-28 10:57:16 +09:00

103 lines
3.2 KiB
Bash

#!/usr/bin/env bash
# allocate-address.sh — atomic creation-order address allocation for the vault.
#
# Reserves the next address of the form c-NNNNNN and increments the counter
# under an exclusive flock. On missing counter file, recovers by scanning the
# vault for the highest existing c-NNNNNN in page frontmatter and resuming from
# max+1. Never silently resets to 1 in a non-empty vault.
#
# Usage:
# ./scripts/allocate-address.sh # prints the reserved address (e.g. c-000042) to stdout
# ./scripts/allocate-address.sh --peek # prints the next value without incrementing
# ./scripts/allocate-address.sh --rebuild # recomputes counter from max observed and exits
#
# Exit codes:
# 0 — success
# 1 — lock acquisition failed (another writer is holding the lock)
# 2 — vault-meta directory missing and cannot be created
# 3 — counter value corrupt or non-numeric
set -euo pipefail
VAULT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
COUNTER_FILE="${VAULT_ROOT}/.vault-meta/address-counter.txt"
LOCK_FILE="${VAULT_ROOT}/.vault-meta/.address.lock"
WIKI_DIR="${VAULT_ROOT}/wiki"
MODE="${1:-allocate}"
mkdir -p "$(dirname "$COUNTER_FILE")" || {
echo "ERR: cannot create .vault-meta/" >&2
exit 2
}
# Acquire exclusive lock with 5-second timeout. Release automatically on scope exit.
exec 9>"$LOCK_FILE"
if ! flock -x -w 5 9; then
echo "ERR: could not acquire address allocator lock within 5s" >&2
exit 1
fi
scan_max_c_address() {
# Emit the largest NNNNNN from "address: c-NNNNNN" lines that appear inside
# the FIRST YAML frontmatter block of each wiki .md file. Code-block examples
# and body prose are excluded. Returns 0 if none found.
if [ ! -d "$WIKI_DIR" ]; then
echo 0
return
fi
find "$WIKI_DIR" -type f -name '*.md' -print0 2>/dev/null \
| xargs -0 awk '
FNR == 1 { state = "pre"; next_is_fm = ($0 == "---") ? 1 : 0 }
FNR == 1 && $0 == "---" { state = "fm"; next }
state == "fm" && $0 == "---" { state = "body"; nextfile }
state == "fm" && match($0, /^address:[[:space:]]+c-[0-9]{6}[[:space:]]*$/) {
if (match($0, /c-[0-9]{6}/)) {
print substr($0, RSTART, RLENGTH)
}
}
' 2>/dev/null \
| sed 's/^c-0*//;s/^$/0/' \
| sort -n \
| tail -1 \
| awk 'BEGIN{n=0} {n=$0} END{print (n+0)}'
}
read_or_recover_counter() {
if [ ! -f "$COUNTER_FILE" ]; then
local max_c
max_c="$(scan_max_c_address)"
echo $((max_c + 1)) > "$COUNTER_FILE"
echo "INFO: counter file missing; recovered from vault scan, set to $((max_c + 1))" >&2
fi
local raw
raw="$(cat "$COUNTER_FILE")"
if ! [[ "$raw" =~ ^[0-9]+$ ]]; then
echo "ERR: counter file content is not a positive integer: $raw" >&2
exit 3
fi
echo "$raw"
}
case "$MODE" in
--peek)
read_or_recover_counter
;;
--rebuild)
max_c="$(scan_max_c_address)"
echo $((max_c + 1)) > "$COUNTER_FILE"
echo "Counter rebuilt: next = $((max_c + 1))"
;;
allocate|"")
current="$(read_or_recover_counter)"
next=$((current + 1))
echo "$next" > "$COUNTER_FILE"
printf 'c-%06d\n' "$current"
;;
*)
echo "ERR: unknown mode: $MODE" >&2
echo "Usage: $0 [allocate|--peek|--rebuild]" >&2
exit 3
;;
esac