Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More chore #47

Merged
merged 3 commits into from
Jul 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading