FossilRepo

Source Blame History 178 lines
4ce269c… ragelink 1 import logging
4ce269c… ragelink 2
4ce269c… ragelink 3 from django.contrib.auth.models import Group, Permission, User
4ce269c… ragelink 4 from django.core.management.base import BaseCommand
4ce269c… ragelink 5
c588255… ragelink 6 from organization.models import Organization, OrganizationMember, OrgRole, Team
4ce269c… ragelink 7 from pages.models import Page
4ce269c… ragelink 8 from projects.models import Project, ProjectTeam
4ce269c… ragelink 9
4ce269c… ragelink 10 logger = logging.getLogger(__name__)
4ce269c… ragelink 11
4ce269c… ragelink 12
4ce269c… ragelink 13 class Command(BaseCommand):
4ce269c… ragelink 14 help = "Seed the database with initial data for development."
4ce269c… ragelink 15
4ce269c… ragelink 16 def add_arguments(self, parser):
4ce269c… ragelink 17 parser.add_argument("--flush", action="store_true", help="Flush non-system tables before seeding.")
4ce269c… ragelink 18
4ce269c… ragelink 19 def handle(self, *args, **options):
4ce269c… ragelink 20 if options["flush"]:
4ce269c… ragelink 21 self.stdout.write("Flushing data...")
4ce269c… ragelink 22 Page.all_objects.all().delete()
4ce269c… ragelink 23 ProjectTeam.all_objects.all().delete()
4ce269c… ragelink 24 Project.all_objects.all().delete()
4ce269c… ragelink 25 Team.all_objects.all().delete()
4ce269c… ragelink 26 OrganizationMember.all_objects.all().delete()
4ce269c… ragelink 27 Organization.all_objects.all().delete()
4ce269c… ragelink 28
4ce269c… ragelink 29 # Groups and permissions
4ce269c… ragelink 30 admin_group, _ = Group.objects.get_or_create(name="Administrators")
4ce269c… ragelink 31 viewer_group, _ = Group.objects.get_or_create(name="Viewers")
4ce269c… ragelink 32
c588255… ragelink 33 # Admin group gets all permissions for org, projects, and pages
c588255… ragelink 34 for app_label in ["organization", "projects", "pages"]:
4ce269c… ragelink 35 perms = Permission.objects.filter(content_type__app_label=app_label)
4ce269c… ragelink 36 admin_group.permissions.add(*perms)
4ce269c… ragelink 37
c588255… ragelink 38 # Viewer group gets view permissions for org, projects, and pages
4ce269c… ragelink 39 view_perms = Permission.objects.filter(
c588255… ragelink 40 content_type__app_label__in=["organization", "projects", "pages"],
4ce269c… ragelink 41 codename__startswith="view_",
4ce269c… ragelink 42 )
4ce269c… ragelink 43 viewer_group.permissions.set(view_perms)
4ce269c… ragelink 44
4ce269c… ragelink 45 # Superuser
4ce269c… ragelink 46 admin_user, created = User.objects.get_or_create(
4ce269c… ragelink 47 username="admin",
4ce269c… ragelink 48 defaults={"email": "[email protected]", "is_staff": True, "is_superuser": True},
4ce269c… ragelink 49 )
4ce269c… ragelink 50 if created:
4ce269c… ragelink 51 admin_user.set_password("admin")
4ce269c… ragelink 52 admin_user.save()
4ce269c… ragelink 53 self.stdout.write(self.style.SUCCESS("Created superuser: admin / admin"))
4ce269c… ragelink 54
4ce269c… ragelink 55 # Regular user
4ce269c… ragelink 56 viewer_user, created = User.objects.get_or_create(
4ce269c… ragelink 57 username="viewer",
4ce269c… ragelink 58 defaults={"email": "[email protected]", "is_staff": False, "is_superuser": False},
4ce269c… ragelink 59 )
4ce269c… ragelink 60 if created:
4ce269c… ragelink 61 viewer_user.set_password("viewer")
4ce269c… ragelink 62 viewer_user.save()
4ce269c… ragelink 63 viewer_user.groups.add(viewer_group)
4ce269c… ragelink 64 self.stdout.write(self.style.SUCCESS("Created viewer user: viewer / viewer"))
4ce269c… ragelink 65
4ce269c… ragelink 66 # Organization
4ce269c… ragelink 67 org, _ = Organization.objects.get_or_create(name="Fossilrepo HQ", defaults={"description": "Default organization"})
4ce269c… ragelink 68 OrganizationMember.objects.get_or_create(member=admin_user, organization=org)
4ce269c… ragelink 69 OrganizationMember.objects.get_or_create(member=viewer_user, organization=org)
4ce269c… ragelink 70
4ce269c… ragelink 71 # Teams
4ce269c… ragelink 72 core_devs, _ = Team.objects.get_or_create(name="Core Devs", defaults={"organization": org, "description": "Core development team"})
4ce269c… ragelink 73 core_devs.members.add(admin_user)
4ce269c… ragelink 74
4ce269c… ragelink 75 contributors, _ = Team.objects.get_or_create(
4ce269c… ragelink 76 name="Contributors", defaults={"organization": org, "description": "Community contributors"}
4ce269c… ragelink 77 )
4ce269c… ragelink 78 contributors.members.add(viewer_user)
4ce269c… ragelink 79
4ce269c… ragelink 80 reviewers, _ = Team.objects.get_or_create(name="Reviewers", defaults={"organization": org, "description": "Code review team"})
4ce269c… ragelink 81 reviewers.members.add(admin_user, viewer_user)
4ce269c… ragelink 82
4ce269c… ragelink 83 # Projects
4ce269c… ragelink 84 projects_data = [
4ce269c… ragelink 85 {"name": "Frontend App", "description": "User-facing web application", "visibility": "internal"},
4ce269c… ragelink 86 {"name": "Backend API", "description": "Core API service", "visibility": "private"},
4ce269c… ragelink 87 {"name": "Documentation", "description": "Project documentation and guides", "visibility": "public"},
4ce269c… ragelink 88 {"name": "Infrastructure", "description": "Deployment and infrastructure tooling", "visibility": "private"},
4ce269c… ragelink 89 ]
4ce269c… ragelink 90 for pdata in projects_data:
4ce269c… ragelink 91 project, _ = Project.objects.get_or_create(
4ce269c… ragelink 92 name=pdata["name"],
4ce269c… ragelink 93 defaults={**pdata, "organization": org, "created_by": admin_user},
4ce269c… ragelink 94 )
4ce269c… ragelink 95
4ce269c… ragelink 96 # Team-project assignments
4ce269c… ragelink 97 frontend = Project.objects.filter(name="Frontend App").first()
4ce269c… ragelink 98 backend = Project.objects.filter(name="Backend API").first()
4ce269c… ragelink 99 docs = Project.objects.filter(name="Documentation").first()
4ce269c… ragelink 100
4ce269c… ragelink 101 if frontend:
4ce269c… ragelink 102 ProjectTeam.objects.get_or_create(project=frontend, team=core_devs, defaults={"role": "admin"})
4ce269c… ragelink 103 ProjectTeam.objects.get_or_create(project=frontend, team=contributors, defaults={"role": "write"})
4ce269c… ragelink 104 if backend:
4ce269c… ragelink 105 ProjectTeam.objects.get_or_create(project=backend, team=core_devs, defaults={"role": "admin"})
4ce269c… ragelink 106 ProjectTeam.objects.get_or_create(project=backend, team=reviewers, defaults={"role": "read"})
4ce269c… ragelink 107 if docs:
4ce269c… ragelink 108 ProjectTeam.objects.get_or_create(project=docs, team=contributors, defaults={"role": "write"})
4ce269c… ragelink 109 ProjectTeam.objects.get_or_create(project=docs, team=reviewers, defaults={"role": "write"})
4ce269c… ragelink 110
4ce269c… ragelink 111 # Sample docs pages
4ce269c… ragelink 112 pages_data = [
4ce269c… ragelink 113 {
4ce269c… ragelink 114 "name": "Getting Started",
4ce269c… ragelink 115 "content": "# Getting Started\n\nWelcome to Fossilrepo. This guide covers initial setup and configuration.\n\n## Prerequisites\n\n- Docker and Docker Compose\n- A domain name (for SSL)\n- S3-compatible storage (for backups)\n\n## Quick Start\n\n1. Clone the repository\n2. Copy `.env.example` to `.env`\n3. Run `fossilrepo-ctl reconfigure`\n4. Run `fossilrepo-ctl start`\n",
4ce269c… ragelink 116 },
4ce269c… ragelink 117 {
4ce269c… ragelink 118 "name": "Admin Guide",
4ce269c… ragelink 119 "content": "# Admin Guide\n\nThis guide covers day-to-day administration of your Fossilrepo instance.\n\n## Managing Users\n\nUsers can be added through the Django admin or the Settings > Members page.\n\n## Backups\n\nLitestream continuously replicates all `.fossil` files to S3. Manual backups can be created with `fossilrepo-ctl backup create`.\n\n## Monitoring\n\nCheck `/health/` for service status and `/status/` for an overview page.\n",
4ce269c… ragelink 120 },
4ce269c… ragelink 121 {
4ce269c… ragelink 122 "name": "Architecture Overview",
4ce269c… ragelink 123 "content": "# Architecture Overview\n\n## Stack\n\n| Component | Technology |\n|-----------|------------|\n| Backend | Django 5 + HTMX |\n| Database | PostgreSQL 16 |\n| SCM | Fossil |\n| Proxy | Caddy |\n| Backups | Litestream → S3 |\n| Jobs | Celery + Redis |\n\n## How It Works\n\nEach Fossil repository is a single `.fossil` SQLite file. Caddy routes subdomain requests to the Fossil server. Django provides the management UI. Litestream continuously replicates repo files to S3.\n",
4ce269c… ragelink 124 },
4ce269c… ragelink 125 ]
4ce269c… ragelink 126 for pdata in pages_data:
4ce269c… ragelink 127 Page.objects.get_or_create(
4ce269c… ragelink 128 name=pdata["name"],
4ce269c… ragelink 129 defaults={**pdata, "organization": org, "created_by": admin_user},
4ce269c… ragelink 130 )
c588255… ragelink 131
c588255… ragelink 132 # --- Seed sample users per role ---
c588255… ragelink 133 roles = OrgRole.objects.all()
c588255… ragelink 134 if not roles.exists():
c588255… ragelink 135 from django.core.management import call_command
c588255… ragelink 136
c588255… ragelink 137 call_command("seed_roles")
c588255… ragelink 138 roles = OrgRole.objects.all()
c588255… ragelink 139
c588255… ragelink 140 role_users = {
c588255… ragelink 141 "admin": {"email": "[email protected]", "first_name": "Admin", "last_name": "User"},
c588255… ragelink 142 "manager": {"email": "[email protected]", "first_name": "Manager", "last_name": "User"},
c588255… ragelink 143 "developer": {"email": "[email protected]", "first_name": "Dev", "last_name": "User"},
c588255… ragelink 144 "viewer": {"email": "[email protected]", "first_name": "Viewer", "last_name": "RoleUser"},
c588255… ragelink 145 }
c588255… ragelink 146
c588255… ragelink 147 for role in roles:
c588255… ragelink 148 slug = role.slug
c588255… ragelink 149 if slug not in role_users:
c588255… ragelink 150 continue
c588255… ragelink 151 info = role_users[slug]
c588255… ragelink 152 username = f"role-{slug}"
c588255… ragelink 153 user, created = User.objects.get_or_create(
c588255… ragelink 154 username=username,
c588255… ragelink 155 defaults={
c588255… ragelink 156 "email": info["email"],
c588255… ragelink 157 "first_name": info["first_name"],
c588255… ragelink 158 "last_name": info["last_name"],
c588255… ragelink 159 "is_active": True,
c588255… ragelink 160 },
c588255… ragelink 161 )
c588255… ragelink 162 if created:
c588255… ragelink 163 user.set_password(username)
c588255… ragelink 164 user.save()
c588255… ragelink 165
c588255… ragelink 166 membership, _ = OrganizationMember.objects.get_or_create(
c588255… ragelink 167 member=user,
c588255… ragelink 168 organization=org,
c588255… ragelink 169 defaults={"created_by": admin_user},
c588255… ragelink 170 )
c588255… ragelink 171 if membership.role != role:
c588255… ragelink 172 membership.role = role
c588255… ragelink 173 membership.save()
c588255… ragelink 174 role.apply_to_user(user)
c588255… ragelink 175
c588255… ragelink 176 self.stdout.write(f" User: {username} / {username} (role: {role.name})")
4ce269c… ragelink 177
4ce269c… ragelink 178 self.stdout.write(self.style.SUCCESS("Seed complete."))

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button