# Programvarepakke

## Hva det er

En programvarepakke er formen programvare reiser i fra dem som skriver den, til
maskinene som kjører den: koden, som et arkiv eller en katalog, sammen med et manifest
som oppgir navn, versjon og hva annet den trenger for å virke. I daglig tale heter den
bare en pakke, med flertall pakker og pakkene, og på engelsk package. Hvert økosystem
trekker grensene på sitt eget sted. For npm er en pakke en fil eller katalog som
beskrives av en package.json-fil, og npm-dokumentasjonen skiller den fra en modul, som
er alt Node.js kan laste med require() eller import; bare moduler som har en
package.json, er pakker. Python bruker ordet om to ting, og [Python Packaging User Guide](https://packaging.python.org/)
holder dem atskilt: en importpakke (import package) er en katalog med moduler som koden
importerer, mens en distribusjonspakke (distribution package) er det versjonerte arkivet
en bruker laster ned og installerer. I Rust samler en pakke én eller flere crates med en
Cargo.toml som sier hvordan de skal bygges. I Debian er enheten binærpakken, .deb-filen
systemet installerer og holder rede på.

En pakke er en distribusjonsenhet, og den er lettest å definere mot naboene. Et
bibliotek er kode; en pakke er måten den koden leveres på, og ett bibliotek kan nå
brukerne som en npm-pakke, et Python-wheel og en Debian-pakke på samme tid. En
pakkebehandler er programmet som leser manifester, henter det de lister opp, og
installerer alt i en rekkefølge som virker: npm, pip, Cargo og apt. Et register eller en
pakkebrønn er stedet der pakkene venter på å bli hentet, f.eks. npm-registeret, PyPI,
crates.io eller et Debian-speil. Pakkebehandlere for et programmeringsspråk installerer
kode for ett prosjekt, som regel i en katalog ved siden av det. Pakkebehandlere for
systemet installerer programvare for hele maskinen og noterer hvilken pakke som la hver
fil hvor.

## Hvorfor det er viktig

Tredjepartskode kommer inn i et prosjekt på én av tre måter: via en pakkebehandler,
kopiert inn i repoet med lisensen og en sjekksum, det som kalles vendoring, eller bygd
fra kildekoden. En pakkebehandler er det vanlige valget, og med god grunn. Den løser opp
hele treet av avhengigheter, oppdaterer dem med én kommando og noterer hva den
installerte. Prisen er tillit: prosjektet stoler nå på hvert manifest i det treet og på
hvert register det kom fra. Vendoring bytter den tilliten mot arbeid, fordi den kopierte
koden bare endres når noen bevisst kopierer inn en ny versjon. Å bygge fra kildekoden
gir mest kontroll og krever mest vedlikehold.

Det er i pakkemanifestet de fleste avveiningene viser seg. Det oppgir som regel et
intervall av versjoner i stedet for én; i npm godtar et caret-intervall som ^10.0.1
enhver senere utgivelse med samme hovedversjon. Intervaller lar rettinger komme uten at
noen redigerer filen, men de betyr også at to installasjoner med en ukes mellomrom kan
gi ulike trær. En låsefil tetter det hullet. npm skriver package-lock.json for å
beskrive det nøyaktige treet den bygde, så senere installasjoner gjenskaper det, Cargo
skriver Cargo.lock med den nøyaktige versjonen av hver avhengighet, og begge forventer
at filen sjekkes inn. Manifestet registrerer hva et prosjekt er villig til å godta;
låsefilen registrerer hva det fikk.

Manifestene sier også hvor sterkt én pakke trenger en annen. Debian Policy Manual
definerer en stige. Depends er absolutt: pakken konfigureres ikke før alt den er
avhengig av, er konfigurert. Recommends er sterk, men ikke absolutt, og gjelder pakker
som finnes sammen med denne i alle unntatt uvanlige installasjoner. Suggests nevner
pakker som gjør den mer nyttig, mens det er helt rimelig å installere uten dem. En
installasjon som utelater en anbefalt pakke, er gyldig, og programvaren i den kan
likevel trenge den pakken til én av oppgavene sine.

Hvert verktøy som vurderer pakker, stoler på metadataene deres. En lisenssjekker leser
den deklarerte lisensen, ofte et SPDX-uttrykk som MIT OR Apache-2.0, der OR gir et valg
og AND krever alle lisensene samtidig. En sårbarhetsskanner sammenligner deklarerte navn
og versjoner med publiserte sikkerhetsvarsler. Når en deklarasjon er feil, tar hvert
verktøy som leser den, feil på samme måte, og derfor fortjener lisensfilene inne i en
levert pakke en egen gjennomlesing.

## Slik ser det ut i praksis

En herdingsgjennomgang av en produksjonsserver fant at jailen for utestenging av
inntrengere aldri hadde stengt ute noen, og én av tre separate årsaker var et forhold
mellom pakker. Utestengingshandlingen kalte et pakkefilterprogram som pakkesystemet
oppgir som anbefalt i stedet for påkrevd, og serveren var bygd uten det. Jailen startet,
talte mislykkede innlogginger og kom frem til en avgjørelse hver gang, og feilet så i
det siste steget, der utestengingen skulle tre i kraft.
[Gjennomgangen av tre SSH-vern](/nb/portfolio/found-three-ssh-brute-force-protections-that-never-worked-110/)
fant det ved å undersøke det kjørende systemet i stedet for konfigurasjonsfilene, og
etterlot en regel om at hver sikkerhetskontroll bakkes opp av en automatisk påstand,
ikke av en kommentar.

En Rust-kjerne som lenkes inn i en levert app, tar med seg hele avhengighetsgrafen sin
inn i produktet, her 644 crates, hver med sin egen lisensdeklarasjon. En avhengighet
flere nivåer ned kan gå over til en copyleft-lisens fra én versjon til den neste, og det
eneste sporet vil være en endret linje i en låsefil få leser.
[Lisensrevisjonen av 644 crates](/nb/portfolio/audited-644-rust-crates-for-licence-compatibility-163/)
kjører ved hvert bygg: den vurderer lisensuttrykket til hver crate mot en
tillatelsesliste, godtar et valg mellom lisenser når én av dem er tillatt, og en
kombinasjon bare når alle er det, og stopper bygget ved alt som ikke har treff. For å
vise at kontrollen kan slå ut, ble den brutt med vilje to ganger, én gang ved å skrive
om lisensuttrykket til en crate og én gang ved å flytte den låste kjernerevisjonen uten
å generere revisjonen på nytt. Siden sier rett ut hvor grensen går: revisjonen leser
deklarerte metadata, som kan være feil, og den er ikke juridisk rådgivning.

Selskapets egen git-forge ble
[installert fra leverandørens pakkebrønn](/nb/portfolio/deployed-the-companys-own-git-forge-121/)
som ett enkelt program som operativsystemet holder i gang. Å legge til en leverandørs
pakkebrønn på en server er en beslutning i seg selv, fordi rutineoppdateringer fra da av
installerer det leverandøren publiserer og signerer.

Dette nettstedet trenger ikke Node for å kjøre. Filen package.json er merket som privat,
noe som får npm til å nekte å publisere den, lister bare utviklingsverktøy, med
caret-intervaller, og ligger ved siden av en innsjekket package-lock.json. Filen
pyproject.toml setter opp en linter, en stavekontroll og en typesjekker og har ingen
`[project]`- eller `[build-system]`-tabell, fordi den ikke beskriver noen pakke som skal
bygges. Ikonene er vendoret: kopiert inn med lisensen fra oppstrøms og en
SHA-256-sjekksum for hver fil.

## Hvor det går galt

### En funksjon melder at alt er i orden, og gjør ingenting

En tjeneste starter, logger at den kjører, og hopper i det stille over én av oppgavene
sine hver gang. Et program den kaller, hører til en anbefalt eller foreslått pakke, som
installasjonen hadde full rett til å utelate. Når en funksjon er avhengig av et eksternt
program, sjekk at programmet finnes ved oppstart, og stopp med en tydelig feilmelding
hvis det mangler. Deklarer det som et absolutt krav i deres egen produksjonssetting, og
test funksjonen fra ende til ende på en server bygd som produksjon.

### To maskiner installerer ulik kode fra samme manifest

Bygget går gjennom på en bærbar maskin og feiler i kontinuerlig integrasjon, eller
omvendt, uten at prosjektets egen kode er endret. Manifestet oppgir versjonsintervaller,
låsefilen ble aldri sjekket inn, og en avhengighet publiserte en ny utgivelse mellom de
to installasjonene. Sjekk inn låsefilen, installer fra den i automatiseringen med
kommandoer som nekter å endre den, f.eks. `npm ci` eller `cargo build --locked`, og
oppdater den som en egen endring som gjennomgås.

### En lisens endres inne i en kjent avhengighet

Et produkt som besto en lisensgjennomgang i fjor, leveres nå med kode under vilkår ingen
har godtatt, mens listen over avhengigheter ser lik ut. En eksisterende avhengighet
endret lisens mellom to versjoner, og en rutineoppdatering dro den nye versjonen inn via
låsefilen. Sjekk deklarerte lisenser mot en eksplisitt tillatelsesliste ved hvert bygg,
så endringen stopper bygget samme dag som den kommer.

## Les mer

- [npm Docs: About packages and modules](https://docs.npmjs.com/about-packages-and-modules)
  — npms egen definisjon av en pakke, og hvorfor en modul ikke alltid er det.
- [Debian Policy Manual: Declaring relationships between packages](https://www.debian.org/doc/debian-policy/ch-relationships.html)
  — Depends, Recommends, Suggests og de andre feltene, og hva en pakkebehandler gjør med
  hvert av dem.
- [Python Packaging User Guide: Writing your pyproject.toml](https://packaging.python.org/en/latest/guides/writing-pyproject-toml/)
  — de tre tabellene i pyproject.toml og metadataene en distribusjonspakke for Python
  deklarerer.
- [The Cargo Book: Cargo.toml vs Cargo.lock](https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html)
  — hvorfor manifestfilen beskriver hva et prosjekt godtar, mens låsefilen registrerer
  nøyaktige versjoner, og når den bør sjekkes inn.
- [SPDX Specification 2.3.1: License expressions](https://spdx.github.io/spdx-spec/v2.3.1/SPDX-license-expressions/)
  — syntaksen pakker bruker for å deklarere én lisens, et valg mellom flere eller et
  sett som gjelder samtidig.

<https://engineer.company/nb/glossary/package/>
