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, keeps others as-is)
64upload_file() {
65 local local_path="$1"
66 local filename
67 filename=$(basename "$local_path")
68 # Strip .html extension for clean URLs, keep other extensions
69 local remote_name
70 if [[ "$filename" == *.html ]]; then
71 remote_name="${filename%.html}"
72 else
73 remote_name="$filename"
74 fi
75
76 if [ "$VERBOSE" = true ]; then
77 echo " Uploading: ${filename} -> ${remote_name}"
78 fi
79
80 if [ "$DRY_RUN" = true ]; then
81 ((UPLOADED++))
82 return 0
83 fi
84
85 local response
86 local http_code
87
88 # Determine content type
89 local content_type="text/html"
90 if [[ "$filename" == *.js ]]; then
91 content_type="application/javascript"
92 fi
93
94 response=$(curl -s -w "\n%{http_code}" -X PUT \
95 "${STORAGE_URL}/${remote_name}" \
96 -H "AccessKey: ${BUNNY_STORAGE_PASSWORD}" \
97 -H "Content-Type: ${content_type}" \
98 --data-binary "@${local_path}")
99
100 http_code=$(echo "$response" | tail -n1)
101
102 if [[ "$http_code" =~ ^2 ]]; then
103 ((UPLOADED++))
104 return 0
105 else
106 echo -e "${RED}Failed to upload ${filename}: HTTP ${http_code}${NC}"
107 echo "$response" | head -n -1
108 return 1
109 fi
110}
111
112# List all files in remote storage
113list_remote_files() {
114 local response
115 response=$(curl -s -X GET "${STORAGE_URL}/" \
116 -H "AccessKey: ${BUNNY_STORAGE_PASSWORD}" \
117 -H "Accept: application/json")
118
119 echo "$response" | jq -r '.[] | select(.IsDirectory == false) | .ObjectName' 2>/dev/null
120}
121
122# Delete a single file from remote
123delete_file() {
124 local remote_name="$1"
125
126 if [ "$VERBOSE" = true ]; then
127 echo " Deleting: ${remote_name}"
128 fi
129
130 if [ "$DRY_RUN" = true ]; then
131 ((DELETED++))
132 return 0
133 fi
134
135 local http_code
136 http_code=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE \
137 "${STORAGE_URL}/${remote_name}" \
138 -H "AccessKey: ${BUNNY_STORAGE_PASSWORD}")
139
140 if [[ "$http_code" =~ ^2 ]]; then
141 ((DELETED++))
142 return 0
143 else
144 echo -e "${RED}Failed to delete ${remote_name}: HTTP ${http_code}${NC}"
145 return 1
146 fi
147}
148
149# Purge pull zone cache
150purge_cache() {
151 echo "Purging CDN cache..."
152
153 if [ "$DRY_RUN" = true ]; then
154 echo -e "${YELLOW} Would purge pull zone ${BUNNY_PULLZONE_ID}${NC}"
155 return 0
156 fi
157
158 local http_code
159 http_code=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
160 "https://api.bunny.net/pullzone/${BUNNY_PULLZONE_ID}/purgeCache" \
161 -H "AccessKey: ${BUNNY_API_KEY}" \
162 -H "Content-Type: application/json")
163
164 if [[ "$http_code" =~ ^2 ]]; then
165 echo -e "${GREEN} Cache purged successfully${NC}"
166 return 0
167 else
168 echo -e "${RED} Failed to purge cache: HTTP ${http_code}${NC}"
169 return 1
170 fi
171}
172
173# ============================================
174# MAIN EXECUTION
175# ============================================
176
177# Find all .html and .js files in root directory
178DEPLOY_FILES=$(find . -maxdepth 1 \( -name "*.html" -o -name "*.js" \) -type f 2>/dev/null)
179
180if [ -z "$DEPLOY_FILES" ]; then
181 echo -e "${YELLOW}No .html or .js files found in current directory${NC}"
182 exit 0
183fi
184
185# Build list of expected remote names (without .html extension)
186LOCAL_NAMES_LIST=$(mktemp)
187trap "rm -f $LOCAL_NAMES_LIST" EXIT
188
189# Step 1: Upload all local files
190echo "Uploading files..."
191echo "$DEPLOY_FILES" | while read -r file; do
192 filename=$(basename "$file")
193 if [[ "$filename" == *.html ]]; then
194 remote_name="${filename%.html}"
195 else
196 remote_name="$filename"
197 fi
198 echo "$remote_name" >> "$LOCAL_NAMES_LIST"
199 upload_file "$file"
200done
201
202echo ""
203
204# Step 2: Delete orphaned remote files
205echo "Checking for orphaned files..."
206REMOTE_FILES=$(list_remote_files)
207
208if [ -n "$REMOTE_FILES" ]; then
209 while IFS= read -r remote_file; do
210 if [ -z "$remote_file" ]; then
211 continue
212 fi
213 if ! grep -qxF "$remote_file" "$LOCAL_NAMES_LIST"; then
214 delete_file "$remote_file"
215 fi
216 done <<< "$REMOTE_FILES"
217fi
218
219echo ""
220
221# Step 3: Purge CDN cache
222purge_cache
223
224# Summary
225echo ""
226echo "========================================"
227echo -e "${GREEN}Deploy complete!${NC}"
228echo " Uploaded: ${UPLOADED} files"
229echo " Deleted: ${DELETED} files"
230if [ "$DRY_RUN" = true ]; then
231 echo -e "${YELLOW} (DRY RUN - no actual changes made)${NC}"
232fi
233echo "========================================"