Install on a VM with systemd
One binary, one configuration directory, one data directory. No database to provision, no cache to run, no message queue. Fifteen minutes on a plain VM.
What is in the tarball
bin/searchcode the server and the CLI, one static binary
systemd/searchcode.service the unit
systemd/oauth2-proxy.service SSO in front of the UI, for auth.mode: proxy
systemd/oauth2-proxy.cfg a worked oauth2-proxy configuration
config/searchcode.yaml a starting configuration that validates as it stands
config/searchcode.yaml.annotated every block that exists, documented
config/conf.d/repositories.yaml where the repository list goes
AGENT-INSTALL.md this install, written for an LLM to execute
openapi.json the API contract for this exact build
codes.json every log code this build can emit, and where
sbom.json CycloneDX, listing what is actually in the binary
SHA256SUMS checksums for all of the above
If an agent is doing this install, hand it AGENT-INSTALL.md from the tarball — the same
steps, self-contained, with an explicit verification after each one and a refusal list for the
shortcuts an agent reaches for. It is docs/install/agent.md in the repository, and the
tarball's copy is the one that matches the build you are installing.
Verify the checksums before you install anything:
sha256sum -c SHA256SUMS
codes.json is the one nobody thinks about until they need it. See
Log codes.
Install
sudo useradd --system --home-dir /var/lib/searchcode \
--shell /usr/sbin/nologin searchcode
sudo install -m0755 bin/searchcode /usr/local/bin/searchcode
sudo install -d -m0755 /etc/searchcode /etc/searchcode/conf.d
sudo install -m0644 config/searchcode.yaml /etc/searchcode/searchcode.yaml
sudo install -m0644 config/conf.d/repositories.yaml /etc/searchcode/conf.d/
sudo install -m0644 systemd/searchcode.service /etc/systemd/system/
git must be on the PATH. The product shells out to the real git binary
deliberately rather than reimplementing the protocol, which is what makes the
git-p4, git-svn and hg-git bridges work — see
Source capability tiers.
Check the configuration before starting anything. It prints every problem, not the first, and exits non-zero on any of them:
sudo -u searchcode searchcode config validate
Then:
sudo systemctl daemon-reload
sudo systemctl enable --now searchcode
enable is what makes it come back after a reboot. start alone does not.
systemctl status searchcode
journalctl -u searchcode -f
The startup banner on stderr names the configuration it read and the addresses it
bound. stdout is pure JSON — one object per line, with ts, level, msg,
component and uniqueCode — and that is the schema to write SIEM rules
against.
First sign-in
The shipped configuration uses auth.mode: local: built-in accounts and no
identity provider. Nobody can sign in until you create somebody:
sudo -u searchcode searchcode user add --admin alice
The password is printed once and must be changed at first sign-in — no session
is issued at all until it is replaced. There is no password-reset email and no
SMTP configuration: this product sends no mail. Recovery is searchcode user passwd, run on the box, and it is audited.
Brute force is handled with backoff rather than lockout. Usernames appear in the commit history this product indexes, so account lockout would ship a working denial of service against any named engineer.
SSO instead
Set auth.mode: proxy and put oauth2-proxy in front of the UI listener
only — port 8081 by default. systemd/oauth2-proxy.cfg is a worked
configuration with the four values you must change marked.
For how the two listeners map onto DNS names and certificates, and what
mcp.oauth.publicUrl and uiUrl then have to be set to, see
Ports, names and what sits in front of each listener.
Do not put it in front of the API and MCP listener on 8080. Agents present a
searchcode-issued credential your proxy did not issue and cannot validate, and
the workaround people reach for next — a skip-auth route for /v1/mcp and
/api/v1/* — is an authentication bypass. The two listeners are separate ports
precisely so this cannot happen by accident.
Three things about proxy mode that get "fixed" by people who read them as oversights:
- Identity headers are stripped unconditionally on every request, including the underscore forms Go does not canonicalise. There is no option to disable it.
- Trust is decided on the direct peer address, from
auth.trustedProxyCIDRs, never onX-Forwarded-For. Trusting a header to decide whether to trust headers is circular. - Groups are replaced, never merged, and a duplicated identity header is a refusal rather than a pick.
There is no native OIDC and there will not be one. Your proxy owns the identity provider conversation, which is what makes the egress claim true in its strongest form: the searchcode process never talks to an identity provider, in any mode.
Adding repositories
searchcode discover enumerates a code host into a conf.d/ fragment you review
and commit. It is a CLI you run; the server never calls a code host.
searchcode discover --provider github --org yourorg \
--out /etc/searchcode/conf.d/repositories.yaml
The repository list is hot-reloaded — no restart. Listen addresses, the auth
mode, instance.dataDir and tracing are restart-only, and a reload carrying a
change to one of them is rejected naming the field rather than half-applied.
The full split is the Reload column in
Configuration reference.
Upgrading
Replace the binary and restart. State lives in /var/lib/searchcode and is
forward-compatible; nothing in the tarball needs to be re-run.
sudo systemctl stop searchcode
sudo install -m0755 bin/searchcode /usr/local/bin/searchcode
sudo systemctl start searchcode
Backup
/var/lib/searchcode/subjects.db is the only durable state that cannot be
reconstructed. See Backup and restore.
If it will not start
searchcode doctor
runs the startup checks against a stopped instance and says which one failed and
what usually causes it. searchcode doctor bundle prints a redacted diagnostic
document to paste into a support ticket — see
searchcode doctor.