import: contenido inicial de la skill bitwarden
This commit is contained in:
139
scripts/query.sh
Normal file
139
scripts/query.sh
Normal file
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env bash
|
||||
# bitwarden skill — helper REST autenticado.
|
||||
#
|
||||
# Uso:
|
||||
# query.sh /status # GET
|
||||
# query.sh /list/object/items # GET
|
||||
# query.sh "/object/item/<uuid>" # GET
|
||||
# query.sh /generate?length=20&special=true # GET
|
||||
# query.sh -X POST -H 'Content-Type: application/json' \
|
||||
# -d '{"name":"foo"}' /object/folder # POST (create)
|
||||
#
|
||||
# Garantiza que `bw serve` esté arriba antes de llamar.
|
||||
#
|
||||
# READ + CREATE ONLY:
|
||||
# - GET siempre permitido
|
||||
# - POST permitido sólo en /object/item, /object/folder, /object/send,
|
||||
# /sync, /unlock, /lock, /generate (sí, hay POST /generate también),
|
||||
# y endpoints de auth
|
||||
# - PUT/DELETE/PATCH bloqueados
|
||||
# - POST a endpoints de mutación (move/restore/confirm/attachment/share)
|
||||
# bloqueados
|
||||
#
|
||||
# El guard NO es la única defensa: el server de Vaultwarden también rechaza
|
||||
# escritura en collections del org NucleOS porque claudecode0 está como
|
||||
# "Can view". Para el vault personal de claudecode0, este guard ES la única
|
||||
# defensa contra modify/delete.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
ENV_FILE="$SKILL_DIR/.env"
|
||||
|
||||
if [[ ! -f "$ENV_FILE" ]]; then
|
||||
echo "ERROR: $ENV_FILE no existe. cp $SKILL_DIR/.env.example $ENV_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
source "$ENV_FILE"
|
||||
set +a
|
||||
|
||||
PORT="${BW_PORT:-8087}"
|
||||
BASE="http://127.0.0.1:${PORT}"
|
||||
|
||||
# ─── Parsear args: separar flags de curl del path ───────────────────────
|
||||
args=()
|
||||
path=""
|
||||
method="GET"
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-X|--request)
|
||||
method="$(echo "${2:-GET}" | tr '[:lower:]' '[:upper:]')"
|
||||
args+=("$1" "$2"); shift 2
|
||||
;;
|
||||
-X*)
|
||||
method="$(echo "${1#-X}" | tr '[:lower:]' '[:upper:]')"
|
||||
args+=("$1"); shift
|
||||
;;
|
||||
--request=*)
|
||||
method="$(echo "${1#--request=}" | tr '[:lower:]' '[:upper:]')"
|
||||
args+=("$1"); shift
|
||||
;;
|
||||
-d|--data|--data-raw|--data-binary|--data-urlencode|-H|--header|-o|--output|-T|--upload-file)
|
||||
args+=("$1" "$2"); shift 2
|
||||
;;
|
||||
--) shift; break ;;
|
||||
-*) args+=("$1"); shift ;;
|
||||
*) path="$1"; shift ;;
|
||||
esac
|
||||
done
|
||||
# Cualquier residual posicional
|
||||
[[ $# -gt 0 && -z "$path" ]] && path="$1"
|
||||
|
||||
if [[ -z "$path" ]]; then
|
||||
cat >&2 <<EOF
|
||||
Uso: query.sh [curl flags] <path>
|
||||
|
||||
Read examples:
|
||||
query.sh /status
|
||||
query.sh /list/object/items
|
||||
query.sh /list/object/folders
|
||||
query.sh "/object/item/<uuid>"
|
||||
query.sh "/object/password/<uuid>"
|
||||
query.sh "/generate?length=20&special=true"
|
||||
|
||||
Create examples (POST):
|
||||
query.sh -X POST -H 'Content-Type: application/json' \\
|
||||
-d '{"name":"new folder"}' /object/folder
|
||||
|
||||
Ver endpoints.md para la cheat sheet completa.
|
||||
EOF
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# Asegurar leading /
|
||||
case "$path" in
|
||||
/*) ;;
|
||||
http*) echo "ERROR: pasá solo el path, no URL completa." >&2; exit 1 ;;
|
||||
*) path="/$path" ;;
|
||||
esac
|
||||
|
||||
# ─── READ + CREATE GUARD ────────────────────────────────────────────────
|
||||
case "$method" in
|
||||
PUT|DELETE|PATCH)
|
||||
echo "ERROR: la skill 'bitwarden' bloquea $method (read+create only)." >&2
|
||||
echo " Si REALMENTE necesitás modificar/eliminar, usá bw CLI directo:" >&2
|
||||
echo " BITWARDENCLI_APPDATA_DIR=$SKILL_DIR/.cache/bw \\" >&2
|
||||
echo " BW_SESSION=\$(cat $SKILL_DIR/.cache/session) bw <cmd>" >&2
|
||||
exit 3
|
||||
;;
|
||||
POST)
|
||||
# Path base sin querystring
|
||||
base_path="${path%%\?*}"
|
||||
case "$base_path" in
|
||||
/object/item|/object/folder|/object/send|/object/org-collection)
|
||||
;; # create permitido
|
||||
/sync|/unlock|/lock|/generate)
|
||||
;; # auth/utility permitidos
|
||||
*)
|
||||
echo "ERROR: POST $base_path bloqueado por la skill." >&2
|
||||
echo " POST permitido sólo en: /object/item, /object/folder, /object/send," >&2
|
||||
echo " /object/org-collection, /sync, /unlock, /lock, /generate." >&2
|
||||
echo " Endpoints como /move, /restore, /confirm, /object/attachment" >&2
|
||||
echo " están bloqueados (modifican estado existente)." >&2
|
||||
exit 3
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
|
||||
# ─── Asegurar bw serve arriba ───────────────────────────────────────────
|
||||
"$SKILL_DIR/scripts/serve-up.sh" >/dev/null
|
||||
|
||||
# ─── Llamar ─────────────────────────────────────────────────────────────
|
||||
exec curl -sS \
|
||||
-H "Accept: application/json" \
|
||||
"${args[@]}" \
|
||||
"${BASE}${path}"
|
||||
26
scripts/serve-down.sh
Normal file
26
scripts/serve-down.sh
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
# bitwarden skill — para `bw serve` y limpia cache de PID.
|
||||
# La session NO se borra (la podés reusar al re-arrancar).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
PID_FILE="$SKILL_DIR/.cache/serve.pid"
|
||||
|
||||
if [[ ! -f "$PID_FILE" ]]; then
|
||||
echo "→ No hay PID file, asumimos que serve no estaba corriendo."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
pid="$(cat "$PID_FILE")"
|
||||
if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
|
||||
echo "→ Matando bw serve (PID $pid)..."
|
||||
kill "$pid" 2>/dev/null || true
|
||||
sleep 1
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
kill -9 "$pid" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
rm -f "$PID_FILE"
|
||||
echo "→ Listo."
|
||||
93
scripts/serve-up.sh
Normal file
93
scripts/serve-up.sh
Normal file
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env bash
|
||||
# bitwarden skill — asegura que `bw serve` esté corriendo.
|
||||
#
|
||||
# Idempotente:
|
||||
# - Si serve responde en localhost:$BW_PORT/status → no hace nada
|
||||
# - Si no, mata cualquier proceso huérfano y relanza
|
||||
# - Si no hay session válida, llama a setup.sh para re-unlock
|
||||
#
|
||||
# Output: solo en caso de error o cuando arranca el daemon (silent en happy path).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
ENV_FILE="$SKILL_DIR/.env"
|
||||
CACHE_DIR="$SKILL_DIR/.cache"
|
||||
SESSION_FILE="$CACHE_DIR/session"
|
||||
PID_FILE="$CACHE_DIR/serve.pid"
|
||||
LOG_FILE="$CACHE_DIR/serve.log"
|
||||
BW_DATA_DIR="$CACHE_DIR/bw"
|
||||
|
||||
if [[ ! -f "$ENV_FILE" ]]; then
|
||||
echo "ERROR: $ENV_FILE no existe." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
source "$ENV_FILE"
|
||||
set +a
|
||||
|
||||
PORT="${BW_PORT:-8087}"
|
||||
HOST="127.0.0.1"
|
||||
BASE="http://${HOST}:${PORT}"
|
||||
|
||||
export BITWARDENCLI_APPDATA_DIR="$BW_DATA_DIR"
|
||||
|
||||
# ─── 1. ¿Ya está corriendo y responde? ───────────────────────────────────
|
||||
if curl -sS -m 2 -o /dev/null -w "%{http_code}" "${BASE}/status" 2>/dev/null | grep -q "200"; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ─── 2. Limpiar PID huérfano si existe ──────────────────────────────────
|
||||
if [[ -f "$PID_FILE" ]]; then
|
||||
old_pid="$(cat "$PID_FILE")"
|
||||
if [[ -n "$old_pid" ]] && kill -0 "$old_pid" 2>/dev/null; then
|
||||
# El PID vive pero no respondía: matarlo
|
||||
kill "$old_pid" 2>/dev/null || true
|
||||
sleep 1
|
||||
kill -9 "$old_pid" 2>/dev/null || true
|
||||
fi
|
||||
rm -f "$PID_FILE"
|
||||
fi
|
||||
|
||||
# ─── 3. Asegurar session válida ─────────────────────────────────────────
|
||||
if [[ ! -s "$SESSION_FILE" ]]; then
|
||||
echo "→ No hay session, corriendo setup.sh..." >&2
|
||||
bash "$SKILL_DIR/scripts/setup.sh" >&2
|
||||
fi
|
||||
|
||||
session="$(cat "$SESSION_FILE")"
|
||||
if [[ -z "$session" ]]; then
|
||||
echo "ERROR: session vacía después de setup. Revisá .env." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# ─── 4. Lanzar bw serve en background ───────────────────────────────────
|
||||
echo "→ Lanzando bw serve en ${BASE}..." >&2
|
||||
|
||||
mkdir -p "$CACHE_DIR"
|
||||
: > "$LOG_FILE"
|
||||
|
||||
# nohup + disown para que sobreviva el exit del script
|
||||
BW_SESSION="$session" \
|
||||
nohup bw serve --hostname "$HOST" --port "$PORT" \
|
||||
> "$LOG_FILE" 2>&1 &
|
||||
serve_pid=$!
|
||||
echo "$serve_pid" > "$PID_FILE"
|
||||
chmod 600 "$PID_FILE" 2>/dev/null || true
|
||||
disown 2>/dev/null || true
|
||||
|
||||
# ─── 5. Esperar a que responda ──────────────────────────────────────────
|
||||
for i in 1 2 3 4 5 6 7 8 9 10; do
|
||||
if curl -sS -m 2 -o /dev/null -w "%{http_code}" "${BASE}/status" 2>/dev/null | grep -q "200"; then
|
||||
echo "→ Listo en ${BASE} (PID $serve_pid)" >&2
|
||||
exit 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# Si llegamos acá, algo falló
|
||||
echo "ERROR: bw serve no respondió en 10s. Logs:" >&2
|
||||
tail -20 "$LOG_FILE" >&2
|
||||
exit 3
|
||||
85
scripts/setup.sh
Normal file
85
scripts/setup.sh
Normal file
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env bash
|
||||
# bitwarden skill — setup inicial.
|
||||
#
|
||||
# Idempotente. Corre las veces que quieras:
|
||||
# - configura el server
|
||||
# - loguea con API key (si no estaba logueado)
|
||||
# - unlock con master password (si estaba locked)
|
||||
# - guarda session en .cache/session (chmod 600)
|
||||
#
|
||||
# Después de esto, query.sh puede levantar `bw serve` y empezar a llamar.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
ENV_FILE="$SKILL_DIR/.env"
|
||||
CACHE_DIR="$SKILL_DIR/.cache"
|
||||
SESSION_FILE="$CACHE_DIR/session"
|
||||
BW_DATA_DIR="$CACHE_DIR/bw"
|
||||
|
||||
if [[ ! -f "$ENV_FILE" ]]; then
|
||||
echo "ERROR: $ENV_FILE no existe." >&2
|
||||
echo " cp $SKILL_DIR/.env.example $ENV_FILE y completá los valores." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$CACHE_DIR" "$BW_DATA_DIR"
|
||||
chmod 700 "$CACHE_DIR" "$BW_DATA_DIR" 2>/dev/null || true
|
||||
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
source "$ENV_FILE"
|
||||
set +a
|
||||
|
||||
: "${BW_SERVER:?BW_SERVER no definido en .env}"
|
||||
: "${BW_EMAIL:?BW_EMAIL no definido en .env}"
|
||||
: "${BW_PASSWORD:?BW_PASSWORD no definido en .env}"
|
||||
|
||||
# Aislar la data de bw a este skill (no contamina ~/.config/Bitwarden CLI)
|
||||
export BITWARDENCLI_APPDATA_DIR="$BW_DATA_DIR"
|
||||
|
||||
echo "→ Configurando server: $BW_SERVER"
|
||||
bw config server "$BW_SERVER" >/dev/null
|
||||
|
||||
# Status: unauthenticated | locked | unlocked
|
||||
status="$(bw status 2>/dev/null | python -c "import json,sys; print(json.load(sys.stdin).get('status','unknown'))" 2>/dev/null || echo "unknown")"
|
||||
echo "→ Status actual: $status"
|
||||
|
||||
# NOTA: usamos `bw login <email> <password>` directo (no `--apikey`) porque
|
||||
# `bw login --apikey` en bw CLI 2026.4.x deja la cuenta en estado roto
|
||||
# (`toWrappedAccountCryptographicState` null) que después rompe el unlock.
|
||||
# Email+password login es equivalentemente seguro porque la master password
|
||||
# ya vive en este .env de todas formas.
|
||||
if [[ "$status" == "unauthenticated" ]]; then
|
||||
echo "→ Login con email+password (claudecode0)..."
|
||||
session="$(bw login "$BW_EMAIL" "$BW_PASSWORD" --raw 2>&1 | tail -1)"
|
||||
if [[ -z "$session" || ${#session} -lt 40 ]]; then
|
||||
echo "ERROR: bw login falló. Output:" >&2
|
||||
echo "$session" >&2
|
||||
exit 2
|
||||
fi
|
||||
elif [[ "$status" == "locked" ]]; then
|
||||
echo "→ Unlock con master password..."
|
||||
session="$(bw unlock "$BW_PASSWORD" --raw 2>&1 | tail -1)"
|
||||
if [[ -z "$session" || ${#session} -lt 40 ]]; then
|
||||
echo "ERROR: bw unlock falló. Output:" >&2
|
||||
echo "$session" >&2
|
||||
exit 2
|
||||
fi
|
||||
else
|
||||
# unlocked: necesitamos session, pero unlock con vault ya unlocked devuelve la session existente
|
||||
session="$(bw unlock "$BW_PASSWORD" --raw 2>&1 | tail -1)"
|
||||
fi
|
||||
|
||||
printf '%s' "$session" > "$SESSION_FILE"
|
||||
chmod 600 "$SESSION_FILE" 2>/dev/null || true
|
||||
echo "→ Session guardada en $SESSION_FILE"
|
||||
|
||||
echo "→ Sync inicial del vault..."
|
||||
BW_SESSION="$(cat "$SESSION_FILE")" bw sync >/dev/null
|
||||
|
||||
# Verificación final
|
||||
final_status="$(BW_SESSION="$(cat "$SESSION_FILE")" bw status | python -c "import json,sys; d=json.load(sys.stdin); print(d.get('status'), '|', d.get('userEmail'), '|', d.get('serverUrl'))")"
|
||||
echo "→ Listo: $final_status"
|
||||
echo ""
|
||||
echo "Próximo paso: query.sh /status"
|
||||
Reference in New Issue
Block a user