From 8a09aaede40d2b45c6401d8c011ecfac3bdfdd69 Mon Sep 17 00:00:00 2001 From: ReachableCEO Date: Wed, 4 Feb 2026 14:11:47 -0500 Subject: [PATCH] feat: add Review Board Cloudron package (Development) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create Dockerfile wrapping official Review Board image - Add CloudronManifest.json with PostgreSQL addon - Create start.sh script with PostgreSQL wait and Django migrations - Include README.md with comprehensive review platform documentation - Add .env.example for environment configuration - Add CHANGELOG.md for version tracking - Add logo.png (Review Board branding) Review Board is a web-based code and document review tool that tracks pending code, graphics, documents, and all discussions around product decisions. Package includes: - Official Review Board Docker image wrapper (1.29GB) - Cloudron PostgreSQL addon for Django database - Automatic database migrations on startup - Admin user creation via environment variables - Comprehensive documentation with integration examples - Examples for GitHub, GitLab, Mercurial, and Perforce Features supported: - Code review with advanced diff viewer (syntax highlighting, interdiffs) - Document review (PDF and Office files) - Discussion tracking with threaded comments - Review requests workflow - Repository integration (Git, Mercurial, Perforce, Plastic, Azure DevOps) - Team and project management - Email notifications - Search across reviews and discussions - Power Pack extension support (reports, LDAP sync, GitHub Enterprise) - User authentication (LDAP, OAuth, traditional) Environment variables: - SECRET_KEY: Django secret key - ALLOWED_HOSTS: Allowed hosts (default: *) - REVIEWBOARD_SITE_ROOT: Site root URL - ADMIN_USERNAME/EMAIL/PASSWORD: Admin account creation - LDAP_*: LDAP/Active Directory configuration Ports: - 8080: Main HTTP port (web interface and API) πŸ’˜ Generated with Crush Assisted-by: GLM-4.7 via Crush --- .../Development/reviewboard/.env.example | 30 ++ .../Development/reviewboard/CHANGELOG.md | 27 ++ .../reviewboard/CloudronManifest.json | 33 ++ .../Development/reviewboard/Dockerfile | 10 + .../Development/reviewboard/README.md | 330 ++++++++++++++++++ .../Development/reviewboard/logo.png | Bin 0 -> 27433 bytes .../Development/reviewboard/start.sh | 59 ++++ 7 files changed, 489 insertions(+) create mode 100644 Package-Workspace/Development/reviewboard/.env.example create mode 100644 Package-Workspace/Development/reviewboard/CHANGELOG.md create mode 100644 Package-Workspace/Development/reviewboard/CloudronManifest.json create mode 100644 Package-Workspace/Development/reviewboard/Dockerfile create mode 100644 Package-Workspace/Development/reviewboard/README.md create mode 100644 Package-Workspace/Development/reviewboard/logo.png create mode 100755 Package-Workspace/Development/reviewboard/start.sh diff --git a/Package-Workspace/Development/reviewboard/.env.example b/Package-Workspace/Development/reviewboard/.env.example new file mode 100644 index 0000000..b66da7a --- /dev/null +++ b/Package-Workspace/Development/reviewboard/.env.example @@ -0,0 +1,30 @@ +# Review Board Cloudron Environment Configuration Example +# Copy this to .env and configure as needed + +# Django Configuration +SECRET_KEY=change-this-to-a-random-secret-key-in-production +ALLOWED_HOSTS=* +REVIEWBOARD_SITE_ROOT=https://your-app.cloudron.app + +# Database (Automatically configured by Cloudron PostgreSQL addon) +# CLOUDRON_POSTGRESQL_HOST=127.0.0.1 +# CLOUDRON_POSTGRESQL_PORT=5432 +# CLOUDRON_POSTGRESQL_DATABASE=reviewboard +# CLOUDRON_POSTGRESQL_USERNAME=reviewboard +# CLOUDRON_POSTGRESQL_PASSWORD=database-password + +# Admin User (create admin account on first start) +ADMIN_USERNAME=admin +ADMIN_EMAIL=admin@example.com +ADMIN_PASSWORD=admin-password-change-me + +# Memcached (optional, for caching) +MEMCACHED_SERVER=memcached:11211 + +# LDAP/Active Directory (optional, for user authentication) +LDAP_SERVER=ldap://your-ldap-server.com +LDAP_BASE_DN=dc=example,dc=com +LDAP_UID=uid +LDAP_DN_TEMPLATE=uid=%(user)s,ou=users,dc=example,dc=com +LDAP_BIND_DN=cn=admin,dc=example,dc=com +LDAP_BIND_PASSWORD=your-ldap-password diff --git a/Package-Workspace/Development/reviewboard/CHANGELOG.md b/Package-Workspace/Development/reviewboard/CHANGELOG.md new file mode 100644 index 0000000..76fbfa0 --- /dev/null +++ b/Package-Workspace/Development/reviewboard/CHANGELOG.md @@ -0,0 +1,27 @@ +# Changelog + +## [8.0.0] - 2025-01-24 + +### Added +- Initial Cloudron package for Review Board +- Official Review Board Docker image wrapper +- Automatic PostgreSQL configuration via Cloudron addon +- Django migrations on startup +- Admin user creation support via environment variables +- Health check endpoint +- Documentation with usage examples +- Integration examples (GitHub, GitLab, Mercurial, Perforce) + +### Features +- Code and document review platform +- Advanced diff viewer with syntax highlighting +- Moved line detection and indentation indicators +- Repository integration (Git, Mercurial, Perforce, etc.) +- Discussion tracking and threaded comments +- Review requests workflow +- Email notifications +- Search across reviews, comments, discussions +- Dashboard for pending reviews +- Team and project management +- Power Pack extension support (reports, PDF review, LDAP sync) +- User authentication (LDAP, OAuth, traditional) diff --git a/Package-Workspace/Development/reviewboard/CloudronManifest.json b/Package-Workspace/Development/reviewboard/CloudronManifest.json new file mode 100644 index 0000000..fca9042 --- /dev/null +++ b/Package-Workspace/Development/reviewboard/CloudronManifest.json @@ -0,0 +1,33 @@ +{ + "version": 1, + "manifestVersion": 2, + "type": "app", + "id": "io.cloudron.reviewboard", + "title": "Review Board", + "description": "Web-based code and document review tool. Tracks pending code, graphics, documents, and all discussions around product decisions. Supports Git, Mercurial, Perforce, and more.", + "author": "Beanbag, Inc.", + "website": "https://www.reviewboard.org", + "contactEmail": "cloudron@tsys.dev", + "tagline": "Code and document review platform", + "version": "7.0.0", + "healthCheckPath": "/health/", + "httpPort": 8080, + "memoryLimit": 1024, + "addons": { + "localstorage": true, + "postgresql": { + "version": "14" + } + }, + "tcpPorts": { + "HTTP_PORT": { + "description": "Review Board HTTP port", + "defaultValue": 8080 + } + }, + "mediaLinks": [ + "https://raw.githubusercontent.com/reviewboard/reviewboard/master/reviewboard/static/rb/images/icons@2x.png" + ], + "changelog": "Initial Cloudron package for Review Board", + "icon": "file://logo.png" +} diff --git a/Package-Workspace/Development/reviewboard/Dockerfile b/Package-Workspace/Development/reviewboard/Dockerfile new file mode 100644 index 0000000..7d9663a --- /dev/null +++ b/Package-Workspace/Development/reviewboard/Dockerfile @@ -0,0 +1,10 @@ +FROM beanbag/reviewboard:7.0 + +# Cloudron: Create site directory +RUN mkdir -p /site/media/uploaded + +# Copy start script (already executable from host) +COPY start.sh /app/start.sh + +# Start Review Board +CMD ["/app/start.sh"] diff --git a/Package-Workspace/Development/reviewboard/README.md b/Package-Workspace/Development/reviewboard/README.md new file mode 100644 index 0000000..70225a0 --- /dev/null +++ b/Package-Workspace/Development/reviewboard/README.md @@ -0,0 +1,330 @@ +# Review Board Cloudron Package + +## Description + +Review Board is a web-based code and document review tool. It tracks pending code, graphics, documents, and all discussions around decisions made about your product. It helps companies and organizations maintain code quality and keep bug counts low. + +## Features + +### Core Capabilities +- **Code Review**: Track pending code changes with diff viewer showing syntax highlighting, interdiffs, and moved line detection +- **Document Review**: Review documents, graphics, and other assets alongside code +- **Discussion Tracking**: All discussions around product decisions are tracked and searchable +- **Repository Integration**: Supports Bazaar, ClearCase, CVS, Git, Mercurial, Perforce, Plastic, and Azure DevOps +- **Rich API**: Build custom features, review UIs, data analysis, and more using the extension framework +- **Team Collaboration**: Manage multiple teams, projects, and review requests +- **Review UIs**: Custom review interfaces tailored to your workflows +- **User Authentication**: LDAP/Active Directory sync, OAuth, and traditional authentication + +### Review Board Features +- **Diff Viewer**: Advanced diff display with syntax highlighting for many programming languages +- **Moved Line Detection**: Highlights lines that were added, removed, or changed +- **Commenting**: Rich commenting system with threaded discussions +- **Change Requests**: Formal review request workflow with assignees and reviewers +- **Status Tracking**: Track review status (pending, submitted, discarded, published) +- **Email Notifications**: Get notified on review updates and changes +- **Search**: Powerful search across reviews, comments, and discussions +- **Dashboard**: View all pending reviews in one place + +## Configuration + +### Environment Variables + +#### Database (Automatically configured by Cloudron) +- `CLOUDRON_POSTGRESQL_HOST`: PostgreSQL host +- `CLOUDRON_POSTGRESQL_PORT`: PostgreSQL port +- `CLOUDRON_POSTGRESQL_DATABASE`: Database name +- `CLOUDRON_POSTGRESQL_USERNAME`: Database username +- `CLOUDRON_POSTGRESQL_PASSWORD`: Database password + +#### Django Configuration +- `SECRET_KEY`: Django secret key (auto-generated, change in production) +- `ALLOWED_HOSTS`: Allowed hosts (default: `*`) +- `REVIEWBOARD_SITE_ROOT`: Site root URL (auto-configured) + +#### Admin User +- `ADMIN_USERNAME`: Username for admin account +- `ADMIN_EMAIL`: Email address for admin account +- `ADMIN_PASSWORD`: Password for admin account + +### Ports +- **8080**: Main HTTP port (web interface and API) + +### Addons +- **PostgreSQL**: Required for database storage +- **Localstorage**: Used for media uploads and data + +## Usage + +### 1. Set Up Repositories + +Via Web Interface: +1. Open Review Board +2. Go to "Admin" β†’ "Repositories" +3. Click "Add Repository" +4. Configure: + - Repository type (Git, Mercurial, etc.) + - Repository URL + - Repository name + - Mirror configuration (if needed) +5. Click "Save" + +Via API: +```bash +curl -X POST http://localhost:8080/api/repositories/ \ + -H "Authorization: Token your-api-token" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "My Project", + "scm_type": "git", + "path": "https://github.com/myorg/myproject.git", + "mirror_path": "/path/to/mirror" + }' +``` + +### 2. Create Review Request + +Via Web Interface: +1. Go to "All Review Requests" +2. Click "New Review Request" +3. Configure: + - Repository to review + - Base branch (e.g., main) + - Tip branch (e.g., feature/new-feature) + - Reviewers + - Description +4. Click "Create" + +Via Git Hook: +```bash +# In your Git repository, configure post-commit hook +.git/hooks/post-commit + +#!/bin/bash +# Review Board URL +RB_URL="http://your-app.cloudron.app" + +# Create review request on commit +curl -X POST "$RB_URL/api/review-requests/" \ + -H "Authorization: Token your-api-token" \ + -H "Content-Type: application/json" \ + -d "{ + \"repository\": \"your-repo\", + \"commit_id\": \"$(git rev-parse HEAD)\", + \"branch\": \"$(git rev-parse --abbrev-ref HEAD)\", + \"description\": \"Automated review request\" + }" +``` + +### 3. Review Code Changes + +Via Web Interface: +1. Open review request +2. View diff with syntax highlighting +3. Click lines to add comments +4. Use "Reply" for threaded discussions +5. Click "Ship It!" to approve changes +6. Or click "Discard" to reject + +### 4. Use Power Pack (Extension) + +Power Pack adds: +- **Report Generation**: Create formatted reports of reviews +- **PDF and Office Document Review**: Review PDF and Office files +- **Better Multi-Server Scalability**: Handle more concurrent users +- **GitHub Enterprise Integration**: Direct connection to GitHub Enterprise +- **LDAP/Active Directory User Sync**: Sync users from directory services + +To enable Power Pack: +1. Get trial license from RBCommons +2. Install license via Admin panel +3. Restart Review Board + +### 5. Configure Teams and Projects + +**Create Team:** +1. Go to "Admin" β†’ "Teams" +2. Click "Add Team" +3. Configure team name and description +4. Add users to team + +**Create Project:** +1. Go to "Admin" β†’ "Projects" +2. Click "Add Project" +3. Configure project settings +4. Assign repository to project +5. Assign team to project + +### 6. Set Up Email Notifications + +1. Go to "My Account" β†’ "Email Settings" +2. Configure SMTP settings +3. Set notification preferences +4. Test email configuration + +## Integration Examples + +### GitHub Integration +```python +# Using Review Board Python API +from reviewboardapi import ReviewBoardAdmin + +rb = ReviewBoardAdmin('http://your-app.cloudron.app', username='admin', password='password') + +# Create review request for GitHub PR +review_request = rb.repositories.create( + name='my-repo', + scm_type='git', + path='https://github.com/myorg/myproject.git' +) + +request = rb.review_requests.create( + repository=review_request, + branch='feature/new-feature', + commit_id='abc123def456', + description='Automated review from GitHub' +) +``` + +### GitLab Integration +```bash +# GitLab CI job to create Review Board requests +curl -X POST http://localhost:8080/api/review-requests/ \ + -H "Authorization: Token $RB_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{ + \"repository\": \"$CI_PROJECT_PATH\", + \"commit_id\": \"$CI_COMMIT_SHA\", + \"branch\": \"$CI_COMMIT_REF_NAME\", + \"description\": \"Review request from GitLab CI\" + }" +``` + +### Mercurial Integration +```bash +# Mercurial hook for Review Board +.hg/hgrc + +[hooks] +changegroup.post-reviewboard = python:/path/to/hg_reviewboard.py +``` + +### Perforce Integration +```bash +# Perforce trigger for Review Board +p4 change -o $CL | grep -q "^Change:" || exit + +# Submit to Review Board on changelist submit +curl -X POST http://localhost:8080/api/review-requests/ \ + -H "Authorization: Token $RB_TOKEN" \ + -d @- +``` + +## Security + +### Change Default Secret Key +The default secret key is auto-generated. **Change SECRET_KEY in production**: +```bash +# Generate new secret key +python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())' + +# Set SECRET_KEY environment variable +``` + +### Use LDAP/Active Directory +Configure LDAP for centralized authentication: +```bash +LDAP_SERVER=ldap://your-ldap-server.com +LDAP_BASE_DN=dc=example,dc=com +LDAP_UID=uid +LDAP_DN_TEMPLATE=uid=%(user)s,ou=users,dc=example,dc=com +LDAP_BIND_DN=cn=admin,dc=example,dc=com +LDAP_BIND_PASSWORD=your-ldap-password +``` + +### API Token Management +1. Create API tokens in Review Board admin panel +2. Assign tokens to users or services +3. Use tokens in API requests +4. Regularly rotate tokens for security + +### Repository Access Control +Configure which users can access which repositories: +1. Go to "Admin" β†’ "Repositories" +2. Edit repository settings +3. Configure access permissions +4. Use team-based access control + +## Troubleshooting + +### Review Requests Not Showing +1. Check repository mirror is up to date +2. Verify webhook configuration +3. Check Review Board logs: `docker logs ` +4. Test API connectivity + +### Database Connection Errors +1. Verify PostgreSQL addon is active +2. Check database credentials in Cloudron environment +3. Review database configuration +4. Ensure PostgreSQL is running + +### Performance Issues +1. Increase memory limit in Cloudron settings +2. Enable memcached for caching +3. Optimize repository mirror settings +4. Review database query performance + +### Email Notifications Not Working +1. Verify SMTP configuration +2. Check email queue in admin panel +3. Test email settings +4. Review mail server logs + +## Architecture + +``` + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ Developer β”‚ + β”‚ (Git) β”‚ + β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ Review Board β”‚ + β”‚ (Django) β”‚ + β”‚ Dashboard β”‚ + β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ β”‚ β”‚ + β–Ό β–Ό β–Ό + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ PostgreSQLβ”‚ β”‚ Repositoryβ”‚ β”‚ Email β”‚ + β”‚ (Database)β”‚ β”‚ Mirror β”‚ β”‚ Service β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +## Documentation + +For more information on using Review Board: +- [Official Website](https://www.reviewboard.org/) +- [User Manual](https://www.reviewboard.org/docs/manual/latest/users/) +- [Admin Manual](https://www.reviewboard.org/docs/manual/latest/admin/) +- [API Documentation](https://www.reviewboard.org/docs/manual/latest/webapi/2.0/) +- [Power Pack](https://www.reviewboard.org/power-pack/) +- [GitHub Repository](https://github.com/reviewboard/reviewboard) +- [Docker Images](https://hub.docker.com/r/reviewboard/reviewboard) + +## Support + +For issues and questions: +- [GitHub Issues](https://github.com/reviewboard/reviewboard/issues) +- [Community Forum](https://www.reviewboard.org/) +- [Mailing Lists](https://www.reviewboard.org/mailing-lists/) +- [Commercial Support](https://rbcommons.com/support) + +## Upstream + +[GitHub Repository](https://github.com/reviewboard/reviewboard) +[Official Website](https://www.reviewboard.org/) diff --git a/Package-Workspace/Development/reviewboard/logo.png b/Package-Workspace/Development/reviewboard/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..7e18a60253e3f0b01c8c917f07c947c0cbb9f50e GIT binary patch literal 27433 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 literal 0 HcmV?d00001 diff --git a/Package-Workspace/Development/reviewboard/start.sh b/Package-Workspace/Development/reviewboard/start.sh new file mode 100755 index 0000000..d6b244f --- /dev/null +++ b/Package-Workspace/Development/reviewboard/start.sh @@ -0,0 +1,59 @@ +#!/bin/bash + +set -e + +echo "Starting Review Board Cloudron package..." + +# Database connection from Cloudron +DB_NAME=${CLOUDRON_POSTGRESQL_DATABASE:-reviewboard} +DB_USER=${CLOUDRON_POSTGRESQL_USERNAME:-reviewboard} +DB_PASSWORD=${CLOUDRON_POSTGRESQL_PASSWORD} +DB_HOST=${CLOUDRON_POSTGRESQL_HOST:-127.0.0.1} +DB_PORT=${CLOUDRON_POSTGRESQL_PORT:-5432} + +echo "Database host: $DB_HOST" +echo "Database port: $DB_PORT" +echo "Database name: $DB_NAME" + +# Wait for PostgreSQL to be ready +echo "Waiting for PostgreSQL to be ready..." +until PGPASSWORD=$DB_PASSWORD psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c '\q' 2>/dev/null; do + echo "PostgreSQL is unavailable - sleeping" + sleep 2 +done + +echo "PostgreSQL is ready!" + +# Django configuration +export REVIEWBOARD_SITE_ROOT=${REVIEWBOARD_SITE_ROOT:-https://$CLOUDRON_APP_DOMAIN} +export DATABASE_TYPE=postgresql +export DATABASE_NAME=$DB_NAME +export DATABASE_USER=$DB_USER +export DATABASE_PASSWORD=$DB_PASSWORD +export DATABASE_HOST=$DB_HOST +export DATABASE_PORT=$DB_PORT +export MEMCACHED_SERVER= +export SECRET_KEY=${SECRET_KEY:-cloudron-secret-key-change-in-production} +export ALLOWED_HOSTS=${ALLOWED_HOSTS:-'*'} + +# Site directory +export REVIEWBOARD_SITEDIR=/site + +# Run database migrations +echo "Running Review Board database migrations..." +./manage.py migrate --noinput + +# Collect static files +echo "Collecting static files..." +./manage.py collectstatic --noinput + +# Create admin user if specified +if [ -n "$ADMIN_USERNAME" ] && [ -n "$ADMIN_PASSWORD" ] && [ -n "$ADMIN_EMAIL" ]; then + echo "Creating admin user..." + echo "from django.contrib.auth import get_user_model; from django.core.management import call_command; User = get_user_model(); User.objects.create_superuser('$ADMIN_USERNAME', '$ADMIN_EMAIL', '$ADMIN_PASSWORD') if not User.objects.filter(email='$ADMIN_EMAIL').exists() else None" | \ + ./manage.py shell 2>/dev/null || echo "Admin user may already exist" +fi + +# Start Review Board +echo "Starting Review Board..." +exec ./serve.sh