FossilRepo

fossilrepo / bootstrap.md
Source Blame History 340 lines
4ce269c… ragelink 1 # fossilrepo -- bootstrap
4ce269c… ragelink 2
4ce269c… ragelink 3 This is the primary conventions document. All agent shims (`CLAUDE.md`, `AGENTS.md`) point here.
4ce269c… ragelink 4
4ce269c… ragelink 5 An agent given this document and a business requirement should be able to generate correct, idiomatic code without exploring the codebase.
4ce269c… ragelink 6
4ce269c… ragelink 7 ---
4ce269c… ragelink 8
4ce269c… ragelink 9 ## What is fossilrepo
4ce269c… ragelink 10
4ce269c… ragelink 11 Omnibus-style installer for a self-hosted Fossil forge. One command gets you a full-stack code hosting platform: VCS, issues, wiki, timeline, web UI, SSL, and continuous backups -- all powered by Fossil SCM.
4ce269c… ragelink 12
4ce269c… ragelink 13 Think GitLab Omnibus, but for Fossil.
4ce269c… ragelink 14
4ce269c… ragelink 15 ---
4ce269c… ragelink 16
4ce269c… ragelink 17 ## Why Fossil
4ce269c… ragelink 18
4ce269c… ragelink 19 A Fossil repo is a single SQLite file. It contains the full VCS history, issue tracker, wiki, forum, and timeline. No external services. No rate limits. Portable -- hand the file to someone and they have everything.
4ce269c… ragelink 20
4ce269c… ragelink 21 For teams running CI agents or automation:
4ce269c… ragelink 22 - Agents commit, file tickets, and update the wiki through one CLI and one protocol
4ce269c… ragelink 23 - No API rate limits when many agents are pushing simultaneously
4ce269c… ragelink 24 - The `.fossil` file IS the project artifact -- a self-contained archive
4ce269c… ragelink 25 - Litestream replicates it to S3 continuously -- backup and point-in-time recovery for free
4ce269c… ragelink 26
4ce269c… ragelink 27 Fossil also has a built-in web UI (skinnable), autosync, peer-to-peer sync, and unversioned content storage (like Git LFS but built-in).
4ce269c… ragelink 28
4ce269c… ragelink 29 ---
4ce269c… ragelink 30
4ce269c… ragelink 31 ## What fossilrepo Does
4ce269c… ragelink 32
4ce269c… ragelink 33 fossilrepo packages everything needed to run a production Fossil server into one installable unit:
4ce269c… ragelink 34
4ce269c… ragelink 35 - **Fossil server** -- serves all repos from a single process
4ce269c… ragelink 36 - **Caddy** -- SSL termination, subdomain-per-repo routing (`reponame.your-domain.com`)
4ce269c… ragelink 37 - **Litestream** -- continuous SQLite replication to S3/MinIO (backup + point-in-time recovery)
4ce269c… ragelink 38 - **CLI** -- repo lifecycle management (create, list, delete) and sync tooling
4ce269c… ragelink 39 - **Sync bridge** -- mirror Fossil repos to GitHub/GitLab as downstream read-only copies
4ce269c… ragelink 40
4ce269c… ragelink 41 New project = `fossil init`. No restart, no config change. Litestream picks it up automatically.
4ce269c… ragelink 42
4ce269c… ragelink 43 ---
4ce269c… ragelink 44
4ce269c… ragelink 45 ## Server Stack
4ce269c… ragelink 46
4ce269c… ragelink 47 ```
4ce269c… ragelink 48 Caddy (SSL termination, routing, subdomain per repo)
4ce269c… ragelink 49 +-- fossil server --repolist /data/repos/
4ce269c… ragelink 50 +-- /data/repos/
4ce269c… ragelink 51 |-- projecta.fossil
4ce269c… ragelink 52 |-- projectb.fossil
4ce269c… ragelink 53 +-- ...
4ce269c… ragelink 54
4ce269c… ragelink 55 Litestream -> S3/MinIO (continuous replication, point-in-time recovery)
4ce269c… ragelink 56 ```
4ce269c… ragelink 57
4ce269c… ragelink 58 One binary serves all repos. The whole platform is: repo creation + subdomain provisioning + Litestream config.
4ce269c… ragelink 59
4ce269c… ragelink 60 ### Sync Bridge
4ce269c… ragelink 61
4ce269c… ragelink 62 Mirrors Fossil to GitHub/GitLab as a downstream copy. Fossil is the source of truth.
4ce269c… ragelink 63
4ce269c… ragelink 64 Maps:
4ce269c… ragelink 65 - Fossil commits -> Git commits
4ce269c… ragelink 66 - Fossil tickets -> GitHub/GitLab Issues (optional, configurable)
4ce269c… ragelink 67 - Fossil wiki -> repo docs (optional, configurable)
4ce269c… ragelink 68
4ce269c… ragelink 69 Triggered on demand or on schedule.
4ce269c… ragelink 70
4ce269c… ragelink 71 ---
4ce269c… ragelink 72
4ce269c… ragelink 73 ## Architecture
4ce269c… ragelink 74
4ce269c… ragelink 75 ```
4ce269c… ragelink 76 fossilrepo/
4ce269c… ragelink 77 |-- config/ # Django settings, URLs, Celery
4ce269c… ragelink 78 |-- core/ # Base models, permissions, middleware
c588255… ragelink 79 |-- accounts/ # Session-based auth
4ce269c… ragelink 80 |-- organization/ # Org + member management
4ce269c… ragelink 81 |-- docker/ # Fossil-specific: Caddyfile, litestream.yml
4ce269c… ragelink 82 |-- templates/ # HTMX templates
4ce269c… ragelink 83 |-- _old_fossilrepo/ # Original server/sync/cli code (being ported)
4ce269c… ragelink 84 +-- docs/ # Architecture guides
4ce269c… ragelink 85 ```
4ce269c… ragelink 86
4ce269c… ragelink 87 ---
4ce269c… ragelink 88
4ce269c… ragelink 89 ## What's Already Built
4ce269c… ragelink 90
4ce269c… ragelink 91 | Layer | What's there |
4ce269c… ragelink 92 |---|---|
c588255… ragelink 93 | Auth | Session-based auth (accounts), login/logout views with templates, rate limiting |
4ce269c… ragelink 94 | Data | Postgres 16, `Tracking` base model (version, created/updated/deleted by+at, soft deletes, history) |
4ce269c… ragelink 95 | API | Django views returning HTML (full pages + HTMX partials) |
4ce269c… ragelink 96 | Permissions | Group-based via `P` enum, checked in every view |
4ce269c… ragelink 97 | Async | Celery worker + beat, Redis broker |
4ce269c… ragelink 98 | Admin | Django Admin with `BaseCoreAdmin` (import/export, tracking fields) |
4ce269c… ragelink 99 | Infra | Docker Compose: postgres, redis, celery-worker, celery-beat, mailpit |
4ce269c… ragelink 100 | CI | GitHub Actions: lint (Ruff) + tests (Postgres + Redis services) |
c588255… ragelink 101 | Seed | `python manage.py seed` creates admin/viewer users, sample data |
4ce269c… ragelink 102 | Frontend | HTMX 2.0 + Alpine.js 3 + Tailwind CSS, server-rendered templates |
4ce269c… ragelink 103
4ce269c… ragelink 104 ---
4ce269c… ragelink 105
4ce269c… ragelink 106 ## App Structure
4ce269c… ragelink 107
4ce269c… ragelink 108 | App | Purpose |
4ce269c… ragelink 109 |---|---|
4ce269c… ragelink 110 | `config` | Django settings, URLs, Celery configuration |
4ce269c… ragelink 111 | `core` | Base models (Tracking, BaseCoreModel), admin (BaseCoreAdmin), permissions (P enum), middleware |
c588255… ragelink 112 | `accounts` | Session-based authentication: login/logout views with rate limiting |
4ce269c… ragelink 113 | `organization` | Organization + OrganizationMember models |
4ce269c… ragelink 114 | `testdata` | `seed` management command for development data |
4ce269c… ragelink 115
4ce269c… ragelink 116 ---
4ce269c… ragelink 117
4ce269c… ragelink 118 ## Conventions
4ce269c… ragelink 119
4ce269c… ragelink 120 ### Models
4ce269c… ragelink 121
4ce269c… ragelink 122 All business models inherit from one of:
4ce269c… ragelink 123
4ce269c… ragelink 124 **`Tracking`** (abstract) -- audit trails:
4ce269c… ragelink 125 ```python
4ce269c… ragelink 126 from core.models import Tracking
4ce269c… ragelink 127
4ce269c… ragelink 128 class Invoice(Tracking):
4ce269c… ragelink 129 amount = models.DecimalField(...)
4ce269c… ragelink 130 ```
4ce269c… ragelink 131 Provides: `version` (auto-increments), `created_at/by`, `updated_at/by`, `deleted_at/by`, `history` (simple_history).
4ce269c… ragelink 132
4ce269c… ragelink 133 **`BaseCoreModel(Tracking)`** (abstract) -- named entities:
4ce269c… ragelink 134 ```python
4ce269c… ragelink 135 from core.models import BaseCoreModel
4ce269c… ragelink 136
c588255… ragelink 137 class Project(BaseCoreModel):
c588255… ragelink 138 visibility = models.CharField(...)
4ce269c… ragelink 139 ```
4ce269c… ragelink 140 Adds: `guid` (UUID), `name`, `slug` (auto-generated, unique), `description`.
4ce269c… ragelink 141
4ce269c… ragelink 142 **Soft deletes:** call `obj.soft_delete(user=request.user)`, never `.delete()`.
4ce269c… ragelink 143
4ce269c… ragelink 144 **ActiveManager:** Use `objects` (excludes deleted) for queries, `all_objects` for admin.
4ce269c… ragelink 145
4ce269c… ragelink 146 ---
4ce269c… ragelink 147
4ce269c… ragelink 148 ### Views (HTMX Pattern)
4ce269c… ragelink 149
4ce269c… ragelink 150 Views return full pages for normal requests, HTMX partials for `HX-Request`:
4ce269c… ragelink 151
4ce269c… ragelink 152 ```python
4ce269c… ragelink 153 @login_required
c588255… ragelink 154 def project_list(request):
c588255… ragelink 155 P.PROJECT_VIEW.check(request.user)
c588255… ragelink 156 projects = Project.objects.all()
4ce269c… ragelink 157
4ce269c… ragelink 158 if request.headers.get("HX-Request"):
c588255… ragelink 159 return render(request, "projects/partials/project_table.html", {"projects": projects})
4ce269c… ragelink 160
c588255… ragelink 161 return render(request, "projects/project_list.html", {"projects": projects})
4ce269c… ragelink 162 ```
4ce269c… ragelink 163
4ce269c… ragelink 164 **URL patterns** follow CRUD convention:
4ce269c… ragelink 165 ```python
4ce269c… ragelink 166 urlpatterns = [
c588255… ragelink 167 path("", views.project_list, name="list"),
c588255… ragelink 168 path("create/", views.project_create, name="create"),
c588255… ragelink 169 path("<slug:slug>/", views.project_detail, name="detail"),
c588255… ragelink 170 path("<slug:slug>/edit/", views.project_update, name="update"),
c588255… ragelink 171 path("<slug:slug>/delete/", views.project_delete, name="delete"),
4ce269c… ragelink 172 ]
4ce269c… ragelink 173 ```
4ce269c… ragelink 174
4ce269c… ragelink 175 ---
4ce269c… ragelink 176
4ce269c… ragelink 177 ### Permissions
4ce269c… ragelink 178
4ce269c… ragelink 179 Group-based. Never user-based. Checked in every view.
4ce269c… ragelink 180
4ce269c… ragelink 181 ```python
4ce269c… ragelink 182 from core.permissions import P
4ce269c… ragelink 183
c588255… ragelink 184 P.PROJECT_VIEW.check(request.user) # raises PermissionDenied if denied
c588255… ragelink 185 P.PROJECT_ADD.check(request.user, raise_error=False) # returns False instead
4ce269c… ragelink 186 ```
4ce269c… ragelink 187
4ce269c… ragelink 188 Template guards:
4ce269c… ragelink 189 ```html
c588255… ragelink 190 {% if perms.projects.view_project %}
c588255… ragelink 191 <a href="{% url 'projects:list' %}">Projects</a>
4ce269c… ragelink 192 {% endif %}
4ce269c… ragelink 193 ```
4ce269c… ragelink 194
4ce269c… ragelink 195 ---
4ce269c… ragelink 196
4ce269c… ragelink 197 ### Admin
4ce269c… ragelink 198
4ce269c… ragelink 199 All admin classes inherit `BaseCoreAdmin`:
4ce269c… ragelink 200 ```python
4ce269c… ragelink 201 from core.admin import BaseCoreAdmin
4ce269c… ragelink 202
c588255… ragelink 203 @admin.register(Project)
c588255… ragelink 204 class ProjectAdmin(BaseCoreAdmin):
c588255… ragelink 205 list_display = ("name", "slug", "visibility", "created_at")
4ce269c… ragelink 206 search_fields = ("name", "slug")
4ce269c… ragelink 207 ```
4ce269c… ragelink 208
4ce269c… ragelink 209 `BaseCoreAdmin` provides: audit fields as readonly, `created_by`/`updated_by` auto-set, import/export.
4ce269c… ragelink 210
4ce269c… ragelink 211 ---
4ce269c… ragelink 212
4ce269c… ragelink 213 ### Templates
4ce269c… ragelink 214
4ce269c… ragelink 215 - `base.html` -- layout with HTMX, Alpine.js, Tailwind CSS, CSRF injection, messages
4ce269c… ragelink 216 - `includes/nav.html` -- navigation bar with permission guards
4ce269c… ragelink 217 - `{app}/partials/*.html` -- HTMX partial templates (no `{% extends %}`)
4ce269c… ragelink 218 - CSRF token sent with all HTMX requests via `htmx:configRequest` event
4ce269c… ragelink 219
4ce269c… ragelink 220 Alpine.js patterns for client-side interactivity:
4ce269c… ragelink 221 ```html
4ce269c… ragelink 222 <div x-data="{ open: false }">
4ce269c… ragelink 223 <button @click="open = !open">Toggle</button>
4ce269c… ragelink 224 <div x-show="open" x-transition>Content</div>
4ce269c… ragelink 225 </div>
4ce269c… ragelink 226 ```
4ce269c… ragelink 227
4ce269c… ragelink 228 ---
4ce269c… ragelink 229
4ce269c… ragelink 230 ### Tests
4ce269c… ragelink 231
4ce269c… ragelink 232 pytest + real Postgres. Assert against database state.
4ce269c… ragelink 233
4ce269c… ragelink 234 ```python
4ce269c… ragelink 235 @pytest.mark.django_db
c588255… ragelink 236 class TestProjectCreate:
c588255… ragelink 237 def test_create_saves_project(self, admin_client, admin_user, org):
c588255… ragelink 238 response = admin_client.post(reverse("projects:create"), {
c588255… ragelink 239 "name": "New App", "visibility": "private", ...
4ce269c… ragelink 240 })
4ce269c… ragelink 241 assert response.status_code == 302
c588255… ragelink 242 project = Project.objects.get(name="New App")
c588255… ragelink 243 assert project.created_by == admin_user
4ce269c… ragelink 244
4ce269c… ragelink 245 def test_create_denied_for_viewer(self, viewer_client):
c588255… ragelink 246 response = viewer_client.get(reverse("projects:create"))
4ce269c… ragelink 247 assert response.status_code == 403
4ce269c… ragelink 248 ```
4ce269c… ragelink 249
4ce269c… ragelink 250 Both allowed AND denied permission cases for every endpoint.
4ce269c… ragelink 251
4ce269c… ragelink 252 ---
4ce269c… ragelink 253
4ce269c… ragelink 254 ### Code Style
4ce269c… ragelink 255
4ce269c… ragelink 256 | Tool | Config |
4ce269c… ragelink 257 |------|--------|
4ce269c… ragelink 258 | Ruff (lint + format) | `pyproject.toml`, line length 140 |
4ce269c… ragelink 259 | Import sorting | Ruff isort rules |
4ce269c… ragelink 260 | Python version | 3.12+ |
4ce269c… ragelink 261
4ce269c… ragelink 262 Run `ruff check .` and `ruff format --check .` before committing.
4ce269c… ragelink 263
4ce269c… ragelink 264 ---
4ce269c… ragelink 265
4ce269c… ragelink 266 ## Adding a New App
4ce269c… ragelink 267
4ce269c… ragelink 268 ```bash
4ce269c… ragelink 269 # 1. Create the app
4ce269c… ragelink 270 python manage.py startapp myapp
4ce269c… ragelink 271
4ce269c… ragelink 272 # 2. Add to INSTALLED_APPS in config/settings.py
4ce269c… ragelink 273
4ce269c… ragelink 274 # 3. Create models inheriting Tracking or BaseCoreModel
4ce269c… ragelink 275
4ce269c… ragelink 276 # 4. Create migrations
4ce269c… ragelink 277 python manage.py makemigrations
4ce269c… ragelink 278
4ce269c… ragelink 279 # 5. Create admin (inherit BaseCoreAdmin)
4ce269c… ragelink 280
4ce269c… ragelink 281 # 6. Create views with @login_required + P.PERMISSION.check()
4ce269c… ragelink 282
4ce269c… ragelink 283 # 7. Create URL patterns (list, detail, create, update, delete)
4ce269c… ragelink 284
4ce269c… ragelink 285 # 8. Create templates (full page + HTMX partials)
4ce269c… ragelink 286
4ce269c… ragelink 287 # 9. Add permission entries to core/permissions.py P enum
4ce269c… ragelink 288
4ce269c… ragelink 289 # 10. Write tests (allowed + denied)
4ce269c… ragelink 290 python -m pytest --cov -v
4ce269c… ragelink 291 ```
4ce269c… ragelink 292
4ce269c… ragelink 293 ---
4ce269c… ragelink 294
4ce269c… ragelink 295 ## Ports (local Docker)
4ce269c… ragelink 296
4ce269c… ragelink 297 | Service | URL |
4ce269c… ragelink 298 |---|---|
4ce269c… ragelink 299 | Django | http://localhost:8000 |
4ce269c… ragelink 300 | Django Admin | http://localhost:8000/admin/ |
4ce269c… ragelink 301 | Health | http://localhost:8000/health/ |
4ce269c… ragelink 302 | Mailpit | http://localhost:8025 |
4ce269c… ragelink 303 | Postgres | localhost:5432 |
4ce269c… ragelink 304 | Redis | localhost:6379 |
4ce269c… ragelink 305
4ce269c… ragelink 306 ---
4ce269c… ragelink 307
4ce269c… ragelink 308 ## Common Commands
4ce269c… ragelink 309
4ce269c… ragelink 310 ```bash
4ce269c… ragelink 311 make up # Start the stack
4ce269c… ragelink 312 make build # Build and start
4ce269c… ragelink 313 make down # Stop the stack
4ce269c… ragelink 314 make migrate # Run migrations
4ce269c… ragelink 315 make migrations # Create migrations
4ce269c… ragelink 316 make seed # Load dev fixtures
4ce269c… ragelink 317 make test # Run tests with coverage
4ce269c… ragelink 318 make lint # Run Ruff check + format
4ce269c… ragelink 319 make superuser # Create Django superuser
4ce269c… ragelink 320 make shell # Shell into container
4ce269c… ragelink 321 make logs # Tail Django logs
4ce269c… ragelink 322 ```
4ce269c… ragelink 323
4ce269c… ragelink 324 ---
4ce269c… ragelink 325
4ce269c… ragelink 326 ## Platform Vision (fossilrepos.com)
4ce269c… ragelink 327
4ce269c… ragelink 328 GitLab model:
4ce269c… ragelink 329 - **Self-hosted** -- open source, run it yourself. fossilrepo is the tool.
4ce269c… ragelink 330 - **Managed** -- fossilrepos.com, hosted for you. Subdomain per repo, modern UI, billing.
4ce269c… ragelink 331
4ce269c… ragelink 332 The platform is Fossil's built-in web UI with a modern skin + thin API wrapper + authentication. Not a rewrite -- Fossil already does the hard parts. The value is the hosting and UX polish.
4ce269c… ragelink 333
4ce269c… ragelink 334 Not being built yet -- get the self-hosted tool right first.
4ce269c… ragelink 335
4ce269c… ragelink 336 ---
4ce269c… ragelink 337
4ce269c… ragelink 338 ## License
4ce269c… ragelink 339
4ce269c… ragelink 340 MIT.

Keyboard Shortcuts

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