feat: add Ollama proxy timeouts, SSH key migration, polish skills, and demo content
- Update all skill SSH commands from sshpass to key-based auth (~/.ssh/archipelago-deploy) - Add proxy_connect_timeout 120s to nginx Ollama location blocks - Add new polish/sweep skills for overnight automation - Add demo content (documents, photos) for demo stack - Add .ssh/ to .gitignore Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
d3f0f1192e
commit
e8a0e1af19
@@ -16,13 +16,13 @@ Build a new Archipelago auto-installer ISO.
|
||||
## Build (on target server — recommended)
|
||||
|
||||
```bash
|
||||
sshpass -p 'EwPDR8q45l0Upx@' ssh -o StrictHostKeyChecking=no archipelago@192.168.1.228 'cd ~/archy/image-recipe && sudo ./build-auto-installer-iso.sh'
|
||||
ssh -i ~/.ssh/archipelago-deploy archipelago@192.168.1.228 'cd ~/archy/image-recipe && sudo ./build-auto-installer-iso.sh'
|
||||
```
|
||||
|
||||
## Copy ISO back to Mac
|
||||
|
||||
```bash
|
||||
sshpass -p 'EwPDR8q45l0Upx@' scp -o StrictHostKeyChecking=no archipelago@192.168.1.228:~/archy/image-recipe/results/archipelago-auto-installer-*.iso .
|
||||
scp -i ~/.ssh/archipelago-deploy archipelago@192.168.1.228:~/archy/image-recipe/results/archipelago-auto-installer-*.iso .
|
||||
```
|
||||
|
||||
**IMPORTANT**: Use `build-auto-installer-iso.sh` only. The deprecated `build-debian-iso.sh` causes boot-to-prompt issues.
|
||||
|
||||
@@ -18,6 +18,6 @@ Deploy all changes to BOTH servers (primary: 192.168.1.228, secondary: 192.168.1
|
||||
|
||||
3. Verify both servers respond:
|
||||
```bash
|
||||
sshpass -p 'EwPDR8q45l0Upx@' ssh -o StrictHostKeyChecking=no archipelago@192.168.1.228 'systemctl is-active archipelago'
|
||||
sshpass -p 'EwPDR8q45l0Upx@' ssh -o StrictHostKeyChecking=no archipelago@192.168.1.198 'systemctl is-active archipelago'
|
||||
ssh -i ~/.ssh/archipelago-deploy archipelago@192.168.1.228 'systemctl is-active archipelago'
|
||||
ssh -i ~/.ssh/archipelago-deploy archipelago@192.168.1.198 'systemctl is-active archipelago'
|
||||
```
|
||||
|
||||
@@ -18,7 +18,7 @@ Deploy all changes to the live server (192.168.1.228).
|
||||
|
||||
3. After deploy completes, verify the server is healthy:
|
||||
```bash
|
||||
sshpass -p 'EwPDR8q45l0Upx@' ssh -o StrictHostKeyChecking=no archipelago@192.168.1.228 'systemctl is-active archipelago nginx && sudo journalctl -u archipelago -n 10 --no-pager'
|
||||
ssh -i ~/.ssh/archipelago-deploy archipelago@192.168.1.228 'systemctl is-active archipelago nginx && sudo journalctl -u archipelago -n 10 --no-pager'
|
||||
```
|
||||
|
||||
4. Report whether the deploy succeeded and if any errors appeared in the logs.
|
||||
|
||||
@@ -4,7 +4,7 @@ description: Run a full diagnostic check on the Archipelago dev server
|
||||
allowed-tools: Bash
|
||||
---
|
||||
|
||||
SSH into the dev server and run a comprehensive diagnostic. Use `sshpass -p 'EwPDR8q45l0Upx@' ssh -o StrictHostKeyChecking=no archipelago@192.168.1.228` for all commands.
|
||||
SSH into the dev server and run a comprehensive diagnostic. Use `ssh -i ~/.ssh/archipelago-deploy archipelago@192.168.1.228` for all commands.
|
||||
|
||||
## Checks to run
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ grep -rn 'console\.\(log\|warn\|error\)' src/ --include='*.ts' --include='*.vue'
|
||||
## Backend Linting (on dev server)
|
||||
|
||||
```bash
|
||||
sshpass -p 'EwPDR8q45l0Upx@' ssh -o StrictHostKeyChecking=no archipelago@192.168.1.228 \
|
||||
ssh -i ~/.ssh/archipelago-deploy archipelago@192.168.1.228 \
|
||||
'source ~/.cargo/env && cd ~/archy/core && cargo clippy --all-targets --all-features 2>&1 && cargo fmt --all -- --check 2>&1'
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
# Skill: Polish Backend Quality
|
||||
|
||||
Fix Rust backend quality issues: eliminate panics, add timeouts, implement connection pooling, fix clippy warnings. The backend must never crash in production.
|
||||
|
||||
## Priority 1: Eliminate Panics
|
||||
|
||||
### Find all unwrap/expect in production code
|
||||
```bash
|
||||
ssh archipelago@192.168.1.228 "cd ~/archy && grep -rn 'unwrap()\|\.expect(' core/archipelago/src/ core/container/src/ core/security/src/ core/performance/src/ --include='*.rs' | grep -v test | grep -v '#\[test\]' | grep -v '_test.rs'"
|
||||
```
|
||||
|
||||
### Fix patterns:
|
||||
|
||||
**Response builder unwraps** (handler.rs):
|
||||
```rust
|
||||
// BAD
|
||||
Response::builder().body(body).unwrap()
|
||||
|
||||
// GOOD
|
||||
Response::builder().body(body).map_err(|e| {
|
||||
tracing::error!("Failed to build response: {}", e);
|
||||
// Return a minimal 500 response
|
||||
})?
|
||||
```
|
||||
|
||||
**Socket address parsing** (main.rs):
|
||||
```rust
|
||||
// BAD
|
||||
addr.parse().expect("Invalid bind address")
|
||||
|
||||
// GOOD
|
||||
addr.parse().context("Invalid bind address")?
|
||||
```
|
||||
|
||||
**TOTP secret creation** (totp.rs):
|
||||
```rust
|
||||
// BAD
|
||||
TOTP::new(...).unwrap()
|
||||
|
||||
// GOOD
|
||||
TOTP::new(...).map_err(|e| anyhow::anyhow!("Failed to create TOTP: {}", e))?
|
||||
```
|
||||
|
||||
**Cosign URL parsing** (image_verifier.rs):
|
||||
```rust
|
||||
// BAD
|
||||
sig_url.strip_prefix("cosign://").unwrap()
|
||||
|
||||
// GOOD
|
||||
sig_url.strip_prefix("cosign://")
|
||||
.ok_or_else(|| anyhow::anyhow!("Invalid cosign URL format: {}", sig_url))?
|
||||
```
|
||||
|
||||
## Priority 2: Add Timeouts
|
||||
|
||||
Every external call must have an explicit timeout:
|
||||
|
||||
```rust
|
||||
// Container operations
|
||||
tokio::time::timeout(Duration::from_secs(30), podman_operation()).await
|
||||
.context("Container operation timed out after 30s")??;
|
||||
|
||||
// HTTP calls (Bitcoin RPC, LND proxy)
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(Duration::from_secs(10))
|
||||
.build()?;
|
||||
|
||||
// Nostr operations
|
||||
tokio::time::timeout(Duration::from_secs(15), nostr_publish()).await
|
||||
.context("Nostr publish timed out")?;
|
||||
```
|
||||
|
||||
## Priority 3: Connection Pooling
|
||||
|
||||
Store a reusable `reqwest::Client` in `RpcHandler`:
|
||||
```rust
|
||||
pub struct RpcHandler {
|
||||
// ... existing fields
|
||||
http_client: reqwest::Client,
|
||||
}
|
||||
|
||||
impl RpcHandler {
|
||||
pub fn new(...) -> Self {
|
||||
let http_client = reqwest::Client::builder()
|
||||
.timeout(Duration::from_secs(10))
|
||||
.pool_max_idle_per_host(5)
|
||||
.build()
|
||||
.expect("Failed to create HTTP client");
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Use `self.http_client` everywhere instead of creating new clients per request.
|
||||
|
||||
## Priority 4: Fix Clippy Warnings
|
||||
|
||||
Run on dev server:
|
||||
```bash
|
||||
ssh archipelago@192.168.1.228 "cd ~/archy && cargo clippy --all-targets --all-features 2>&1"
|
||||
```
|
||||
|
||||
Known warnings to fix:
|
||||
- `should_implement_trait`: Implement `FromStr` for `AppManifest`
|
||||
- `get_first` → `.first()`
|
||||
- `assign_op_pattern` → use `+=`
|
||||
- `wildcard_in_or_patterns` → remove redundant `_`
|
||||
- `redundant_field_names` → shorthand
|
||||
- `very_complex_type` → type alias
|
||||
- `if_else_collapse` → simplify
|
||||
|
||||
## Priority 5: Replace println with tracing
|
||||
|
||||
```bash
|
||||
ssh archipelago@192.168.1.228 "cd ~/archy && grep -rn 'println!\|eprintln!' core/ --include='*.rs' | grep -v test | grep -v target/"
|
||||
```
|
||||
|
||||
Replace:
|
||||
- `println!("...")` → `tracing::info!("...")`
|
||||
- `eprintln!("...")` → `tracing::warn!("...")`
|
||||
|
||||
## Priority 6: Remove Dead Code
|
||||
|
||||
- Remove `#[allow(dead_code)]` annotations, verify if types are actually used
|
||||
- Remove unused fields (e.g., `identity_dir` in NodeIdentity)
|
||||
- Remove unused methods (e.g., `verify()`, `did_key()` in NodeIdentity)
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
ssh archipelago@192.168.1.228 "cd ~/archy && cargo clippy --all-targets --all-features 2>&1 | grep -c 'warning'"
|
||||
# Should be 0
|
||||
|
||||
ssh archipelago@192.168.1.228 "cd ~/archy && grep -rn 'unwrap()\|\.expect(' core/archipelago/src/ --include='*.rs' | grep -v test | grep -v '_test.rs' | wc -l"
|
||||
# Should be 0 (or near-zero with justified exceptions)
|
||||
|
||||
ssh archipelago@192.168.1.228 "cd ~/archy && grep -rn 'println!\|eprintln!' core/ --include='*.rs' | grep -v test | grep -v target/ | wc -l"
|
||||
# Should be 0
|
||||
```
|
||||
|
||||
## Build & Deploy
|
||||
|
||||
All Rust changes MUST be built on the dev server, never macOS:
|
||||
```bash
|
||||
./scripts/deploy-to-target.sh --live
|
||||
```
|
||||
|
||||
After deploy, verify:
|
||||
```bash
|
||||
ssh -i ~/.ssh/archipelago-deploy archipelago@192.168.1.228 "systemctl status archipelago && curl -s http://localhost:5678/health"
|
||||
```
|
||||
@@ -0,0 +1,176 @@
|
||||
# Skill: Polish Deployment Pipeline
|
||||
|
||||
Harden deploy-to-target.sh with rollback capability, pre-deploy checks, post-deploy health verification, and deployment locking.
|
||||
|
||||
## 1. Pre-Deploy Checks
|
||||
|
||||
Add to the beginning of deploy-to-target.sh:
|
||||
|
||||
```bash
|
||||
pre_deploy_checks() {
|
||||
echo "Running pre-deploy checks..."
|
||||
|
||||
# SSH key exists
|
||||
if [ ! -f "$SSH_KEY" ]; then
|
||||
echo "ERROR: SSH key not found at $SSH_KEY"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Target reachable
|
||||
ssh $SSH_OPTS "$TARGET_HOST" "echo ok" >/dev/null 2>&1 || {
|
||||
echo "ERROR: Cannot reach $TARGET_HOST"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Disk space (need 2GB free)
|
||||
local free_kb=$(ssh $SSH_OPTS "$TARGET_HOST" "df /home | tail -1 | awk '{print \$4}'")
|
||||
if [ "$free_kb" -lt 2097152 ]; then
|
||||
echo "ERROR: Need 2GB free disk space, have $(( free_kb / 1024 ))MB"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Pre-deploy checks passed"
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Backup Before Deploy
|
||||
|
||||
Before overwriting binary or frontend:
|
||||
|
||||
```bash
|
||||
backup_current() {
|
||||
echo "Backing up current deployment..."
|
||||
ssh $SSH_OPTS "$TARGET_HOST" "
|
||||
# Backup binary
|
||||
if [ -f /usr/local/bin/archipelago ]; then
|
||||
sudo cp /usr/local/bin/archipelago /usr/local/bin/archipelago.backup
|
||||
fi
|
||||
# Backup frontend
|
||||
if [ -d /opt/archipelago/web-ui ]; then
|
||||
sudo cp -a /opt/archipelago/web-ui /opt/archipelago/web-ui.backup
|
||||
fi
|
||||
# Backup nginx config
|
||||
if [ -f /etc/nginx/sites-available/archipelago ]; then
|
||||
sudo cp /etc/nginx/sites-available/archipelago /etc/nginx/sites-available/archipelago.backup
|
||||
fi
|
||||
"
|
||||
echo "Backup complete"
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Post-Deploy Health Check
|
||||
|
||||
After restarting services:
|
||||
|
||||
```bash
|
||||
health_check() {
|
||||
echo "Running post-deploy health check..."
|
||||
local max_attempts=15
|
||||
local attempt=0
|
||||
|
||||
while [ $attempt -lt $max_attempts ]; do
|
||||
attempt=$((attempt + 1))
|
||||
local status=$(ssh $SSH_OPTS "$TARGET_HOST" "curl -s -o /dev/null -w '%{http_code}' http://localhost:5678/health" 2>/dev/null)
|
||||
if [ "$status" = "200" ]; then
|
||||
echo "Health check passed (attempt $attempt)"
|
||||
return 0
|
||||
fi
|
||||
echo "Health check attempt $attempt/$max_attempts (status: $status)"
|
||||
sleep 2
|
||||
done
|
||||
|
||||
echo "ERROR: Health check failed after $max_attempts attempts"
|
||||
return 1
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Rollback on Failure
|
||||
|
||||
If health check fails:
|
||||
|
||||
```bash
|
||||
rollback() {
|
||||
echo "ROLLING BACK deployment..."
|
||||
ssh $SSH_OPTS "$TARGET_HOST" "
|
||||
# Restore binary
|
||||
if [ -f /usr/local/bin/archipelago.backup ]; then
|
||||
sudo cp /usr/local/bin/archipelago.backup /usr/local/bin/archipelago
|
||||
fi
|
||||
# Restore frontend
|
||||
if [ -d /opt/archipelago/web-ui.backup ]; then
|
||||
sudo rm -rf /opt/archipelago/web-ui
|
||||
sudo mv /opt/archipelago/web-ui.backup /opt/archipelago/web-ui
|
||||
fi
|
||||
# Restore nginx
|
||||
if [ -f /etc/nginx/sites-available/archipelago.backup ]; then
|
||||
sudo cp /etc/nginx/sites-available/archipelago.backup /etc/nginx/sites-available/archipelago
|
||||
sudo nginx -t && sudo systemctl reload nginx
|
||||
fi
|
||||
# Restart with old binary
|
||||
sudo systemctl restart archipelago
|
||||
"
|
||||
echo "Rollback complete. Previous version restored."
|
||||
}
|
||||
```
|
||||
|
||||
## 5. Deployment Lock
|
||||
|
||||
Prevent concurrent deploys:
|
||||
|
||||
```bash
|
||||
LOCK_FILE="/tmp/archipelago-deploy.lock"
|
||||
|
||||
acquire_lock() {
|
||||
exec 9>"$LOCK_FILE"
|
||||
flock -n 9 || {
|
||||
echo "ERROR: Another deployment is in progress"
|
||||
exit 1
|
||||
}
|
||||
trap "flock -u 9; rm -f $LOCK_FILE" EXIT
|
||||
}
|
||||
```
|
||||
|
||||
## 6. Nginx Config Validation
|
||||
|
||||
Before reloading nginx:
|
||||
|
||||
```bash
|
||||
validate_nginx() {
|
||||
ssh $SSH_OPTS "$TARGET_HOST" "sudo nginx -t" 2>&1 || {
|
||||
echo "ERROR: Nginx config invalid. Restoring backup..."
|
||||
ssh $SSH_OPTS "$TARGET_HOST" "
|
||||
sudo cp /etc/nginx/sites-available/archipelago.backup /etc/nginx/sites-available/archipelago
|
||||
sudo nginx -t && sudo systemctl reload nginx
|
||||
"
|
||||
return 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Integration
|
||||
|
||||
The deploy flow becomes:
|
||||
1. `acquire_lock`
|
||||
2. `pre_deploy_checks`
|
||||
3. `backup_current`
|
||||
4. Build + deploy (existing logic)
|
||||
5. `validate_nginx`
|
||||
6. Restart services
|
||||
7. `health_check || rollback`
|
||||
|
||||
## Verification
|
||||
|
||||
Test the rollback:
|
||||
1. Deploy a working version
|
||||
2. Intentionally break the binary (e.g., truncate it)
|
||||
3. Deploy the broken version
|
||||
4. Verify rollback triggers and previous version is restored
|
||||
5. Verify service is healthy after rollback
|
||||
|
||||
## Deploy
|
||||
|
||||
```bash
|
||||
./scripts/deploy-to-target.sh --live
|
||||
```
|
||||
|
||||
After modifying the deploy script itself, test with a known-good deploy first.
|
||||
@@ -0,0 +1,82 @@
|
||||
# Skill: Polish Error Handling
|
||||
|
||||
Fix silent error handling patterns across the entire codebase. Every async operation must have visible, actionable error feedback for the user.
|
||||
|
||||
## What to Fix
|
||||
|
||||
### Frontend (neode-ui/src/)
|
||||
|
||||
1. **Silent catch blocks**: Find and replace all `.catch(() => {})` patterns
|
||||
- Search: `grep -rn "catch.*=>.*{}" --include="*.vue" --include="*.ts" src/`
|
||||
- Replace with: proper error logging + user-visible feedback (toast, inline error, or modal)
|
||||
- Pattern:
|
||||
```typescript
|
||||
.catch((err) => {
|
||||
console.error('[ComponentName] operation failed:', err)
|
||||
errorMessage.value = formatError(err)
|
||||
})
|
||||
```
|
||||
|
||||
2. **Unhandled router.push**: Find `router.push(...).catch(() => {})`
|
||||
- Replace with: `router.push(...).catch(console.error)` minimum
|
||||
- Or handle NavigationDuplicated gracefully
|
||||
|
||||
3. **Silent try/catch**: Find `try { ... } catch { /* empty */ }`
|
||||
- Every catch block must either: log the error, show user feedback, or explicitly comment why it's safe to ignore
|
||||
|
||||
4. **Missing error states**: For each view, verify:
|
||||
- `ref<string | null>` error variable exists
|
||||
- Error is displayed in template (inline message, not just console)
|
||||
- Error clears on retry or navigation
|
||||
|
||||
### Backend (core/)
|
||||
|
||||
5. **Silent error swallowing**: Find `unwrap_or_default()` on serialization
|
||||
- Replace with proper error propagation or logging
|
||||
- Pattern: `.map_err(|e| anyhow::anyhow!("Serialization failed: {}", e))?`
|
||||
|
||||
6. **Error response consistency**: All RPC errors should use:
|
||||
- Consistent error codes (not random negative numbers)
|
||||
- Human-readable messages
|
||||
- Consistent JSON structure
|
||||
|
||||
## Verification
|
||||
|
||||
After fixes, run:
|
||||
```bash
|
||||
# Zero silent catches
|
||||
grep -rn "catch.*=>.*{}\|catch\s*{" neode-ui/src/ --include="*.vue" --include="*.ts" | grep -v node_modules | grep -v "console\|error\|log\|warn"
|
||||
|
||||
# Zero empty catch blocks
|
||||
grep -rn "catch.*{$" neode-ui/src/ --include="*.vue" --include="*.ts" -A1 | grep -P "^\d+-\s*\}"
|
||||
```
|
||||
|
||||
Both should return zero results.
|
||||
|
||||
## Error Display Pattern
|
||||
|
||||
Use this consistent pattern for user-facing errors:
|
||||
```typescript
|
||||
const errorMessage = ref<string | null>(null)
|
||||
|
||||
async function doAction() {
|
||||
errorMessage.value = null
|
||||
try {
|
||||
await rpcClient.someCall()
|
||||
} catch (err) {
|
||||
errorMessage.value = err instanceof Error ? err.message : 'Operation failed'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Template:
|
||||
```vue
|
||||
<p v-if="errorMessage" class="text-red-400 text-sm mt-2">{{ errorMessage }}</p>
|
||||
```
|
||||
|
||||
## Deploy After Fixes
|
||||
|
||||
Always deploy and verify on live server after making changes:
|
||||
```bash
|
||||
./scripts/deploy-to-target.sh --live
|
||||
```
|
||||
@@ -0,0 +1,120 @@
|
||||
# Skill: Polish Form Validation
|
||||
|
||||
Improve all form inputs to have real-time validation feedback, proper trimming, disabled states during submission, and consistent error messaging.
|
||||
|
||||
## Forms to Polish
|
||||
|
||||
### 1. Login.vue — Password Setup
|
||||
- Real-time validation as user types (debounced 300ms):
|
||||
- Length >= 8 chars (show checkmark/X)
|
||||
- Passwords match (show match indicator)
|
||||
- Trim input on submit
|
||||
- Disable submit button while `isSubmitting`
|
||||
- Clear error on new input
|
||||
|
||||
### 2. Login.vue — TOTP Verification
|
||||
- `inputmode="numeric"` + `pattern="[0-9]*"`
|
||||
- Auto-submit when 6 digits entered
|
||||
- Show session timeout countdown if applicable
|
||||
- Trim and strip non-numeric characters on paste
|
||||
|
||||
### 3. Settings.vue — Password Change
|
||||
- Real-time strength validation:
|
||||
- 12+ characters
|
||||
- Has uppercase, lowercase, digit, special char
|
||||
- New password matches confirmation
|
||||
- Show strength meter (weak/medium/strong)
|
||||
- Disable button during submission
|
||||
- Show spinner in button during async operation
|
||||
|
||||
### 4. Any other form inputs found across views
|
||||
|
||||
## Validation Pattern
|
||||
|
||||
```typescript
|
||||
const password = ref('')
|
||||
const confirmPassword = ref('')
|
||||
const isSubmitting = ref(false)
|
||||
|
||||
const passwordErrors = computed(() => {
|
||||
const errors: string[] = []
|
||||
if (password.value.length > 0 && password.value.length < 8)
|
||||
errors.push('Must be at least 8 characters')
|
||||
return errors
|
||||
})
|
||||
|
||||
const passwordsMatch = computed(() =>
|
||||
confirmPassword.value.length > 0 && password.value === confirmPassword.value
|
||||
)
|
||||
|
||||
async function submit() {
|
||||
if (isSubmitting.value) return
|
||||
isSubmitting.value = true
|
||||
try {
|
||||
await rpcClient.call(...)
|
||||
} catch (err) {
|
||||
errorMessage.value = formatError(err)
|
||||
} finally {
|
||||
isSubmitting.value = false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Template Pattern
|
||||
|
||||
```vue
|
||||
<input v-model="password" type="password" class="glass-input" />
|
||||
<ul v-if="passwordErrors.length" class="text-red-400 text-xs mt-1 space-y-0.5">
|
||||
<li v-for="err in passwordErrors" :key="err">{{ err }}</li>
|
||||
</ul>
|
||||
|
||||
<button
|
||||
class="glass-button"
|
||||
:disabled="isSubmitting || passwordErrors.length > 0"
|
||||
@click="submit"
|
||||
>
|
||||
<span v-if="isSubmitting">Saving...</span>
|
||||
<span v-else>Save</span>
|
||||
</button>
|
||||
```
|
||||
|
||||
## Input Trimming
|
||||
|
||||
All text inputs should be trimmed before submission:
|
||||
```typescript
|
||||
const trimmed = password.value.trim()
|
||||
```
|
||||
|
||||
## Error Message Consistency
|
||||
|
||||
Create or use a `formatError` utility:
|
||||
```typescript
|
||||
function formatError(err: unknown): string {
|
||||
if (err instanceof Error) {
|
||||
if (err.message.includes('fetch') || err.message.includes('network'))
|
||||
return 'Unable to reach server. Check your connection.'
|
||||
if (err.message.includes('401') || err.message.includes('unauthorized'))
|
||||
return 'Session expired. Please log in again.'
|
||||
return err.message
|
||||
}
|
||||
return 'Something went wrong. Please try again.'
|
||||
}
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
For each form:
|
||||
- [ ] Real-time validation shows feedback as user types
|
||||
- [ ] Submit button disabled during operation
|
||||
- [ ] Submit button disabled when validation fails
|
||||
- [ ] Inputs trimmed before submission
|
||||
- [ ] Error messages are user-friendly (no raw error strings)
|
||||
- [ ] Success feedback shown after completion
|
||||
|
||||
## Deploy After Fixes
|
||||
|
||||
```bash
|
||||
./scripts/deploy-to-target.sh --live
|
||||
```
|
||||
|
||||
Test each form with: valid input, invalid input, empty input, whitespace-only input, rapid double-click on submit.
|
||||
@@ -0,0 +1,83 @@
|
||||
# Skill: Polish Loading States
|
||||
|
||||
Add skeleton loaders, loading indicators, timeout warnings, and empty states to all async views. No view should ever show a blank screen.
|
||||
|
||||
## Skeleton Loader Component
|
||||
|
||||
Create or use a `SkeletonLoader.vue` component with the glassmorphism style:
|
||||
- Background: `bg-white/5` with shimmer animation
|
||||
- Rounded corners matching the card it replaces
|
||||
- Animate with CSS `@keyframes shimmer` (translate gradient left to right)
|
||||
- Must use global classes from style.css, not inline Tailwind
|
||||
|
||||
## Views to Fix
|
||||
|
||||
For EACH view in `neode-ui/src/views/`, verify these states exist:
|
||||
|
||||
### 1. Loading State
|
||||
- Show skeleton placeholders immediately on mount
|
||||
- Pattern:
|
||||
```vue
|
||||
<template>
|
||||
<div v-if="isLoading">
|
||||
<!-- Skeleton matching the layout -->
|
||||
</div>
|
||||
<div v-else>
|
||||
<!-- Real content -->
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
### 2. Empty State
|
||||
- When data loads but is empty (zero items)
|
||||
- Show helpful message with CTA
|
||||
- Pattern:
|
||||
```vue
|
||||
<div v-if="!isLoading && items.length === 0" class="glass-card text-center py-12">
|
||||
<p class="text-white/60">No apps installed yet</p>
|
||||
<router-link to="/marketplace" class="glass-button mt-4">Browse Marketplace</router-link>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 3. Timeout Warning
|
||||
- After 15 seconds of loading, show "Taking longer than expected..."
|
||||
- After 30 seconds, show troubleshooting options
|
||||
- Pattern:
|
||||
```typescript
|
||||
const loadingTooLong = ref(false)
|
||||
let timeout: ReturnType<typeof setTimeout>
|
||||
|
||||
onMounted(() => {
|
||||
timeout = setTimeout(() => { loadingTooLong.value = true }, 15000)
|
||||
})
|
||||
|
||||
watch(isLoading, (val) => { if (!val) clearTimeout(timeout) })
|
||||
```
|
||||
|
||||
## Priority Views (must have all 3 states)
|
||||
|
||||
1. **Apps.vue** — app grid skeleton, "No apps installed" empty state
|
||||
2. **AppDetails.vue** — detail card skeleton, loading indicator
|
||||
3. **Marketplace.vue** — app card grid skeleton, "Loading apps..." with timeout
|
||||
4. **Dashboard.vue** — metric card skeletons
|
||||
5. **Cloud.vue** — file list skeleton, "No files" empty state
|
||||
6. **Settings.vue** — settings section skeleton
|
||||
7. **Server.vue** — server info skeleton
|
||||
|
||||
## Verification
|
||||
|
||||
For each view, confirm:
|
||||
- [ ] `isLoading` ref exists and is set properly
|
||||
- [ ] Template has `v-if="isLoading"` skeleton section
|
||||
- [ ] Template has empty state for zero-data case
|
||||
- [ ] Loading timeout warning after 15s
|
||||
- [ ] Skeleton uses global classes, not inline Tailwind
|
||||
|
||||
## Deploy After Fixes
|
||||
|
||||
Always deploy and verify on live server:
|
||||
```bash
|
||||
./scripts/deploy-to-target.sh --live
|
||||
```
|
||||
|
||||
Test by throttling network in browser DevTools to observe loading states.
|
||||
@@ -0,0 +1,157 @@
|
||||
# Skill: Polish Security
|
||||
|
||||
Security hardening pass for systemd, nginx, secrets management, and rate limiting.
|
||||
|
||||
## 1. Systemd Service Hardening
|
||||
|
||||
File: `image-recipe/configs/archipelago.service`
|
||||
|
||||
Add these directives to the `[Service]` section:
|
||||
```ini
|
||||
PrivateTmp=yes
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=yes
|
||||
ReadWritePaths=/var/lib/archipelago
|
||||
SystemCallFilter=@system-service
|
||||
SystemCallFilter=~@privileged @resources
|
||||
```
|
||||
|
||||
After editing, sync to server and verify:
|
||||
```bash
|
||||
ssh archipelago@192.168.1.228 "sudo systemd-analyze security archipelago"
|
||||
```
|
||||
|
||||
## 2. Nginx Security Headers
|
||||
|
||||
File: `image-recipe/configs/nginx-archipelago.conf`
|
||||
|
||||
### Add HSTS (HTTPS block only):
|
||||
```nginx
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||
```
|
||||
|
||||
### Fix CSP (remove unsafe-inline):
|
||||
Replace:
|
||||
```nginx
|
||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; connect-src 'self' ws: wss:; frame-src 'self' http://localhost:* http://192.168.*:*;" always;
|
||||
```
|
||||
|
||||
With CSP that uses nonces or hashes for inline scripts/styles. If inline scripts can't be removed yet, document which ones and plan their removal.
|
||||
|
||||
### Add rate limiting zones:
|
||||
```nginx
|
||||
# In http block:
|
||||
limit_req_zone $binary_remote_addr zone=api:10m rate=30r/s;
|
||||
limit_req_zone $binary_remote_addr zone=auth:10m rate=5r/m;
|
||||
|
||||
# On login/auth endpoints:
|
||||
limit_req zone=auth burst=3 nodelay;
|
||||
|
||||
# On API endpoints:
|
||||
limit_req zone=api burst=50 nodelay;
|
||||
```
|
||||
|
||||
### Custom log format (strip tokens):
|
||||
```nginx
|
||||
log_format no_tokens '$remote_addr - $remote_user [$time_local] "$request_method $uri $server_protocol" $status $body_bytes_sent "$http_referer"';
|
||||
access_log /var/log/nginx/archipelago_access.log no_tokens;
|
||||
```
|
||||
|
||||
## 3. Secrets Management
|
||||
|
||||
### Remove hardcoded RPC credentials from scripts
|
||||
File: `scripts/deploy-to-target.sh`
|
||||
|
||||
Replace:
|
||||
```bash
|
||||
-e CORE_RPC_USERNAME=archipelago -e CORE_RPC_PASSWORD=archipelago123
|
||||
```
|
||||
|
||||
With:
|
||||
```bash
|
||||
-e CORE_RPC_USERNAME=archipelago -e CORE_RPC_PASSWORD=$(cat /var/lib/archipelago/secrets/bitcoin-rpc-pass)
|
||||
```
|
||||
|
||||
### Generate secrets on first boot
|
||||
File: `scripts/first-boot-containers.sh`
|
||||
|
||||
Add at the top:
|
||||
```bash
|
||||
SECRETS_DIR="/var/lib/archipelago/secrets"
|
||||
mkdir -p "$SECRETS_DIR"
|
||||
chmod 700 "$SECRETS_DIR"
|
||||
|
||||
# Generate Bitcoin RPC password if not exists
|
||||
if [ ! -f "$SECRETS_DIR/bitcoin-rpc-pass" ]; then
|
||||
openssl rand -base64 24 > "$SECRETS_DIR/bitcoin-rpc-pass"
|
||||
chmod 600 "$SECRETS_DIR/bitcoin-rpc-pass"
|
||||
fi
|
||||
```
|
||||
|
||||
### Remove hardcoded credentials from Rust backend
|
||||
File: `core/archipelago/src/api/rpc/bitcoin.rs`
|
||||
|
||||
Replace:
|
||||
```rust
|
||||
.basic_auth("archipelago", Some("archipelago123"))
|
||||
```
|
||||
|
||||
With:
|
||||
```rust
|
||||
let rpc_user = std::env::var("ARCHIPELAGO_BITCOIN_RPC_USER").unwrap_or_else(|_| "archipelago".into());
|
||||
let rpc_pass = std::env::var("ARCHIPELAGO_BITCOIN_RPC_PASS").unwrap_or_else(|_| "archipelago123".into());
|
||||
.basic_auth(&rpc_user, Some(&rpc_pass))
|
||||
```
|
||||
|
||||
## 4. Rate Limiting on Backend
|
||||
|
||||
File: `core/archipelago/src/api/handler.rs`
|
||||
|
||||
Add rate limiting to unauthenticated endpoints:
|
||||
- `/archipelago/node-message` — 10 req/min per IP
|
||||
- `/electrs-status` — 30 req/min per IP
|
||||
|
||||
Use an in-memory `HashMap<IpAddr, (Instant, u32)>` with cleanup on access.
|
||||
|
||||
## 5. SSH Hardening
|
||||
|
||||
File: `scripts/deploy-to-target.sh`
|
||||
|
||||
Replace:
|
||||
```bash
|
||||
SSH_OPTS="-o StrictHostKeyChecking=no"
|
||||
```
|
||||
|
||||
With:
|
||||
```bash
|
||||
SSH_OPTS="-o StrictHostKeyChecking=accept-new"
|
||||
```
|
||||
|
||||
And add SSH key validation:
|
||||
```bash
|
||||
if [ ! -f "$SSH_KEY" ]; then
|
||||
echo "ERROR: SSH key not found at $SSH_KEY"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
## Verification Checklist
|
||||
|
||||
- [ ] `systemd-analyze security archipelago` score < 5.0 (lower is better)
|
||||
- [ ] Nginx headers pass: `curl -I http://192.168.1.228 | grep -i 'strict-transport\|content-security\|x-frame'`
|
||||
- [ ] No hardcoded passwords in scripts: `grep -rn 'archipelago123' scripts/ core/`
|
||||
- [ ] Rate limiting works: rapid-fire requests get 429
|
||||
- [ ] SSH key required (no password fallback)
|
||||
|
||||
## Deploy
|
||||
|
||||
After changes, sync configs and deploy:
|
||||
```bash
|
||||
./scripts/deploy-to-target.sh --live
|
||||
```
|
||||
|
||||
Then sync to ISO recipe:
|
||||
```bash
|
||||
# Run /sync-configs skill
|
||||
```
|
||||
@@ -0,0 +1,167 @@
|
||||
# Skill: Polish WebSocket & Real-Time
|
||||
|
||||
Improve WebSocket reliability, reconnection UX, heartbeat, session timeout detection, and connection status indicators.
|
||||
|
||||
## 1. Connection Status Indicator
|
||||
|
||||
### Create or update connection status display
|
||||
- **Location**: App.vue header or create ConnectionStatus.vue component
|
||||
- **States**: Connected (green), Reconnecting (amber pulse), Disconnected (red)
|
||||
- **Data source**: `wsClient.isConnected()` from websocket.ts
|
||||
- **Style**: Use existing design tokens, small dot + text in header area
|
||||
|
||||
```vue
|
||||
<div class="flex items-center gap-1.5">
|
||||
<div :class="[
|
||||
'w-2 h-2 rounded-full',
|
||||
isConnected ? 'bg-green-400' : isReconnecting ? 'bg-amber-400 animate-pulse' : 'bg-red-400'
|
||||
]" />
|
||||
<span class="text-xs text-white/40">
|
||||
{{ isConnected ? '' : isReconnecting ? 'Reconnecting...' : 'Offline' }}
|
||||
</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Fix OnlineStatusPill.vue
|
||||
- Connect to actual WebSocket state instead of hardcoded "Online"
|
||||
- Use the app store's connection state
|
||||
|
||||
## 2. Reconnection UX
|
||||
|
||||
### Don't silently give up
|
||||
File: `api/websocket.ts`
|
||||
|
||||
After max reconnect attempts (currently 10), instead of silently stopping:
|
||||
- Set a `permanentlyDisconnected` flag
|
||||
- Emit event that App.vue listens to
|
||||
- Show persistent banner: "Connection lost. Click to retry." or "Refresh page to reconnect."
|
||||
|
||||
```typescript
|
||||
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
||||
this.shouldReconnect = false
|
||||
this.notifyConnectionState(false)
|
||||
// Emit permanent disconnect event
|
||||
this.onPermanentDisconnect?.()
|
||||
}
|
||||
```
|
||||
|
||||
### Allow manual reconnect
|
||||
Add a `forceReconnect()` method that resets attempt counter and tries again:
|
||||
```typescript
|
||||
forceReconnect() {
|
||||
this.reconnectAttempts = 0
|
||||
this.shouldReconnect = true
|
||||
this.connect()
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Heartbeat Improvement
|
||||
|
||||
File: `api/websocket.ts`
|
||||
|
||||
Current: 60-second stale detection (passive).
|
||||
Target: 30-second active ping with 5-second pong timeout.
|
||||
|
||||
```typescript
|
||||
private startHeartbeat() {
|
||||
this.heartbeatInterval = setInterval(() => {
|
||||
if (this.ws?.readyState === WebSocket.OPEN) {
|
||||
this.ws.send(JSON.stringify({ type: 'ping' }))
|
||||
this.pongTimeout = setTimeout(() => {
|
||||
// No pong received — connection is dead
|
||||
this.ws?.close()
|
||||
this.handleReconnect()
|
||||
}, 5000)
|
||||
}
|
||||
}, 30000)
|
||||
}
|
||||
|
||||
// In message handler:
|
||||
if (data.type === 'pong') {
|
||||
clearTimeout(this.pongTimeout)
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
Note: Backend must respond to `ping` with `pong`. Check handler.rs WebSocket handler.
|
||||
|
||||
## 4. Session Timeout Detection
|
||||
|
||||
File: `api/rpc-client.ts`
|
||||
|
||||
When RPC returns 401 or 403:
|
||||
```typescript
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
// Session expired — redirect to login
|
||||
window.location.href = '/login'
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
This should be in the base `call()` method so it applies to all RPC calls.
|
||||
|
||||
## 5. Fix Race Condition on Reconnect
|
||||
|
||||
File: `stores/app.ts` or `api/websocket.ts`
|
||||
|
||||
Problem: `isWsSubscribed` flag doesn't prevent duplicate listeners on rapid reconnect.
|
||||
|
||||
Fix: Use listener deduplication:
|
||||
```typescript
|
||||
private listeners = new Map<string, Set<Function>>()
|
||||
|
||||
subscribe(event: string, callback: Function) {
|
||||
if (!this.listeners.has(event)) {
|
||||
this.listeners.set(event, new Set())
|
||||
}
|
||||
this.listeners.get(event)!.add(callback)
|
||||
}
|
||||
```
|
||||
|
||||
Or simpler: remove all listeners before reconnect, then re-add:
|
||||
```typescript
|
||||
onReconnect() {
|
||||
// Clear old subscriptions
|
||||
this.removeAllListeners()
|
||||
// Re-subscribe
|
||||
this.setupSubscriptions()
|
||||
}
|
||||
```
|
||||
|
||||
## 6. Message Queuing During Disconnect
|
||||
|
||||
When WebSocket is down, queue subscription requests:
|
||||
```typescript
|
||||
private pendingSubscriptions: Array<() => void> = []
|
||||
|
||||
subscribe(event: string, callback: Function) {
|
||||
if (this.ws?.readyState !== WebSocket.OPEN) {
|
||||
this.pendingSubscriptions.push(() => this.subscribe(event, callback))
|
||||
return
|
||||
}
|
||||
// Normal subscribe logic
|
||||
}
|
||||
|
||||
onReconnected() {
|
||||
// Replay pending subscriptions
|
||||
const pending = [...this.pendingSubscriptions]
|
||||
this.pendingSubscriptions = []
|
||||
pending.forEach(fn => fn())
|
||||
}
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
1. **Kill backend** → frontend shows "Disconnected" → restart backend → frontend reconnects and shows "Connected"
|
||||
2. **Toggle wifi** → status indicator updates → wifi back → auto-reconnect
|
||||
3. **Wait for session timeout** → next RPC call redirects to login
|
||||
4. **Rapid reconnect** → no duplicate event handlers (check with DevTools)
|
||||
5. **Leave tab in background** → come back → status is accurate
|
||||
|
||||
## Deploy
|
||||
|
||||
```bash
|
||||
./scripts/deploy-to-target.sh --live
|
||||
```
|
||||
|
||||
Test with browser DevTools Network tab to observe WebSocket frames.
|
||||
@@ -0,0 +1,104 @@
|
||||
# Skill: Production Polish (Overnight Orchestrator)
|
||||
|
||||
Main entry point for the Archipelago production polish plan. Reads `plan.md` at the project root, determines the current week based on today's date, and executes the tasks for that week.
|
||||
|
||||
## How It Works
|
||||
|
||||
1. Read `plan.md` from the project root
|
||||
2. Determine the current week from the schedule:
|
||||
- Week 1: March 10–16 — Silent Failures & Error Handling
|
||||
- Week 2: March 17–23 — Loading States & Visual Feedback
|
||||
- Week 3: March 24–30 — Form Validation & Input Quality
|
||||
- Week 4: March 31 – April 6 — Backend Robustness
|
||||
- Week 5: April 7–13 — WebSocket & Real-Time Quality
|
||||
- Week 6: April 14–20 — Deployment & Infrastructure Hardening
|
||||
- Week 7: April 21–27 — Accessibility, Polish & Edge Cases
|
||||
- Week 8: April 28 – May 4 — Integration Testing, Final Sweep & ISO
|
||||
3. Execute tasks for the current week, in order
|
||||
4. After completing tasks, run `/sweep` to verify
|
||||
5. Deploy and verify with `/deploy` then `/check-server`
|
||||
|
||||
## Execution Flow
|
||||
|
||||
### Step 1: Read the plan
|
||||
```
|
||||
Read plan.md and find the current week's section
|
||||
```
|
||||
|
||||
### Step 2: Check what's already done
|
||||
Run the verification checks for the current week's tasks. For example in Week 1:
|
||||
- Count remaining `.catch(() => {})` patterns
|
||||
- Count remaining `console.log` outside dev guards
|
||||
- Count remaining `unwrap()` in backend production code
|
||||
- Check if hardcoded credentials still exist
|
||||
|
||||
### Step 3: Work on the next incomplete task
|
||||
Pick the first task in the current week that still has violations (hasn't met its acceptance criteria). Fix violations one file at a time:
|
||||
1. Read the file
|
||||
2. Apply the fix following the pattern described in the task
|
||||
3. Verify the fix compiles/type-checks
|
||||
4. Move to the next violation
|
||||
|
||||
### Step 4: Verify after each batch of fixes
|
||||
After fixing all violations for a task:
|
||||
- Frontend: `cd neode-ui && npx vue-tsc --noEmit`
|
||||
- Backend: `ssh archipelago@192.168.1.228 "cd ~/archy && cargo check"`
|
||||
- Run the task's specific acceptance grep/check
|
||||
|
||||
### Step 5: Deploy when a task is complete
|
||||
When all violations for a task are fixed and verified:
|
||||
```bash
|
||||
./scripts/deploy-to-target.sh --live
|
||||
```
|
||||
Then verify:
|
||||
```bash
|
||||
ssh -i ~/.ssh/archipelago-deploy archipelago@192.168.1.228 "systemctl is-active archipelago && curl -s http://localhost:5678/health"
|
||||
```
|
||||
|
||||
### Step 6: Move to the next task
|
||||
Repeat Steps 3-5 for the next incomplete task in the current week.
|
||||
|
||||
### Step 7: When all tasks are done
|
||||
Run `/sweep` for a full quality report. If clean, the week is complete.
|
||||
|
||||
## Rules
|
||||
|
||||
- **Never change functionality** — only improve quality of existing code
|
||||
- **Never change the design** — use existing glassmorphism classes, color tokens, and layout patterns
|
||||
- **Always deploy after changes** — don't leave undeployed code
|
||||
- **Always verify after deploy** — check server health
|
||||
- **Build Rust on the dev server** — never compile Rust on macOS
|
||||
- **Commit after each completed task** — atomic commits with `fix:` or `refactor:` prefix
|
||||
- **If something breaks, revert** — don't push forward with broken code
|
||||
|
||||
## Arguments
|
||||
|
||||
If `$ARGUMENTS` is provided:
|
||||
- `week N` — Force execution of week N regardless of date
|
||||
- `task N.M` — Execute only task N.M (e.g., `task 1.3`)
|
||||
- `status` — Show completion status for all weeks without executing
|
||||
- `sweep` — Run sweep only, no fixes
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
/polish # Auto-detect week, work on next incomplete task
|
||||
/polish week 1 # Force Week 1 tasks
|
||||
/polish task 1.3 # Work on just task 1.3
|
||||
/polish status # Show what's done and what's left
|
||||
/polish sweep # Just run the quality sweep
|
||||
```
|
||||
|
||||
## For Overnight TUI
|
||||
|
||||
Launch with:
|
||||
```
|
||||
/loop 30m /polish
|
||||
```
|
||||
|
||||
Each 30-minute cycle:
|
||||
1. Checks current week
|
||||
2. Finds next incomplete task
|
||||
3. Fixes as many violations as possible in the time available
|
||||
4. Deploys and verifies
|
||||
5. Reports progress
|
||||
@@ -5,7 +5,7 @@ allowed-tools: Bash
|
||||
argument-hint: "[backend|nginx|container-name]"
|
||||
---
|
||||
|
||||
View logs from the Archipelago server (192.168.1.228). Use `sshpass -p 'EwPDR8q45l0Upx@' ssh -o StrictHostKeyChecking=no archipelago@192.168.1.228` for all commands.
|
||||
View logs from the Archipelago server (192.168.1.228). Use `ssh -i ~/.ssh/archipelago-deploy archipelago@192.168.1.228` for all commands.
|
||||
|
||||
If $ARGUMENTS is provided, show logs for that specific service. Otherwise, show backend logs by default.
|
||||
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
# Skill: Quality Sweep
|
||||
|
||||
Full automated quality sweep across the entire codebase. Detects regressions, violations, and quality issues. This is the overnight watchdog.
|
||||
|
||||
Run all checks below sequentially. For each check, use the Grep tool (not bash grep) for local file scanning, and Bash for remote/build commands. Report a summary at the end.
|
||||
|
||||
## Checks
|
||||
|
||||
### 1. TypeScript Type Check
|
||||
Run in bash:
|
||||
```bash
|
||||
cd /Users/dorian/Projects/archy/neode-ui && npx vue-tsc --noEmit 2>&1 | tail -20
|
||||
```
|
||||
PASS = zero errors. Count any errors found.
|
||||
|
||||
### 2. Frontend Violations
|
||||
Use the Grep tool to scan `neode-ui/src/` for each pattern. Count matches for each:
|
||||
|
||||
**Silent catch blocks** — pattern: `catch\s*\(\s*\)\s*=>?\s*\{\s*\}` or `\.catch\(\(\)\s*=>\s*\{\}` in `*.vue` and `*.ts` files
|
||||
|
||||
**console.log in prod** — pattern: `console\.(log|warn|error)` in `*.vue` and `*.ts` files. Exclude lines containing `import.meta.env.DEV` or `// dev-only`
|
||||
|
||||
**any type usage** — pattern: `:\s*any[^a-zA-Z]|as\s+any[^a-zA-Z]` in `*.vue` and `*.ts` files. Exclude `.d.ts` files
|
||||
|
||||
**TODO/FIXME/HACK** — pattern: `TODO|FIXME|HACK|XXX` in `*.vue` and `*.ts` files
|
||||
|
||||
**Banned CSS classes** — pattern: `gradient-button|gradient-card` in `*.vue` files
|
||||
|
||||
### 3. Backend Violations (via SSH)
|
||||
Run in bash:
|
||||
```bash
|
||||
ssh -i ~/.ssh/archipelago-deploy archipelago@192.168.1.228 "
|
||||
echo '--- unwrap/expect ---'
|
||||
grep -rn 'unwrap()\|\.expect(' ~/archy/core/archipelago/src/ ~/archy/core/container/src/ ~/archy/core/security/src/ --include='*.rs' | grep -v test | grep -v '_test.rs' | grep -v target/ | wc -l
|
||||
|
||||
echo '--- println/eprintln ---'
|
||||
grep -rn 'println!\|eprintln!' ~/archy/core/ --include='*.rs' | grep -v test | grep -v target/ | wc -l
|
||||
|
||||
echo '--- TODO/FIXME ---'
|
||||
grep -rn 'TODO\|FIXME\|HACK' ~/archy/core/ --include='*.rs' | grep -v target/ | wc -l
|
||||
"
|
||||
```
|
||||
|
||||
### 4. Hardcoded Credentials
|
||||
Use Grep tool locally — pattern: `archipelago123|password123` in `core/` and `scripts/` directories, excluding `target/`, `node_modules/`, and `deploy-config.sh`
|
||||
|
||||
### 5. Server Health
|
||||
Run in bash:
|
||||
```bash
|
||||
ssh -i ~/.ssh/archipelago-deploy archipelago@192.168.1.228 "
|
||||
echo 'service:' \$(systemctl is-active archipelago)
|
||||
echo 'health:' \$(curl -s -o /dev/null -w '%{http_code}' http://localhost:5678/health)
|
||||
echo 'containers:' \$(podman ps -q 2>/dev/null | wc -l || docker ps -q | wc -l)
|
||||
echo 'errors:' \$(journalctl -u archipelago --since '1 hour ago' --no-pager -p err 2>/dev/null | wc -l)
|
||||
echo 'disk:' \$(df -h / | tail -1 | awk '{print \$5}')
|
||||
"
|
||||
```
|
||||
|
||||
### 6. Frontend Build
|
||||
Run in bash:
|
||||
```bash
|
||||
cd /Users/dorian/Projects/archy/neode-ui && npm run build 2>&1 | tail -5
|
||||
```
|
||||
PASS = exit code 0.
|
||||
|
||||
## Report Format
|
||||
|
||||
After all checks, output a summary exactly like this:
|
||||
|
||||
```
|
||||
=== SWEEP REPORT ===
|
||||
|
||||
TypeScript: PASS/FAIL (N errors)
|
||||
Silent catches: PASS/FAIL (N)
|
||||
Console.log: PASS/FAIL (N)
|
||||
Any types: PASS/FAIL (N)
|
||||
TODOs: PASS/FAIL (N)
|
||||
Banned classes: PASS/FAIL (N)
|
||||
Backend unwrap: PASS/FAIL (N)
|
||||
Backend println: PASS/FAIL (N)
|
||||
Hardcoded creds: PASS/FAIL (N)
|
||||
Server health: PASS/FAIL
|
||||
Frontend build: PASS/FAIL
|
||||
|
||||
Total violations: N
|
||||
```
|
||||
|
||||
PASS = zero violations for that check. FAIL = one or more.
|
||||
|
||||
## Auto-Fix Rules
|
||||
|
||||
Safe to auto-fix without asking:
|
||||
- `cargo fmt --all` on dev server (formatting only)
|
||||
- Trailing whitespace removal
|
||||
- Import ordering
|
||||
|
||||
Do NOT auto-fix (flag for review):
|
||||
- Error handling changes
|
||||
- Logic or behavior changes
|
||||
- Anything in core/ Rust files beyond formatting
|
||||
|
||||
## Reference
|
||||
|
||||
Full plan with weekly task breakdown: `plan.md` (project root)
|
||||
Current week's focus determines which violations are highest priority.
|
||||
@@ -11,12 +11,12 @@ Sync system configuration files from the live server back to the repo for ISO bu
|
||||
|
||||
1. **Capture systemd service**:
|
||||
```bash
|
||||
sshpass -p 'EwPDR8q45l0Upx@' ssh -o StrictHostKeyChecking=no archipelago@192.168.1.228 'sudo cat /etc/systemd/system/archipelago.service' > image-recipe/configs/archipelago.service
|
||||
ssh -i ~/.ssh/archipelago-deploy archipelago@192.168.1.228 'sudo cat /etc/systemd/system/archipelago.service' > image-recipe/configs/archipelago.service
|
||||
```
|
||||
|
||||
2. **Capture nginx config**:
|
||||
```bash
|
||||
sshpass -p 'EwPDR8q45l0Upx@' ssh -o StrictHostKeyChecking=no archipelago@192.168.1.228 'sudo cat /etc/nginx/sites-available/archipelago' > image-recipe/configs/nginx-archipelago.conf
|
||||
ssh -i ~/.ssh/archipelago-deploy archipelago@192.168.1.228 'sudo cat /etc/nginx/sites-available/archipelago' > image-recipe/configs/nginx-archipelago.conf
|
||||
```
|
||||
|
||||
3. **Capture any custom scripts** in `/opt/archipelago/scripts/` if they've changed.
|
||||
|
||||
@@ -13,7 +13,7 @@ Run or create tests for $ARGUMENTS.
|
||||
### Run existing tests
|
||||
```bash
|
||||
# On dev server (never build Rust on macOS)
|
||||
sshpass -p 'EwPDR8q45l0Upx@' ssh -o StrictHostKeyChecking=no archipelago@192.168.1.228 \
|
||||
ssh -i ~/.ssh/archipelago-deploy archipelago@192.168.1.228 \
|
||||
'source ~/.cargo/env && cd ~/archy/core && cargo test --all-features 2>&1'
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user