1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
// source-forge publishes the source tree of a project as a browseable,
// syntax-highlighted static site, rendered on demand from SQLite.
//
// It has four subcommands:
//
//	project  Declare, redirect, or list published projects. A project must be
//	         declared with `project add` — which also creates its bare repo —
//	         before anything can be pushed to it; the post-receive hook
//	         rejects pushes to any repo with no declared project.
//	ingest   Read a `git archive` tar stream on stdin and store the tree in
//	         SQLite under a fresh generation, then atomically make it live.
//	         Directory listings are computed here, honouring any checked-in
//	         ".source-forge" metadata files (see meta.go).
//	serve    Serve directory listings and highlighted files over HTTP,
//	         rendering each file lazily and caching the highlighted HTML back
//	         in the DB.
//	git-post-receive-hook
//	         Run as a git post-receive hook: read pushed ref updates on stdin
//	         and re-ingest the tree on every push to the project's declared
//	         branch (see `project add`). Installed once, generically, via
//	         core.hooksPath rather than per repo — see nixos-module.nix.
//	         Echoes the published URL back to the pusher when given
//	         --base-url.
//
// The design goal is scraper resilience: only rendered static content is ever
// exposed (no git history, no per-request compute after the first render of a
// file), so crawlers hitting every link cost almost nothing.
//
// When ingesting from a git repo, two extra artifacts are stored alongside the
// tree so it can be consumed offline: a clonable git bundle at
// "/<project>.bundle", and a gzipped tarball at "/<project>.tar.gz" that is
// directly usable as a Nix flake (`nix run https://host/<project>.tar.gz#pkg`)
// without running a git server.
//
// Typical use:
//
//	source-forge project add --db forge.db --repos /var/lib/source-forge \
//	    --project Profpatsch --branch canon
//	git push ssh://source-forge@host/var/lib/source-forge/Profpatsch.git canon
//	source-forge serve --db forge.db --addr 127.0.0.1:8790 \
//	    --base-url http://localhost:8790
package main

import (
	"fmt"
	"os"
)

func main() {
	if len(os.Args) < 2 {
		usage()
		os.Exit(2)
	}
	cmd := os.Args[1]
	args := os.Args[2:]

	var err error
	switch cmd {
	case "project":
		err = runProject(args)
	case "ingest":
		err = runIngest(args)
	case "serve":
		err = runServe(args)
	case "git-post-receive-hook":
		err = runPostReceiveHook(args)
	case "-h", "--help", "help":
		usage()
		return
	default:
		fmt.Fprintf(os.Stderr, "source-forge: unknown subcommand %q\n\n", cmd)
		usage()
		os.Exit(2)
	}

	if err != nil {
		fmt.Fprintf(os.Stderr, "source-forge %s: %v\n", cmd, err)
		os.Exit(1)
	}
}

func usage() {
	fmt.Fprint(os.Stderr, `source-forge — on-demand source browser backed by SQLite

Usage:
  source-forge project add        --db <path> --repos <dir> --project <name> [--branch <name>] [--base-url <url>]
  source-forge project set-branch --db <path> [--repos <dir>] --project <name> --branch <name>
  source-forge project list       --db <path>
  source-forge ingest --db <path> --project <name> --branch <name> < archive.tar
  source-forge serve  --db <path> --addr <host:port> [--base-url <url>]
  source-forge git-post-receive-hook --db <path> [--git-dir <path>] [--base-url <url>]

Run 'source-forge <subcommand> --help' for subcommand flags.
`)
}