Self-hosted feedback portal that speaks to your forge. Single binary, no account needed to report, and accepted items become real issues in GitHub, GitLab, Forgejo or Gitea.
  • Go 55.5%
  • TypeScript 30.6%
  • CSS 8.2%
  • Shell 5.2%
  • Makefile 0.3%
  • Other 0.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
k4wit0 d56a3b7af2
All checks were successful
CI / check (push) Successful in 1m9s
CI / tests (push) Successful in 1m25s
fix(install): an installation made by an earlier version is still an installation
Reported from a real machine: upgrading an existing server asked all the
questions again and then refused, saying port 8080 was in use — by the very
service it was about to upgrade.

The detection looked for the binary where *this* version puts it, so anything
installed by an earlier one was invisible. It is the unit that says an
installation is here, and that has been in the same place all along. The old
layout kept its settings as Environment= lines inside that unit rather than in
a configuration file, so they are read out of it: an upgrade keeps the port
somebody chose instead of quietly moving their server to 8080.

The service is now stopped before the port is looked at, which is what makes
the check mean "somebody else has this port". And the binary the previous
layout left in /usr/local/bin is removed, because leaving an outdated one on
the PATH under the same name is how somebody debugging at midnight runs the
version before last.

The look is the house's now: the banner, the welcome line, the questions in
the order the others ask them, and the same three closing lines.

The installed command is universal again. It carried LASTHELP_CHANNEL=beta,
which is a thing to remember and to get wrong, when the installer asks which
channel you want — the variable stays for the case that has no terminal to ask
on.

Verified against a machine set up by the previous installer, with something
really listening on the port it had chosen: the upgrade is recognised, port
9001 and the proxy setting survive, the database is backed up, the old binary
goes, and the hardened unit replaces the inline one.
2026-09-09 10:28:44 +02:00
.forgejo/workflows fix(ci): a beta is published as one 2026-09-08 08:29:17 +02:00
assets docs: the README takes the shape the house uses 2026-09-08 20:46:17 +02:00
cmd fix(install): refuse an openssl too old to check the signature 2026-09-07 21:04:54 +02:00
deploy feat(install): the house installer, and a shell it actually runs in 2026-09-09 01:04:55 +02:00
docs fix(install): an installation made by an earlier version is still an installation 2026-09-09 10:28:44 +02:00
internal feat(install): the house installer, and a shell it actually runs in 2026-09-09 01:04:55 +02:00
scripts feat(install): the house installer, and a shell it actually runs in 2026-09-09 01:04:55 +02:00
web i18n: translate the new interface into the other eighteen languages 2026-09-09 00:25:53 +02:00
.dockerignore feat: project skeleton with embedded frontend and reproducible build 2026-09-06 23:04:16 +02:00
.env.example feat: telling people, in both directions 2026-09-07 09:23:25 +02:00
.gitignore feat: project skeleton with embedded frontend and reproducible build 2026-09-06 23:04:16 +02:00
bun.lock feat: project skeleton with embedded frontend and reproducible build 2026-09-06 23:04:16 +02:00
CHANGELOG.md release: The house installer, in a shell it actually runs in 2026-09-09 01:06:36 +02:00
CONTRIBUTING.md fix: run the rule that was written and never called 2026-09-07 16:05:05 +02:00
docker-compose.yml feat: project skeleton with embedded frontend and reproducible build 2026-09-06 23:04:16 +02:00
Dockerfile feat: project skeleton with embedded frontend and reproducible build 2026-09-06 23:04:16 +02:00
go.mod build: tidy go.mod, and make lint do it from now on 2026-09-07 20:59:11 +02:00
go.sum feat: accounts, sessions and the one place that decides permissions 2026-09-07 04:27:16 +02:00
install.sh fix(install): an installation made by an earlier version is still an installation 2026-09-09 10:28:44 +02:00
lasthelp.key feat(portal): the report form asks a bug what makes it usable 2026-09-08 07:28:34 +02:00
LICENSE feat: project skeleton with embedded frontend and reproducible build 2026-09-06 23:04:16 +02:00
Makefile build: tidy go.mod, and make lint do it from now on 2026-09-07 20:59:11 +02:00
NOTICE feat: accounts, sessions and the one place that decides permissions 2026-09-07 04:27:16 +02:00
package.json release: The house installer, in a shell it actually runs in 2026-09-09 01:06:36 +02:00
README.md fix(install): an installation made by an earlier version is still an installation 2026-09-09 10:28:44 +02:00
SECURITY.md feat(security): HSTS, nothing private in an index, and the list as a test 2026-09-08 23:14:30 +02:00
tsconfig.json feat: project skeleton with embedded frontend and reproducible build 2026-09-06 23:04:16 +02:00


LastHelp

LastHelp

The front door your forge does not have.

Documentation   Latest release   Apache 2.0


A LastHelp board





📖 Read the Documentation for detailed guides on installation, your first repository, connecting a forge, notifications and keeping the queue readable.


Status

Beta. One release is published and signed, and this section says what is actually behind it — a README describing a server nobody wrote is the first thing that makes people stop trusting one.

Working end to end, each of them checked by driving a real browser before it ships: organisations and boards, reporting without an account, the secret follow link, the moderation queue, publishing, voting, comment threads, similar-item hints while you type, the roadmap, the panel — overview, the cross-board listing and triage from it — the anti-abuse layers, notifications in both directions, the forge bridge for GitHub, GitLab, Gitea and Forgejo, installing, and updating in place.

Not there yet: Bitbucket, and a stable release — the only release is a prerelease, so answer beta when the installer asks which channel you want.


Why LastHelp

Self-hosting your forge usually means turning registration off, and that closes the only door outsiders had for telling you something is broken. Opening registration to the world is not the answer; a separate front door is. The open-source feedback tools do not talk to any forge, and the ones that do are closed, paid and hosted by somebody else — so nobody serves the person who self-hosts their own forge, which is almost always the same person who would self-host a feedback portal. LastHelp was built with a simple rule: anybody can tell you something without an account, and nothing reaches a public page until a person says so.

  • Reporting without an account — a bug or an idea, in one form, from anybody. What they get back is a secret link: the only way to their own report, and the reason no sign-up is needed at all.
  • A bridge to your forge — GitHub, GitLab, Gitea and Forgejo, hosted anywhere including your own network. A triaged report becomes an issue, or is attached to one that already exists — which is what a project with four thousand open issues actually needs — and when that issue closes or reopens, the report follows.
  • A roadmap nobody maintains — three columns built from the state of each report, so it cannot be out of date. Ideas gather votes, which is what turns "somebody asked for this once" into an order of work.
  • A queue that stays readable — nothing is published without a person, and the decision sits on the row: publish, reject, spam. One listing covers every board an account has a role in, filtered by state, kind, status, board or words, with the filters in the address so a narrowed view can be sent to somebody else.
  • An overview worth opening — the last thirty days as a chart you can ask about day by day, the most wanted ideas, the newest reports and the work per board. Every number on it leads to the listing it counts.
  • Anti-abuse without a third party — a signed single-use form token, a timing window, a honeypot, rate limits keyed on the address a caller cannot choose, duplicate and link heuristics, an operator word list, and a breaker that closes a board rather than hand somebody a queue they will not open. Every refusal is counted by layer, because a filter nobody can measure is a filter quietly turning away people who never complain.
  • Notifications in both directions — the team is told through ntfy, Gotify, a webhook or mail, configured per repository. Whoever reported something follows an Atom feed that needs no address, no account and no configuration, or leaves an email if a relay is set up.
  • One binary, one file — no database server, no runtime, no dependencies. SQLite and the whole interface are compiled in, and a backup is a copy of one file — or a button in the panel.
  • Twenty languages, translated by hand — not machine-filled — with right-to-left laid out properly for Arabic, Persian and Urdu. English and Spanish are complete; about a hundred strings from the newest screens are still waiting in the other eighteen and fall back to English until they are done.
  • Reproducible builds-trimpath, CGO_ENABLED=0 and a frontend stamped with a digest of its own output, so two builds of the same commit produce the same bytes and you can check ours against yours.

Bitbucket is not bridged yet. Everything else above works end to end, and is checked by driving a real browser rather than by hoping.


Security & Privacy

Security

Passwords are hashed with Argon2id. Sessions are opaque tokens in an HttpOnly, SameSite=Lax cookie, marked Secure over HTTPS, and CSRF is a double-submit token checked alongside Sec-Fetch-Site — a header the browser writes from the real origins and a page cannot forge. Every response carries a Content-Security-Policy with script-src 'self', so an injected script does not run, and Referrer-Policy: no-referrer, because the address of a report carries the token that opens it. Rate limits are keyed on the address our own proxy added rather than the first one the caller sent, because a proxy appends to X-Forwarded-For and reading the left-hand entry lets anybody choose their own identity. Forge tokens are stored encrypted and never shown again, request bodies are bounded per handler, and every release is signed.

For a detailed breakdown, including what the threat model does not cover, see the Security Policy.

Privacy

Reporting is anonymous by design. No account, no tracking, no analytics, and no telemetry of any kind: nothing about you or your instance is ever reported to us. The email address a reporter may leave is optional, asked for only when a mail relay exists to use it, stored encrypted, visible only to the team, and deleted when the report closes.

And no AI. Nothing anybody writes here is sent to a third party — the whole point is that it reaches one database and nowhere else.


Installation Methods

Quick Install (Linux)

Run this command to install or update LastHelp:

curl -fsSL https://git.codigosh.com/CodigoSH/LastHelp/raw/branch/main/install.sh | sudo sh

It asks which port to use, which release channel to follow and whether a reverse proxy is in front, and remembers all three the next time you run it. Answer beta to the channel question today: there is no stable release yet, and the default only ever picks a stable one, so nobody following this command lands on a prerelease by accident. Without a terminal to ask on — a container, a cron job — every answer is its default and LASTHELP_CHANNEL=beta supplies that one.

It verifies the signature over the checksums before anything reaches the disk, installs a systemd service, keeps the database in /var/lib/lasthelp and serves on port 8080 unless you answer otherwise when it asks. The first account created is the administrator: open the address it prints and claim the instance — a server with somebody in it cannot be claimed again.


Docker

The repository ships a Dockerfile and a docker-compose.yml, and the image is built from source: there is no published image yet.

git clone https://git.codigosh.com/CodigoSH/LastHelp.git
cd LastHelp
docker compose up -d
services:
  lasthelp:
    image: lasthelp:latest
    build: .
    container_name: lasthelp
    restart: unless-stopped
    ports:
      - "8080:8080"
    volumes:
      - lasthelp-data:/data
    environment:
      LASTHELP_PORT: "8080"
      LASTHELP_DB_FILE: "/data/lasthelp.db"
      # Turn this on only when a reverse proxy really is in front. With it on
      # and no proxy there, the caller chooses the address we attribute their
      # requests to, and the rate limits stop meaning anything.
      LASTHELP_TRUST_PROXY: "false"

volumes:
  lasthelp-data:

The image runs as its own unprivileged user and the binary inside refuses to update itself, because updating a container is pulling a new image.

Every setting the binary reads is listed in .env.example and explained under Settings.


Updates

Updates are delivered directly from our own Forgejo instance — no proxy, no intermediary and no telemetry.

The updater checks the signature over the checksum list before it downloads anything, then the binary's own checksum, then makes the candidate prove it runs and report its schema, then refuses any binary older than the database, takes a backup, and only then replaces anything. None of those has a switch. In a container it says to pull an image instead of offering a button that would silently revert.

For detailed instructions see the Updating guide, or the Security Policy for how the signing works.


Build

Requires Go and bun.

make build      # frontend + binaries into bin/
make run        # build and run
make test       # go test, the architecture rules and the frontend tests
make drive      # walk the whole journey in a real browser
make live-forge # and again against a real Forgejo, Gitea or GitLab in a container
make shots      # regenerate the images this repository shows

Support & Recognition

We are an open-source initiative managed through Open Collective Europe.

Financial Support

If you find our tools useful, consider supporting our infrastructure through an unconditional donation.

Acknowledgments

Special thanks to everyone supporting our mission.

Organizations

We appreciate the organizations that support us as a gesture of goodwill to help maintain our infrastructure.


Credits

  • Everything drawn here: the mark, the icons and the charts are drawn by hand in this repository — no icon library, no chart library, no frontend framework. The whole frontend is TypeScript compiled by bun, and its only dependency is the type definitions.

Full attribution, including the Go modules compiled into the binaries, lives in NOTICE.


A project by CodigoSH — built to last.