<<<<<<< HEAD # RosterChirp A modern, self-hosted team messaging Progressive Web App (PWA) built for small to medium teams. RosterChirp runs via Docker Compose with PostgreSQL and supports both single-tenant (self-hosted) and multi-tenant (hosted) deployments. Development was vibe-coded using Claude.ai. **Current version:** 0.13.1 ======= # rosterchirp A modern, self-hosted team messaging Progressive Web App (PWA) built for small to medium teams. rosterchirp runs entirely in a single Docker container with no external database dependencies — all data is stored locally using SQLite. >>>>>>> 1af039ab0a72560aace9b284d541f5201c920e28 --- ## Features ### Messaging - **Real-time messaging** — WebSocket-powered (Socket.io); messages appear instantly across all clients - **Image attachments** — Attach and send images via the + menu; auto-compressed client-side before upload - **Camera capture** — Take a photo directly from the + menu on mobile devices - **Emoji picker** — Send standalone emoji messages at large size via the + menu - **Message replies** — Quote and reply to any message with an inline preview - **Emoji reactions** — Quick-react with common emojis or open the full emoji picker; one reaction per user, replaceable - **@Mentions** — Type `@` to search and tag users using `@[Display Name]` syntax; autocomplete scoped to group members; mentioned users receive a notification - **Link previews** — URLs are automatically expanded with Open Graph metadata (title, image, site name) - **Typing indicators** — See when others are composing a message - **Image lightbox** — Tap any image to open it full-screen with pinch-to-zoom support - **Message grouping** — Consecutive messages from the same user are visually grouped; avatar and name shown only on first message - **Last message preview** — Sidebar shows "You:" prefix when the current user sent the last message ### Channels & Groups - **Public channels** — Admin-created; all users are automatically added - **Private groups / DMs** — Any user can create; membership is invite-only by the owner - **Direct messages** — One-to-one private conversations; sidebar title always shows the other user's real name - **Duplicate group prevention** — Creating a private group with the same member set as an existing group redirects to the existing group automatically - **Read-only channels** — Admin-configurable announcement-style channels; only admins can post - **Support group** — A private admin-only group that receives submissions from the login page contact form - **Custom group names** — Each user can set a personal display name for any group, visible only to them - **Group Messages** — Managed private groups (created and controlled by admins via Group Manager) appear in a separate "Private Group Messages" section in the sidebar ### Schedule - **Team schedule** — Full calendar view for creating and managing team events (Team plan) - **Desktop & mobile views** — Dedicated layout for each; desktop shows a full monthly grid, mobile shows a scrollable event list - **Event types** — Colour-coded event categories (configurable by admins) - **Recurring events** — Create daily, weekly, or custom-interval recurring events; only future occurrences are shown - **Availability** — Users can mark their availability per event - **Keyword filter** — Search events by keyword with word-boundary matching; quoted terms match exactly - **Type filter** — Filter events by event type across the current month (including past events, shown greyed) - **Past event protection** — New events cannot be created with a start date/time in the past ### Users & Profiles - **Authentication** — Email/password login with optional Remember Me (30-day session) - **Forced password change** — New users must change their password on first login - **User profiles** — Custom display name, avatar upload, About Me text - **Profile popup** — Click any user's avatar in chat to view their profile card - **Admin badge** — Admins display a role badge; can be hidden per-user in Profile settings - **Online presence** — Real-time online/offline status tracked per user - **Last seen** — Users' last online timestamp updated on disconnect ### Notifications - **In-app notifications** — Mention alerts with toast notifications - **Unread indicators** — Private groups with new unread messages are highlighted and bolded in the sidebar - **Push notifications** — Firebase Cloud Messaging (FCM) push notifications for mentions and new private messages when the app is backgrounded or closed (Android PWA; requires HTTPS and Firebase setup) ### Admin & Settings - **User Manager** — Create, suspend, activate, delete users; reset passwords; change roles - **Bulk CSV import** — Import multiple users at once from a CSV file - **Group Manager** — Create and manage private groups and their membership centrally (Team plan) - **App branding** — Customize app name, logo, and icons via the Settings panel (Brand+ plan) - **Reset to defaults** — One-click reset of all branding customizations - **Version display** — Current app version shown in the Settings panel - **Default user password** — Configurable via `USER_PASS` env var; shown live in User Manager - **Feature flags** — Plan-gated features (branding, group manager, schedule manager) controlled via settings ### User Deletion - Deleting a user scrubs their email, name, and avatar immediately - Their messages are marked deleted (content removed); direct message threads become read-only - Group memberships, sessions, push subscriptions, and notifications are purged - Suspended users retain all data and can be re-activated ### Help & Onboarding - **Getting Started modal** — Appears automatically on first login; users can dismiss permanently with "Do not show again" - **Help menu item** — Always accessible from the user menu regardless of dismissed state - **Editable help content** — `data/help.md` is edited before build and baked into the image at build time ### PWA - **Installable** — Install to home screen on mobile and desktop via the browser install prompt - **Adaptive icons** — Separate `any` and `maskable` icon entries; maskable icons sized for Android circular crop - **Dynamic app icon** — Uploaded logo is automatically resized and used as the PWA shortcut icon - **Dynamic manifest** — App name and icons update live when changed in Settings - **Pull-to-refresh disabled** — In PWA standalone mode, pull-to-refresh is disabled to prevent a layout shift bug on mobile ### Contact Form - **Login page contact form** — A "Contact Support" button on the login page opens a form that posts directly into the admin Support group --- ## Deployment Modes | Mode | Description | |---|---| | `selfhost` | Single tenant — one team, one database schema. Default. | | `host` | Multi-tenant — one schema per tenant, provisioned via subdomains. Requires `APP_DOMAIN`, `HOST_SLUG`, and `HOST_ADMIN_KEY`. | Set `APP_TYPE=selfhost` or `APP_TYPE=host` in `.env`. --- ## Plans & Feature Flags | Plan | Features | |---|---| | **RosterChirp-Chat** | Messaging, channels, DMs, profiles, push notifications | | **RosterChirp-Brand** | Everything in Chat + custom branding (logo, app name, icons) | | **RosterChirp-Team** | Everything in Brand + Group Manager + Schedule Manager | Feature flags are stored in the database `settings` table and can be toggled by the admin. --- ## Tech Stack | Layer | Technology | |---|---| | Backend | Node.js, Express, Socket.io | | Database | PostgreSQL 16 (via `pg`) | | Frontend | React 18, Vite | | Push notifications | Firebase Cloud Messaging (FCM) | | Image processing | sharp | | Containerization | Docker, Docker Compose v2 | | Reverse proxy / SSL | Caddy (recommended) | --- ## Requirements - **Docker** and **Docker Compose v2** - A domain name with DNS pointed at your server (required for HTTPS and push notifications) - Ports **80** and **443** open on your server firewall (if using Caddy for SSL) - (Optional) A Firebase project for push notifications --- ## Building the Image All builds use `build.sh`. No host Node.js installation is required. > **Tip:** Edit `data/help.md` before running `build.sh` to customise the Getting Started help content baked into the image. ```bash # Build and tag as :latest only ./build.sh # Build and tag as a specific version ./build.sh 0.13.1 ``` --- ## Installation ### 1. Clone the repository ```bash <<<<<<< HEAD git clone https://your-git/youruser/rosterchirp.git ======= git clone https://your-gitea/youruser/rosterchirp.git >>>>>>> 1af039ab0a72560aace9b284d541f5201c920e28 cd rosterchirp ``` ### 2. Build the Docker image ```bash ./build.sh 0.13.1 ``` ### 3. Configure environment ```bash cp .env.example .env nano .env ``` At minimum, set `ADMIN_EMAIL`, `ADMIN_PASS`, `ADMIN_NAME`, `JWT_SECRET`, and `DB_PASSWORD`. ### 4. Start the services ```bash docker compose up -d docker compose logs -f rosterchirp ``` ### 5. Log in Open `http://your-server:3000`, log in with your `ADMIN_EMAIL` and `ADMIN_PASS`, and change your password when prompted. --- ## HTTPS & SSL <<<<<<< HEAD RosterChirp does not manage SSL itself. Use **Caddy** as a reverse proxy. ======= rosterchirp does not manage SSL itself. Use **Caddy** as a reverse proxy. >>>>>>> 1af039ab0a72560aace9b284d541f5201c920e28 ### Caddyfile ``` chat.yourdomain.com { reverse_proxy rosterchirp:3000 } ``` ### docker-compose.yaml (with Caddy) ```yaml services: rosterchirp: <<<<<<< HEAD image: rosterchirp:${ROSTERCHIRP_VERSION:-latest} ======= image: rosterchirp:${rosterchirp_VERSION:-latest} >>>>>>> 1af039ab0a72560aace9b284d541f5201c920e28 container_name: rosterchirp restart: unless-stopped expose: - "3000" environment: - NODE_ENV=production - APP_TYPE=${APP_TYPE:-selfhost} - ADMIN_NAME=${ADMIN_NAME:-Admin User} - ADMIN_EMAIL=${ADMIN_EMAIL:-admin@rosterchirp.local} - ADMIN_PASS=${ADMIN_PASS:-Admin@1234} - USER_PASS=${USER_PASS:-user@1234} - ADMPW_RESET=${ADMPW_RESET:-false} - JWT_SECRET=${JWT_SECRET:-changeme} <<<<<<< HEAD - APP_NAME=${APP_NAME:-RosterChirp} - DEFCHAT_NAME=${DEFCHAT_NAME:-General Chat} - DB_HOST=db - DB_NAME=${DB_NAME:-rosterchirp} - DB_USER=${DB_USER:-rosterchirp} - DB_PASSWORD=${DB_PASSWORD} - ROSTERCHIRP_VERSION=${ROSTERCHIRP_VERSION:-latest} volumes: - rosterchirp_uploads:/app/uploads depends_on: db: condition: service_healthy db: image: postgres:16-alpine container_name: rosterchirp_db restart: unless-stopped environment: - POSTGRES_DB=${DB_NAME:-rosterchirp} - POSTGRES_USER=${DB_USER:-rosterchirp} - POSTGRES_PASSWORD=${DB_PASSWORD} volumes: - rosterchirp_db:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-rosterchirp}"] interval: 10s timeout: 5s retries: 5 ======= - APP_NAME=${APP_NAME:-rosterchirp} - rosterchirp_VERSION=${rosterchirp_VERSION:-latest} volumes: - rosterchirp_db:/app/data - rosterchirp_uploads:/app/uploads >>>>>>> 1af039ab0a72560aace9b284d541f5201c920e28 caddy: image: caddy:alpine container_name: caddy restart: unless-stopped ports: - "80:80" - "443:443" - "443:443/udp" volumes: - ./Caddyfile:/etc/caddy/Caddyfile:ro - caddy_data:/data - caddy_certs:/config depends_on: - rosterchirp volumes: rosterchirp_db: rosterchirp_uploads: caddy_data: caddy_certs: ``` --- ## Environment Variables | Variable | Default | Description | |---|---|---| <<<<<<< HEAD | `APP_TYPE` | `selfhost` | Deployment mode: `selfhost` (single tenant) or `host` (multi-tenant) | | `ROSTERCHIRP_VERSION` | `latest` | Docker image tag to run | ======= | `rosterchirp_VERSION` | `latest` | Docker image tag to run | >>>>>>> 1af039ab0a72560aace9b284d541f5201c920e28 | `TZ` | `UTC` | Container timezone (e.g. `America/Toronto`) | | `ADMIN_NAME` | `Admin User` | Display name of the default admin account | | `ADMIN_EMAIL` | `admin@rosterchirp.local` | Login email for the default admin account | | `ADMIN_PASS` | `Admin@1234` | Initial password for the default admin account | | `USER_PASS` | `user@1234` | Default temporary password for bulk-imported users when no password is specified in CSV | | `ADMPW_RESET` | `false` | If `true`, resets the admin password to `ADMIN_PASS` on every restart. Emergency recovery only. | | `JWT_SECRET` | *(insecure default)* | Secret used to sign auth tokens. **Must be changed in production.** | <<<<<<< HEAD | `APP_NAME` | `RosterChirp` | Initial application name (can also be changed in Settings UI) | | `DEFCHAT_NAME` | `General Chat` | Name of the default public channel created on first run | | `DB_HOST` | `db` | PostgreSQL hostname | | `DB_NAME` | `rosterchirp` | PostgreSQL database name | | `DB_USER` | `rosterchirp` | PostgreSQL username | | `DB_PASSWORD` | *(required)* | PostgreSQL password. **Avoid `!` — shell interpolation issue with Docker Compose.** | | `APP_DOMAIN` | — | Base domain for multi-tenant host mode (e.g. `example.com`) | | `HOST_SLUG` | — | Subdomain slug for the host control panel (e.g. `chathost` → `chathost.example.com`) | | `HOST_ADMIN_KEY` | — | Secret key for the host control plane API | ======= | `PORT` | `3000` | Host port to bind (without Caddy) | | `APP_NAME` | `rosterchirp` | Initial application name (can also be changed in Settings UI) | | `DEFCHAT_NAME` | `General Chat` | Name of the default public group created on first run | >>>>>>> 1af039ab0a72560aace9b284d541f5201c920e28 ### Firebase Push Notification Variables (optional) | Variable | Description | |---|---| | `FIREBASE_API_KEY` | Firebase web app API key | | `FIREBASE_PROJECT_ID` | Firebase project ID | | `FIREBASE_MESSAGING_SENDER_ID` | Firebase messaging sender ID | | `FIREBASE_APP_ID` | Firebase web app ID | | `FIREBASE_VAPID_KEY` | Web Push certificate public key (from Firebase Cloud Messaging tab) | | `FIREBASE_SERVICE_ACCOUNT` | Full service account JSON, stringified (remove all newlines) | > `ADMIN_EMAIL` and `ADMIN_PASS` are only used on the **first run**. Once the database is seeded they are ignored — unless `ADMPW_RESET=true`. ### Example `.env` ```env <<<<<<< HEAD ROSTERCHIRP_VERSION=0.13.1 APP_TYPE=selfhost ======= rosterchirp_VERSION=1.0.0 >>>>>>> 1af039ab0a72560aace9b284d541f5201c920e28 TZ=America/Toronto ADMIN_NAME=Your Name ADMIN_EMAIL=admin@yourdomain.com ADMIN_PASS=ChangeThisNow! USER_PASS=Welcome@123 ADMPW_RESET=false JWT_SECRET=replace-this-with-a-long-random-string-at-least-32-chars <<<<<<< HEAD APP_NAME=RosterChirp ======= PORT=3000 APP_NAME=rosterchirp >>>>>>> 1af039ab0a72560aace9b284d541f5201c920e28 DEFCHAT_NAME=General Chat DB_NAME=rosterchirp DB_USER=rosterchirp DB_PASSWORD=a-strong-db-password ``` --- ## First Login & Setup Checklist 1. Log in with `ADMIN_EMAIL` / `ADMIN_PASS` 2. Change your password when prompted 3. Read the **Getting Started** guide that appears on first login 4. Open ⚙️ **Settings** → upload a logo and set the app name 5. Open 👥 **User Manager** to create accounts for your team --- ## User Management Accessible from the bottom-left menu (admin only). | Action | Description | |---|---| | Create user | Set name, email, temporary password, and role | | Bulk CSV import | Upload a CSV to create multiple users at once | | Reset password | User is forced to set a new password on next login | | Suspend | Blocks login; messages are preserved | | Activate | Re-enables a suspended account | | Delete | Scrubs account data; messages are removed; threads become read-only | | Change role | Promote member → admin or demote admin → member | ### CSV Import Format ```csv name,email,password,role John Doe,john@example.com,TempPass123,member Jane Smith,jane@example.com,,admin ``` - `password` is optional — defaults to the value of `USER_PASS` if omitted - All imported users must change their password on first login --- ## Group Types | | Public Channels | Private Groups | Direct Messages | |---|---|---|---| | Who can create | Admin only | Any user | Any user | | Membership | All users (automatic) | Invite-only by owner | Two users only | | Sidebar title | Group name | Group name (customisable per user) | Other user's real name | | Rename | Admin only | Owner only | ❌ Not allowed | | Read-only mode | ✅ Optional | ❌ N/A | ❌ N/A | | Duplicate prevention | N/A | ✅ Redirects to existing | ✅ Redirects to existing | | Managed (Group Manager) | ❌ | ✅ Optional | ❌ | ### @Mention Scoping - **Public channels** — all active users appear in the `@` autocomplete - **Private groups** — only members of that group appear - **Direct messages** — only the other participant appears --- ## Custom Group Names Any user can set a personal display name for any group: 1. Open the group and tap the **ⓘ info** icon 2. Enter a name under **Your custom name** and tap **Save** 3. The custom name appears in your sidebar and chat header only 4. Message Info shows: `Custom Name (Owner's Name)` 5. Clear the field and tap **Save** to revert to the owner's name --- ## Schedule The Schedule page (Team plan) provides a full team calendar: - **Desktop view** — Monthly grid with event cards per day - **Mobile view** — Scrollable event list with a date picker - **Event types** — Colour-coded categories created by admins - **Recurring events** — Set daily, weekly, or custom recurrence intervals - **Availability** — Members can mark availability per event - **Keyword search** — Unquoted terms match word prefixes; quoted terms match whole words exactly - **Type filter** — Filter by event type across the full current month --- ## Push Notifications RosterChirp uses **Firebase Cloud Messaging (FCM)** for push notifications. HTTPS is required. ### Setup 1. Create a Firebase project at [console.firebase.google.com](https://console.firebase.google.com) 2. Add a **Web app** → copy the config values into `.env` 3. Go to **Project Settings → Cloud Messaging → Web Push certificates** → generate a key pair → copy the public key as `FIREBASE_VAPID_KEY` 4. Go to **Project Settings → Service accounts → Generate new private key** → download the JSON → stringify it (remove all newlines) → set as `FIREBASE_SERVICE_ACCOUNT` Push notifications are sent for: - New messages in private groups (to all members except the sender) - New messages in public channels (to all subscribers except the sender) - Image messages show as `📷 Image` --- ## Help Content The Getting Started guide is sourced from `data/help.md`. Edit before running `build.sh` — it is baked into the image at build time. ```bash nano data/help.md ./build.sh 0.13.1 ``` Users can access the guide at any time via **User menu → Help**. --- ## Data Persistence | Volume | Container path | Contents | |---|---|---| <<<<<<< HEAD | `rosterchirp_db` | `/var/lib/postgresql/data` | PostgreSQL data directory | ======= | `rosterchirp_db` | `/app/data` | SQLite database (`rosterchirp.db`), `help.md` | >>>>>>> 1af039ab0a72560aace9b284d541f5201c920e28 | `rosterchirp_uploads` | `/app/uploads` | Avatars, logos, PWA icons, message images | ### Backup ```bash # Backup database <<<<<<< HEAD docker compose exec db pg_dump -U rosterchirp rosterchirp | gzip > rosterchirp_db_$(date +%Y%m%d).sql.gz # Restore database gunzip -c rosterchirp_db_20240101.sql.gz | docker compose exec -T db psql -U rosterchirp rosterchirp ======= docker run --rm \ -v rosterchirp_db:/data \ -v $(pwd):/backup alpine \ tar czf /backup/rosterchirp_db_$(date +%Y%m%d).tar.gz -C /data . >>>>>>> 1af039ab0a72560aace9b284d541f5201c920e28 # Backup uploads docker run --rm \ -v rosterchirp_uploads:/data \ -v $(pwd):/backup alpine \ tar czf /backup/rosterchirp_uploads_$(date +%Y%m%d).tar.gz -C /data . ``` --- ## Upgrades & Rollbacks Database migrations run automatically on startup. There is no manual migration step. ```bash # Upgrade <<<<<<< HEAD ./build.sh 0.13.1 # Set ROSTERCHIRP_VERSION=0.13.1 in .env docker compose up -d # Rollback # Set ROSTERCHIRP_VERSION=0.12.x in .env ======= ./build.sh 1.1.0 # Set rosterchirp_VERSION=1.1.0 in .env docker compose up -d # Rollback # Set rosterchirp_VERSION=1.0.0 in .env >>>>>>> 1af039ab0a72560aace9b284d541f5201c920e28 docker compose up -d ``` Data volumes are untouched in both cases. --- ## PWA Icons | File | Purpose | |---|---| | `icon-192.png` / `icon-512.png` | Standard icons — desktop PWA shortcuts (`purpose: any`) | | `icon-192-maskable.png` / `icon-512-maskable.png` | Adaptive icons — Android home screen (`purpose: maskable`); logo at 75% scale on solid background | --- ## ADMPW_RESET Flag Resets the **admin account** password to `ADMIN_PASS` on every container restart. Use only when the admin password has been lost. ```env # Enable for recovery ADMPW_RESET=true # Disable after recovering access ADMPW_RESET=false ``` A ⚠️ warning banner is shown on the login page and in Settings when active. --- ## Development ```bash # Backend (port 3000) cd backend && npm install && npm run dev # Frontend (port 5173) cd frontend && npm install && npm run dev ``` The Vite dev server proxies all `/api` and `/socket.io` requests to the backend automatically. You will need a running PostgreSQL instance and a `.env` file in the project root. --- ## License Proprietary — all rights reserved.