mirror of
https://github.com/siderolabs/talos.git
synced 2025-10-07 05:31:20 +02:00
feat: move dashboard package & run it in tty2
Move dashboard package into a common location where both Talos and talosctl can use it. Add support for overriding stdin, stdout, stderr and ctt in process runner. Create a dashboard service which runs the dashboard on /dev/tty2. Redirect kernel messages to tty1 and switch to tty2 after starting the dashboard on it. Related to siderolabs/talos#6841, siderolabs/talos#4791. Signed-off-by: Utku Ozdemir <utku.ozdemir@siderolabs.com>
This commit is contained in:
parent
36e077ead4
commit
f55f5df739
@ -476,6 +476,8 @@ RUN ln /rootfs/sbin/init /rootfs/sbin/poweroff
|
|||||||
RUN chmod +x /rootfs/sbin/poweroff
|
RUN chmod +x /rootfs/sbin/poweroff
|
||||||
RUN ln /rootfs/sbin/init /rootfs/sbin/wrapperd
|
RUN ln /rootfs/sbin/init /rootfs/sbin/wrapperd
|
||||||
RUN chmod +x /rootfs/sbin/wrapperd
|
RUN chmod +x /rootfs/sbin/wrapperd
|
||||||
|
RUN ln /rootfs/sbin/init /rootfs/sbin/dashboard
|
||||||
|
RUN chmod +x /rootfs/sbin/dashboard
|
||||||
# NB: We run the cleanup step before creating extra directories, files, and
|
# NB: We run the cleanup step before creating extra directories, files, and
|
||||||
# symlinks to avoid accidentally cleaning them up.
|
# symlinks to avoid accidentally cleaning them up.
|
||||||
COPY ./hack/cleanup.sh /toolchain/bin/cleanup.sh
|
COPY ./hack/cleanup.sh /toolchain/bin/cleanup.sh
|
||||||
@ -525,6 +527,8 @@ RUN ln /rootfs/sbin/init /rootfs/sbin/poweroff
|
|||||||
RUN chmod +x /rootfs/sbin/poweroff
|
RUN chmod +x /rootfs/sbin/poweroff
|
||||||
RUN ln /rootfs/sbin/init /rootfs/sbin/wrapperd
|
RUN ln /rootfs/sbin/init /rootfs/sbin/wrapperd
|
||||||
RUN chmod +x /rootfs/sbin/wrapperd
|
RUN chmod +x /rootfs/sbin/wrapperd
|
||||||
|
RUN ln /rootfs/sbin/init /rootfs/sbin/dashboard
|
||||||
|
RUN chmod +x /rootfs/sbin/dashboard
|
||||||
# NB: We run the cleanup step before creating extra directories, files, and
|
# NB: We run the cleanup step before creating extra directories, files, and
|
||||||
# symlinks to avoid accidentally cleaning them up.
|
# symlinks to avoid accidentally cleaning them up.
|
||||||
COPY ./hack/cleanup.sh /toolchain/bin/cleanup.sh
|
COPY ./hack/cleanup.sh /toolchain/bin/cleanup.sh
|
||||||
|
@ -10,7 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/siderolabs/talos/cmd/talosctl/cmd/talos/dashboard"
|
"github.com/siderolabs/talos/internal/pkg/dashboard"
|
||||||
"github.com/siderolabs/talos/pkg/machinery/client"
|
"github.com/siderolabs/talos/pkg/machinery/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ Keyboard shortcuts:
|
|||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return WithClient(func(ctx context.Context, c *client.Client) error {
|
return WithClient(func(ctx context.Context, c *client.Client) error {
|
||||||
return dashboard.Main(ctx, c, dashboardCmdFlags.interval)
|
return dashboard.Main(ctx, c, dashboardCmdFlags.interval, true)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -77,6 +77,19 @@ machine:
|
|||||||
title = "Machine Configuration"
|
title = "Machine Configuration"
|
||||||
description="""\
|
description="""\
|
||||||
Strategic merge config patches correctly support merging `.vlans` sections of the network interface.
|
Strategic merge config patches correctly support merging `.vlans` sections of the network interface.
|
||||||
|
"""
|
||||||
|
|
||||||
|
[notes.dashboard]
|
||||||
|
title = "Talos Dashboard on TTY2"
|
||||||
|
description="""\
|
||||||
|
Talos now starts a text-based UI dashboard on virtual console `/dev/tty2` and switches to it by default upon boot.
|
||||||
|
Kernel logs remain available on `/dev/tty1`.
|
||||||
|
|
||||||
|
To switch TTYs, use the `Alt+F1` through `Alt+F2` keys.
|
||||||
|
|
||||||
|
You can disable this behavior by setting the kernel parameter `talos.dashboard.disabled=1`.
|
||||||
|
|
||||||
|
This behavior is disabled by default on SBCs.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
[make_deps]
|
[make_deps]
|
||||||
|
49
internal/app/dashboard/main.go
Normal file
49
internal/app/dashboard/main.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// 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 dashboard implements dashboard functionality.
|
||||||
|
package dashboard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
|
|
||||||
|
"github.com/siderolabs/talos/internal/pkg/dashboard"
|
||||||
|
"github.com/siderolabs/talos/pkg/grpc/middleware/authz"
|
||||||
|
"github.com/siderolabs/talos/pkg/machinery/client"
|
||||||
|
"github.com/siderolabs/talos/pkg/machinery/constants"
|
||||||
|
"github.com/siderolabs/talos/pkg/machinery/role"
|
||||||
|
"github.com/siderolabs/talos/pkg/startup"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Main is the entrypoint into dashboard.
|
||||||
|
func Main() {
|
||||||
|
if err := dashboardMain(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func dashboardMain() error {
|
||||||
|
startup.LimitMaxProcs(constants.DashboardMaxProcs)
|
||||||
|
|
||||||
|
md := metadata.Pairs()
|
||||||
|
authz.SetMetadata(md, role.MakeSet(role.Admin))
|
||||||
|
adminCtx := metadata.NewOutgoingContext(context.Background(), md)
|
||||||
|
|
||||||
|
c, err := client.New(adminCtx,
|
||||||
|
client.WithUnixSocket(constants.MachineSocketPath),
|
||||||
|
client.WithGRPCDialOptions(grpc.WithTransportCredentials(insecure.NewCredentials())),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error connecting to the machine service: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dashboard.Main(adminCtx, c, 5*time.Second, false)
|
||||||
|
}
|
@ -24,6 +24,7 @@ import (
|
|||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
|
|
||||||
"github.com/siderolabs/talos/internal/app/apid"
|
"github.com/siderolabs/talos/internal/app/apid"
|
||||||
|
"github.com/siderolabs/talos/internal/app/dashboard"
|
||||||
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime"
|
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime"
|
||||||
v1alpha1runtime "github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1"
|
v1alpha1runtime "github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1"
|
||||||
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader"
|
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader"
|
||||||
@ -316,6 +317,10 @@ func main() {
|
|||||||
case "/sbin/wrapperd":
|
case "/sbin/wrapperd":
|
||||||
wrapperd.Main()
|
wrapperd.Main()
|
||||||
|
|
||||||
|
return
|
||||||
|
case "/sbin/dashboard":
|
||||||
|
dashboard.Main()
|
||||||
|
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
@ -94,6 +94,7 @@ func (b *BananaPiM64) Install(disk string) (err error) {
|
|||||||
func (b *BananaPiM64) KernelArgs() procfs.Parameters {
|
func (b *BananaPiM64) KernelArgs() procfs.Parameters {
|
||||||
return []*procfs.Parameter{
|
return []*procfs.Parameter{
|
||||||
procfs.NewParameter("console").Append("tty0").Append("ttyS0,115200"),
|
procfs.NewParameter("console").Append("tty0").Append("ttyS0,115200"),
|
||||||
|
procfs.NewParameter(constants.KernelParamDashboardDisabled).Append("1"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,6 +80,7 @@ func (b JetsonNano) KernelArgs() procfs.Parameters {
|
|||||||
// trying to kexec. Seems the drivers state is not reset properly.
|
// trying to kexec. Seems the drivers state is not reset properly.
|
||||||
// disabling kexec until we have further knowledge on this
|
// disabling kexec until we have further knowledge on this
|
||||||
procfs.NewParameter("sysctl.kernel.kexec_load_disabled").Append("1"),
|
procfs.NewParameter("sysctl.kernel.kexec_load_disabled").Append("1"),
|
||||||
|
procfs.NewParameter(constants.KernelParamDashboardDisabled).Append("1"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,6 +91,7 @@ func (l *LibretechAllH3CCH5) Install(disk string) (err error) {
|
|||||||
func (l *LibretechAllH3CCH5) KernelArgs() procfs.Parameters {
|
func (l *LibretechAllH3CCH5) KernelArgs() procfs.Parameters {
|
||||||
return []*procfs.Parameter{
|
return []*procfs.Parameter{
|
||||||
procfs.NewParameter("console").Append("tty0").Append("ttyS0,115200"),
|
procfs.NewParameter("console").Append("tty0").Append("ttyS0,115200"),
|
||||||
|
procfs.NewParameter(constants.KernelParamDashboardDisabled).Append("1"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,6 +80,7 @@ func (n *NanoPiR4S) KernelArgs() procfs.Parameters {
|
|||||||
return []*procfs.Parameter{
|
return []*procfs.Parameter{
|
||||||
procfs.NewParameter("console").Append("tty0").Append("ttyS2,1500000n8"),
|
procfs.NewParameter("console").Append("tty0").Append("ttyS2,1500000n8"),
|
||||||
procfs.NewParameter("sysctl.kernel.kexec_load_disabled").Append("1"),
|
procfs.NewParameter("sysctl.kernel.kexec_load_disabled").Append("1"),
|
||||||
|
procfs.NewParameter(constants.KernelParamDashboardDisabled).Append("1"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,6 +92,7 @@ func (b Pine64) Install(disk string) (err error) {
|
|||||||
func (b Pine64) KernelArgs() procfs.Parameters {
|
func (b Pine64) KernelArgs() procfs.Parameters {
|
||||||
return []*procfs.Parameter{
|
return []*procfs.Parameter{
|
||||||
procfs.NewParameter("console").Append("tty0").Append("ttyS0,115200"),
|
procfs.NewParameter("console").Append("tty0").Append("ttyS0,115200"),
|
||||||
|
procfs.NewParameter(constants.KernelParamDashboardDisabled).Append("1"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,6 +91,7 @@ func (r *Rock64) Install(disk string) (err error) {
|
|||||||
func (r *Rock64) KernelArgs() procfs.Parameters {
|
func (r *Rock64) KernelArgs() procfs.Parameters {
|
||||||
return []*procfs.Parameter{
|
return []*procfs.Parameter{
|
||||||
procfs.NewParameter("console").Append("tty0").Append("ttyS2,115200n8"),
|
procfs.NewParameter("console").Append("tty0").Append("ttyS2,115200n8"),
|
||||||
|
procfs.NewParameter(constants.KernelParamDashboardDisabled).Append("1"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,6 +87,7 @@ func (r *Rockpi4) KernelArgs() procfs.Parameters {
|
|||||||
return []*procfs.Parameter{
|
return []*procfs.Parameter{
|
||||||
procfs.NewParameter("console").Append("tty0").Append("ttyS2,1500000n8"),
|
procfs.NewParameter("console").Append("tty0").Append("ttyS2,1500000n8"),
|
||||||
procfs.NewParameter("sysctl.kernel.kexec_load_disabled").Append("1"),
|
procfs.NewParameter("sysctl.kernel.kexec_load_disabled").Append("1"),
|
||||||
|
procfs.NewParameter(constants.KernelParamDashboardDisabled).Append("1"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,6 +86,7 @@ func (r *Rockpi4c) KernelArgs() procfs.Parameters {
|
|||||||
return []*procfs.Parameter{
|
return []*procfs.Parameter{
|
||||||
procfs.NewParameter("console").Append("tty0").Append("ttyS2,1500000n8"),
|
procfs.NewParameter("console").Append("tty0").Append("ttyS2,1500000n8"),
|
||||||
procfs.NewParameter("sysctl.kernel.kexec_load_disabled").Append("1"),
|
procfs.NewParameter("sysctl.kernel.kexec_load_disabled").Append("1"),
|
||||||
|
procfs.NewParameter(constants.KernelParamDashboardDisabled).Append("1"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,6 +49,7 @@ func (r *RPi4) KernelArgs() procfs.Parameters {
|
|||||||
return []*procfs.Parameter{
|
return []*procfs.Parameter{
|
||||||
procfs.NewParameter("console").Append("tty0").Append("ttyAMA0,115200"),
|
procfs.NewParameter("console").Append("tty0").Append("ttyAMA0,115200"),
|
||||||
procfs.NewParameter("sysctl.kernel.kexec_load_disabled").Append("1"),
|
procfs.NewParameter("sysctl.kernel.kexec_load_disabled").Append("1"),
|
||||||
|
procfs.NewParameter(constants.KernelParamDashboardDisabled).Append("1"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,6 +49,7 @@ func (r *RPiGeneric) KernelArgs() procfs.Parameters {
|
|||||||
return []*procfs.Parameter{
|
return []*procfs.Parameter{
|
||||||
procfs.NewParameter("console").Append("tty0").Append("ttyAMA0,115200"),
|
procfs.NewParameter("console").Append("tty0").Append("ttyAMA0,115200"),
|
||||||
procfs.NewParameter("sysctl.kernel.kexec_load_disabled").Append("1"),
|
procfs.NewParameter("sysctl.kernel.kexec_load_disabled").Append("1"),
|
||||||
|
procfs.NewParameter(constants.KernelParamDashboardDisabled).Append("1"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,9 @@
|
|||||||
package v1alpha1
|
package v1alpha1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/siderolabs/go-pointer"
|
||||||
"github.com/siderolabs/go-procfs/procfs"
|
"github.com/siderolabs/go-procfs/procfs"
|
||||||
|
|
||||||
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime"
|
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime"
|
||||||
@ -106,6 +109,15 @@ func (*Sequencer) Initialize(r runtime.Runtime) []runtime.Phase {
|
|||||||
"earlyServices",
|
"earlyServices",
|
||||||
StartUdevd,
|
StartUdevd,
|
||||||
StartMachined,
|
StartMachined,
|
||||||
|
).AppendWithDeferredCheck(
|
||||||
|
func() bool {
|
||||||
|
disabledStr := procfs.ProcCmdline().Get(constants.KernelParamDashboardDisabled).First()
|
||||||
|
disabled, _ := strconv.ParseBool(pointer.SafeDeref(disabledStr)) //nolint:errcheck
|
||||||
|
|
||||||
|
return !disabled
|
||||||
|
},
|
||||||
|
"dashboard",
|
||||||
|
StartDashboard,
|
||||||
).AppendWithDeferredCheck(
|
).AppendWithDeferredCheck(
|
||||||
func() bool {
|
func() bool {
|
||||||
return r.State().Machine().Installed()
|
return r.State().Machine().Installed()
|
||||||
|
@ -54,6 +54,7 @@ import (
|
|||||||
"github.com/siderolabs/talos/internal/app/machined/pkg/system/events"
|
"github.com/siderolabs/talos/internal/app/machined/pkg/system/events"
|
||||||
"github.com/siderolabs/talos/internal/app/machined/pkg/system/services"
|
"github.com/siderolabs/talos/internal/app/machined/pkg/system/services"
|
||||||
"github.com/siderolabs/talos/internal/app/maintenance"
|
"github.com/siderolabs/talos/internal/app/maintenance"
|
||||||
|
"github.com/siderolabs/talos/internal/pkg/console"
|
||||||
"github.com/siderolabs/talos/internal/pkg/cri"
|
"github.com/siderolabs/talos/internal/pkg/cri"
|
||||||
"github.com/siderolabs/talos/internal/pkg/etcd"
|
"github.com/siderolabs/talos/internal/pkg/etcd"
|
||||||
"github.com/siderolabs/talos/internal/pkg/install"
|
"github.com/siderolabs/talos/internal/pkg/install"
|
||||||
@ -203,6 +204,15 @@ func CreateSystemCgroups(seq runtime.Sequence, data interface{}) (runtime.TaskEx
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: constants.CgroupDashboard,
|
||||||
|
resources: &cgroupsv2.Resources{
|
||||||
|
Memory: &cgroupsv2.Memory{
|
||||||
|
Min: pointer.To[int64](constants.CgroupDashboardReservedMemory),
|
||||||
|
Low: pointer.To[int64](constants.CgroupDashboardLowMemory),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range groups {
|
for _, c := range groups {
|
||||||
@ -792,6 +802,19 @@ func StartMachined(_ runtime.Sequence, _ interface{}) (runtime.TaskExecutionFunc
|
|||||||
}, "startMachined"
|
}, "startMachined"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StartDashboard represents the task to start dashboard.
|
||||||
|
func StartDashboard(_ runtime.Sequence, _ interface{}) (runtime.TaskExecutionFunc, string) {
|
||||||
|
return func(_ context.Context, _ *log.Logger, r runtime.Runtime) error {
|
||||||
|
ttyNumber := constants.DashboardTTY
|
||||||
|
|
||||||
|
system.Services(r).LoadAndStart(&services.Dashboard{
|
||||||
|
TTYNumber: ttyNumber,
|
||||||
|
})
|
||||||
|
|
||||||
|
return console.Switch(ttyNumber)
|
||||||
|
}, "startDashboard"
|
||||||
|
}
|
||||||
|
|
||||||
// StartUdevd represents the task to start udevd.
|
// StartUdevd represents the task to start udevd.
|
||||||
func StartUdevd(seq runtime.Sequence, data interface{}) (runtime.TaskExecutionFunc, string) {
|
func StartUdevd(seq runtime.Sequence, data interface{}) (runtime.TaskExecutionFunc, string) {
|
||||||
return func(ctx context.Context, logger *log.Logger, r runtime.Runtime) (err error) {
|
return func(ctx context.Context, logger *log.Logger, r runtime.Runtime) (err error) {
|
||||||
|
@ -76,16 +76,25 @@ func (p *processRunner) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *processRunner) build() (cmd *exec.Cmd, logCloser io.Closer, err error) {
|
type commandWrapper struct {
|
||||||
|
cmd *exec.Cmd
|
||||||
|
afterStart func()
|
||||||
|
afterTermination func() error
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:gocyclo
|
||||||
|
func (p *processRunner) build() (commandWrapper, error) {
|
||||||
args := []string{
|
args := []string{
|
||||||
fmt.Sprintf("-name=%s", p.args.ID),
|
fmt.Sprintf("-name=%s", p.args.ID),
|
||||||
fmt.Sprintf("-dropped-caps=%s", strings.Join(p.opts.DroppedCapabilities, ",")),
|
fmt.Sprintf("-dropped-caps=%s", strings.Join(p.opts.DroppedCapabilities, ",")),
|
||||||
fmt.Sprintf("-cgroup-path=%s", p.opts.CgroupPath),
|
fmt.Sprintf("-cgroup-path=%s", p.opts.CgroupPath),
|
||||||
fmt.Sprintf("-oom-score=%d", p.opts.OOMScoreAdj),
|
fmt.Sprintf("-oom-score=%d", p.opts.OOMScoreAdj),
|
||||||
|
fmt.Sprintf("-uid=%d", p.opts.UID),
|
||||||
}
|
}
|
||||||
|
|
||||||
args = append(args, p.args.ProcessArgs...)
|
args = append(args, p.args.ProcessArgs...)
|
||||||
|
|
||||||
cmd = exec.Command("/sbin/wrapperd", args...)
|
cmd := exec.Command("/sbin/wrapperd", args...)
|
||||||
|
|
||||||
// Set the environment for the service.
|
// Set the environment for the service.
|
||||||
cmd.Env = append([]string{fmt.Sprintf("PATH=%s", constants.PATH)}, p.opts.Env...)
|
cmd.Env = append([]string{fmt.Sprintf("PATH=%s", constants.PATH)}, p.opts.Env...)
|
||||||
@ -93,9 +102,7 @@ func (p *processRunner) build() (cmd *exec.Cmd, logCloser io.Closer, err error)
|
|||||||
// Setup logging.
|
// Setup logging.
|
||||||
w, err := p.opts.LoggingManager.ServiceLog(p.args.ID).Writer()
|
w, err := p.opts.LoggingManager.ServiceLog(p.args.ID).Writer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("service log handler: %w", err)
|
return commandWrapper{}, fmt.Errorf("service log handler: %w", err)
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var writer io.Writer
|
var writer io.Writer
|
||||||
@ -105,20 +112,92 @@ func (p *processRunner) build() (cmd *exec.Cmd, logCloser io.Closer, err error)
|
|||||||
writer = w
|
writer = w
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Stdout = writer
|
// close the writer if we exit early due to an error
|
||||||
cmd.Stderr = writer
|
closeWriter := true
|
||||||
|
|
||||||
return cmd, w, nil
|
defer func() {
|
||||||
|
if closeWriter {
|
||||||
|
w.Close() //nolint:errcheck
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var afterStartFuncs []func()
|
||||||
|
|
||||||
|
if p.opts.StdinFile != "" {
|
||||||
|
stdin, err := os.Open(p.opts.StdinFile)
|
||||||
|
if err != nil {
|
||||||
|
return commandWrapper{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Stdin = stdin
|
||||||
|
|
||||||
|
afterStartFuncs = append(afterStartFuncs, func() {
|
||||||
|
stdin.Close() //nolint:errcheck
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.opts.StdoutFile != "" {
|
||||||
|
stdout, err := os.OpenFile(p.opts.StdoutFile, os.O_WRONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
return commandWrapper{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Stdout = stdout
|
||||||
|
|
||||||
|
afterStartFuncs = append(afterStartFuncs, func() {
|
||||||
|
stdout.Close() //nolint:errcheck
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
cmd.Stdout = writer
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.opts.StderrFile != "" {
|
||||||
|
stderr, err := os.OpenFile(p.opts.StderrFile, os.O_WRONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
return commandWrapper{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Stderr = stderr
|
||||||
|
|
||||||
|
afterStartFuncs = append(afterStartFuncs, func() {
|
||||||
|
stderr.Close() //nolint:errcheck
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
cmd.Stderr = writer
|
||||||
|
}
|
||||||
|
|
||||||
|
ctty, cttySet := p.opts.Ctty.Get()
|
||||||
|
if cttySet {
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
|
Setsid: true,
|
||||||
|
Setctty: true,
|
||||||
|
Ctty: ctty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closeWriter = false
|
||||||
|
|
||||||
|
return commandWrapper{
|
||||||
|
cmd: cmd,
|
||||||
|
afterStart: func() {
|
||||||
|
for _, f := range afterStartFuncs {
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
afterTermination: func() error {
|
||||||
|
return w.Close()
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gocyclo
|
//nolint:gocyclo
|
||||||
func (p *processRunner) run(eventSink events.Recorder) error {
|
func (p *processRunner) run(eventSink events.Recorder) error {
|
||||||
cmd, logCloser, err := p.build()
|
cmdWrapper, err := p.build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error building command: %w", err)
|
return fmt.Errorf("error building command: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer logCloser.Close() //nolint:errcheck
|
defer cmdWrapper.afterTermination() //nolint:errcheck
|
||||||
|
|
||||||
notifyCh := make(chan reaper.ProcessInfo, 8)
|
notifyCh := make(chan reaper.ProcessInfo, 8)
|
||||||
|
|
||||||
@ -127,16 +206,20 @@ func (p *processRunner) run(eventSink events.Recorder) error {
|
|||||||
defer reaper.Stop(notifyCh)
|
defer reaper.Stop(notifyCh)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = cmd.Start(); err != nil {
|
err = cmdWrapper.cmd.Start()
|
||||||
|
|
||||||
|
cmdWrapper.afterStart()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("error starting process: %w", err)
|
return fmt.Errorf("error starting process: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
eventSink(events.StateRunning, "Process %s started with PID %d", p, cmd.Process.Pid)
|
eventSink(events.StateRunning, "Process %s started with PID %d", p, cmdWrapper.cmd.Process.Pid)
|
||||||
|
|
||||||
waitCh := make(chan error)
|
waitCh := make(chan error)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
waitCh <- reaper.WaitWrapper(usingReaper, notifyCh, cmd)
|
waitCh <- reaper.WaitWrapper(usingReaper, notifyCh, cmdWrapper.cmd)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
@ -148,7 +231,7 @@ func (p *processRunner) run(eventSink events.Recorder) error {
|
|||||||
eventSink(events.StateStopping, "Sending SIGTERM to %s", p)
|
eventSink(events.StateStopping, "Sending SIGTERM to %s", p)
|
||||||
|
|
||||||
//nolint:errcheck
|
//nolint:errcheck
|
||||||
_ = cmd.Process.Signal(syscall.SIGTERM)
|
_ = cmdWrapper.cmd.Process.Signal(syscall.SIGTERM)
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
@ -160,13 +243,13 @@ func (p *processRunner) run(eventSink events.Recorder) error {
|
|||||||
eventSink(events.StateStopping, "Sending SIGKILL to %s", p)
|
eventSink(events.StateStopping, "Sending SIGKILL to %s", p)
|
||||||
|
|
||||||
//nolint:errcheck
|
//nolint:errcheck
|
||||||
_ = cmd.Process.Signal(syscall.SIGKILL)
|
_ = cmdWrapper.cmd.Process.Signal(syscall.SIGKILL)
|
||||||
}
|
}
|
||||||
|
|
||||||
// wait for process to terminate
|
// wait for process to terminate
|
||||||
<-waitCh
|
<-waitCh
|
||||||
|
|
||||||
return logCloser.Close()
|
return cmdWrapper.afterTermination()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *processRunner) String() string {
|
func (p *processRunner) String() string {
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/containerd/containerd/oci"
|
"github.com/containerd/containerd/oci"
|
||||||
"github.com/opencontainers/runtime-spec/specs-go"
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"github.com/siderolabs/gen/maps"
|
"github.com/siderolabs/gen/maps"
|
||||||
|
"github.com/siderolabs/gen/optional"
|
||||||
|
|
||||||
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime"
|
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime"
|
||||||
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/logging"
|
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/logging"
|
||||||
@ -66,6 +67,16 @@ type Options struct {
|
|||||||
OverrideSeccompProfile func(*specs.LinuxSeccomp)
|
OverrideSeccompProfile func(*specs.LinuxSeccomp)
|
||||||
// DroppedCapabilities is the list of capabilities to drop.
|
// DroppedCapabilities is the list of capabilities to drop.
|
||||||
DroppedCapabilities []string
|
DroppedCapabilities []string
|
||||||
|
// StdinFile is the path to the file to use as stdin.
|
||||||
|
StdinFile string
|
||||||
|
// StdoutFile is the path to the file to use as stdout.
|
||||||
|
StdoutFile string
|
||||||
|
// StderrFile is the path to the file to use as stderr.
|
||||||
|
StderrFile string
|
||||||
|
// Ctty is the controlling tty.
|
||||||
|
Ctty optional.Optional[int]
|
||||||
|
// UID is the user id of the process.
|
||||||
|
UID uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
// Option is the functional option func.
|
// Option is the functional option func.
|
||||||
@ -174,3 +185,38 @@ func WithDroppedCapabilities(caps map[string]struct{}) Option {
|
|||||||
args.DroppedCapabilities = maps.Keys(caps)
|
args.DroppedCapabilities = maps.Keys(caps)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithStdinFile sets the path to the file to use as stdin.
|
||||||
|
func WithStdinFile(path string) Option {
|
||||||
|
return func(args *Options) {
|
||||||
|
args.StdinFile = path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithStdoutFile sets the path to the file to use as stdout.
|
||||||
|
func WithStdoutFile(path string) Option {
|
||||||
|
return func(args *Options) {
|
||||||
|
args.StdoutFile = path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithStderrFile sets the path to the file to use as stderr.
|
||||||
|
func WithStderrFile(path string) Option {
|
||||||
|
return func(args *Options) {
|
||||||
|
args.StdoutFile = path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithCtty sets the controlling tty.
|
||||||
|
func WithCtty(ctty int) Option {
|
||||||
|
return func(args *Options) {
|
||||||
|
args.Ctty = optional.Some(ctty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithUID sets the user id of the process.
|
||||||
|
func WithUID(uid uint32) Option {
|
||||||
|
return func(args *Options) {
|
||||||
|
args.UID = uid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
73
internal/app/machined/pkg/system/services/dashboard.go
Normal file
73
internal/app/machined/pkg/system/services/dashboard.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
// 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/.
|
||||||
|
|
||||||
|
//nolint:golint,dupl
|
||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime"
|
||||||
|
"github.com/siderolabs/talos/internal/app/machined/pkg/system/events"
|
||||||
|
"github.com/siderolabs/talos/internal/app/machined/pkg/system/runner"
|
||||||
|
"github.com/siderolabs/talos/internal/app/machined/pkg/system/runner/process"
|
||||||
|
"github.com/siderolabs/talos/internal/app/machined/pkg/system/runner/restart"
|
||||||
|
"github.com/siderolabs/talos/internal/pkg/capability"
|
||||||
|
"github.com/siderolabs/talos/pkg/conditions"
|
||||||
|
"github.com/siderolabs/talos/pkg/machinery/constants"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Dashboard implements the Service interface. It serves as the concrete type with
|
||||||
|
// the required methods.
|
||||||
|
type Dashboard struct {
|
||||||
|
TTYNumber int
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID implements the Service interface.
|
||||||
|
func (d *Dashboard) ID(_ runtime.Runtime) string {
|
||||||
|
return "dashboard"
|
||||||
|
}
|
||||||
|
|
||||||
|
// PreFunc implements the Service interface.
|
||||||
|
func (d *Dashboard) PreFunc(_ context.Context, _ runtime.Runtime) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostFunc implements the Service interface.
|
||||||
|
func (d *Dashboard) PostFunc(_ runtime.Runtime, _ events.ServiceState) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Condition implements the Service interface.
|
||||||
|
func (d *Dashboard) Condition(_ runtime.Runtime) conditions.Condition {
|
||||||
|
return conditions.WaitForFileToExist(constants.MachineSocketPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DependsOn implements the Service interface.
|
||||||
|
func (d *Dashboard) DependsOn(_ runtime.Runtime) []string {
|
||||||
|
return []string{machinedServiceID}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Runner implements the Service interface.
|
||||||
|
func (d *Dashboard) Runner(r runtime.Runtime) (runner.Runner, error) {
|
||||||
|
tty := fmt.Sprintf("/dev/tty%d", d.TTYNumber)
|
||||||
|
|
||||||
|
return restart.New(process.NewRunner(false, &runner.Args{
|
||||||
|
ID: d.ID(r),
|
||||||
|
ProcessArgs: []string{"/sbin/dashboard"},
|
||||||
|
},
|
||||||
|
runner.WithLoggingManager(r.Logging()),
|
||||||
|
runner.WithEnv([]string{"TERM=linux"}),
|
||||||
|
runner.WithStdinFile(tty),
|
||||||
|
runner.WithStdoutFile(tty),
|
||||||
|
runner.WithCtty(1),
|
||||||
|
runner.WithOOMScoreAdj(-400),
|
||||||
|
runner.WithDroppedCapabilities(capability.AllCapabilitiesSetLowercase()),
|
||||||
|
runner.WithCgroupPath(constants.CgroupDashboard),
|
||||||
|
runner.WithUID(constants.DashboardUserID),
|
||||||
|
),
|
||||||
|
restart.WithType(restart.Forever),
|
||||||
|
), nil
|
||||||
|
}
|
@ -27,6 +27,8 @@ import (
|
|||||||
"github.com/siderolabs/talos/pkg/machinery/role"
|
"github.com/siderolabs/talos/pkg/machinery/role"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const machinedServiceID = "machined"
|
||||||
|
|
||||||
var rules = map[string]role.Set{
|
var rules = map[string]role.Set{
|
||||||
"/cluster.ClusterService/HealthCheck": role.MakeSet(role.Admin, role.Reader),
|
"/cluster.ClusterService/HealthCheck": role.MakeSet(role.Admin, role.Reader),
|
||||||
|
|
||||||
@ -132,12 +134,12 @@ func (s *machinedService) Main(ctx context.Context, r runtime.Runtime, logWriter
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ensure socket dir exists
|
// ensure socket dir exists
|
||||||
if err := os.MkdirAll(filepath.Dir(constants.MachineSocketPath), 0o750); err != nil {
|
if err := os.MkdirAll(filepath.Dir(constants.MachineSocketPath), 0o770); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// set the final leaf to be world-executable to make apid connect to the socket
|
// set the final leaf to be world-executable to make apid connect to the socket
|
||||||
if err := os.Chmod(filepath.Dir(constants.MachineSocketPath), 0o751); err != nil {
|
if err := os.Chmod(filepath.Dir(constants.MachineSocketPath), 0o771); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,7 +178,7 @@ type Machined struct {
|
|||||||
|
|
||||||
// ID implements the Service interface.
|
// ID implements the Service interface.
|
||||||
func (m *Machined) ID(r runtime.Runtime) string {
|
func (m *Machined) ID(r runtime.Runtime) string {
|
||||||
return "machined"
|
return machinedServiceID
|
||||||
}
|
}
|
||||||
|
|
||||||
// PreFunc implements the Service interface.
|
// PreFunc implements the Service interface.
|
||||||
@ -203,7 +205,7 @@ func (m *Machined) DependsOn(r runtime.Runtime) []string {
|
|||||||
func (m *Machined) Runner(r runtime.Runtime) (runner.Runner, error) {
|
func (m *Machined) Runner(r runtime.Runtime) (runner.Runner, error) {
|
||||||
svc := &machinedService{m.Controller}
|
svc := &machinedService{m.Controller}
|
||||||
|
|
||||||
return goroutine.NewRunner(r, "machined", svc.Main, runner.WithLoggingManager(r.Logging())), nil
|
return goroutine.NewRunner(r, machinedServiceID, svc.Main, runner.WithLoggingManager(r.Logging())), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HealthFunc implements the HealthcheckedService interface.
|
// HealthFunc implements the HealthcheckedService interface.
|
||||||
|
@ -28,15 +28,18 @@ var (
|
|||||||
droppedCaps string
|
droppedCaps string
|
||||||
cgroupPath string
|
cgroupPath string
|
||||||
oomScore int
|
oomScore int
|
||||||
|
uid int
|
||||||
)
|
)
|
||||||
|
|
||||||
// Main is the entrypoint into /sbin/wrapperd.
|
// Main is the entrypoint into /sbin/wrapperd.
|
||||||
// nolint: gocyclo
|
//
|
||||||
|
//nolint:gocyclo
|
||||||
func Main() {
|
func Main() {
|
||||||
flag.StringVar(&name, "name", "", "process name")
|
flag.StringVar(&name, "name", "", "process name")
|
||||||
flag.StringVar(&droppedCaps, "dropped-caps", "", "comma-separated list of capabilities to drop")
|
flag.StringVar(&droppedCaps, "dropped-caps", "", "comma-separated list of capabilities to drop")
|
||||||
flag.StringVar(&cgroupPath, "cgroup-path", "", "cgroup path to use")
|
flag.StringVar(&cgroupPath, "cgroup-path", "", "cgroup path to use")
|
||||||
flag.IntVar(&oomScore, "oom-score", 0, "oom score to set")
|
flag.IntVar(&oomScore, "oom-score", 0, "oom score to set")
|
||||||
|
flag.IntVar(&uid, "uid", 0, "uid to set for the process")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
currentPid := os.Getpid()
|
currentPid := os.Getpid()
|
||||||
@ -78,9 +81,9 @@ func Main() {
|
|||||||
} else if droppedCaps != "" {
|
} else if droppedCaps != "" {
|
||||||
caps := strings.Split(droppedCaps, ",")
|
caps := strings.Split(droppedCaps, ",")
|
||||||
dropCaps := slices.Map(caps, func(c string) cap.Value {
|
dropCaps := slices.Map(caps, func(c string) cap.Value {
|
||||||
capability, err := cap.FromName(c)
|
capability, capErr := cap.FromName(c)
|
||||||
if err != nil {
|
if capErr != nil {
|
||||||
log.Fatalf("failed to parse capability: %v", err)
|
log.Fatalf("failed to parse capability: %v", capErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
return capability
|
return capability
|
||||||
@ -88,15 +91,22 @@ func Main() {
|
|||||||
|
|
||||||
// drop capabilities
|
// drop capabilities
|
||||||
iab := cap.IABGetProc()
|
iab := cap.IABGetProc()
|
||||||
if err := iab.SetVector(cap.Bound, true, dropCaps...); err != nil {
|
if err = iab.SetVector(cap.Bound, true, dropCaps...); err != nil {
|
||||||
log.Fatalf("failed to set capabilities: %v", err)
|
log.Fatalf("failed to set capabilities: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := iab.SetProc(); err != nil {
|
if err = iab.SetProc(); err != nil {
|
||||||
log.Fatalf("failed to apply capabilities: %v", err)
|
log.Fatalf("failed to apply capabilities: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if uid > 0 {
|
||||||
|
err = unix.Setuid(uid)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to setuid: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := unix.Exec(flag.Args()[0], flag.Args()[0:], os.Environ()); err != nil {
|
if err := unix.Exec(flag.Args()[0], flag.Args()[0:], os.Environ()); err != nil {
|
||||||
log.Fatalf("failed to exec: %v", err)
|
log.Fatalf("failed to exec: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -8,25 +8,47 @@ package capability
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/siderolabs/gen/maps"
|
||||||
"kernel.org/pub/linux/libs/security/libcap/cap"
|
"kernel.org/pub/linux/libs/security/libcap/cap"
|
||||||
|
|
||||||
"github.com/siderolabs/talos/pkg/machinery/constants"
|
"github.com/siderolabs/talos/pkg/machinery/constants"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AllGrantableCapabilities returns list of capabilities that can be granted to the container based on
|
// AllCapabilitiesSet returns the set of all available capabilities.
|
||||||
// process bounding capabilities.
|
//
|
||||||
func AllGrantableCapabilities() []string {
|
// Returned capabilities are in UPPERCASE.
|
||||||
capabilities := []string{}
|
func AllCapabilitiesSet() map[string]struct{} {
|
||||||
|
capabilities := make(map[string]struct{})
|
||||||
|
|
||||||
for v := cap.Value(0); v < cap.MaxBits(); v++ {
|
for v := cap.Value(0); v < cap.MaxBits(); v++ {
|
||||||
if set, _ := cap.GetBound(v); set { //nolint:errcheck
|
if set, _ := cap.GetBound(v); set { //nolint:errcheck
|
||||||
if _, ok := constants.DefaultDroppedCapabilities[v.String()]; ok {
|
capabilities[strings.ToUpper(v.String())] = struct{}{}
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
capabilities = append(capabilities, strings.ToUpper(v.String()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return capabilities
|
return capabilities
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AllCapabilitiesSetLowercase returns the set of all available capabilities.
|
||||||
|
//
|
||||||
|
// Returned capabilities are in lowercase.
|
||||||
|
func AllCapabilitiesSetLowercase() map[string]struct{} {
|
||||||
|
return maps.Map(AllCapabilitiesSet(),
|
||||||
|
func(capability string, _ struct{}) (string, struct{}) {
|
||||||
|
return strings.ToLower(capability), struct{}{}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllGrantableCapabilities returns list of capabilities that can be granted to the container based on
|
||||||
|
// process bounding capabilities.
|
||||||
|
//
|
||||||
|
// Returned capabilities are in UPPERCASE.
|
||||||
|
func AllGrantableCapabilities() []string {
|
||||||
|
allCapabilities := AllCapabilitiesSet()
|
||||||
|
|
||||||
|
for dropped := range constants.DefaultDroppedCapabilities {
|
||||||
|
delete(allCapabilities, strings.ToUpper(dropped))
|
||||||
|
}
|
||||||
|
|
||||||
|
return maps.Keys(allCapabilities)
|
||||||
|
}
|
||||||
|
66
internal/pkg/console/console.go
Normal file
66
internal/pkg/console/console.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
// 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 console contains console-related functionality.
|
||||||
|
package console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// vtActivate activates the specified virtual terminal.
|
||||||
|
// See VT_ACTIVATE:
|
||||||
|
// https://man7.org/linux/man-pages/man2/ioctl_console.2.html
|
||||||
|
// https://github.com/torvalds/linux/blob/v6.2/include/uapi/linux/vt.h#L42
|
||||||
|
vtActivate uintptr = 0x5606
|
||||||
|
|
||||||
|
// tioclSetKmsgRedirect redirects kernel messages to the specified tty.
|
||||||
|
// See TIOCL_SETKMSGREDIRECT:
|
||||||
|
// https://github.com/torvalds/linux/blob/v6.2/include/uapi/linux/tiocl.h#L33
|
||||||
|
// https://github.com/torvalds/linux/blob/v6.2/drivers/tty/vt/vt.c#L3242
|
||||||
|
tioclSetKmsgRedirect byte = 11
|
||||||
|
)
|
||||||
|
|
||||||
|
// Switch switches the active console to the specified tty.
|
||||||
|
func Switch(ttyNumber int) error {
|
||||||
|
// redirect the kernel logs to tty1 instead of the currently used one,
|
||||||
|
// so that dashboard on tty2 does not get flooded with kernel logs
|
||||||
|
if err := redirectKernelLogs(1); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// we need a valid fd to any tty because ioctl requires it
|
||||||
|
tty0, err := os.OpenFile("/dev/tty0", os.O_RDWR, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer tty0.Close() //nolint: errcheck
|
||||||
|
|
||||||
|
if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, tty0.Fd(), vtActivate, uintptr(ttyNumber)); errno != 0 {
|
||||||
|
return fmt.Errorf("failed to activate console: %w", errno)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// redirectKernelLogs redirects kernel logs to the specified tty.
|
||||||
|
func redirectKernelLogs(ttyNumber int) error {
|
||||||
|
tty, err := os.OpenFile(fmt.Sprintf("/dev/tty%d", ttyNumber), os.O_RDWR, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
args := [2]byte{tioclSetKmsgRedirect, byte(ttyNumber)}
|
||||||
|
|
||||||
|
if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, tty.Fd(), syscall.TIOCLINUX, uintptr(unsafe.Pointer(&args))); errno != 0 {
|
||||||
|
return fmt.Errorf("failed to set redirect for kmsg: %w", errno)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tty.Close()
|
||||||
|
}
|
@ -11,7 +11,7 @@ import (
|
|||||||
ui "github.com/gizak/termui/v3"
|
ui "github.com/gizak/termui/v3"
|
||||||
"github.com/gizak/termui/v3/widgets"
|
"github.com/gizak/termui/v3/widgets"
|
||||||
|
|
||||||
"github.com/siderolabs/talos/cmd/talosctl/cmd/talos/dashboard/data"
|
"github.com/siderolabs/talos/internal/pkg/dashboard/data"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SystemGauges quickly show CPU/mem load.
|
// SystemGauges quickly show CPU/mem load.
|
@ -8,7 +8,7 @@ import (
|
|||||||
"github.com/gizak/termui/v3/widgets"
|
"github.com/gizak/termui/v3/widgets"
|
||||||
"github.com/siderolabs/gen/slices"
|
"github.com/siderolabs/gen/slices"
|
||||||
|
|
||||||
"github.com/siderolabs/talos/cmd/talosctl/cmd/talos/dashboard/data"
|
"github.com/siderolabs/talos/internal/pkg/dashboard/data"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BaseGraph represents the widget with some usage graph.
|
// BaseGraph represents the widget with some usage graph.
|
@ -10,7 +10,7 @@ import (
|
|||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
"github.com/gizak/termui/v3/widgets"
|
"github.com/gizak/termui/v3/widgets"
|
||||||
|
|
||||||
"github.com/siderolabs/talos/cmd/talosctl/cmd/talos/dashboard/data"
|
"github.com/siderolabs/talos/internal/pkg/dashboard/data"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LoadAvgInfo represents the widget with load average info.
|
// LoadAvgInfo represents the widget with load average info.
|
@ -10,7 +10,7 @@ import (
|
|||||||
"github.com/gizak/termui/v3/widgets"
|
"github.com/gizak/termui/v3/widgets"
|
||||||
"github.com/siderolabs/gen/maps"
|
"github.com/siderolabs/gen/maps"
|
||||||
|
|
||||||
"github.com/siderolabs/talos/cmd/talosctl/cmd/talos/dashboard/data"
|
"github.com/siderolabs/talos/internal/pkg/dashboard/data"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NodeTabs represents the bottom bar with node list.
|
// NodeTabs represents the bottom bar with node list.
|
@ -8,7 +8,7 @@ import (
|
|||||||
ui "github.com/gizak/termui/v3"
|
ui "github.com/gizak/termui/v3"
|
||||||
"github.com/gizak/termui/v3/widgets"
|
"github.com/gizak/termui/v3/widgets"
|
||||||
|
|
||||||
"github.com/siderolabs/talos/cmd/talosctl/cmd/talos/dashboard/data"
|
"github.com/siderolabs/talos/internal/pkg/dashboard/data"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BaseSparklineGroup represents the widget with some sparklines.
|
// BaseSparklineGroup represents the widget with some sparklines.
|
@ -15,7 +15,7 @@ import (
|
|||||||
ui "github.com/gizak/termui/v3"
|
ui "github.com/gizak/termui/v3"
|
||||||
"github.com/gizak/termui/v3/widgets"
|
"github.com/gizak/termui/v3/widgets"
|
||||||
|
|
||||||
"github.com/siderolabs/talos/cmd/talosctl/cmd/talos/dashboard/data"
|
"github.com/siderolabs/talos/internal/pkg/dashboard/data"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProcessTable represents the widget with process info.
|
// ProcessTable represents the widget with process info.
|
@ -7,8 +7,8 @@ package components_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/siderolabs/talos/cmd/talosctl/cmd/talos/dashboard/components"
|
"github.com/siderolabs/talos/internal/pkg/dashboard/components"
|
||||||
"github.com/siderolabs/talos/cmd/talosctl/cmd/talos/dashboard/data"
|
"github.com/siderolabs/talos/internal/pkg/dashboard/data"
|
||||||
"github.com/siderolabs/talos/pkg/machinery/api/machine"
|
"github.com/siderolabs/talos/pkg/machinery/api/machine"
|
||||||
)
|
)
|
||||||
|
|
@ -10,7 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/gizak/termui/v3/widgets"
|
"github.com/gizak/termui/v3/widgets"
|
||||||
|
|
||||||
"github.com/siderolabs/talos/cmd/talosctl/cmd/talos/dashboard/data"
|
"github.com/siderolabs/talos/internal/pkg/dashboard/data"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TopLine represents the top bar with host info.
|
// TopLine represents the top bar with host info.
|
@ -12,8 +12,8 @@ import (
|
|||||||
"github.com/siderolabs/talos/pkg/machinery/client"
|
"github.com/siderolabs/talos/pkg/machinery/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Main is the entrypoint into talosctl dashboard command.
|
// Main is the entrypoint into the dashboard.
|
||||||
func Main(ctx context.Context, c *client.Client, interval time.Duration) error {
|
func Main(ctx context.Context, c *client.Client, interval time.Duration, allowExitKeys bool) error {
|
||||||
ui := &UI{}
|
ui := &UI{}
|
||||||
|
|
||||||
source := &APISource{
|
source := &APISource{
|
||||||
@ -24,5 +24,5 @@ func Main(ctx context.Context, c *client.Client, interval time.Duration) error {
|
|||||||
dataCh := source.Run(ctx)
|
dataCh := source.Run(ctx)
|
||||||
defer source.Stop()
|
defer source.Stop()
|
||||||
|
|
||||||
return ui.Main(ctx, dataCh)
|
return ui.Main(ctx, dataCh, allowExitKeys)
|
||||||
}
|
}
|
@ -12,7 +12,7 @@ import (
|
|||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
"google.golang.org/protobuf/types/known/emptypb"
|
"google.golang.org/protobuf/types/known/emptypb"
|
||||||
|
|
||||||
"github.com/siderolabs/talos/cmd/talosctl/cmd/talos/dashboard/data"
|
"github.com/siderolabs/talos/internal/pkg/dashboard/data"
|
||||||
"github.com/siderolabs/talos/pkg/machinery/client"
|
"github.com/siderolabs/talos/pkg/machinery/client"
|
||||||
)
|
)
|
||||||
|
|
@ -9,8 +9,8 @@ import (
|
|||||||
|
|
||||||
ui "github.com/gizak/termui/v3"
|
ui "github.com/gizak/termui/v3"
|
||||||
|
|
||||||
"github.com/siderolabs/talos/cmd/talosctl/cmd/talos/dashboard/components"
|
"github.com/siderolabs/talos/internal/pkg/dashboard/components"
|
||||||
"github.com/siderolabs/talos/cmd/talosctl/cmd/talos/dashboard/data"
|
"github.com/siderolabs/talos/internal/pkg/dashboard/data"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DataWidget is a widget which consumes Data to draw itself.
|
// DataWidget is a widget which consumes Data to draw itself.
|
||||||
@ -49,7 +49,7 @@ type UI struct {
|
|||||||
// Main is the UI entrypoint.
|
// Main is the UI entrypoint.
|
||||||
//
|
//
|
||||||
//nolint:gocyclo
|
//nolint:gocyclo
|
||||||
func (u *UI) Main(ctx context.Context, dataCh <-chan *data.Data) error {
|
func (u *UI) Main(ctx context.Context, dataCh <-chan *data.Data, allowExitKeys bool) error {
|
||||||
if err := ui.Init(); err != nil {
|
if err := ui.Init(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -131,7 +131,9 @@ func (u *UI) Main(ctx context.Context, dataCh <-chan *data.Data) error {
|
|||||||
case e := <-uiEvents:
|
case e := <-uiEvents:
|
||||||
switch e.ID {
|
switch e.ID {
|
||||||
case "q", "<C-c>":
|
case "q", "<C-c>":
|
||||||
|
if allowExitKeys {
|
||||||
return nil
|
return nil
|
||||||
|
}
|
||||||
case "<Resize>":
|
case "<Resize>":
|
||||||
payload := e.Payload.(ui.Resize) //nolint:errcheck,forcetypeassert
|
payload := e.Payload.(ui.Resize) //nolint:errcheck,forcetypeassert
|
||||||
|
|
@ -181,6 +181,10 @@ func RunInstallerContainer(disk, platform, ref string, cfg config.Provider, opts
|
|||||||
args = append(args, "--extra-kernel-arg", fmt.Sprintf("%s=%s", constants.KernelParamEquinixMetalEvents, *c))
|
args = append(args, "--extra-kernel-arg", fmt.Sprintf("%s=%s", constants.KernelParamEquinixMetalEvents, *c))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c := procfs.ProcCmdline().Get(constants.KernelParamDashboardDisabled).First(); c != nil {
|
||||||
|
args = append(args, "--extra-kernel-arg", fmt.Sprintf("%s=%s", constants.KernelParamDashboardDisabled, *c))
|
||||||
|
}
|
||||||
|
|
||||||
specOpts := []oci.SpecOpts{
|
specOpts := []oci.SpecOpts{
|
||||||
oci.WithImageConfig(img),
|
oci.WithImageConfig(img),
|
||||||
oci.WithProcessArgs(args...),
|
oci.WithProcessArgs(args...),
|
||||||
|
@ -49,6 +49,9 @@ const (
|
|||||||
// cgroups version to use (default is cgroupsv2, setting this kernel arg to '0' forces cgroupsv1).
|
// cgroups version to use (default is cgroupsv2, setting this kernel arg to '0' forces cgroupsv1).
|
||||||
KernelParamCGroups = "talos.unified_cgroup_hierarchy"
|
KernelParamCGroups = "talos.unified_cgroup_hierarchy"
|
||||||
|
|
||||||
|
// KernelParamDashboardDisabled is the kernel parameter name for disabling the dashboard.
|
||||||
|
KernelParamDashboardDisabled = "talos.dashboard.disabled"
|
||||||
|
|
||||||
// BoardNone indicates that the install is not for a specific board.
|
// BoardNone indicates that the install is not for a specific board.
|
||||||
BoardNone = "none"
|
BoardNone = "none"
|
||||||
|
|
||||||
@ -422,6 +425,10 @@ const (
|
|||||||
// ApidUserID is the user ID for apid.
|
// ApidUserID is the user ID for apid.
|
||||||
ApidUserID = 50
|
ApidUserID = 50
|
||||||
|
|
||||||
|
// DashboardUserID is the user ID for dashboard.
|
||||||
|
// We use the same user ID as apid so that the dashboard can write to the machined unix socket.
|
||||||
|
DashboardUserID = ApidUserID
|
||||||
|
|
||||||
// TrustdPort is the port for the trustd service.
|
// TrustdPort is the port for the trustd service.
|
||||||
TrustdPort = 50001
|
TrustdPort = 50001
|
||||||
|
|
||||||
@ -546,6 +553,9 @@ const (
|
|||||||
// CgroupExtensions is the cgroup name for system extension processes.
|
// CgroupExtensions is the cgroup name for system extension processes.
|
||||||
CgroupExtensions = CgroupSystem + "/extensions"
|
CgroupExtensions = CgroupSystem + "/extensions"
|
||||||
|
|
||||||
|
// CgroupDashboard is the cgroup name for dashboard process.
|
||||||
|
CgroupDashboard = CgroupSystem + "/dashboard"
|
||||||
|
|
||||||
// CgroupPodRuntime is the cgroup name for kubernetes containerd runtime processes.
|
// CgroupPodRuntime is the cgroup name for kubernetes containerd runtime processes.
|
||||||
CgroupPodRuntime = "/podruntime/runtime"
|
CgroupPodRuntime = "/podruntime/runtime"
|
||||||
|
|
||||||
@ -558,6 +568,12 @@ const (
|
|||||||
// CgroupKubeletReservedMemory is the hard memory protection for the kubelet processes.
|
// CgroupKubeletReservedMemory is the hard memory protection for the kubelet processes.
|
||||||
CgroupKubeletReservedMemory = 64 * 1024 * 1024
|
CgroupKubeletReservedMemory = 64 * 1024 * 1024
|
||||||
|
|
||||||
|
// CgroupDashboardReservedMemory is the hard memory protection for the dashboard process.
|
||||||
|
CgroupDashboardReservedMemory = 85 * 1024 * 1024
|
||||||
|
|
||||||
|
// CgroupDashboardLowMemory is the low memory value for the dashboard process.
|
||||||
|
CgroupDashboardLowMemory = 100 * 1024 * 1024
|
||||||
|
|
||||||
// FlannelCNI is the string to use Tanos-managed Flannel CNI (default).
|
// FlannelCNI is the string to use Tanos-managed Flannel CNI (default).
|
||||||
FlannelCNI = "flannel"
|
FlannelCNI = "flannel"
|
||||||
|
|
||||||
@ -790,8 +806,14 @@ const (
|
|||||||
// TrustdMaxProcs is the maximum number of GOMAXPROCS for trustd.
|
// TrustdMaxProcs is the maximum number of GOMAXPROCS for trustd.
|
||||||
TrustdMaxProcs = 2
|
TrustdMaxProcs = 2
|
||||||
|
|
||||||
|
// DashboardMaxProcs is the maximum number of GOMAXPROCS for dashboard.
|
||||||
|
DashboardMaxProcs = 2
|
||||||
|
|
||||||
// APIAuthzRoleMetadataKey is the gRPC metadata key used to submit a role with os:impersonator.
|
// APIAuthzRoleMetadataKey is the gRPC metadata key used to submit a role with os:impersonator.
|
||||||
APIAuthzRoleMetadataKey = "talos-role"
|
APIAuthzRoleMetadataKey = "talos-role"
|
||||||
|
|
||||||
|
// DashboardTTY is the number of the TTY device (/dev/ttyN) for dashboard.
|
||||||
|
DashboardTTY = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
// See https://linux.die.net/man/3/klogctl
|
// See https://linux.die.net/man/3/klogctl
|
||||||
|
@ -214,3 +214,13 @@ Talos defaults to always using the unified cgroup hierarchy (`cgroupsv2`), but `
|
|||||||
can be forced with `talos.unified_cgroup_hierarchy=0`.
|
can be forced with `talos.unified_cgroup_hierarchy=0`.
|
||||||
|
|
||||||
> Note: `cgroupsv1` is deprecated and it should be used only for compatibility with workloads which don't support `cgroupsv2` yet.
|
> Note: `cgroupsv1` is deprecated and it should be used only for compatibility with workloads which don't support `cgroupsv2` yet.
|
||||||
|
|
||||||
|
#### `talos.dashboard.disabled`
|
||||||
|
|
||||||
|
By default, Talos redirects kernel logs to virtual console `/dev/tty1` and starts the dashboard on `/dev/tty2`,
|
||||||
|
then switches to the dashboard tty.
|
||||||
|
|
||||||
|
If you set `talos.dashboard.disabled=1`, this behavior will be disabled.
|
||||||
|
Kernel logs will be sent to the currently active console and the dashboard will not be started.
|
||||||
|
|
||||||
|
It is set to be `1` by default on SBCs.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user