{"id":47734603,"url":"https://github.com/rodrgds/openpost","last_synced_at":"2026-05-09T12:11:40.837Z","repository":{"id":345935654,"uuid":"1187916096","full_name":"rodrgds/openpost","owner":"rodrgds","description":"Lightweight self-hosted open source social media scheduler","archived":false,"fork":false,"pushed_at":"2026-05-03T10:37:00.000Z","size":8106,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-03T12:29:53.051Z","etag":null,"topics":["golang","single-binary","social-media","svelte","sveltekit"],"latest_commit_sha":null,"homepage":"https://op.rgo.pt","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rodrgds.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":"ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-03-21T11:12:13.000Z","updated_at":"2026-05-03T10:05:37.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/rodrgds/openpost","commit_stats":null,"previous_names":["rodrgds/opencode","rodrgds/openpost"],"tags_count":50,"template":false,"template_full_name":null,"purl":"pkg:github/rodrgds/openpost","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rodrgds%2Fopenpost","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rodrgds%2Fopenpost/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rodrgds%2Fopenpost/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rodrgds%2Fopenpost/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rodrgds","download_url":"https://codeload.github.com/rodrgds/openpost/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rodrgds%2Fopenpost/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32818524,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-08T08:22:46.396Z","status":"online","status_checked_at":"2026-05-09T02:00:06.633Z","response_time":123,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["golang","single-binary","social-media","svelte","sveltekit"],"created_at":"2026-04-02T22:17:42.214Z","updated_at":"2026-05-09T12:11:40.805Z","avatar_url":"https://github.com/rodrgds.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/rodrgds/openpost\" target=\"_blank\"\u003e\n    \u003cimg alt=\"OpenPost Logo\" src=\"./frontend/static/logo.svg\" width=\"280\"/\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n\u003ca href=\"LICENSE\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/License-MIT-blue.svg\" alt=\"License\"\u003e\n\u003c/a\u003e\n\u003ca href=\"https://golang.org\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Go-1.25+-00ADD8?style=flat\u0026logo=go\" alt=\"Go Version\"\u003e\n\u003c/a\u003e\n\u003ca href=\"https://svelte.dev\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Svelte-5-FF3E00?style=flat\u0026logo=svelte\" alt=\"Svelte Version\"\u003e\n\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cdiv align=\"center\"\u003e\n  \u003cstrong\u003e\n  \u003ch2\u003eA lightweight, self-hosted social media scheduler with web and Android app\u003c/h2\u003e\u003cbr /\u003e\n  \u003c/strong\u003e\n  The open-source alternative to Typefully, Buffer, and Hypefury.\u003cbr /\u003e\n  One lightweight binary. No dependencies. Full control.\n\u003c/div\u003e\n\n\u003cdiv align=\"center\"\u003e\n  \u003cbr /\u003e\n  \u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"./assets/logos/x-white.svg\"\u003e\n    \u003cimg alt=\"X (Twitter)\" src=\"./assets/logos/x.svg\" width=\"24\"\u003e\n  \u003c/picture\u003e\n  \u0026nbsp;\n  \u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"./assets/logos/mastodon-white.svg\"\u003e\n    \u003cimg alt=\"Mastodon\" src=\"./assets/logos/mastodon.svg\" width=\"24\"\u003e\n  \u003c/picture\u003e\n  \u0026nbsp;\n  \u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"./assets/logos/bluesky-white.svg\"\u003e\n    \u003cimg alt=\"Bluesky\" src=\"./assets/logos/bluesky.svg\" width=\"24\"\u003e\n  \u003c/picture\u003e\n  \u0026nbsp;\n  \u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"./assets/logos/threads-white.svg\"\u003e\n    \u003cimg alt=\"Threads\" src=\"./assets/logos/threads.svg\" width=\"24\"\u003e\n  \u003c/picture\u003e\n  \u0026nbsp;\n  \u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"./assets/logos/linkedin-white.svg\"\u003e\n    \u003cimg alt=\"LinkedIn\" src=\"./assets/logos/linkedin.svg\" width=\"24\"\u003e\n  \u003c/picture\u003e\n\u003c/div\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cbr /\u003e\n  \u003ca href=\"#-features\"\u003e\u003cstrong\u003eFeatures\u003c/strong\u003e\u003c/a\u003e\n  ·\n  \u003ca href=\"#-supported-platforms\"\u003e\u003cstrong\u003ePlatforms\u003c/strong\u003e\u003c/a\u003e\n  ·\n  \u003ca href=\"#-quick-start\"\u003e\u003cstrong\u003eQuick Start\u003c/strong\u003e\u003c/a\u003e\n  ·\n  \u003ca href=\"#-configuration\"\u003e\u003cstrong\u003eConfiguration\u003c/strong\u003e\u003c/a\u003e\n  ·\n  \u003ca href=\"#-tech-stack\"\u003e\u003cstrong\u003eTech Stack\u003c/strong\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cbr /\u003e\n\n## 🚀 Features\n\n- **Single Binary Deployment** - Everything compiled into one executable. No Docker required (but supported).\n- **Multi-Platform Posting** - Schedule posts to X (Twitter), Mastodon, Bluesky, Threads, and LinkedIn.\n- **Media Uploads** - Attach images and videos to posts. Platform-specific media requirements handled automatically.\n- **Post Threading** - Create multi-post threads that publish sequentially across all platforms.\n- **Custom Mastodon Instances** - Connect accounts from any number of Mastodon servers (configured via JSON env var).\n- **Workspaces \u0026 Teams** - Multi-tenant architecture with role-based access control.\n- **Background Scheduling** - SQLite-backed job queue survives server restarts.\n- **Encrypted Tokens** - AES-256-GCM encryption for all OAuth tokens at rest.\n- **Modern UI** - SvelteKit frontend with TailwindCSS, responsive design.\n- **Self-Hosted** - Your data stays on your server. No third-party tracking.\n\n## 📱 Supported Platforms\n\n| Platform | Status | Notes |\n|----------|--------|-------|\n| X (Twitter) | ✅ Supported | OAuth 2.0 PKCE |\n| Mastodon | ✅ Supported | Custom instances |\n| Bluesky | ✅ Implemented | App passwords (no setup) |\n| Threads | ✅ Implemented | Meta Graph API |\n| LinkedIn | ✅ Implemented | OAuth 2.0 + Posts API |\n\n## ⚡ Quick Start\n\n### Prerequisites\n\n- Go 1.25+ (for building from source)\n- Bun or npm (for frontend builds)\n- OAuth credentials for the platforms you want to use\n\n### Building from Source\n\n```bash\n# Clone the repository\ngit clone https://github.com/rodrgds/openpost.git\ncd openpost\n\n# Install frontend dependencies and build\ncd frontend\nbun install\nbun run build\n\n# Build the Go binary (includes embedded frontend)\ncd ../backend\ncp .env.example .env\n# Edit .env with your credentials\ngo build -o openpost ./cmd/openpost\n\n# Run\n./openpost\n```\n\nThe application will start on `http://localhost:8080`.\n\n### One-Liner Build\n\n```bash\n# From project root\ncd frontend \u0026\u0026 bun install \u0026\u0026 bun run build \u0026\u0026 cd ../backend \u0026\u0026 go build -o openpost ./cmd/openpost\n```\n\n### Development Mode\n\nFor development, you can run the frontend and backend separately:\n\n```bash\n# Terminal 1: Frontend (with hot reload)\ncd frontend\nbun run dev\n\n# Terminal 2: Backend (with hot reload)\ncd backend\ngo run ./cmd/openpost\n```\n\nThe frontend dev server runs on `http://localhost:5173` and proxies API calls to the backend.\n\n## 🐳 Docker\n\n```bash\n# Build the image\ndocker build -t openpost -f docker/Dockerfile .\n\n# Run\ndocker run -d \\\n  -p 8080:8080 \\\n  -v openpost_data:/data \\\n  -e JWT_SECRET=your-secret \\\n  -e ENCRYPTION_KEY=your-encryption-key \\\n  openpost\n```\n\n## 📦 Docker Registry\n\n### Building and Pushing\n\n```bash\n# Build the image\ndocker build -t ghcr.io/rodrgds/openpost:latest -f docker/Dockerfile .\n\n# Tag for version\ndocker tag ghcr.io/rodrgds/openpost:latest ghcr.io/rodrgds/openpost:v0.1.0\n\n# Push to registry\ndocker push ghcr.io/rodrgds/openpost:latest\ndocker push ghcr.io/rodrgds/openpost:v0.1.0\n```\n\n### Using Pre-built Image\n\n```yaml\n# docker-compose.yml\nservices:\n  openpost:\n    image: ghcr.io/rodrgds/openpost:latest\n    restart: unless-stopped\n    environment:\n      - JWT_SECRET=your-secret\n      - ENCRYPTION_KEY=your-encryption-key\n    volumes:\n      - openpost_data:/data\n    ports:\n      - \"8080:8080\"\n```\n\n## 🐧 How I Self-Host (NixOS + Docker)\n\nI personally run OpenPost on my own NixOS server using Docker (via Podman OCI containers). I wrote a custom Nix module that sets up the container with persistent storage, health checks, SOPS secret management, and a Caddy reverse proxy. If you're also on NixOS, feel free to use it as a reference:\n\n🔗 **[My Nix module for OpenPost](https://github.com/rodrgds/nix-config/blob/main/modules/services/openpost/default.nix)**\n\n## ⚙️ Configuration\n\nAll configuration is done via environment variables or a `.env` file:\n\n| Variable | Required | Description |\n|----------|----------|-------------|\n| `JWT_SECRET` | ✅ Yes | Secret key for JWT tokens (32+ chars) |\n| `ENCRYPTION_KEY` | ✅ Yes | AES-256 key for token encryption |\n| `OPENPOST_MEDIA_PATH` | No | Local media storage path (default: `./media`) |\n| `OPENPOST_MEDIA_URL` | No | URL path for serving media (default: `/media`) |\n| `TWITTER_CLIENT_ID` | For X | Twitter OAuth client ID |\n| `TWITTER_CLIENT_SECRET` | For X | Twitter OAuth secret |\n| `MASTODON_SERVERS` | For Mastodon | JSON array of Mastodon server configs |\n| `MASTODON_REDIRECT_URI` | No | OAuth callback URI (default: OOB) |\n| `LINKEDIN_CLIENT_ID` | For LinkedIn | LinkedIn OAuth client ID |\n| `LINKEDIN_CLIENT_SECRET` | For LinkedIn | LinkedIn OAuth secret |\n| `OPENPOST_DISABLE_LINKEDIN_THREAD_REPLIES` | No | Disable LinkedIn thread child replies when app lacks `w_member_social_feed` |\n| `THREADS_CLIENT_ID` | For Threads | Meta App ID |\n| `THREADS_CLIENT_SECRET` | For Threads | Meta App Secret |\n| `OPENPOST_PORT` | No | Server port (default: 8080) |\n| `OPENPOST_DB_PATH` | No | SQLite database path |\n| `OPENPOST_FRONTEND_URL` | No | CORS origin for frontend |\n\n**Note:** Bluesky doesn't require any env vars - users connect directly with their handle and app password.\n\n### Generating Secrets\n\n```bash\n# Generate JWT secret\nopenssl rand -base64 32\n\n# Generate encryption key\nopenssl rand -base64 32\n```\n\n### OAuth Setup\n\n#### Twitter/X\n\n1. Go to [Twitter Developer Portal](https://developer.twitter.com/en/portal/dashboard)\n2. Create a new App\n3. Enable OAuth 2.0\n4. Set callback URL: `http://localhost:8080/api/v1/accounts/x/callback`\n5. Copy Client ID and Secret to your `.env`\n\n#### Mastodon\n\nMastodon supports **multiple servers** via the `MASTODON_SERVERS` environment variable. Each server needs its own OAuth app because Mastodon client credentials are per-instance.\n\n1. For each Mastodon instance, go to Settings → Development → New Application\n2. Set the redirect URI to: `urn:ietf:wg:oauth:2.0:oob` (or your callback URL)\n3. Copy the Client ID and Secret\n4. Add to your `.env`:\n\n```env\nMASTODON_REDIRECT_URI=urn:ietf:wg:oauth:2.0:oob\nMASTODON_SERVERS='[\n  {\"name\":\"Personal\",\"client_id\":\"abc123\",\"client_secret\":\"xyz789\",\"instance_url\":\"https://mastodon.social\"},\n  {\"name\":\"Work\",\"client_id\":\"def456\",\"client_secret\":\"uvw012\",\"instance_url\":\"https://fosstodon.org\"}\n]'\n```\n\nThe `name` is a label shown in the UI. You can configure as many servers as you need.\n\n#### Bluesky\n\nBluesky uses app passwords - no setup needed. Users just enter their handle and app password when connecting:\n\n1. Go to [Bluesky App Passwords](https://bsky.app/settings/app-passwords)\n2. Create a new app password\n3. In OpenPost, click Connect on Bluesky and enter your handle + app password\n\nSee [docs/bluesky-integration.md](docs/bluesky-integration.md) for more details.\n\n#### LinkedIn\n\n1. Go to [LinkedIn Developer Portal](https://www.linkedin.com/developers/apps)\n2. Create a new app and request \"Share on LinkedIn\" product\n3. Request approval for `w_member_social_feed` (Social Actions create) in your app products/permissions\n4. Add redirect URL: `http://localhost:8080/api/v1/accounts/linkedin/callback`\n5. Copy Client ID and Secret to your `.env`\n\n**Important:** LinkedIn thread replies (posting comments on the first post) require `w_member_social_feed` approval. If your app only has `w_member_social`, first posts may succeed but replies/comments will fail with `ACCESS_DENIED: partnerApiSocialActions.CREATE`.\n\nSee [docs/linkedin-integration.md](docs/linkedin-integration.md) for detailed setup instructions.\n\n#### Threads\n\n1. Go to [Meta for Developers](https://developers.facebook.com/)\n2. Create a new app (Business type) and add Threads API product\n3. Add redirect URL: `http://localhost:8080/api/v1/accounts/threads/callback`\n4. Copy App ID and App Secret to your `.env` as `THREADS_CLIENT_ID` and `THREADS_CLIENT_SECRET`\n\nSee [docs/threads-integration.md](docs/threads-integration.md) for detailed setup instructions.\n\n## 🏗️ Tech Stack\n\n**Frontend:**\n- SvelteKit 5 (with runes)\n- TailwindCSS 4\n- Paraglide (i18n)\n- Vitest (testing)\n\n**Backend:**\n- Go 1.25+ (Echo framework)\n- SQLite (Bun ORM)\n- Background job queue (SQLite-backed polling)\n\n**Deployment:**\n- Single Go binary with embedded static files\n- Docker support\n- Zero external dependencies (no Redis, no PostgreSQL required)\n\n## 📁 Project Structure\n\n```\nopenpost/\n├── frontend/                  # SvelteKit frontend (web + Android app)\n│   ├── src/\n│   │   ├── lib/\n│   │   │   ├── api/       # API client (openapi-fetch)\n│   │   │   ├── components/# UI components\n│   │   │   └── stores/    # Auth, UI state\n│   │   └── routes/        # SvelteKit routes\n│   ├── android/            # Android native app (Capacitor)\n│   └── package.json\n│\n├── backend/\n│   ├── cmd/openpost/       # Main entry point\n│   │   └── public/        # Embedded SvelteKit build output (not source)\n│   ├── internal/\n│   │   ├── api/            # HTTP handlers \u0026 middleware\n│   │   │   ├── handlers/  # Posts, Auth, Media, OAuth handlers\n│   │   │   └── middleware/\n│   │   │       └── auth.go# JWT authentication middleware\n│   │   ├── config/         # Configuration loading\n│   │   ├── database/       # SQLite setup\n│   │   ├── models/         # Bun ORM models\n│   │   │   ├── models.go\n│   │   │   └── models_test.go\n│   │   ├── platform/       # Platform adapter interface + implementations\n│   │   │   ├── adapter.go # PlatformAdapter interface\n│   │   │   ├── http.go    # Shared HTTP helpers\n│   │   │   ├── x.go       # Twitter/X adapter\n│   │   │   ├── mastodon.go# Mastodon adapter\n│   │   │   ├── bluesky.go # Bluesky adapter\n│   │   │   ├── linkedin.go# LinkedIn adapter\n│   │   │   └── threads.go # Threads adapter\n│   │   ├── queue/          # Background job worker\n│   │   └── services/       # Business logic\n│   │       ├── auth/       # JWT \u0026 password handling\n│   │       │   ├── auth.go\n│   │       │   └── auth_test.go\n│   │       ├── crypto/     # Token encryption\n│   │       │   ├── encrypt.go\n│   │       │   └── encrypt_test.go\n│   │       ├── mediastore/ # Local/S3 media storage\n│   │       ├── publisher/  # Post publishing logic\n│   │       └── tokenmanager/ # Token refresh management\n│   ├── .golangci.yml       # Linter configuration\n│   ├── go.mod              # Go module definition\n│   └── go.sum              # Go module checksums\n│\n├── docs/                   # Platform integration docs\n├── AGENTS.md               # AI agent guidelines\n├── PLAN.md                 # Implementation roadmap\n└── README.md\n```\n\n## 🔐 Security\n\n- **Tokens are encrypted at rest** using AES-256-GCM\n- **Passwords hashed** with bcrypt\n- **JWT authentication** with configurable expiry\n- **No external services** - all data stays on your server\n- **OAuth PKCE** for Twitter authentication\n\n## 🗺️ Roadmap\n\nSee [PLAN.md](PLAN.md) for the complete implementation status and roadmap.\n\n### Current Status (MVP)\n\n- [x] User authentication (register/login)\n- [x] Workspace management (multi-tenant)\n- [x] Twitter/X OAuth\n- [x] Mastodon OAuth (multi-server support)\n- [x] Bluesky OAuth (AT Protocol)\n- [x] LinkedIn OAuth\n- [x] Threads OAuth (Meta Graph API)\n- [x] Post scheduling with background worker\n- [x] Media upload support\n- [x] Post threading (multi-post threads)\n- [x] Single binary deployment\n- [x] Token refresh for all platforms\n- [x] Platform adapter architecture\n\n### Coming Soon\n\n- [ ] Post analytics\n- [ ] Email notifications\n- [ ] Webhook support\n\n## 🤝 Contributing\n\nWe welcome contributions! Please see [AGENTS.md](AGENTS.md) for development guidelines.\n\n1. Fork the repository\n2. Create a feature branch (`git checkout -b feature/amazing-feature`)\n3. Commit your changes (`git commit -m 'Add amazing feature'`)\n4. Push to the branch (`git push origin feature/amazing-feature`)\n5. Open a Pull Request\n\n## 📄 License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## 🙏 Acknowledgments\n\n- Inspired by [Postiz](https://github.com/gitroomhq/postiz-app) and [Typefully](https://typefully.com)\n- Built with [Echo](https://echo.labstack.com/), [SvelteKit](https://kit.svelte.dev/), and [Bun ORM](https://bun.uptrace.dev/)\n\n---\n\n\u003cp align=\"center\"\u003e\n  Made with ❤️ by the OpenPost community\n\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frodrgds%2Fopenpost","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frodrgds%2Fopenpost","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frodrgds%2Fopenpost/lists"}