Minimal static site (Astro + MDX) for blog, diary, and projects.
bun install
bun run devIn development, /cdn/* is proxied to the R2 public origin so image paths work locally.
bun run check
bun run buildPhoto assets are stored in two variants:
- Production/content URL (use this):
https://cdn.egeuysal.com/photo-1080/<name>.jpg - Archive/full-quality source:
https://cdn.egeuysal.com/photo/<name>.jpg
Use the upload script:
scripts/photo-upload.sh /absolute/path/to/image.jpg img-31This uploads:
r2:photos/photo/img-31.jpg(full-quality source)r2:photos/photo-1080/img-31.jpg(production/content image, max width 1080, max height 1350)
Optional flags:
scripts/photo-upload.sh ./frame.png img-32 --quality 72 --overwriteNotes:
- Content frontmatter for photos should use
https://cdn.egeuysal.com/photo-1080/<name>.jpg. - Keep
/photooriginals for full-quality display and future re-exports. - If immutable caching is enabled, prefer new names over overwriting existing objects.
- Blog:
src/content/blog/*.mdx - Diary:
src/content/diary/YYYY-MM-DD.mdx
Convex stores post views, likes, comments, reports, blocks, and comment images. Public writes go through /api/engagement; the browser never receives the Convex write secret.
Set the same write secret on the production Convex deployment before the first push:
bunx convex env set --prod INTERACTION_WRITE_SECRET <secret>
bunx convex deployment token create vercel-production --prodStore the returned deploy key as CONVEX_DEPLOY_KEY in Vercel production. Preview deployments need a separate preview-scoped key.
The private /editor/ route edits blog MDX and commits it to master, which triggers the normal Git-based Vercel deployment. Retrieve the local admin password from Keychain with:
security find-generic-password -s www-admin-password -wThe existing iA Writer Micropub endpoint remains the site publisher and keeps its original response contract. New Micropub commits are fanned out asynchronously by the GitHub webhook at https://egeuysal.com/api/github-webhook; the private editor publishes directly after writing GitHub. Both paths send new posts to configured X, LinkedIn, and email channels. Editing an existing post does not repost it.
Set these additional Vercel environment variables to enable distribution:
X_ACCESS_TOKEN= # X user OAuth token with tweet.write (fallback)
X_REFRESH_TOKEN= # X OAuth 2 refresh token with offline.access
X_CLIENT_ID= # X OAuth 2 public client ID
LINKEDIN_ACCESS_TOKEN= # LinkedIn OAuth token with w_member_social
LINKEDIN_AUTHOR_URN=urn:li:person:<your-member-id>
LINKEDIN_API_VERSION=202603
RESEND_API_KEY=
NEWSLETTER_FROM=Ege Uysal <hi@egeuysal.com>
NEWSLETTER_TOKEN_SECRET=
GITHUB_WEBHOOK_SECRET=One-time setup:
- In the X Developer Portal, enable OAuth 2.0 with
tweet.writeandoffline.access, then setX_CLIENT_ID,X_ACCESS_TOKEN, andX_REFRESH_TOKEN. These are user credentials, not the app-only bearer token; the refresh token keeps publishing alive after the access token expires. - In the LinkedIn Developer Portal, create/select an app with the Share on LinkedIn product, authorize
w_member_social, and set the returned member token andurn:li:person:<member-id>. - Verify
egeuysal.comin Resend, create an API key, setRESEND_API_KEYandNEWSLETTER_FROM, then generateNEWSLETTER_TOKEN_SECRETwithopenssl rand -hex 32. - Add a repository push webhook to
https://egeuysal.com/api/github-webhookwith the same random value inGITHUB_WEBHOOK_SECRET; subscribe to thepushevent and send JSON. - For other publishing platforms, use the site's RSS feed at
/rss.xmlor add a separately reviewed distribution adapter.
The newsletter uses double opt-in and one-click unsubscribe. Readers do not need passwords or accounts; likes and comments continue using the existing anonymous, rate-limited flow.
Set these environment variables in Vercel (or your host):
PUBLIC_SITE_URL=https://egeuysal.com
CONVEX_URL=https://your-production-deployment.convex.cloud
CONVEX_DEPLOY_KEY=...
INTERACTION_WRITE_SECRET=...
INTERACTION_ACTOR_SALT=...
ADMIN_PASSWORD=...
ADMIN_SESSION_SECRET=...
GITHUB_TOKEN=...This is used for canonical URLs, OG/Twitter URLs, RSS, robots, sitemap, and JSON-LD.