Files
owen 981587e83d Initial release: self-hostable APT repository server and CLI
urapt is a self-hostable APT repository server with a companion CLI for
pushing and managing Debian .deb packages.

Server (urapt-server):
- REST API + APT endpoint, SQLite storage (pure-Go modernc driver, no CGO)
- .deb files stored content-addressed on disk, reference-counted for dedup
- Server-managed RSA-4096 OpenPGP signing key (ProtonMail/go-crypto)
- APT indices (Release/InRelease/Packages[.gz/.xz]) generated on demand
  from the DB, cached in memory, signed with the server key
- Full APT model: repositories -> distributions -> components -> architectures
- Bearer-token auth for REST; HTTP Basic auth for private-repo APT reads
- First registrant becomes admin; repo-scoped permissions
  (read/write/read-write/admin) plus owner and server-admin roles
- Multipart package push with control-field extraction, list/show/delete,
  pool serving, blob ref-count cleanup
- Audit log

CLI (urapt):
- register/login/logout/whoami, token management
- repo/distro/component/arch CRUD, member management
- push/pull/ls/show/rm for packages
- apt-config helper that emits apt setup commands (key, sources.list,
  auth.conf for private repos)

Packaging & docs:
- Dockerfile (multi-stage distroless), docker-compose.yml, sample config
- README quick start, architecture overview, config reference, security notes
- PLAN.md design blueprint, CHANGELOG.md, GPL-3.0 LICENSE
- GitHub Actions CI (test, lint, cross-build for linux/darwin amd64/arm64)
- Makefile release target producing static binaries + tarballs + checksums

Tests cover the data-access layer, auth/permission checks, APT index
generation, .deb parsing, GPG signing, the REST API, and the typed API
client. Verified end-to-end on a Raspberry Pi (arm64) pushing and installing
a real package.
2026-06-28 16:57:34 -05:00

51 lines
1.5 KiB
Go

// Package store provides the data-access layer for urapt-server: typed
// methods over the SQLite database backing all domain objects (users, tokens,
// repositories, distributions, components, architectures, packages, blobs,
// gpg keys, audit log).
package store
import (
"context"
"database/sql"
"fmt"
"time"
"github.com/google/uuid"
)
// Store is the entrypoint to the data-access layer. All methods are safe for
// concurrent use; the underlying *sql.DB is configured with a single writer
// connection (see shared/db).
type Store struct {
db *sql.DB
now func() string
}
// New constructs a Store wrapping db.
func New(db *sql.DB) *Store {
return &Store{db: db, now: nowISO}
}
// DB returns the underlying database (used by app for raw queries if needed).
func (s *Store) DB() *sql.DB { return s.db }
// Now returns the current timestamp in the urapt canonical form.
func (s *Store) Now() string { return s.now() }
// newID returns a fresh UUIDv4 string.
func newID() string { return uuid.NewString() }
// nowISO returns the current UTC time in RFC3339 form.
func nowISO() string { return time.Now().UTC().Format(time.RFC3339Nano) }
// exec is a small helper for ExecContext with a context.
func (s *Store) exec(ctx context.Context, query string, args ...any) (sql.Result, error) {
return s.db.ExecContext(ctx, query, args...)
}
// ErrNotFound is returned by Get-style methods when no row matches.
var ErrNotFound = fmt.Errorf("not found")
// isErrNoRows returns true if err is sql.ErrNoRows.
func isErrNoRows(err error) bool { return err == sql.ErrNoRows }