Tools for the Atmosphere
tools.slices.network
quickslice
atproto
html
1#!/usr/bin/env bash
2set -euo pipefail
3
4# Bunny CDN Deploy Script for tools.slices.network
5# Syncs .html files to Bunny Storage with clean URLs (no .html extension)
6
7# Colors for output
8RED='\033[0;31m'
9GREEN='\033[0;32m'
10YELLOW='\033[1;33m'
11NC='\033[0m' # No Color
12
13# Counters
14UPLOADED=0
15DELETED=0
16
17# Parse arguments
18DRY_RUN=false
19VERBOSE=false
20
21while [[ $# -gt 0 ]]; do
22 case $1 in
23 --dry-run)
24 DRY_RUN=true
25 shift
26 ;;
27 --verbose|-v)
28 VERBOSE=true
29 shift
30 ;;
31 *)
32 echo -e "${RED}Unknown option: $1${NC}"
33 exit 1
34 ;;
35 esac
36done
37
38# Load .env file if it exists
39if [ -f .env ]; then
40 set -a
41 source .env
42 set +a
43fi
44
45# Required environment variables
46: "${BUNNY_API_KEY:?BUNNY_API_KEY environment variable is required}"
47: "${BUNNY_STORAGE_PASSWORD:?BUNNY_STORAGE_PASSWORD environment variable is required}"
48: "${BUNNY_STORAGE_ZONE:?BUNNY_STORAGE_ZONE environment variable is required}"
49: "${BUNNY_STORAGE_HOST:?BUNNY_STORAGE_HOST environment variable is required}"
50: "${BUNNY_PULLZONE_ID:?BUNNY_PULLZONE_ID environment variable is required}"
51
52# Configuration
53STORAGE_URL="https://${BUNNY_STORAGE_HOST}/${BUNNY_STORAGE_ZONE}"
54
55echo "Bunny CDN Deploy - tools.slices.network"
56echo "========================================"
57echo "Storage Zone: ${BUNNY_STORAGE_ZONE}"
58if [ "$DRY_RUN" = true ]; then
59 echo -e "${YELLOW}DRY RUN MODE - No changes will be made${NC}"
60fi
61echo ""
62
63# Upload a single file (strips .html extension for clean URLs)
64upload_file() {
65 local local_path="$1"
66 local filename
67 filename=$(basename "$local_path")
68 # Strip .html extension for clean URLs
69 local remote_name="${filename%.html}"
70
71 if [ "$VERBOSE" = true ]; then
72 echo " Uploading: ${filename} -> ${remote_name}"
73 fi
74
75 if [ "$DRY_RUN" = true ]; then
76 ((UPLOADED++))
77 return 0
78 fi
79
80 local response
81 local http_code
82
83 response=$(curl -s -w "\n%{http_code}" -X PUT \
84 "${STORAGE_URL}/${remote_name}" \
85 -H "AccessKey: ${BUNNY_STORAGE_PASSWORD}" \
86 -H "Content-Type: text/html" \
87 --data-binary "@${local_path}")
88
89 http_code=$(echo "$response" | tail -n1)
90
91 if [[ "$http_code" =~ ^2 ]]; then
92 ((UPLOADED++))
93 return 0
94 else
95 echo -e "${RED}Failed to upload ${filename}: HTTP ${http_code}${NC}"
96 echo "$response" | head -n -1
97 return 1
98 fi
99}
100
101# List all files in remote storage
102list_remote_files() {
103 local response
104 response=$(curl -s -X GET "${STORAGE_URL}/" \
105 -H "AccessKey: ${BUNNY_STORAGE_PASSWORD}" \
106 -H "Accept: application/json")
107
108 echo "$response" | jq -r '.[] | select(.IsDirectory == false) | .ObjectName' 2>/dev/null
109}
110
111# Delete a single file from remote
112delete_file() {
113 local remote_name="$1"
114
115 if [ "$VERBOSE" = true ]; then
116 echo " Deleting: ${remote_name}"
117 fi
118
119 if [ "$DRY_RUN" = true ]; then
120 ((DELETED++))
121 return 0
122 fi
123
124 local http_code
125 http_code=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE \
126 "${STORAGE_URL}/${remote_name}" \
127 -H "AccessKey: ${BUNNY_STORAGE_PASSWORD}")
128
129 if [[ "$http_code" =~ ^2 ]]; then
130 ((DELETED++))
131 return 0
132 else
133 echo -e "${RED}Failed to delete ${remote_name}: HTTP ${http_code}${NC}"
134 return 1
135 fi
136}
137
138# Purge pull zone cache
139purge_cache() {
140 echo "Purging CDN cache..."
141
142 if [ "$DRY_RUN" = true ]; then
143 echo -e "${YELLOW} Would purge pull zone ${BUNNY_PULLZONE_ID}${NC}"
144 return 0
145 fi
146
147 local http_code
148 http_code=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
149 "https://api.bunny.net/pullzone/${BUNNY_PULLZONE_ID}/purgeCache" \
150 -H "AccessKey: ${BUNNY_API_KEY}" \
151 -H "Content-Type: application/json")
152
153 if [[ "$http_code" =~ ^2 ]]; then
154 echo -e "${GREEN} Cache purged successfully${NC}"
155 return 0
156 else
157 echo -e "${RED} Failed to purge cache: HTTP ${http_code}${NC}"
158 return 1
159 fi
160}
161
162# ============================================
163# MAIN EXECUTION
164# ============================================
165
166# Find all .html files in root directory
167HTML_FILES=$(find . -maxdepth 1 -name "*.html" -type f 2>/dev/null)
168
169if [ -z "$HTML_FILES" ]; then
170 echo -e "${YELLOW}No .html files found in current directory${NC}"
171 exit 0
172fi
173
174# Build list of expected remote names (without .html extension)
175LOCAL_NAMES_LIST=$(mktemp)
176trap "rm -f $LOCAL_NAMES_LIST" EXIT
177
178# Step 1: Upload all local .html files
179echo "Uploading files..."
180echo "$HTML_FILES" | while read -r file; do
181 filename=$(basename "$file")
182 remote_name="${filename%.html}"
183 echo "$remote_name" >> "$LOCAL_NAMES_LIST"
184 upload_file "$file"
185done
186
187echo ""
188
189# Step 2: Delete orphaned remote files
190echo "Checking for orphaned files..."
191REMOTE_FILES=$(list_remote_files)
192
193if [ -n "$REMOTE_FILES" ]; then
194 while IFS= read -r remote_file; do
195 if [ -z "$remote_file" ]; then
196 continue
197 fi
198 if ! grep -qxF "$remote_file" "$LOCAL_NAMES_LIST"; then
199 delete_file "$remote_file"
200 fi
201 done <<< "$REMOTE_FILES"
202fi
203
204echo ""
205
206# Step 3: Purge CDN cache
207purge_cache
208
209# Summary
210echo ""
211echo "========================================"
212echo -e "${GREEN}Deploy complete!${NC}"
213echo " Uploaded: ${UPLOADED} files"
214echo " Deleted: ${DELETED} files"
215if [ "$DRY_RUN" = true ]; then
216 echo -e "${YELLOW} (DRY RUN - no actual changes made)${NC}"
217fi
218echo "========================================"