···11+# tools.slices.network
22+33+Tools for the Atmosphere, inspired by [Simon Willison's HTML tools](https://simonwillison.net/2025/Dec/10/html-tools/)
44+55+## Tools
66+77+- [statusphere](https://tools.slices.network/statusphere) - Statusphere client
+218
deploy.sh
···11+#!/usr/bin/env bash
22+set -euo pipefail
33+44+# Bunny CDN Deploy Script for tools.slices.network
55+# Syncs .html files to Bunny Storage with clean URLs (no .html extension)
66+77+# Colors for output
88+RED='\033[0;31m'
99+GREEN='\033[0;32m'
1010+YELLOW='\033[1;33m'
1111+NC='\033[0m' # No Color
1212+1313+# Counters
1414+UPLOADED=0
1515+DELETED=0
1616+1717+# Parse arguments
1818+DRY_RUN=false
1919+VERBOSE=false
2020+2121+while [[ $# -gt 0 ]]; do
2222+ case $1 in
2323+ --dry-run)
2424+ DRY_RUN=true
2525+ shift
2626+ ;;
2727+ --verbose|-v)
2828+ VERBOSE=true
2929+ shift
3030+ ;;
3131+ *)
3232+ echo -e "${RED}Unknown option: $1${NC}"
3333+ exit 1
3434+ ;;
3535+ esac
3636+done
3737+3838+# Load .env file if it exists
3939+if [ -f .env ]; then
4040+ set -a
4141+ source .env
4242+ set +a
4343+fi
4444+4545+# Required environment variables
4646+: "${BUNNY_API_KEY:?BUNNY_API_KEY environment variable is required}"
4747+: "${BUNNY_STORAGE_PASSWORD:?BUNNY_STORAGE_PASSWORD environment variable is required}"
4848+: "${BUNNY_STORAGE_ZONE:?BUNNY_STORAGE_ZONE environment variable is required}"
4949+: "${BUNNY_STORAGE_HOST:?BUNNY_STORAGE_HOST environment variable is required}"
5050+: "${BUNNY_PULLZONE_ID:?BUNNY_PULLZONE_ID environment variable is required}"
5151+5252+# Configuration
5353+STORAGE_URL="https://${BUNNY_STORAGE_HOST}/${BUNNY_STORAGE_ZONE}"
5454+5555+echo "Bunny CDN Deploy - tools.slices.network"
5656+echo "========================================"
5757+echo "Storage Zone: ${BUNNY_STORAGE_ZONE}"
5858+if [ "$DRY_RUN" = true ]; then
5959+ echo -e "${YELLOW}DRY RUN MODE - No changes will be made${NC}"
6060+fi
6161+echo ""
6262+6363+# Upload a single file (strips .html extension for clean URLs)
6464+upload_file() {
6565+ local local_path="$1"
6666+ local filename
6767+ filename=$(basename "$local_path")
6868+ # Strip .html extension for clean URLs
6969+ local remote_name="${filename%.html}"
7070+7171+ if [ "$VERBOSE" = true ]; then
7272+ echo " Uploading: ${filename} -> ${remote_name}"
7373+ fi
7474+7575+ if [ "$DRY_RUN" = true ]; then
7676+ ((UPLOADED++))
7777+ return 0
7878+ fi
7979+8080+ local response
8181+ local http_code
8282+8383+ response=$(curl -s -w "\n%{http_code}" -X PUT \
8484+ "${STORAGE_URL}/${remote_name}" \
8585+ -H "AccessKey: ${BUNNY_STORAGE_PASSWORD}" \
8686+ -H "Content-Type: text/html" \
8787+ --data-binary "@${local_path}")
8888+8989+ http_code=$(echo "$response" | tail -n1)
9090+9191+ if [[ "$http_code" =~ ^2 ]]; then
9292+ ((UPLOADED++))
9393+ return 0
9494+ else
9595+ echo -e "${RED}Failed to upload ${filename}: HTTP ${http_code}${NC}"
9696+ echo "$response" | head -n -1
9797+ return 1
9898+ fi
9999+}
100100+101101+# List all files in remote storage
102102+list_remote_files() {
103103+ local response
104104+ response=$(curl -s -X GET "${STORAGE_URL}/" \
105105+ -H "AccessKey: ${BUNNY_STORAGE_PASSWORD}" \
106106+ -H "Accept: application/json")
107107+108108+ echo "$response" | jq -r '.[] | select(.IsDirectory == false) | .ObjectName' 2>/dev/null
109109+}
110110+111111+# Delete a single file from remote
112112+delete_file() {
113113+ local remote_name="$1"
114114+115115+ if [ "$VERBOSE" = true ]; then
116116+ echo " Deleting: ${remote_name}"
117117+ fi
118118+119119+ if [ "$DRY_RUN" = true ]; then
120120+ ((DELETED++))
121121+ return 0
122122+ fi
123123+124124+ local http_code
125125+ http_code=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE \
126126+ "${STORAGE_URL}/${remote_name}" \
127127+ -H "AccessKey: ${BUNNY_STORAGE_PASSWORD}")
128128+129129+ if [[ "$http_code" =~ ^2 ]]; then
130130+ ((DELETED++))
131131+ return 0
132132+ else
133133+ echo -e "${RED}Failed to delete ${remote_name}: HTTP ${http_code}${NC}"
134134+ return 1
135135+ fi
136136+}
137137+138138+# Purge pull zone cache
139139+purge_cache() {
140140+ echo "Purging CDN cache..."
141141+142142+ if [ "$DRY_RUN" = true ]; then
143143+ echo -e "${YELLOW} Would purge pull zone ${BUNNY_PULLZONE_ID}${NC}"
144144+ return 0
145145+ fi
146146+147147+ local http_code
148148+ http_code=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
149149+ "https://api.bunny.net/pullzone/${BUNNY_PULLZONE_ID}/purgeCache" \
150150+ -H "AccessKey: ${BUNNY_API_KEY}" \
151151+ -H "Content-Type: application/json")
152152+153153+ if [[ "$http_code" =~ ^2 ]]; then
154154+ echo -e "${GREEN} Cache purged successfully${NC}"
155155+ return 0
156156+ else
157157+ echo -e "${RED} Failed to purge cache: HTTP ${http_code}${NC}"
158158+ return 1
159159+ fi
160160+}
161161+162162+# ============================================
163163+# MAIN EXECUTION
164164+# ============================================
165165+166166+# Find all .html files in root directory
167167+HTML_FILES=$(find . -maxdepth 1 -name "*.html" -type f 2>/dev/null)
168168+169169+if [ -z "$HTML_FILES" ]; then
170170+ echo -e "${YELLOW}No .html files found in current directory${NC}"
171171+ exit 0
172172+fi
173173+174174+# Build list of expected remote names (without .html extension)
175175+LOCAL_NAMES_LIST=$(mktemp)
176176+trap "rm -f $LOCAL_NAMES_LIST" EXIT
177177+178178+# Step 1: Upload all local .html files
179179+echo "Uploading files..."
180180+echo "$HTML_FILES" | while read -r file; do
181181+ filename=$(basename "$file")
182182+ remote_name="${filename%.html}"
183183+ echo "$remote_name" >> "$LOCAL_NAMES_LIST"
184184+ upload_file "$file"
185185+done
186186+187187+echo ""
188188+189189+# Step 2: Delete orphaned remote files
190190+echo "Checking for orphaned files..."
191191+REMOTE_FILES=$(list_remote_files)
192192+193193+if [ -n "$REMOTE_FILES" ]; then
194194+ while IFS= read -r remote_file; do
195195+ if [ -z "$remote_file" ]; then
196196+ continue
197197+ fi
198198+ if ! grep -qxF "$remote_file" "$LOCAL_NAMES_LIST"; then
199199+ delete_file "$remote_file"
200200+ fi
201201+ done <<< "$REMOTE_FILES"
202202+fi
203203+204204+echo ""
205205+206206+# Step 3: Purge CDN cache
207207+purge_cache
208208+209209+# Summary
210210+echo ""
211211+echo "========================================"
212212+echo -e "${GREEN}Deploy complete!${NC}"
213213+echo " Uploaded: ${UPLOADED} files"
214214+echo " Deleted: ${DELETED} files"
215215+if [ "$DRY_RUN" = true ]; then
216216+ echo -e "${YELLOW} (DRY RUN - no actual changes made)${NC}"
217217+fi
218218+echo "========================================"