Mateusz Urbanek 7c6d26184c
fix: set content-disposition on S3
S3 does not set proper Content-Disposition, and this header might be
ignored by e.g. browsers if set before redirect. We need to include it
when adding new object to the cache.

Additionally, if the object was previously cached, we need to ensure
that Content-Disposition header will be properly updated.

Signed-off-by: Mateusz Urbanek <mateusz.urbanek@siderolabs.com>
2025-08-12 08:55:45 +02:00

111 lines
2.8 KiB
Go

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package http
import (
"context"
_ "embed"
"fmt"
"io"
"net/http"
"strings"
"text/template"
"github.com/blang/semver/v4"
"github.com/julienschmidt/httprouter"
"github.com/siderolabs/gen/ensure"
"github.com/siderolabs/image-factory/internal/profile"
)
//go:embed standard.ipxe
var standardIPXE string
//go:embed secureboot.ipxe
var securebootIPXE string
// handlePXE delivers a PXE script to boot Talos.
func (f *Frontend) handlePXE(ctx context.Context, w http.ResponseWriter, _ *http.Request, p httprouter.Params) error {
schematicID := p.ByName("schematic")
schematic, err := f.schematicFactory.Get(ctx, schematicID)
if err != nil {
return err
}
versionTag := p.ByName("version")
if !strings.HasPrefix(versionTag, "v") {
versionTag = "v" + versionTag
}
version, err := semver.Parse(versionTag[1:])
if err != nil {
return fmt.Errorf("error parsing version: %w", err)
}
// the PXE format is just platform+arch, so if we append cmdline, it should parse
path := "cmdline-" + p.ByName("path")
prof, err := profile.ParseFromPath(path, version.String())
if err != nil {
return fmt.Errorf("error parsing profile from path: %w", err)
}
prof, err = profile.EnhanceFromSchematic(ctx, prof, schematic, f.artifactsManager, f.secureBootService, versionTag)
if err != nil {
return fmt.Errorf("error enhancing profile from schematic: %w", err)
}
if err = prof.Validate(); err != nil {
return fmt.Errorf("error validating profile: %w", err)
}
// build the cmdline
asset, err := f.assetBuilder.Build(ctx, prof, version.String(), path)
if err != nil {
return err
}
reader, err := asset.Reader()
if err != nil {
return err
}
defer reader.Close() //nolint:errcheck
cmdline, err := io.ReadAll(reader)
if err != nil {
return err
}
if prof.SecureBootEnabled() {
return ensure.Value(template.New("secureboot.ipxe").
Parse(securebootIPXE)).
Execute(w,
struct {
UKIURL string
Cmdline string
}{
UKIURL: f.options.ExternalPXEURL.JoinPath("image", schematicID, versionTag, fmt.Sprintf("%s-%s-secureboot-uki.efi", prof.Platform, prof.Arch)).String(),
Cmdline: string(cmdline),
},
)
}
return ensure.Value(template.New("standard.ipxe").
Parse(standardIPXE)).
Execute(w,
struct {
KernelURL string
Cmdline string
InitramfsURL string
}{
KernelURL: f.options.ExternalPXEURL.JoinPath("image", schematicID, versionTag, fmt.Sprintf("kernel-%s", prof.Arch)).String(),
Cmdline: string(cmdline),
InitramfsURL: f.options.ExternalPXEURL.JoinPath("image", schematicID, versionTag, fmt.Sprintf("initramfs-%s.xz", prof.Arch)).String(),
},
)
}