Kai Ole Hartwig
pinup · documentation

Getting started

pinup is one static binary. It needs git on the machine, a token for the platform it writes to, and a configuration in Renovate's language. Four steps from the download to a scheduled job.

  • Binary:go install github.com/ohartwig/pinup/cmd/pinup@latest or the signed releases (pinup-linux-amd64, -arm64, pinup-darwin-arm64, -amd64, SHA256SUMS)
  • Check:pinup version prints what you have

From the binary to a scheduled job

1

A plan without a token

The first thing to run is whatif: it resolves the configuration, extracts the dependencies, looks versions up and writes a plan. It changes nothing and needs no platform token (private registries aside).

 

cd a-repository
pinup whatif --repo . --config .pinup.yaml --report plan.json

 

The plan says what pinup would do, and for everything it would not do, why: every held update carries its reason, its thaw time and the rule that holds it. --now 2026-01-01T00:00:00Z plans as of another moment; that is how you check a schedule or a release age without waiting.

2

A run against a project

run does what whatif does and then pushes the branches, opens or updates the merge requests and writes the dashboard issue. It needs the platform and a token:

 

export PINUP_GITLAB_URL=https://gitlab.example.org
export PINUP_GITLAB_TOKEN=glpat-…            # api, write_repository
export PINUP_GIT_NAME="Dependency Bot" PINUP_GIT_EMAIL=bot@example.org
export PINUP_SIGNING_FORMAT=none              # or openpgp / ssh with PINUP_SIGNING_KEY

pinup run --project group/project --config 'local>group/runner-config' --report plan.json

 

--dry-run stops after the plan, with the platform's view of the existing merge requests. --repo . runs against a checkout instead of cloning. On GitHub the same command reads PINUP_GITHUB_TOKEN and --project owner/repository.

--config is a file or a local> preset the platform serves: a default.json in a runner project that every repository's own configuration file extends. The repositories keep their files; pinup answers the runner's name from the file it was started with, without a fetch.

3

Every project the token can see

pinup run --autodiscover '["group/**", "!group/archive/**"]' --config … --report 'reports/%s.json'

 

The list is every project the token can see that is not archived, filtered by the globs (! negates); eight projects run at a time, each in its own clone. A %s in --report becomes the project path.

4

A CI job

The shape of a scheduled job on GitLab: the toolchain image carries pinup and whatever the lock refreshes need (composer, npm, go), the token is a masked variable, the plan is an artefact.

 

pinup:scan:
  image: ghcr.io/ohartwig/pinup-toolchain:0
  rules:
    - if: $CI_PIPELINE_SOURCE == "schedule"
  variables:
    PINUP_CACHE: .pinup/cache.db
  cache:
    key: pinup-$CI_JOB_NAME
    paths: [.pinup/cache.db]
  script:
    - pinup run --autodiscover '["group/**"]' --config 'local>group/runner' --report 'reports/%s.json'
  artifacts:
    paths: [reports/]
    when: always

 

PINUP_CACHE keeps release lists, first-seen records, advisories and release notes between runs: a release whose registry publishes no timestamp gets its age from when this cache first saw it. That is why a run with an empty cache warns about minimumReleaseAge rather than trusting it. Complete files for GitLab and GitHub Actions are under docs/examples.

Next

Configuration

Renovate's language, the three layers, the merge semantics, every supported key, the environment.

Read →
Configuration

Back to pinup

The overview: the problem, the fit check, the quickstart, every chapter.

pinup →
Back to pinup