Local backup, drafts & scheduling for open social media
A Next.js application for viewing and managing your Bluesky social media backup data and other open social media platforms (coming soon), create draft posts, and schedule them to be published, all local and private.
Backup
- Backup your Bluesky account locally
- Prune posts older than a given deadline
- Repost previously pruned posts (coming soon)
Scheduling
- Create drafts of posts and group them for scheduling (data all stored on local file system)
- Create schedules to publish from the draft post groups
- This allows you to create a group of "Mundaine Monday" posts and schedule them to post every week on a Monday.
- Sort upcoming drafts so they post in the order you want
This app reads your Bluesky backup files and displays them in a clean, browseable interface. You can create schedules and have posts automatically sent (assuming you are continuously running this application).
Make sure your .env file in the root of the project has this variable:
APP_DATA_ENCRYPTION_KEY='your value here'APP_DATA_ENCRYPTION_KEY is an encryption key for encoding settings including social media credentials for the app. For maximum security it should be 32 characters long.
You can also set a specific default data location for the project. This is
useful for local development if you have multiple accounts you're testing with.
You can have a different directory for each one. Set this default directory in
your .env file. If it does not exist, it should be created automatically.
DEFAULT_DATA_LOCATION="/Users/me/BskyBackup/data"Also, if you want to make sure you don't accidentally post or delete to the live
account, you can set this in your .env file.
PREVENT_POSTING="true"Install dependencies:
npm iRun the development server:
npm run devOpen https://localhost:3000 in your browser.
You should be redirected to https://localhost:3000/settings until you input required information for setup such as adding an account. Credentials are encrypted on your filesystem and never sent to a third party.
I recommend setting up an app password specifically for this. On the Bluesky app you can go to Settings -> Privacy & Security -> App passwords to generate one. There is no need to enable direct messages access at this time.
This application uses Umzug to manage database schema and application data migrations. In Docker environments, migrations are automatically run during container startup. For development, you can also manage them manually using the CLI.
When running in a Docker container, the startup process is:
- Run Migrations: Execute any pending migrations using
npm run migration:up - Start Application: Launch the Next.js application with
npm start - Initialize Services: Call
/api/util?action=initto initialize background services - Health Check: Container reports healthy status once fully operational
This process is handled automatically by the Docker entrypoint script.
View which migrations have been executed and which are pending:
npm run migration:statusExecute all pending migrations manually (development only):
npm run migration:upRollback the most recently executed migration (development only):
npm run migration:downGenerate a new migration file:
npm run migration:create "add user preferences"This creates a new migration file in src/migrations/files/ with the timestamp and name.
Each migration file exports up and down functions:
import type { MigrationContext } from '../umzug.js'
export async function up({ service }: MigrationContext) {
console.log('Running migration: Add user preferences')
// Get current app data
const appData = await service.getAppData()
// Modify data structure
if (appData?.settings) {
appData.settings.userPreferences = {
theme: 'light',
notifications: true
}
}
// Save changes
await service.saveAppData(appData)
console.log('Added user preferences to app data')
}
export async function down({ service }: MigrationContext) {
console.log('Rolling back migration: Add user preferences')
const appData = await service.getAppData()
// Remove the changes
if (appData?.settings?.userPreferences) {
delete appData.settings.userPreferences
}
await service.saveAppData(appData)
console.log('Removed user preferences from app data')
}The service object provides methods to:
service.getAppData()- Get current application dataservice.saveAppData(data)- Save modified application dataservice.isPreviousVersionLessThan(version)- Version comparison helpersservice.compareVersions(v1, v2)- Compare semantic versions
Migration state is tracked in:
.migrations.json- Records which migrations have been executed.app-version.json- Tracks application version changes
These files are automatically created and managed. Do not edit them manually.
- Test Migrations: Always test both
upanddownfunctions - Backup Data: Consider backing up critical data before major migrations
- Atomic Operations: Keep migrations focused on single changes
- Version Checks: Use version comparison helpers for conditional logic
- Error Handling: Migrations will automatically stop on errors
Migration Failed in Development
β Migration 20251201_example.ts failed: Error message- Check the error message and fix the migration file
- Migrations stop on first failure to prevent data corruption
Migration Failed in Docker
- Check container logs:
docker logs <container_name> - Container will exit if migrations fail during startup
- Fix the migration and rebuild the container
Module Not Found Errors
- Ensure all imports use relative paths with
.jsextensions - Check that imported files exist and are properly exported
This project uses Jest for unit testing.
Make sure you have all dependencies installed:
npm installRun the tests with:
npm run testTest files are located alongside source files, typically in __tests__
directories (e.g., src/app/api/helpers/__tests__/).
If you add new features, consider adding or updating test files to cover your
changes. For more information on Jest configuration, see jest.config.ts.
Make sure to map the directory you want your backup and draft data to live in
to /app/data when creating your container.
ARM:
docker buildx build --platform linux/arm64 --load -t local/bsky-backup:latest .AMD:
docker buildx build --platform linux/amd64 --load -t local/bsky-backup:latest .- Config:
/src/config.ts- Backup paths and API credentials - Types:
/src/types.ts- TypeScript interfaces for Bluesky data
- Main Page:
/src/app/page.tsx- Entry point, renders PostList - PostList:
/src/components/PostList.tsx- Main feed component with compound ToolBar - Post:
/src/components/Post.tsx- Individual post display - EmbedCarousel:
/src/components/EmbedCarousel.tsx- Image gallery component
- Backup Service:
/src/app/api/services/BackupService.ts- File operations and data processing - Bluesky Helper:
/src/app/api/helpers/bluesky.ts- API interactions and post management - Transform Helper:
/src/helpers/transformFeedViewPostToPostData.tsx- Data transformation utilities
- Image Server:
/src/app/api/images/[...path]/route.ts- Serves local backup images - Backup API:
/src/app/api/backup/route.ts- Backup operations - Prune API:
/src/app/api/prune/route.ts- Delete old posts
- Reads backup from:
~/Library/Mobile Documents/com~apple~CloudDocs/Bluesky Backup/backup/ - Serves images through
/api/images/endpoint - Handles both local and remote image fallbacks
- View all posts with metadata
- Filter by replies, media, date ranges
- Prune old posts directly from the interface
- Backup current posts
- Lazy loading images with fallbacks
- Responsive design with Tailwind CSS
// Main page renders both components
<ToolBar />
<PostList />// In your components, use the context
const { posts, isLoading, filters } = useBskyBackupContext()// Images are automatically served through the API
<img src={`/api/images/${localImagePath}`} />src/
βββ app/
β βββ api/ # API routes
β βββ page.tsx # Main page
β βββ layout.tsx # Root layout
βββ components/ # React components
βββ helpers/ # Data transformation
βββ types.ts # TypeScript definitions
βββ config.ts # Configuration- Server Components: Main page fetches data at build time
- Client Components: Interactive UI marked with
'use client' - Compound Components:
PostList.ToolBarpattern for related components - Type Safety: Full TypeScript coverage for Bluesky data structures
- Image 404s: Check backup path in config and API route logs
- Type Errors: Ensure proper imports from
/types.ts - Auth Errors: Verify Bluesky credentials in config
- Next.js 15+ - React framework with App Router
- TypeScript - Type safety
- Tailwind CSS - Styling
- @atproto/api - Bluesky API client
- Multiple accounts
- Reposting deleted posts
- Repost to previous datetime
- Repost as new post. Duplicate?
- Advanced filtering for backup and drafts
- Post search functionality
- Export options
- Statistics dashboard
- Automated backup scheduling
- Mastodon support