Plan: Forgejo PyPI Migration — CI Pipelines for All Python Repos
Vision
Every Python repo on the platform has a Woodpecker CI pipeline that lints, tests, builds, and publishes to Forgejo's private PyPI registry. No more public pypi.org leaks. One pattern, all repos.
Projects & Repos Touched
| Repo | Package | Origin | Woodpecker | Phase |
|---|---|---|---|---|
| forgejo-sdk | ldraney-forgejo-sdk | Forgejo | Activated (ID 17) | 1 DONE |
| forgejo-mcp | ldraney-forgejo-mcp | Forgejo | Activated (ID 18) | 1 DONE |
| pal-e-auth | pal-e-auth-ldraney | Forgejo | Activated (ID 4) | 1 DONE |
| pal-e-docs-mcp | pal-e-docs-mcp | Forgejo | Activated (ID 19) | 1 DONE |
| gcal-sdk | gcal-sdk-ldraney | Forgejo | Activated (ID 12) | 2 DONE |
| gmail-sdk | gmail-sdk-ldraney | Forgejo | Activated (ID 10) | 2 DONE |
| linkedin-sdk | ldraney-linkedin-sdk | Forgejo | Activated (ID 14) | 2 DONE |
| notion-sdk | notion-sdk-ldraney | Forgejo | Activated (ID 8) | 2 DONE |
| notion-mcp | ldraney-notion-mcp | Forgejo | Activated (ID 9) | 2 DONE |
| gcal-mcp | gcal-mcp-ldraney | Forgejo | Activated (ID 13) | 2 DONE |
| gmail-mcp | gmail-mcp-ldraney | Forgejo | Activated (ID 11) | 2 DONE |
| linkedin-mcp-scheduler | linkedin-mcp-scheduler-ldraney | Forgejo | Activated (ID 15) | 2 DONE |
| mcp-remote-auth | mcp-remote-auth-ldraney | Forgejo | Activated (ID 7) | 2 DONE |
| gcal-mcp-remote | gcal-mcp-remote-ldraney | GitHub | Not activated | 3 |
| gmail-mcp-remote | gmail-mcp-remote-ldraney | GitHub | Not activated | 3 |
| linkedin-scheduler-remote | linkedin-scheduler-remote-ldraney | GitHub | Not activated | 3 |
| notion-mcp-remote | notion-mcp-remote-ldraney | GitHub | Not activated | 3 |
Not included: ebay-sdk and ebay-oauth are not on Forgejo at all. They can be migrated later if needed. woodpecker-sdk is already done (reference implementation).
Context
woodpecker-sdk just shipped the proven pipeline pattern:
.woodpecker.yml (lint/test/publish), ruff config in pyproject.toml, Forgejo PyPI publishing via twine, global Woodpecker secrets for auth. PR #6 merged after 3 QA rounds. The pattern is validated and ready to roll out.22 packages are currently on public pypi.org with no reason to be public. The Forgejo PyPI registry is live and proven. This plan applies the woodpecker-sdk pipeline template to every remaining Python repo.
What's already done:
- [x] Forgejo PyPI registry verified and operational
- [x]
~/.pypircconfigured for Forgejo - [x] Woodpecker global secrets set:
forgejo_publish_user,forgejo_publish_token,forgejo_pypi_url,forgejo_url,forgejo_user,forgejo_password - [x] woodpecker-sdk pipeline template proven (PR #6,
plan-2026-02-28-woodpecker-sdk-mcpPhase 2) - [x] All Phase 1 + Phase 2 repos activated in Woodpecker (16 of 16 non-remote repos)
- [x] Phase 1 complete: 4/4 repos merged, pipelines green, packages published to Forgejo PyPI
- [x] Phase 2 complete: 9/9 repos merged, all pipelines green, packages published to Forgejo PyPI (2026-03-01)
- [x] All 13 packages verified installable from Forgejo PyPI via
pip install --index-url(2026-03-01)
Previous Plan
plan-2026-02-28-woodpecker-sdk-mcp — Phase 2 established the patternDepends On
None — pattern is proven, global secrets are set, registry is live
Decisions Made
| Decision | Rationale |
|---|---|
| Reuse woodpecker-sdk pipeline template exactly | Proven across 3 QA rounds. Lint (ruff pinned), test (pytest), publish (build + twine, main-only). Adapt test step per repo (some repos have tests, some don't). |
| Repos with GitHub origin: switch to Forgejo | Woodpecker watches Forgejo. Local clones pointing at GitHub need origin updated to Forgejo. Forgejo repos already exist for all of these (mirrors or manual creates). |
| Phase by readiness, not by service | Phase 1 = Forgejo-native repos (ready now, parallel agents). Phase 2 = GitHub-origin repos (need origin switch first). Phase 3 = remote MCP proxies (lowest priority, may be deprecated). |
| Parallel agents per phase | Each repo gets its own agent in a worktree. The work is identical and independent — perfect for parallelization. One issue per repo, one PR per repo. |
| Skip tests step if repo has no tests | Some repos (especially older MCPs) may not have a tests/ directory. Pipeline should still lint and publish, just skip the test step. |
| Ruff config: same rules everywhere | py310, line-length 120, E/F/W/I. Matches woodpecker-sdk and <code>todo-ruff-standardization</code>. |
| Delete old .woodpecker.yaml files | pal-e-auth had both .woodpecker.yaml (old pypi.org) and .woodpecker.yml (new Forgejo). Woodpecker loads both, causing secret conflicts. Must delete old files. |
| Clean up stale repo-level pypi_token secrets | 10 repos had old pypi_token repo-level secrets from pypi.org era. Must be cleaned up before Phase 2 to avoid the same issue. |
Phases
Phase 1 — Forgejo-native repos (COMPLETE)
Slug:
Status: COMPLETE — 4/4 repos merged, all pipelines verified green, all packages published to Forgejo PyPI.
phase-2026-03-01-1-forgejo-nativeStatus: COMPLETE — 4/4 repos merged, all pipelines verified green, all packages published to Forgejo PyPI.
Results:
| Repo | Issue | PR | Pipeline | Notes |
|---|---|---|---|---|
| forgejo-sdk | <a href="https://forgejo.tail5b443a.ts.net/forgejo_admin/forgejo-sdk/issues/1">#1</a> | <a href="https://forgejo.tail5b443a.ts.net/forgejo_admin/forgejo-sdk/pulls/2">#2 merged</a> | #3 all green | 40/40 tests, published to Forgejo PyPI |
| forgejo-mcp | <a href="https://forgejo.tail5b443a.ts.net/forgejo_admin/forgejo-mcp/issues/1">#1</a> | <a href="https://forgejo.tail5b443a.ts.net/forgejo_admin/forgejo-mcp/pulls/2">#2 merged</a> | #1 lint+publish OK, #4 409 (version exists) | No tests, version bump needed for re-publish |
| pal-e-auth | <a href="https://forgejo.tail5b443a.ts.net/forgejo_admin/pal-e-auth/issues/7">#7</a> | <a href="https://forgejo.tail5b443a.ts.net/forgejo_admin/pal-e-auth/pulls/8">#8 merged</a> | #8 all green | 29/29 tests, old .woodpecker.yaml deleted, published to Forgejo PyPI |
| pal-e-docs-mcp | <a href="https://forgejo.tail5b443a.ts.net/forgejo_admin/pal-e-docs-mcp/issues/3">#3</a> | <a href="https://forgejo.tail5b443a.ts.net/forgejo_admin/pal-e-docs-mcp/pulls/4">#4 merged</a> | #1 all green | No tests, published to Forgejo PyPI |
Lessons learned:
- Repos activated BEFORE merge miss the push webhook — use Forgejo API commit to trigger
- Old .woodpecker.yaml files cause dual-config loading — MUST delete before adding .woodpecker.yml
- Stale repo-level pypi_token secrets cause pipeline errors when deleted but still referenced
- forgejo-sdk tests require forgejo_url/forgejo_user/forgejo_password secrets — added as globals
- Poetry-core build backend doesn't expose dev deps as pip extras — test step needs explicit pip install
- 409 Conflict on publish = version already exists in registry (expected, bump version to fix)
- Woodpecker repos can be activated via API: POST /api/repos?forge_remote_id={id} (no UI needed)
- Woodpecker secret hierarchy: global > org > repo (most specific wins). Global secrets are ideal for shared Forgejo PyPI creds.
Phase 2a — Pre-work (COMPLETE)
Status: COMPLETE — all pre-work done 2026-03-01
Discovery: All 9 Phase 2 repos already had old-pattern
.woodpecker.yaml pipelines publishing to pypi.org via pypi_token, plus ruff formatting already applied in prior work. The actual Phase 2 work is narrower than initially scoped: delete old .woodpecker.yaml, create new .woodpecker.yml (Forgejo PyPI pattern), and add [tool.ruff] config to pyproject.toml. All 9 repos have tests/ directories and pyproject.toml.Completed:
- [x] Deleted stale
pypi_tokenrepo-level secrets from all 9 repos (Woodpecker IDs 7-15). Non-pypi secrets (notion_api_key, google_oauth_token, linkedin_*) left in place for test steps. - [x] Checked for old
.woodpecker.yamlfiles — confirmed present in all 9 repos (deleted by agents as part of pipeline swap) - [x] Switched local git origin from GitHub to Forgejo for 6 cloned repos: gcal-sdk, linkedin-sdk, notion-mcp, gmail-mcp, linkedin-mcp-scheduler, mcp-remote-auth
- [x] Cloned 3 missing repos from Forgejo: gmail-sdk, notion-sdk, gcal-mcp
- [x] Pulled latest from Forgejo on all 9 repos — all local clones now current with Forgejo main
Phase 2b — Pipeline swap (COMPLETE)
Slug:
Goal: Replace old pypi.org
Owner: Agent (parallel — one agent per repo, 9 agents spawned simultaneously)
Status: COMPLETE — 9/9 PRs merged, all pipelines green, all packages published to Forgejo PyPI.
phase-2026-03-01-2-github-originGoal: Replace old pypi.org
.woodpecker.yaml with new Forgejo PyPI .woodpecker.yml on all 9 repos. Add [tool.ruff] config. Fix lint issues.Owner: Agent (parallel — one agent per repo, 9 agents spawned simultaneously)
Status: COMPLETE — 9/9 PRs merged, all pipelines green, all packages published to Forgejo PyPI.
Results:
| Repo | Issue | PR | Pipeline | Notes |
|---|---|---|---|---|
| gcal-sdk | <a href="https://forgejo.tail5b443a.ts.net/forgejo_admin/gcal-sdk/issues/6">#6</a> | <a href="https://forgejo.tail5b443a.ts.net/forgejo_admin/gcal-sdk/pulls/7">#7 merged</a> | #12 success | google_oauth_token secret in test step |
| gmail-sdk | <a href="https://forgejo.tail5b443a.ts.net/forgejo_admin/gmail-sdk/issues/4">#4</a> | <a href="https://forgejo.tail5b443a.ts.net/forgejo_admin/gmail-sdk/pulls/5">#5 merged</a> | #9 success | |
| linkedin-sdk | <a href="https://forgejo.tail5b443a.ts.net/forgejo_admin/linkedin-sdk/issues/6">#6</a> | <a href="https://forgejo.tail5b443a.ts.net/forgejo_admin/linkedin-sdk/pulls/7">#7 merged</a> | #14 success | linkedin_access_token, linkedin_person_id in test step |
| notion-sdk | <a href="https://forgejo.tail5b443a.ts.net/forgejo_admin/notion-sdk/issues/8">#8</a> | <a href="https://forgejo.tail5b443a.ts.net/forgejo_admin/notion-sdk/pulls/9">#9 merged</a> | #15 success | notion_api_key secret in test step |
| notion-mcp | <a href="https://forgejo.tail5b443a.ts.net/forgejo_admin/notion-mcp/issues/4">#4</a> | <a href="https://forgejo.tail5b443a.ts.net/forgejo_admin/notion-mcp/pulls/5">#5 merged</a> | #8 success | |
| gcal-mcp | <a href="https://forgejo.tail5b443a.ts.net/forgejo_admin/gcal-mcp/issues/4">#4</a> | <a href="https://forgejo.tail5b443a.ts.net/forgejo_admin/gcal-mcp/pulls/5">#5 merged</a> | #8 success | |
| gmail-mcp | <a href="https://forgejo.tail5b443a.ts.net/forgejo_admin/gmail-mcp/issues/4">#4</a> | <a href="https://forgejo.tail5b443a.ts.net/forgejo_admin/gmail-mcp/pulls/5">#5 merged</a> | #8 success | Fixed [dependency-groups] -> [project.optional-dependencies] |
| linkedin-mcp-scheduler | <a href="https://forgejo.tail5b443a.ts.net/forgejo_admin/linkedin-mcp-scheduler/issues/4">#4</a> | <a href="https://forgejo.tail5b443a.ts.net/forgejo_admin/linkedin-mcp-scheduler/pulls/5">#5 merged</a> | #9 success | |
| mcp-remote-auth | <a href="https://forgejo.tail5b443a.ts.net/forgejo_admin/mcp-remote-auth/issues/4">#4</a> | <a href="https://forgejo.tail5b443a.ts.net/forgejo_admin/mcp-remote-auth/pulls/5">#5 merged</a> | #8 success |
Phase 3 — Remote MCP proxies (low priority, may deprecate)
Slug:
Goal: Evaluate whether the -remote MCP proxy repos are still needed. If yes, apply pipeline. If deprecated, archive.
Owner: Agent
Issue: TBD
Status: Not started
phase-2026-03-01-3-remote-proxiesGoal: Evaluate whether the -remote MCP proxy repos are still needed. If yes, apply pipeline. If deprecated, archive.
Owner: Agent
Issue: TBD
Status: Not started
Repos: gcal-mcp-remote, gmail-mcp-remote, linkedin-scheduler-remote, notion-mcp-remote
Phase 4 — Yank public pypi.org packages + update references
Slug:
Goal: Yank all 22 packages on pypi.org to signal deprecation. Update all
Owner: Main session + Agent
Issue: TBD
Status: Not started
phase-2026-03-01-4-yank-and-updateGoal: Yank all 22 packages on pypi.org to signal deprecation. Update all
pip install references across the platform to use Forgejo's --index-url.Owner: Main session + Agent
Issue: TBD
Status: Not started
Key Files
| Phase | File | Repo | Change |
|---|---|---|---|
| 1-2 | .woodpecker.yml | Each repo | CI pipeline (copied from woodpecker-sdk template) |
| 1-2 | pyproject.toml | Each repo | Add [tool.ruff] section |
| 1-2 | src/**/*.py, tests/**/*.py | Each repo | Ruff format fixes (mechanical) |
| 4 | Various config files | Platform-wide | Update pip install references to Forgejo index |
Verification
- [x] All Phase 1 repos (4/4) have .woodpecker.yml + ruff config + published to Forgejo PyPI
- [x] Woodpecker pipelines green on all Phase 1 repos
- [x] pal-e-docs-mcp PR reviewed, merged, pipeline verified
- [x] Phase 2a pre-work complete: secrets cleaned, origins switched, repos cloned
- [x] Phase 2b: 9/9 PRs opened, QA-reviewed, merged
- [x] Phase 2b: All 9 Woodpecker pipelines green, packages published to Forgejo PyPI
- [x]
pip install --index-url {forgejo} {package}verified for all 13 packages (2026-03-01) - [ ] Public pypi.org packages yanked (Phase 4)
Next Plan Seeds
- Automated version bumping (tag-based release across all repos)
- Shared .woodpecker.yml template repo (DRY pipeline config)
- Local pip config to default to Forgejo index (
pip config set global.index-url)
Related
plan-2026-02-28-woodpecker-sdk-mcp— Phase 2 established the pattern (reference implementation)todo-forgejo-pypi— originating TODOtodo-ruff-standardization— ruff config standardization (achieved by this plan)plan-2026-02-25-mcp-gateway-migration— may deprecate Phase 3 reposservice-onboarding-sop— container images → Harbor, Python packages → Forgejo