Session log: 1Password skill setup, Lonestar MDM fix, credentials migration planning

- Activated 1Password skill for Claude Code (extracted from .skill ZIP)
- Resolved Lonestar Electrical MDM issue: ManageEngine was configured as
  third-party EMM in Google Workspace, causing persistent enrollment prompts
  on joser's personal phone
- Scoped credentials.md migration to 1Password (op:// refs + MSP vaults)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-24 13:06:56 -07:00
parent acb1a86e2f
commit 4a3a0bfb69
10 changed files with 1296 additions and 0 deletions

View File

@@ -0,0 +1,222 @@
# 1Password Integration Patterns
Common patterns for integrating 1Password with developer tools and AI workflows.
## Claude Code / Claude Desktop
### Claude Desktop MCP Config
Store API keys securely and reference them in `claude_desktop_config.json`:
```bash
# Store the key
op item create --category API_CREDENTIAL --title "My MCP Server" \
--vault Dev api_key[password]=your-key-here
# Get the secret reference
# op://Dev/My MCP Server/api_key
```
```json
{
"mcpServers": {
"my-server": {
"command": "op",
"args": ["run", "--", "node", "/path/to/server.js"],
"env": {
"API_KEY": "op://Dev/My MCP Server/api_key"
}
}
}
}
```
### Claude Code Shell Environment
```bash
# .env.tpl (safe to commit — no real secrets)
ANTHROPIC_API_KEY=op://Dev/Anthropic/api_key
OPENAI_API_KEY=op://Dev/OpenAI/api_key
# ✅ Wrap claude with op run — secrets injected into subprocess only
op run --env-file=.env.tpl -- claude
# ✅ Or export individually for interactive shell use
export ANTHROPIC_API_KEY=$(op read "op://Dev/Anthropic/api_key")
claude
```
### In CLAUDE.md (project secrets reference)
```markdown
## Secrets Setup
Secrets are managed via 1Password. Run before working:
```bash
op run --env-file=.env.tpl -- claude
```
Do NOT commit `.env` — commit `.env.tpl` only.
```
## n8n
### Environment Injection at Startup
```bash
# n8n.env.tpl (commit this)
N8N_ENCRYPTION_KEY=op://Dev/n8n/encryption_key
DB_POSTGRESDB_PASSWORD=op://Dev/n8n-postgres/password
N8N_BASIC_AUTH_PASSWORD=op://Dev/n8n/basic_auth_password
# docker-compose.yml startup
op run --env-file=n8n.env.tpl -- docker compose up -d n8n
```
### n8n Credential Storage via API
Use n8n's credential API to push secrets from 1Password into n8n:
```bash
# Get secret from 1Password
API_KEY=$(op read "op://Dev/Some Service/api_key")
# Push to n8n credential (HTTP Request)
curl -s -X POST "https://n8n.example.com/api/v1/credentials" \
-H "X-N8N-API-KEY: $(op read 'op://Dev/n8n/api_key')" \
-H "Content-Type: application/json" \
-d "{\"name\": \"Service Credential\", \"type\": \"httpHeaderAuth\", \"data\": {\"name\": \"Authorization\", \"value\": \"Bearer $API_KEY\"}}"
```
## Docker / Docker Compose
```yaml
# docker-compose.yml
services:
app:
image: myapp:latest
environment:
DATABASE_URL: ${DATABASE_URL}
API_KEY: ${API_KEY}
```
```bash
# .env.tpl
DATABASE_URL=op://Dev/Postgres/connection_string
API_KEY=op://Dev/MyApp/api_key
# Start with injection
op run --env-file=.env.tpl -- docker compose up
```
## Python Scripts
```python
import subprocess
def get_secret(reference: str) -> str:
"""Read a secret from 1Password using a secret reference."""
result = subprocess.run(
["op", "read", reference],
capture_output=True, text=True, check=True
)
return result.stdout.strip()
# Usage
api_key = get_secret("op://Dev/Anthropic/api_key")
```
Or using the 1Password Python SDK (if available):
```bash
pip install onepassword-sdk
```
```python
import asyncio
import onepassword
async def main():
client = await onepassword.Client.authenticate(
auth=os.environ["OP_SERVICE_ACCOUNT_TOKEN"],
integration_name="My Script",
integration_version="1.0.0",
)
secret = await client.secrets.resolve("op://Dev/Anthropic/api_key")
```
## GitHub Actions / CI
```yaml
# .github/workflows/deploy.yml
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: 1password/load-secrets-action@v2
with:
export-env: true
env:
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
ANTHROPIC_API_KEY: op://Dev/Anthropic/api_key
DEPLOY_KEY: op://Dev/Deploy/private_key
- run: deploy-script.sh # ANTHROPIC_API_KEY is available
```
## Shell / .zshrc Auto-Load
```bash
# ~/.zshrc
# Auto-load common dev secrets on shell start (optional — only if you trust your machine)
load_dev_secrets() {
if command -v op &>/dev/null && op whoami &>/dev/null 2>&1; then
source <(op run --env-file=~/.config/dev.env.tpl -- env 2>/dev/null) && \
echo "✅ Dev secrets loaded from 1Password"
fi
}
# Call explicitly when needed:
alias load-secrets='load_dev_secrets'
```
## Supabase
```bash
# Store Supabase credentials
op item create --category API_CREDENTIAL --title "Supabase - My Project" \
--vault Dev \
url[text]=https://myproject.supabase.co \
anon_key[password]=eyJ... \
service_key[password]=eyJ...
# Use in scripts
SUPABASE_URL=$(op read "op://Dev/Supabase - My Project/url")
SUPABASE_KEY=$(op read "op://Dev/Supabase - My Project/service_key")
```
## Replit
Replit has its own Secrets manager, but for local dev before deploying:
```bash
# Generate a .env from 1Password, then paste values into Replit Secrets UI
op run --env-file=.env.tpl -- env | grep -E "^(ANTHROPIC|SUPABASE|N8N)"
# Copy output values → paste into Replit Secrets one by one
```
## Rotation Workflow
When rotating a credential:
```bash
# 1. Update in the service (get new key)
NEW_KEY="new-key-from-service"
# 2. Update in 1Password
op item edit "Service Name" api_key[password]="$NEW_KEY"
# 3. Verify
op read "op://Dev/Service Name/api_key"
# 4. Re-inject wherever used
source <(op run --env-file=.env.tpl -- env)
# Or restart services that use the key
```

View File

@@ -0,0 +1,171 @@
# 1Password CLI (op) Command Reference
## Authentication
```bash
# Sign in (interactive)
op signin
# Sign in to specific account
op signin --account team-name.1password.com
# Check who you're signed in as
op whoami
# List accounts
op account list
# Service account (CI/CD — set env var, no signin needed)
export OP_SERVICE_ACCOUNT_TOKEN="your-token"
```
## Items
```bash
# List items
op item list
op item list --vault Dev
op item list --categories API_CREDENTIAL
# Get item details
op item get "Item Title"
op item get "Item Title" --vault Dev
op item get "Item Title" --format json
# Get a specific field
op item get "Item Title" --fields api_key
op item get "Item Title" --fields label=api_key
# Read using secret reference (most common)
op read "op://Dev/Item Title/api_key"
# Create item
op item create --category API_CREDENTIAL --title "My API Key" api_key[password]=sk-abc123
op item create --category LOGIN --title "Service Account" --vault Dev \
username[text]=myuser password[password]=mypass
# Edit/update item
op item edit "Item Title" api_key[password]=new-value
op item edit "Item Title" --vault Dev new_field[text]=value
# Delete item
op item delete "Item Title"
op item delete "Item Title" --vault Dev
# Move item to different vault
op item move "Item Title" --current-vault Dev --destination-vault Personal
```
## Vaults
```bash
# List vaults
op vault list
op vault list --format json
# Create vault
op vault create "New Vault"
# Get vault details
op vault get "Vault Name"
```
## Secrets Injection
```bash
# Run command with secrets from .env template (RECOMMENDED)
op run --env-file=.env.tpl -- your-command arg1 arg2
# Inject into Docker
op run --env-file=.env.tpl -- docker compose up
# Inject a single reference via env var (op run picks up op:// values automatically)
export API_KEY="op://Dev/MyApp/api_key"
op run -- node app.js # API_KEY is resolved at runtime
# ⚠️ AVOID: sourcing op run output into the current shell
# source <(op run --env-file=.env.tpl -- env) ← UNSAFE
# If secret values contain $(...) or backticks, they execute as shell code.
# Use 'op run -- your-command' instead (secrets stay in subprocess only).
```
## Password Generation
```bash
# Generate at item creation time (no standalone command)
op item create --category PASSWORD --title "Generated Secret" \
--generate-password='letters,digits,symbols,32'
# Generate with custom recipe
op item create --category LOGIN --title "My Login" \
--generate-password='letters,digits,20'
# Or use openssl for scripted generation
openssl rand -base64 32 | tr -d '=+/'
```
## Document / File Management
```bash
# Store a file
op document create ./private-key.pem --title "SSH Private Key" --vault Dev
# Get a file
op document get "SSH Private Key" --output ./private-key.pem
# List documents
op document list
```
## Service Accounts (CI/CD)
```bash
# Create service account (in 1Password UI: Settings → Developer → Service Accounts)
# Then set token as env var:
export OP_SERVICE_ACCOUNT_TOKEN="ops_eyJ..."
# No signin needed — op commands work automatically
op item list # works with service account token
op read "op://vault/item/field"
```
## Connect (Self-hosted, advanced)
```bash
# For teams running 1Password Connect server
export OP_CONNECT_HOST="https://your-connect-server"
export OP_CONNECT_TOKEN="your-connect-token"
# Then op commands use Connect instead of 1Password.com
op item get "Item Title"
```
## Output Formats
Valid values: `json` or `human-readable` (default).
```bash
op item list --format=json # Machine-readable JSON
op item get "Item" --format=json # Full item JSON
op item list # Human-readable (default)
op vault list --format=json # Vaults as JSON
```
## Useful Patterns
```bash
# Find item by field value (search)
op item list --format=json | \
python3 -c "import sys,json; [print(i['title']) for i in json.load(sys.stdin)]"
# Export all items in a vault to JSON (backup)
op item list --vault Dev --format=json | \
python3 -c "import sys,json; ids=[i['id'] for i in json.load(sys.stdin)]"
# (then loop to get each)
# Check if a specific item exists
op item get "My Item" &>/dev/null && echo "exists" || echo "not found"
# Get item ID (for scripting)
op item get "My Item" --format=json | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])"
```

View File

@@ -0,0 +1,120 @@
# 1Password Secret References
Secret references are the safest way to use secrets — they point to 1Password without exposing actual values in code or config files.
## Syntax
```
op://vault/item/field
op://vault/item/section/field
```
**Examples:**
```bash
op://Dev/Anthropic/api_key
op://Personal/AWS/access_key_id
op://Dev/Supabase/section/service_key
```
## Reading a Secret Reference
```bash
# Single secret
op read "op://Dev/Anthropic/api_key"
# Into a variable
export ANTHROPIC_API_KEY=$(op read "op://Dev/Anthropic/api_key")
# Multiple secrets via op run
op run --env-file=.env.tpl -- your-command
```
## .env Template Files
Store references in a `.env.tpl` file (safe to commit to **private** repos):
> **Privacy note:** `.env.tpl` contains your vault names, item names, and field names —
> e.g. `op://Dev/Anthropic/api_key`. This reveals the structure of your 1Password vault
> to anyone who can read the file. For **private repos**, this is fine. For **public repos**,
> consider whether your vault/item naming reveals anything sensitive (client names, internal
> service names, etc.). Real secret values are never exposed — only the structure.
```bash
# .env.tpl — commit this
ANTHROPIC_API_KEY=op://Dev/Anthropic/api_key
N8N_API_KEY=op://Dev/n8n/api_key
SUPABASE_SERVICE_KEY=op://Dev/Supabase/service_key
NOTION_TOKEN=op://Dev/Notion/api_token
```
Then inject at runtime:
```bash
# ✅ RECOMMENDED — run your command with secrets injected into subprocess only
op run --env-file=.env.tpl -- npm start
op run --env-file=.env.tpl -- node server.js
op run --env-file=.env.tpl -- docker compose up
# ✅ OK — read a single secret into a variable for immediate use
export ANTHROPIC_API_KEY=$(op read "op://Dev/Anthropic/api_key")
# ⚠️ AVOID — sourcing op run output exposes secrets in current shell
# and is unsafe if any secret value contains shell metacharacters like $(...):
# source <(op run --env-file=.env.tpl -- env) ← DON'T DO THIS
# ⚠️ AVOID — writing resolved secrets to disk (don't commit .env)
# op run --env-file=.env.tpl -- env > .env ← only if truly necessary
```
## In Config Files
Claude Desktop (`claude_desktop_config.json`):
```json
{
"mcpServers": {
"my-server": {
"command": "op",
"args": ["run", "--", "node", "server.js"],
"env": {
"API_KEY": "op://Dev/MyServer/api_key"
}
}
}
}
```
Docker Compose:
```yaml
services:
app:
image: myapp
environment:
- DATABASE_URL=op://Dev/Postgres/connection_string
```
Run with: `op run -- docker compose up`
n8n (environment injection):
```bash
# In your n8n startup script
op run --env-file=n8n.env.tpl -- docker compose up n8n
```
## Finding Field Names
```bash
# List all fields in an item
op item get "Item Name" --format=json | \
python3 -c "import sys,json; [print(f['label']) for f in json.load(sys.stdin)['fields'] if f.get('value')]"
# Or view interactively
op item get "Item Name"
```
## Common Field Names by Category
| Category | Common Fields |
|----------|---------------|
| API_CREDENTIAL | `api_key`, `credential`, `token` |
| LOGIN | `username`, `password` |
| DATABASE | `connection_string`, `host`, `port`, `username`, `password` |
| SECURE_NOTE | `notesPlain` |
| SERVER | `hostname`, `port`, `username`, `password` |

View File

@@ -0,0 +1,75 @@
#!/usr/bin/env bash
# check_setup.sh — Verify 1Password CLI is installed and authenticated
# Usage: bash check_setup.sh
set -euo pipefail
PASS=0
FAIL=0
check() {
local label="$1"
local cmd="$2"
if eval "$cmd" &>/dev/null; then
echo "$label"
((PASS++)) || true
else
echo "$label"
((FAIL++)) || true
fi
}
echo "=== 1Password CLI Setup Check ==="
echo ""
# 1. CLI installed
check "op CLI installed" "command -v op"
# 2. Version
if command -v op &>/dev/null; then
echo " Version: $(op --version)"
fi
echo ""
echo "--- Authentication ---"
# 3. Signed in
check "Signed in to 1Password" "op account list 2>/dev/null | grep -q '.'"
# 4. Can list vaults
check "Can list vaults" "op vault list &>/dev/null"
# Show accounts if authenticated
if op account list &>/dev/null 2>&1; then
echo ""
echo " Accounts:"
op account list 2>/dev/null | tail -n +2 | while read -r line; do
echo "$line"
done
echo ""
echo " Vaults:"
op vault list --format=json 2>/dev/null | \
python3 -c "import sys,json; [print(f' • {v[\"name\"]} ({v[\"id\"]})') for v in json.load(sys.stdin)]" 2>/dev/null || true
fi
echo ""
echo "--- Environment ---"
# 5. OP_SERVICE_ACCOUNT_TOKEN (CI/CD pattern)
if [[ -n "${OP_SERVICE_ACCOUNT_TOKEN:-}" ]]; then
echo " ✅ OP_SERVICE_ACCOUNT_TOKEN is set (service account mode)"
else
echo " OP_SERVICE_ACCOUNT_TOKEN not set (interactive/desktop app mode)"
fi
echo ""
echo "==================================="
if [[ $FAIL -eq 0 ]]; then
echo "✅ All checks passed. 1Password CLI is ready."
else
echo "⚠️ $FAIL check(s) failed. See above."
echo ""
echo "Install: https://developer.1password.com/docs/cli/get-started/"
echo "Sign in: op signin"
fi

View File

@@ -0,0 +1,142 @@
#!/usr/bin/env bash
# env_from_op.sh — Generate a .env file from 1Password items
#
# Usage:
# bash env_from_op.sh # Interactive: prompts for vault + items
# bash env_from_op.sh --vault Dev # Use specific vault
# bash env_from_op.sh --item "My Project" # Export all fields from one item
# bash env_from_op.sh --output .env # Write to file (default: .env)
# bash env_from_op.sh --dry-run # Print without writing
#
# Output format:
# FIELD_NAME=op://Vault/Item/field # Secret references (safest)
# FIELD_NAME=actual_value # Resolved values (with --resolve)
set -euo pipefail
VAULT=""
ITEM=""
OUTPUT=".env"
DRY_RUN=false
RESOLVE=false
# Parse args
while [[ $# -gt 0 ]]; do
case $1 in
--vault) VAULT="$2"; shift 2 ;;
--item) ITEM="$2"; shift 2 ;;
--output) OUTPUT="$2"; shift 2 ;;
--dry-run) DRY_RUN=true; shift ;;
--resolve) RESOLVE=true; shift ;;
*) echo "Unknown option: $1"; exit 1 ;;
esac
done
# Check op is available
if ! command -v op &>/dev/null; then
echo "❌ 1Password CLI (op) not found. Install: https://developer.1password.com/docs/cli/get-started/"
exit 1
fi
# If no item specified, list items and prompt
if [[ -z "$ITEM" ]]; then
echo "Available items in vault '${VAULT:-all vaults}':"
if [[ -n "$VAULT" ]]; then
op item list --vault "$VAULT" --format=json | \
python3 -c "import sys,json; [print(f' {i[\"title\"]}') for i in json.load(sys.stdin)]"
else
op item list --format=json | \
python3 -c "import sys,json; [print(f' [{i[\"vault\"][\"name\"]}] {i[\"title\"]}') for i in json.load(sys.stdin)]"
fi
echo ""
read -rp "Enter item title: " ITEM
fi
echo "Fetching '${ITEM}' from 1Password..."
# Get item as JSON
if [[ -n "$VAULT" ]]; then
ITEM_JSON=$(op item get "$ITEM" --vault "$VAULT" --format=json)
else
ITEM_JSON=$(op item get "$ITEM" --format=json)
fi
VAULT_NAME=$(echo "$ITEM_JSON" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['vault']['name'])")
ITEM_TITLE=$(echo "$ITEM_JSON" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['title'])")
# Build .env content
ENV_CONTENT=$(echo "$ITEM_JSON" | python3 - <<'PYEOF'
import sys, json, re
data = json.load(sys.stdin)
vault = data['vault']['name']
title = data['title']
lines = []
SKIP_LABELS = {'username', 'password', 'notesPlain', 'notes'}
SKIP_TYPES = {'CONCEALED'} if False else set() # resolved mode: don't skip
for field in data.get('fields', []):
label = field.get('label', '')
value = field.get('value', '')
field_id = field.get('id', '')
ftype = field.get('type', '')
# Skip empty, metadata, or UI-only fields
if not value or not label:
continue
if label.lower() in {'username', 'notesplain', 'notes', 'password'} and ftype not in {'CONCEALED', 'URL'}:
continue
# Convert label to ENV_VAR format
env_key = re.sub(r'[^A-Z0-9_]', '_', label.upper().replace(' ', '_').replace('-', '_'))
env_key = re.sub(r'_+', '_', env_key).strip('_')
# Use secret reference (safer than raw value)
ref = f"op://{vault}/{title}/{label}"
lines.append(f"{env_key}={ref}")
print('\n'.join(lines))
PYEOF
)
# Handle resolve flag — replace refs with real values
if $RESOLVE; then
echo "⚠️ Writing resolved values (actual secrets). Handle carefully."
FINAL_CONTENT=""
while IFS= read -r line; do
if [[ "$line" =~ ^([A-Z_]+)=(op://.+)$ ]]; then
key="${BASH_REMATCH[1]}"
ref="${BASH_REMATCH[2]}"
value=$(op read "$ref" 2>/dev/null || echo "ERROR_READING")
FINAL_CONTENT+="${key}=${value}"$'\n'
else
FINAL_CONTENT+="$line"$'\n'
fi
done <<< "$ENV_CONTENT"
ENV_CONTENT="$FINAL_CONTENT"
fi
# Header
HEADER="# Generated from 1Password: ${VAULT_NAME}/${ITEM_TITLE}
# Generated: $(date -u +%Y-%m-%dT%H:%M:%SZ)
# Load with: op run --env-file=.env -- <command>
# or: eval \$(op run --env-file=.env -- env | grep KEY)
"
FULL_CONTENT="${HEADER}${ENV_CONTENT}"
if $DRY_RUN; then
echo ""
echo "--- .env preview ---"
echo "$FULL_CONTENT"
echo "--- end ---"
else
echo "$FULL_CONTENT" > "$OUTPUT"
echo "✅ Written to $OUTPUT (${#ENV_CONTENT} chars, $(echo "$ENV_CONTENT" | grep -c '=' || true) vars)"
echo ""
echo "To use:"
echo " op run --env-file=$OUTPUT -- your-command"
echo " source <(op run --env-file=$OUTPUT -- env)"
fi

View File

@@ -0,0 +1,52 @@
#!/usr/bin/env bash
# launch-in-terminal.sh — Open a script in a NEW Terminal.app window
#
# This is how the 1Password skill keeps secrets OUT of Claude Code.
# Claude generates the script, then calls this launcher.
# The script runs in Terminal.app — Claude never sees what you type.
#
# Usage:
# bash launch-in-terminal.sh /path/to/script.sh
# bash launch-in-terminal.sh /path/to/script.sh "window title"
set -euo pipefail
SCRIPT_PATH="${1:-}"
TITLE="${2:-1Password Setup}"
if [[ -z "$SCRIPT_PATH" ]]; then
echo "Usage: bash launch-in-terminal.sh /path/to/script.sh"
exit 1
fi
if [[ ! -f "$SCRIPT_PATH" ]]; then
echo "❌ Script not found: $SCRIPT_PATH"
exit 1
fi
chmod +x "$SCRIPT_PATH"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " Opening Terminal.app to collect secrets"
echo " Script: $SCRIPT_PATH"
echo ""
echo " ⚠️ Type your secrets in the Terminal"
echo " window that is about to open."
echo " Claude Code cannot see that window."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
osascript <<APPLESCRIPT
tell application "Terminal"
activate
set newTab to do script "echo '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'; echo ' ${TITLE}'; echo ' Type secrets here — Claude Code cannot see this window'; echo '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'; echo ''; bash ${SCRIPT_PATH}"
end tell
APPLESCRIPT
echo "✅ Terminal.app opened. Complete the prompts there, then return here."
echo " (This window will wait for you to press Enter when done)"
echo ""
read -rp "Press Enter once you've finished in Terminal.app... "
echo ""
echo "Continuing..."

View File

@@ -0,0 +1,124 @@
#!/usr/bin/env bash
# store-mcp-credentials.sh — Store MCP server credentials in 1Password
#
# ⚠️ RUN THIS IN TERMINAL.APP — NOT IN CLAUDE CODE
# Claude Code can see everything typed in its terminal.
# Open Terminal.app separately, then run this script.
#
# Usage (Claude will generate a pre-filled version for you):
# bash store-mcp-credentials.sh \
# --vault Dev \
# --item "My MCP Server" \
# --set "url=https://api.example.com" \
# --set "log_level=error" \
# --secret "api_key" \
# --secret "webhook_secret"
#
# Options:
# --vault 1Password vault name (default: Dev)
# --item Item title in 1Password
# --set Non-secret field: key=value (pre-filled, visible)
# --secret Secret field: prompted with hidden input
# --update Update existing item instead of creating new
set -euo pipefail
VAULT="Dev"
ITEM=""
UPDATE=false
declare -a SET_FIELDS=()
declare -a SECRET_FIELDS=()
while [[ $# -gt 0 ]]; do
case $1 in
--vault) VAULT="$2"; shift 2 ;;
--item) ITEM="$2"; shift 2 ;;
--set) SET_FIELDS+=("$2"); shift 2 ;;
--secret) SECRET_FIELDS+=("$2"); shift 2 ;;
--update) UPDATE=true; shift ;;
*) echo "Unknown option: $1"; exit 1 ;;
esac
done
if [[ -z "$ITEM" ]]; then
read -rp "Item title in 1Password: " ITEM
fi
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " Storing: $ITEM"
echo " Vault: $VAULT"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
# Show pre-filled fields
if [[ ${#SET_FIELDS[@]} -gt 0 ]]; then
echo "Pre-filled fields:"
for field in "${SET_FIELDS[@]}"; do
key="${field%%=*}"
val="${field#*=}"
echo " $key = $val"
done
echo ""
fi
# Prompt for secret fields
declare -a SECRET_VALUES=()
if [[ ${#SECRET_FIELDS[@]} -gt 0 ]]; then
echo "Enter secret values (input is hidden):"
for field in "${SECRET_FIELDS[@]}"; do
read -rsp " $field: " secret_val
echo ""
SECRET_VALUES+=("${field}[password]=${secret_val}")
done
echo ""
fi
# Build op field args for non-secret fields
declare -a OP_FIELDS=()
for field in "${SET_FIELDS[@]}"; do
key="${field%%=*}"
val="${field#*=}"
OP_FIELDS+=("${key}[text]=${val}")
done
# Combine all fields
ALL_FIELDS=("${OP_FIELDS[@]+"${OP_FIELDS[@]}"}" "${SECRET_VALUES[@]+"${SECRET_VALUES[@]}"}")
echo "Saving to 1Password..."
if $UPDATE; then
op item edit "$ITEM" --vault "$VAULT" "${ALL_FIELDS[@]}"
echo ""
echo "✅ Updated '$ITEM' in vault '$VAULT'"
else
# Try create, fall back to update if already exists
if op item get "$ITEM" --vault "$VAULT" &>/dev/null 2>&1; then
echo " Item already exists — updating instead..."
op item edit "$ITEM" --vault "$VAULT" "${ALL_FIELDS[@]}"
echo ""
echo "✅ Updated '$ITEM' in vault '$VAULT'"
else
op item create \
--category API_CREDENTIAL \
--title "$ITEM" \
--vault "$VAULT" \
"${ALL_FIELDS[@]}"
echo ""
echo "✅ Created '$ITEM' in vault '$VAULT'"
fi
fi
echo ""
echo "Secret references for your config:"
for field in "${SET_FIELDS[@]}"; do
key="${field%%=*}"
echo " op://${VAULT}/${ITEM}/${key}"
done
for field in "${SECRET_FIELDS[@]}"; do
echo " op://${VAULT}/${ITEM}/${field}"
done
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " Done. You can close this terminal."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

View File

@@ -0,0 +1,91 @@
#!/usr/bin/env bash
# store_secret.sh — Store or update a secret in 1Password
#
# Usage:
# bash store_secret.sh --title "My API Key" --field "api_key" --value "sk-..."
# bash store_secret.sh --title "Project Creds" --vault Dev --category API_CREDENTIAL
# bash store_secret.sh --update --title "Existing Item" --field "api_key" --value "new-value"
# bash store_secret.sh --from-env MY_VAR # Store from environment variable
set -euo pipefail
TITLE=""
FIELD="credential"
VALUE=""
VAULT=""
CATEGORY="API_CREDENTIAL"
UPDATE=false
FROM_ENV=""
GENERATE=false
GENERATE_LENGTH=32
while [[ $# -gt 0 ]]; do
case $1 in
--title) TITLE="$2"; shift 2 ;;
--field) FIELD="$2"; shift 2 ;;
--value) VALUE="$2"; shift 2 ;;
--vault) VAULT="$2"; shift 2 ;;
--category) CATEGORY="$2"; shift 2 ;;
--update) UPDATE=true; shift ;;
--from-env) FROM_ENV="$2"; shift 2 ;;
--generate) GENERATE=true; shift ;;
--length) GENERATE_LENGTH="$2"; shift 2 ;;
*) echo "Unknown option: $1"; exit 1 ;;
esac
done
# Validate
if [[ -z "$TITLE" ]]; then
read -rp "Item title: " TITLE
fi
# Get value from env var if requested
if [[ -n "$FROM_ENV" ]]; then
VALUE="${!FROM_ENV:-}"
if [[ -z "$VALUE" ]]; then
echo "❌ Environment variable $FROM_ENV is not set or empty"
exit 1
fi
FIELD="${FROM_ENV}"
echo "Using value from \$$FROM_ENV"
fi
# Generate a secure credential if requested
if $GENERATE; then
VALUE=$(openssl rand -base64 "$GENERATE_LENGTH" | tr -d '=+/' | head -c "$GENERATE_LENGTH")
echo "🔐 Generated secure credential ($GENERATE_LENGTH chars)"
fi
# Prompt for value if still empty
if [[ -z "$VALUE" ]]; then
read -rsp "Value (hidden): " VALUE
echo ""
fi
VAULT_FLAG=""
[[ -n "$VAULT" ]] && VAULT_FLAG="--vault $VAULT"
if $UPDATE; then
echo "Updating '${FIELD}' in '${TITLE}'..."
op item edit "$TITLE" $VAULT_FLAG "${FIELD}[password]=${VALUE}"
echo "✅ Updated '${FIELD}' in '${TITLE}'"
else
echo "Creating '${TITLE}' in 1Password..."
RESULT=$(op item create \
--category "$CATEGORY" \
--title "$TITLE" \
$VAULT_FLAG \
"${FIELD}[password]=${VALUE}" \
--format=json)
ITEM_ID=$(echo "$RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
VAULT_NAME=$(echo "$RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin)['vault']['name'])")
echo "✅ Created '${TITLE}' (ID: ${ITEM_ID})"
echo ""
echo "Secret reference:"
echo " op://${VAULT_NAME}/${TITLE}/${FIELD}"
echo ""
echo "Read it back:"
echo " op read \"op://${VAULT_NAME}/${TITLE}/${FIELD}\""
fi