Skip to content

Commit

Permalink
Merge pull request #47 from numtide/more-chore
Browse files Browse the repository at this point in the history
More chore
  • Loading branch information
zimbatm authored Jul 28, 2024
2 parents a07415f + 36fc4a0 commit 747b599
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 45 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ You can use the following environment variables to configure nar-serve:
|:-- |:-- |:-- |
| `PORT` | `8383` | Port number on which nar-service listens |
| `HTTP_ADDR` | `:$PORT` | HTTP address to bind the server to. When set, takes precedence over $PORT. |
| `NAR_CACHE_URL` | `https://cache.nixos.org` | The URL of the Nix store from which NARs are fetched |
| `NIX_CACHE_URL` | `https://cache.nixos.org` | The URL of the Nix store from which NARs are fetched |

## Contributing

Expand Down
47 changes: 21 additions & 26 deletions api/unpack/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,39 @@ import (
"io"
"mime"
"net/http"
"os"
"path/filepath"
"strings"

"github.com/numtide/nar-serve/pkg/libstore"
"github.com/numtide/nar-serve/pkg/nar"
"github.com/numtide/nar-serve/pkg/narinfo"

"github.com/ulikunitz/xz"
"github.com/klauspost/compress/zstd"
"github.com/ulikunitz/xz"
)

// MountPath is where this handler is supposed to be mounted
const MountPath = "/nix/store/"

var nixCache = mustBinaryCacheReader()
type Handler struct {
cache libstore.BinaryCacheReader
mountPath string
}

func mustBinaryCacheReader() libstore.BinaryCacheReader {
r, err := libstore.NewBinaryCacheReader(context.Background(), getEnv("NAR_CACHE_URL", "https://cache.nixos.org"))
if err != nil {
panic(err)
func NewHandler(cache libstore.BinaryCacheReader, mountPath string) *Handler {
return &Handler{
cache: cache,
mountPath: mountPath,
}
return r
}

// MountPath is where this handler is supposed to be mounted
func (h *Handler) MountPath() string {
return h.mountPath
}

// Handler is the entry-point for @now/go as well as the stub main.go net/http
func Handler(w http.ResponseWriter, req *http.Request) {
func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
ctx := req.Context()
// remove the mount path from the path
path := strings.TrimPrefix(req.URL.Path, MountPath)
path := strings.TrimPrefix(req.URL.Path, h.mountPath)
// ignore trailing slashes
path = strings.TrimRight(path, "/")

Expand All @@ -52,7 +55,7 @@ func Handler(w http.ResponseWriter, req *http.Request) {
narName := strings.Split(narDir, "-")[0]

// Get the NAR info to find the NAR
narinfo, err := getNarInfo(ctx, narName)
narinfo, err := getNarInfo(ctx, h.cache, narName)
if err != nil {
http.Error(w, err.Error(), 500)
return
Expand All @@ -62,7 +65,7 @@ func Handler(w http.ResponseWriter, req *http.Request) {
// TODO: consider keeping a LRU cache
narPATH := narinfo.URL
fmt.Println("fetching the NAR:", narPATH)
file, err := nixCache.GetFile(ctx, narPATH)
file, err := h.cache.GetFile(ctx, narPATH)
if err != nil {
http.Error(w, err.Error(), 500)
return
Expand Down Expand Up @@ -157,7 +160,7 @@ func Handler(w http.ResponseWriter, req *http.Request) {

// Make sure the symlink is absolute

if !strings.HasPrefix(redirectPath, MountPath) {
if !strings.HasPrefix(redirectPath, h.mountPath) {
fmt.Fprintf(w, "found symlink out of store: %s\n", redirectPath)
} else {
http.Redirect(w, req, redirectPath, http.StatusMovedPermanently)
Expand All @@ -179,7 +182,7 @@ func Handler(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", ctype)
w.Header().Set("Content-Length", fmt.Sprintf("%d", hdr.Size))
if req.Method != "HEAD" {
io.CopyN(w, narReader, hdr.Size)
_, _ = io.CopyN(w, narReader, hdr.Size)
}
default:
http.Error(w, fmt.Sprintf("BUG: unknown NAR header type: %s", hdr.Type), 500)
Expand All @@ -192,16 +195,8 @@ func Handler(w http.ResponseWriter, req *http.Request) {
}
}

func getEnv(name, def string) string {
value := os.Getenv(name)
if value == "" {
return def
}
return value
}

// TODO: consider keeping a LRU cache
func getNarInfo(ctx context.Context, key string) (*narinfo.NarInfo, error) {
func getNarInfo(ctx context.Context, nixCache libstore.BinaryCacheReader, key string) (*narinfo.NarInfo, error) {
path := fmt.Sprintf("%s.narinfo", key)
fmt.Println("Fetching the narinfo:", path, "from:", nixCache.URL())
r, err := nixCache.GetFile(ctx, path)
Expand Down
2 changes: 1 addition & 1 deletion deploy/tf_aws_apprunner/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ resource "aws_apprunner_service" "nar_serve" {
image_configuration {
port = "8383"
runtime_environment_variables = {
NAR_CACHE_URL = var.cache_url
NIX_CACHE_URL = var.cache_url
}
}
image_identifier = "${aws_ecr_repository.nar_serve.repository_url}:${var.image_tag}"
Expand Down
49 changes: 33 additions & 16 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,32 +1,31 @@
package main

import (
"embed"
"io"
"context"
"log"
_ "embed"
"net/http"
"text/template"
"os"

"github.com/numtide/nar-serve/pkg/libstore"
"github.com/numtide/nar-serve/api/unpack"

"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)

//go:embed views/*
var viewsFS embed.FS
//go:embed views/index.html
var indexHTML string

func indexHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
f, _ := viewsFS.Open("views/index.html")
_, _ = io.Copy(w, f)
}
var indexHTMLTmpl = template.Must(template.New("index.html").Parse(indexHTML))

//go:embed views/robots.txt
var robotsTXT []byte

func robotsHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
f, _ := viewsFS.Open("views/robots.txt")
_, _ = io.Copy(w, f)
f.Close()
_, _ = w.Write(robotsTXT)
}

func healthzHandler(w http.ResponseWriter, r *http.Request) {
Expand All @@ -35,14 +34,23 @@ func healthzHandler(w http.ResponseWriter, r *http.Request) {

func main() {
var (
port = getEnv("PORT", "8383")
addr = getEnv("HTTP_ADDR", "")
port = getEnv("PORT", "8383")
addr = getEnv("HTTP_ADDR", "")
nixCacheURL = getEnv("NIX_CACHE_URL", getEnv("NAR_CACHE_URL", "https://cache.nixos.org"))
)

if addr == "" {
addr = ":" + port
}

cache, err := libstore.NewBinaryCacheReader(context.Background(), nixCacheURL)
if err != nil {
panic(err)
}

// FIXME: get the mountPath from the binary cache /nix-cache-info file
h := unpack.NewHandler(cache, "/nix/store/")

r := chi.NewRouter()

r.Use(middleware.RequestID)
Expand All @@ -51,11 +59,20 @@ func main() {
r.Use(middleware.CleanPath)
r.Use(middleware.GetHead)

r.Get("/", indexHandler)
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
data := struct {
NixCacheURL string
}{ nixCacheURL }

if err := indexHTMLTmpl.Execute(w, data); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
})
r.Get("/healthz", healthzHandler)
r.Get("/robots.txt", robotsHandler)
r.Get(unpack.MountPath+"*", unpack.Handler)
r.Method("GET", h.MountPath()+"*", h)

log.Println("nixCacheURL=", nixCacheURL)
log.Println("addr=", addr)
log.Fatal(http.ListenAndServe(addr, r))
}
Expand Down
2 changes: 1 addition & 1 deletion views/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<div class="container-lg px-3 my-5 markdown-body">
<h1>nar-serve</h1>

<p>All the files in <a href="https://cache.nixos.org">cache.nixos.org</a> are packed in NAR files which makes them not directly accessible. This service allows to dowload, decompress, unpack and serve any file in the cache on the fly.</p>
<p>All the files in <a href="{{ .NixCacheURL }}">{{ .NixCacheURL }}</a> are packed in NAR files which makes them not directly accessible. This service allows to dowload, decompress, unpack and serve any file in the cache on the fly.</p>

<h2>Use cases</h2>

Expand Down

0 comments on commit 747b599

Please sign in to comment.