mirror of
https://github.com/juherr/kill-the-news.git
synced 2026-06-20 22:03:48 +00:00
db31e33a8b
Slim README down to project overview (why, features, architecture, security) with a short Installation quick-start that links to the new INSTALL.md. Repoint setup.sh references and CLAUDE.md maintenance list. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
212 lines
7.0 KiB
Bash
Executable File
212 lines
7.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
set -euo pipefail
|
|
|
|
echo "🚀 Setting up kill-the-news..."
|
|
|
|
if ! command -v npm >/dev/null 2>&1 || ! command -v npx >/dev/null 2>&1 || ! command -v node >/dev/null 2>&1; then
|
|
echo "❌ Error: Node.js (with npm and npx) is required but not found."
|
|
echo "Install Node.js from https://nodejs.org/en/download/ and run setup again."
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -f "wrangler-example.toml" ]; then
|
|
echo "❌ Error: wrangler-example.toml not found."
|
|
exit 1
|
|
fi
|
|
|
|
WORKER_NAME="$(grep -E '^name = "' wrangler-example.toml | head -1 | cut -d'"' -f2)"
|
|
if [ -z "$WORKER_NAME" ]; then
|
|
WORKER_NAME="kill-the-news"
|
|
fi
|
|
|
|
echo "📦 Installing dependencies..."
|
|
npm install
|
|
|
|
echo "🔒 Checking Cloudflare authentication..."
|
|
set +e
|
|
WHOAMI_OUTPUT="$(npx wrangler whoami 2>&1)"
|
|
WHOAMI_STATUS=$?
|
|
set -e
|
|
if [ "$WHOAMI_STATUS" -ne 0 ] || echo "$WHOAMI_OUTPUT" | grep -qi "not authenticated"; then
|
|
echo "❌ You are not logged in to Cloudflare. Please run:"
|
|
echo "npx wrangler login"
|
|
echo "After login completes, run this setup script again."
|
|
exit 1
|
|
fi
|
|
echo "✅ Cloudflare authentication verified"
|
|
|
|
extract_namespace_ids_from_json() {
|
|
local worker_name="$1"
|
|
node - "$worker_name" <<'NODE'
|
|
const fs = require("node:fs");
|
|
const workerName = process.argv[2];
|
|
|
|
let namespaces;
|
|
try {
|
|
namespaces = JSON.parse(fs.readFileSync(0, "utf8"));
|
|
} catch {
|
|
process.exit(0);
|
|
}
|
|
|
|
if (!Array.isArray(namespaces)) {
|
|
process.exit(0);
|
|
}
|
|
|
|
const findByTitle = (title) => {
|
|
const match = namespaces.find((namespace) => namespace?.title === title && typeof namespace?.id === "string");
|
|
return match?.id ?? "";
|
|
};
|
|
|
|
const mainId = findByTitle(`${workerName}-EMAIL_STORAGE`);
|
|
const previewId = findByTitle(`${workerName}-EMAIL_STORAGE_preview`);
|
|
process.stdout.write(`${mainId}\n${previewId}`);
|
|
NODE
|
|
}
|
|
|
|
get_kv_namespace_ids() {
|
|
echo "🔍 Retrieving KV namespace IDs..."
|
|
|
|
local output
|
|
if ! output="$(npx wrangler kv namespace list --json 2>/dev/null)"; then
|
|
echo "❌ Error listing KV namespaces. Please check your Cloudflare authentication."
|
|
return 1
|
|
fi
|
|
|
|
local ids
|
|
ids="$(printf '%s' "$output" | extract_namespace_ids_from_json "$WORKER_NAME")"
|
|
MAIN_ID="$(printf '%s\n' "$ids" | sed -n '1p')"
|
|
PREVIEW_ID="$(printf '%s\n' "$ids" | sed -n '2p')"
|
|
|
|
if [ -z "$MAIN_ID" ] || [ -z "$PREVIEW_ID" ]; then
|
|
MAIN_ID="$(echo "$output" | grep -o '"id": *"[^"]*"' | head -1 | cut -d'"' -f4)"
|
|
PREVIEW_ID="$(echo "$output" | grep -o '"id": *"[^"]*"' | head -2 | tail -1 | cut -d'"' -f4)"
|
|
fi
|
|
|
|
if [ -z "$MAIN_ID" ] || [ -z "$PREVIEW_ID" ]; then
|
|
echo "❌ Failed to extract KV namespace IDs. Please run manually:"
|
|
echo "npx wrangler kv namespace list"
|
|
echo "And update the IDs in wrangler.toml"
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
echo "🗄️ Creating KV namespaces..."
|
|
npx wrangler kv namespace create EMAIL_STORAGE >/dev/null 2>&1 || true
|
|
npx wrangler kv namespace create EMAIL_STORAGE --preview >/dev/null 2>&1 || true
|
|
|
|
if ! get_kv_namespace_ids; then
|
|
echo "❌ Setup cannot continue without KV namespace IDs."
|
|
exit 1
|
|
fi
|
|
|
|
echo "📊 KV Namespace Status:"
|
|
echo " ✅ Main KV namespace ID: $MAIN_ID"
|
|
echo " ✅ Preview KV namespace ID: $PREVIEW_ID"
|
|
|
|
echo "🔐 Setting up admin password..."
|
|
read -r -p "Enter admin password: " admin_password
|
|
if [ -z "$admin_password" ]; then
|
|
echo "❌ No admin password provided."
|
|
exit 1
|
|
fi
|
|
|
|
set +e
|
|
SECRET_OUTPUT="$(printf '%s' "$admin_password" | npx wrangler secret put ADMIN_PASSWORD --env production --name "$WORKER_NAME" 2>&1)"
|
|
SECRET_STATUS=$?
|
|
set -e
|
|
if [ "$SECRET_STATUS" -ne 0 ]; then
|
|
echo "❌ Failed to set admin password for production environment"
|
|
echo "Error: $SECRET_OUTPUT"
|
|
exit 1
|
|
fi
|
|
echo "✅ Admin password set for production environment"
|
|
|
|
read -r -p "Enter your domain (e.g., yourdomain.com): " domain
|
|
domain="${domain#https://}"
|
|
domain="${domain#http://}"
|
|
domain="${domain%%/*}"
|
|
if [ -z "$domain" ]; then
|
|
echo "❌ No domain provided. Cannot continue."
|
|
exit 1
|
|
fi
|
|
echo "✅ Domain: $domain"
|
|
|
|
ENABLE_R2=false
|
|
R2_BUCKET=""
|
|
R2_PREVIEW_BUCKET=""
|
|
read -r -p "Enable email attachments stored in R2? [y/N]: " enable_r2
|
|
if [[ "$enable_r2" =~ ^[Yy]$ ]]; then
|
|
R2_BUCKET="${WORKER_NAME}-attachments"
|
|
R2_PREVIEW_BUCKET="${R2_BUCKET}-preview"
|
|
echo "🪣 Creating R2 buckets..."
|
|
set +e
|
|
R2_OUT="$(npx wrangler r2 bucket create "$R2_BUCKET" 2>&1)"
|
|
R2_STATUS=$?
|
|
R2_PREVIEW_OUT="$(npx wrangler r2 bucket create "$R2_PREVIEW_BUCKET" 2>&1)"
|
|
R2_PREVIEW_STATUS=$?
|
|
set -e
|
|
# An existing bucket is fine; only treat real failures as blocking.
|
|
echo "$R2_OUT" | grep -qi "already exists" && R2_STATUS=0
|
|
echo "$R2_PREVIEW_OUT" | grep -qi "already exists" && R2_PREVIEW_STATUS=0
|
|
if [ "$R2_STATUS" -eq 0 ] && [ "$R2_PREVIEW_STATUS" -eq 0 ]; then
|
|
ENABLE_R2=true
|
|
echo " ✅ R2 bucket: $R2_BUCKET"
|
|
echo " ✅ R2 preview bucket: $R2_PREVIEW_BUCKET"
|
|
else
|
|
echo " ⚠️ Could not create R2 buckets (is R2 enabled on your account?)."
|
|
echo " Attachments will stay disabled — see INSTALL.md → 'Email attachments (R2)'."
|
|
echo "$R2_OUT"
|
|
fi
|
|
fi
|
|
|
|
escape_sed_replacement() {
|
|
printf '%s' "$1" | sed -e 's/[\/&]/\\&/g'
|
|
}
|
|
|
|
KV_ID_ESCAPED="$(escape_sed_replacement "$MAIN_ID")"
|
|
KV_PREVIEW_ID_ESCAPED="$(escape_sed_replacement "$PREVIEW_ID")"
|
|
DOMAIN_ESCAPED="$(escape_sed_replacement "$domain")"
|
|
COMPATIBILITY_DATE_ESCAPED="$(escape_sed_replacement "$(date +%F)")"
|
|
|
|
echo "📝 Creating and configuring wrangler.toml..."
|
|
cp wrangler-example.toml wrangler.toml
|
|
|
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
sed -i '' "s/REPLACE_WITH_YOUR_DOMAIN/$DOMAIN_ESCAPED/g" wrangler.toml
|
|
sed -i '' "s/REPLACE_WITH_YOUR_KV_NAMESPACE_ID/$KV_ID_ESCAPED/g" wrangler.toml
|
|
sed -i '' "s/REPLACE_WITH_YOUR_PREVIEW_KV_NAMESPACE_ID/$KV_PREVIEW_ID_ESCAPED/g" wrangler.toml
|
|
sed -i '' "s/REPLACE_WITH_COMPATIBILITY_DATE/$COMPATIBILITY_DATE_ESCAPED/g" wrangler.toml
|
|
else
|
|
sed -i "s/REPLACE_WITH_YOUR_DOMAIN/$DOMAIN_ESCAPED/g" wrangler.toml
|
|
sed -i "s/REPLACE_WITH_YOUR_KV_NAMESPACE_ID/$KV_ID_ESCAPED/g" wrangler.toml
|
|
sed -i "s/REPLACE_WITH_YOUR_PREVIEW_KV_NAMESPACE_ID/$KV_PREVIEW_ID_ESCAPED/g" wrangler.toml
|
|
sed -i "s/REPLACE_WITH_COMPATIBILITY_DATE/$COMPATIBILITY_DATE_ESCAPED/g" wrangler.toml
|
|
fi
|
|
|
|
if [ "$ENABLE_R2" = true ]; then
|
|
echo "🔗 Enabling R2 attachment binding in wrangler.toml..."
|
|
node - "wrangler.toml" "$R2_BUCKET" "$R2_PREVIEW_BUCKET" <<'NODE'
|
|
const fs = require("node:fs");
|
|
const [file, bucket, previewBucket] = process.argv.slice(2);
|
|
let txt = fs.readFileSync(file, "utf8");
|
|
txt = txt.split("REPLACE_WITH_YOUR_PREVIEW_BUCKET_NAME").join(previewBucket);
|
|
txt = txt.split("REPLACE_WITH_YOUR_BUCKET_NAME").join(bucket);
|
|
// Uncomment the commented r2_buckets blocks (global + [env.production]).
|
|
txt = txt.replace(
|
|
/# r2_buckets = \[\n#(\s+\{ binding = "ATTACHMENT_BUCKET".*\})\n# \]/g,
|
|
'r2_buckets = [\n$1\n]',
|
|
);
|
|
fs.writeFileSync(file, txt);
|
|
NODE
|
|
echo " ✅ ATTACHMENT_BUCKET bound to $R2_BUCKET"
|
|
fi
|
|
|
|
echo "✅ wrangler.toml has been created and configured successfully!"
|
|
echo ""
|
|
echo "✅ Setup complete! Next steps:"
|
|
echo "1. Configure email ingestion — Cloudflare Email Workers or ForwardEmail (see INSTALL.md for details)"
|
|
echo "2. Deploy with 'npm run deploy'"
|