blumeops/docs/tutorials/contributing.md
Erich Blume a87c997ee1
All checks were successful
Deploy Fly.io Proxy / deploy (push) Successful in 1m28s
Expose Forgejo publicly at forge.eblu.me (#278)
## Summary

Expose Forgejo publicly at `forge.eblu.me` via the Fly.io reverse proxy — the first dynamic, authenticated public-facing service.

- **Forgejo hardening:** Domain changed to forge.eblu.me, SSH stays on forge.ops.eblu.me, reverse proxy trust headers configured, local registration locked to external-only (Authentik SSO)
- **Tailscale Ingress:** ExternalName Service + Ingress in tailscale-operator creates forge.tail8d86e.ts.net endpoint
- **Fly.io proxy:** nginx server block with rate-limited auth endpoints (3r/s), fail2ban with custom nginx-deny action, security headers, /swagger blocked, WebSocket support, 512m body limit
- **Authentik:** OAuth callback updated to forge.eblu.me
- **DNS/TLS:** CNAME record in Pulumi, cert in fly-setup
- **Rename:** ~29 files updated from forge.ops.eblu.me to forge.eblu.me (HTTPS refs only; SSH, container builds, and Caddy table kept as-is)

## Deployment Order

1. `mise run provision-indri -- --tags forgejo` (config changes)
2. Verify forge.ops.eblu.me still works
3. `argocd app set tailscale-operator --revision feature/forge-public && argocd app sync tailscale-operator`
4. Verify `curl https://forge.tail8d86e.ts.net`
5. `cd fly && fly deploy`
6. Verify pre-DNS: `curl -H "Host: forge.eblu.me" https://blumeops-proxy.fly.dev/`
7. `fly certs add forge.eblu.me -a blumeops-proxy`
8. `argocd app set authentik --revision feature/forge-public && argocd app sync authentik`
9. `mise run dns-preview && mise run dns-up`
10. Full verification (see below)
11. Rehearse `mise run fly-shutoff`
12. After merge: reset ArgoCD revisions to main, re-sync

## Verification Checklist

- [ ] forge.eblu.me loads, shows public repos
- [ ] forge.ops.eblu.me still works from tailnet
- [ ] SSH clone via forge.ops.eblu.me:2222 works
- [ ] HTTPS clone via forge.eblu.me works
- [ ] UI shows forge.eblu.me for HTTPS clone, forge.ops.eblu.me for SSH
- [ ] /swagger returns 403
- [ ] Rapid login attempts trigger 429 rate limit
- [ ] fail2ban bans after 5 failed logins in 10 minutes
- [ ] ArgoCD can still sync (SSH unaffected)
- [ ] `mise run fly-shutoff` stops all public traffic
- [ ] `mise run services-check` passes

Reviewed-on: #278
2026-03-03 08:40:41 -08:00

4.1 KiB

title modified tags
Contributing 2026-02-07
tutorials
contributing

Your First Contribution

Audiences: Contributor

This tutorial walks through making your first contribution to BluemeOps - from understanding the codebase to submitting a pull request.

Prerequisites

Before contributing, you'll need:

Tooling Setup

The repo includes a Brewfile and mise.toml for easy setup, but these are optional - install the tools however you prefer.

Required Tools

  • tea - Gitea/Forgejo CLI for creating PRs
  • argocd - ArgoCD CLI for deployments
  • prek - Git hooks for validation

Using Brewfile (Optional)

brew bundle  # installs tea, argocd, mise, etc.

Using Mise (Optional)

Mise manages language toolchains and runs tasks:

mise install  # installs Python, Node.js, etc. from mise.toml

Git Hooks (prek)

Git hooks validate changes on git commit:

prek install
prek run --all-files  # verify setup

All hooks should pass on a fresh clone.

Understanding the Codebase

BlumeOps manages infrastructure through three main systems:

System Directory What It Manages
Ansible ansible/ Services running directly on indri
ArgoCD argocd/ Kubernetes services in the cluster
Pulumi pulumi/ [[tailscale

Most contributions involve either Ansible roles or ArgoCD manifests.

The Contribution Workflow

1. Clone and Branch

git clone ssh://git@forge.ops.eblu.me:2222/eblume/blumeops.git
cd blumeops
git checkout -b feature/your-change-name

2. Make Your Changes

Depending on what you're changing:

For Kubernetes services:

  • Edit manifests in argocd/manifests/<service>/
  • Or create new Application in argocd/apps/
  • For new apps, set targetRevision to your feature branch for testing
  • For existing apps, you'll need to temporarily change the revision via argocd app set

For Indri services:

  • Edit or create roles in ansible/roles/
  • Update ansible/playbooks/indri.yml if adding a role

For documentation:

  • Edit files in docs/
  • Add changelog fragment (see below)

3. Add a Changelog Fragment

For user-visible changes:

echo "Description of your change" > docs/changelog.d/your-branch.feature.md

Fragment types (file suffix):

  • .feature.md - New functionality
  • .bugfix.md - Bug fixes
  • .infra.md - Infrastructure changes
  • .doc.md - Documentation
  • .misc.md - Other

4. Test Your Changes

Before pushing, always test:

For Kubernetes changes:

# Preview what will change
argocd app diff <service>

For DNS changes:

mise run dns-preview

5. Commit and Push

git add <files>
git commit -m "Brief description of change"
git push -u origin feature/your-change-name

6. Create a Pull Request

tea pr create --title "Your PR Title" --description "$(cat <<'EOF'
## Summary
- What you changed
- Why you changed it

## Deployment and Testing
- [ ] Tested locally / dry run
- [ ] Ready for ArgoCD sync / Ansible apply

EOF
)"

7. Wait for Review

Erich will review your PR and may leave comments. Check for feedback:

mise run pr-comments <pr_number>

Address each comment, then Erich will:

  1. Approve the changes
  2. Deploy them (you don't need to do this)
  3. Merge the PR

A simple first contribution - adding a service to the Homepage dashboard (go.ops.eblu.me):

  1. Find the service's Ingress in argocd/manifests/<service>/
  2. Add homepage annotations:
annotations:
  gethomepage.dev/enabled: "true"
  gethomepage.dev/name: "Service Name"
  gethomepage.dev/group: "Apps"
  gethomepage.dev/icon: "service.png"
  1. Create PR and wait for sync