{"id":24165949,"url":"https://github.com/rioredwards/pi-site","last_synced_at":"2026-05-07T19:18:21.121Z","repository":{"id":270515671,"uuid":"910609566","full_name":"rioredwards/pi-site","owner":"rioredwards","description":"Self-hosted on my raspberry-pi! 🥧","archived":false,"fork":false,"pushed_at":"2025-02-02T03:31:05.000Z","size":12762,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-02T04:18:55.717Z","etag":null,"topics":["learning","nextjs","raspberry-pi","self-hosted","typescript"],"latest_commit_sha":null,"homepage":"https://pi.rioedwards.com/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rioredwards.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-12-31T19:57:46.000Z","updated_at":"2025-01-12T01:38:31.000Z","dependencies_parsed_at":"2024-12-31T21:22:26.516Z","dependency_job_id":"0663c7d9-7dc5-4ce2-b225-7b207bb2abce","html_url":"https://github.com/rioredwards/pi-site","commit_stats":null,"previous_names":["rioredwards/pi-site"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rioredwards%2Fpi-site","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rioredwards%2Fpi-site/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rioredwards%2Fpi-site/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rioredwards%2Fpi-site/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rioredwards","download_url":"https://codeload.github.com/rioredwards/pi-site/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241476435,"owners_count":19968916,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["learning","nextjs","raspberry-pi","self-hosted","typescript"],"created_at":"2025-01-12T20:13:24.139Z","updated_at":"2026-05-07T19:18:21.102Z","avatar_url":"https://github.com/rioredwards.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# DogTown - Dog Photo Gallery\n\nA Next.js 15 portfolio website for showcasing dog photos with AI-powered validation. Built for self-hosting on a Raspberry Pi using Docker, PostgreSQL, and Cloudflare Tunnel.\n\n## Features\n\n- **Photo Gallery**: Responsive grid with infinite scroll pagination\n- **Lightbox Viewing**: Full-screen photo viewing with navigation\n- **AI Image Validation**: NSFW detection + dog verification via FastAPI service\n- **Authentication**: NextAuth with GitHub and Google OAuth\n- **User Profiles**: View any user's profile and their uploaded photos\n- **Profile Editing**: Users can customize their display name and profile picture\n- **Admin System**: Configurable admin users who can delete any photo\n- **Photo Upload**: Upload dog photos with AI validation and celebratory confetti\n- **Stats Dashboard**: Live system monitoring with 3D Raspberry Pi model\n- **About Page**: MDX-powered with lightbox galleries and interactive components\n- **Analytics**: Umami self-hosted analytics integration\n- **PostgreSQL Database**: Drizzle ORM with migration support\n- **Self-Hosted**: Runs on Raspberry Pi with Docker + Nginx\n\n## Prerequisites\n\n1. A Linux server (Raspberry Pi, Ubuntu, etc.)\n2. Domain name with Cloudflare DNS (for tunnel)\n3. GitHub and/or Google OAuth apps configured\n4. SSH access to your server\n\n## Quick Start (Local Development)\n\n### 1. Install Dependencies\n\n```bash\nnpm install\n```\n\n### 2. Configure Environment\n\n```bash\ncp .env.local.example .env.local\n# Edit .env.local with your values\n```\n\n### 3. Start Services\n\n```bash\n# Start all services (Postgres, AI validator, Next.js) via Docker\nnpm run dev\n\n# Or run separately:\ndocker compose up -d             # Start services only\nnpm run db:push                  # Push database schema (first time)\nnpm run dev:next                 # Start Next.js dev server\n```\n\nVisit http://localhost:3000\n\n### 4. Stop Services\n\n```bash\nnpm run dev:stop\n```\n\n## Production Deployment\n\n### Initial Setup\n\n1. **Create your production env file locally**:\n\n   ```bash\n   cp .env.example .env.prod\n   # Edit .env.prod with production values\n   ```\n\n2. **Sync env file to your server**:\n\n   ```bash\n   rsync -avz .env.prod pi@your-server:~/pi-site/.env.prod\n   ```\n\n3. **SSH into your server and deploy**:\n\n   ```bash\n   ssh your-server\n   git clone https://github.com/rioredwards/pi-site.git ~/pi-site\n   cd ~/pi-site\n   chmod +x deploy.sh\n   ./deploy.sh\n   ```\n\n   The deployment script will:\n   - Install Docker if needed\n   - Set up Nginx reverse proxy\n   - Build and start all containers\n   - Run database migrations automatically\n   - Clean up old Docker images\n\n### Updating Production\n\n```bash\n# On your server\ncd ~/pi-site\n./update.sh\n```\n\nOr simply re-run `./deploy.sh` - it's safe to run repeatedly.\n\n## Project Structure\n\n```\npi-site/\n├── app/                      # Next.js App Router\n│   ├── db/                   # Database schema, client, and migrations\n│   │   ├── schema.ts         # Drizzle schema (photos, users tables)\n│   │   ├── drizzle.ts        # Database client\n│   │   ├── actions.ts        # Server actions\n│   │   └── migrations/       # SQL migration files\n│   ├── lib/                  # Utilities and types\n│   ├── api/                  # API routes\n│   ├── profile/              # User profile pages\n│   │   ├── [userId]/         # View any user's profile\n│   │   └── edit/             # Edit own profile\n│   ├── stats/                # System stats dashboard\n│   ├── about/                # About page (MDX)\n│   └── auth.ts               # NextAuth configuration\n├── components/               # React components\n├── scripts/                  # Build and deployment scripts\n│   ├── run-migrations.js     # Production migration runner\n│   └── docker-entrypoint.sh  # Container startup script\n├── ai-img-validator/         # FastAPI image validation service\n├── docker-compose.yml        # Production services\n├── docker-compose.dev.yml    # Development services\n├── docker-compose.staging.yml # Staging services\n├── Dockerfile                # Next.js app container\n├── deploy.sh                 # Initial deployment script\n└── update.sh                 # Update script\n```\n\n## Environment Variables\n\n### Required\n\n```bash\n# Database\nPOSTGRES_USER=myuser\nPOSTGRES_PASSWORD=your-password\nPOSTGRES_DB=mydatabase\nDATABASE_URL=postgres://myuser:password@db:5432/mydatabase\n\n# NextAuth\nAUTH_SECRET=your-secret-here\nNEXTAUTH_URL=https://your-domain.com\nGITHUB_CLIENT_ID=your-github-id\nGITHUB_CLIENT_SECRET=your-github-secret\nGOOGLE_CLIENT_ID=your-google-id\nGOOGLE_CLIENT_SECRET=your-google-secret\n\n# Image handling\nPUBLIC_IMG_VALIDATOR_BASE_URL=http://ai-img-validator:8000  # prod\nIMG_UPLOAD_DIR=/data/uploads/images                          # prod\n\n# App keys\nSECRET_KEY=your-secret-key\nNEXT_PUBLIC_SAFE_KEY=your-public-key\n\n# System Profiler (for /stats page)\nSYSTEM_PROFILER_BASE_URL=http://system-profiler:8787  # prod\nSYSTEM_PROFILER_AUTH_TOKEN=your-auth-token\n```\n\n### Development vs Production\n\n| Variable                        | Development              | Production                     |\n| ------------------------------- | ------------------------ | ------------------------------ |\n| `DATABASE_URL`                  | `...@localhost:5432/...` | `...@db:5432/...`              |\n| `PUBLIC_IMG_VALIDATOR_BASE_URL` | `http://localhost:8000`  | `http://ai-img-validator:8000` |\n| `IMG_UPLOAD_DIR`                | `./.data/uploads/images` | `/data/uploads/images`         |\n\n### Optional\n\n```bash\n# Admin users (comma-separated provider-accountId format)\nADMIN_USER_IDS=github-123456,google-789012\n\n# Umami Analytics\nNEXT_PUBLIC_UMAMI_WEBSITE_ID=your-website-id\nNEXT_PUBLIC_UMAMI_URL=https://your-umami-instance.com\n```\n\n## Tech Stack\n\n- **Frontend**: Next.js 15, React 19, Tailwind CSS v4, Three.js\n- **Content**: MDX for rich content pages\n- **Backend**: Next.js Server Actions, NextAuth v4\n- **Database**: PostgreSQL 17 + Drizzle ORM\n- **AI Service**: FastAPI (Python) with NSFW + dog detection\n- **Analytics**: Umami (self-hosted)\n- **Deployment**: Docker, Nginx, Cloudflare Tunnel\n- **Icons**: Lucide React, Hugeicons\n\n## Development Commands\n\n```bash\n# Development (Docker-based)\nnpm run dev                    # Start all services (Postgres, AI validator, etc.) via Docker\nnpm run dev:next               # Start Next.js dev server only (if services already running)\nnpm run dev:stop               # Stop dev services\nnpm run dev:logs               # View dev service logs\n\n# Staging (full stack locally in Docker)\nnpm run staging                # Start all services in Docker (staging config)\nnpm run staging:stop           # Stop staging\nnpm run staging:logs           # View staging logs\nnpm run staging:reset          # Wipe staging (including volumes)\n\n# Database\nnpm run db:generate            # Generate migrations from schema changes\nnpm run db:migrate             # Run pending migrations\nnpm run db:push                # Push schema directly (dev only)\nnpm run db:studio              # Open Drizzle Studio\n\n# Build\nnpm run build                  # Build for production\nnpm run start                  # Start production server\nnpm run lint                   # Run ESLint\nnpm run typecheck              # Run TypeScript check\n```\n\n## Database Management\n\n### Migration Workflow\n\n1. Make changes to `app/db/schema.ts`\n2. Generate migration: `npm run db:generate`\n3. Commit the migration files in `app/db/migrations/`\n4. Deploy - migrations run automatically at container startup\n\n### Local Development\n\n```bash\n# Quick schema sync (no migrations)\nnpm run db:push\n\n# View database in browser\nnpm run db:studio\n\n# Direct database access\ndocker exec -it pi_site_dev-db-1 psql -U myuser -d mydatabase\n```\n\n### Production\n\n```bash\n# SSH into server\nssh your-server\ncd ~/pi-site\n\n# Access database\ndocker compose exec db psql -U myuser -d mydatabase\n```\n\n## OAuth Setup\n\n### GitHub OAuth App\n\n1. Go to https://github.com/settings/developers\n2. Create a new OAuth App\n3. Set **Authorization callback URL** to:\n   ```\n   https://your-domain.com/api/auth/callback/github\n   ```\n4. Add Client ID and Secret to `.env.prod`\n\n### Google OAuth App\n\n1. Go to https://console.cloud.google.com/apis/credentials\n2. Create OAuth 2.0 Client ID\n3. Add to **Authorized redirect URIs**:\n   ```\n   https://your-domain.com/api/auth/callback/google\n   ```\n4. Add Client ID and Secret to `.env.prod`\n\n## Admin Users\n\nTo make a user an admin:\n\n1. Sign in to the app\n2. Check your user ID in the database:\n   ```sql\n   SELECT DISTINCT user_id FROM photos;\n   ```\n3. Add your user ID to `.env.prod`:\n   ```bash\n   ADMIN_USER_IDS=github-123456,google-789012\n   ```\n4. Restart the app\n\nAdmins can delete any photo; regular users can only delete their own.\n\n## Troubleshooting\n\n### Database Connection Issues\n\n- Ensure DATABASE_URL uses `db` as hostname in production (Docker network)\n- Use `localhost` only in development\n- Restart containers: `docker compose down \u0026\u0026 docker compose up -d`\n\n### Auth Issues\n\n- Ensure `AUTH_SECRET` is set\n- Ensure `NEXTAUTH_URL` matches your actual domain\n- Restart containers after changing env vars\n\n### Image Upload Fails\n\n- Check AI validator is running: `docker compose ps`\n- View logs: `docker compose logs ai-img-validator`\n- Ensure images are \u003c 5MB and are JPEG/PNG/WebP format\n\n### Docker IPv6 Issues\n\nIf Docker can't pull images due to IPv6 issues:\n\n```bash\n# Disable IPv6 for Docker\nsudo tee /etc/docker/daemon.json \u003e /dev/null \u003c\u003c'EOF'\n{\n  \"ip6tables\": false,\n  \"ipv6\": false\n}\nEOF\nsudo systemctl restart docker\n```\n\n### Disk Space (Old Docker Images)\n\nThe deploy script automatically prunes old images. For manual cleanup:\n\n```bash\ndocker image prune -f           # Remove unused images\ndocker system prune -af         # Remove everything unused (careful!)\n```\n\n## License\n\nMIT\n\n## Credits\n\nBased on the [Next.js Self-Hosting Example](https://github.com/leerob/next-self-host) by Lee Robinson.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frioredwards%2Fpi-site","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frioredwards%2Fpi-site","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frioredwards%2Fpi-site/lists"}