diff --git a/KERNEL_README.md b/KERNEL_README.md new file mode 100644 index 000000000..c34a750d0 --- /dev/null +++ b/KERNEL_README.md @@ -0,0 +1,42 @@ +# neko fork for onkernel + +This is a public fork of the [m1k1o/neko](https://github.com/m1k1o/neko) repository. + +## Overview + +We maintain this fork to provide a customized `base` image that is used in our browser images at [onkernel/kernel-images](https://github.com/onkernel/kernel-images/tree/main/images). + +## Building + +To build images from this fork, use the build script from the repository root: + +```bash +# Build the base image +./build base + +# Build with custom repository and tag +./build base --repository your-repo/neko --tag custom-tag +``` + +The `--repository` and `--tag` options allow you to specify exactly which image you're building, making it easy to reference back to specific builds in `kernel-images`. + +## Keeping in sync with upstream + +To merge the latest changes from the upstream neko repository: + +```bash +# Run the sync script to create a new branch and merge upstream changes +./scripts/sync-upstream.sh + +# Or merge directly into your current branch +./scripts/sync-upstream.sh --no-new-branch + +# To merge a specific upstream branch +./scripts/sync-upstream.sh --upstream-branch $branch +``` + +After running the sync script: + +1. Resolve any merge conflicts +2. Test the build to ensure compatibility +3. Push the changes and create a PR for review diff --git a/server/Dockerfile b/server/Dockerfile index 356096518..1aacd0b45 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -1,4 +1,4 @@ -ARG BASE_IMAGE=golang:1.24-bullseye +ARG BASE_IMAGE=golang:1.25.0 FROM $BASE_IMAGE AS server WORKDIR /src diff --git a/server/Dockerfile.bookworm b/server/Dockerfile.bookworm index e3f67371c..552880080 100644 --- a/server/Dockerfile.bookworm +++ b/server/Dockerfile.bookworm @@ -1,4 +1,4 @@ -ARG BASE_IMAGE=golang:1.24-bookworm +ARG BASE_IMAGE=golang:1.25-bookworm FROM $BASE_IMAGE AS server WORKDIR /src diff --git a/server/cmd/serve.go b/server/cmd/serve.go index b8ff37c47..38bd357c4 100644 --- a/server/cmd/serve.go +++ b/server/cmd/serve.go @@ -3,6 +3,7 @@ package cmd import ( "os" "os/signal" + "syscall" "github.com/rs/zerolog" "github.com/rs/zerolog/log" @@ -236,7 +237,7 @@ func (c *serve) Run(cmd *cobra.Command, args []string) { c.logger.Info().Msg("neko ready") quit := make(chan os.Signal, 1) - signal.Notify(quit, os.Interrupt) + signal.Notify(quit, os.Interrupt, syscall.SIGTERM) sig := <-quit c.logger.Warn().Msgf("received %s, attempting graceful shutdown", sig) diff --git a/server/go.mod b/server/go.mod index 2b74dd7d5..2875bd792 100644 --- a/server/go.mod +++ b/server/go.mod @@ -1,8 +1,6 @@ module github.com/m1k1o/neko/server -go 1.24.0 - -toolchain go1.24.5 +go 1.25.0 require ( github.com/PaesslerAG/gval v1.2.4 @@ -12,6 +10,7 @@ require ( github.com/gorilla/websocket v1.5.3 github.com/kataras/go-events v0.0.3 github.com/mitchellh/mapstructure v1.5.0 + github.com/onkernel/kernel-images/server v0.0.0-20250912023508-e0ca1d95b771 github.com/pion/ice/v2 v2.3.38 github.com/pion/interceptor v0.1.40 github.com/pion/logging v0.2.4 diff --git a/server/go.sum b/server/go.sum index 4889a2418..bf54b1f90 100644 --- a/server/go.sum +++ b/server/go.sum @@ -56,6 +56,8 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/onkernel/kernel-images/server v0.0.0-20250912023508-e0ca1d95b771 h1:Z+Zu7ILww5/4q70h1oA+CYBCM0QCuNtAtGDUxjIyp1A= +github.com/onkernel/kernel-images/server v0.0.0-20250912023508-e0ca1d95b771/go.mod h1:8BxVW6vmlK9as98BTOYIjsVPpXObs+b4aZyZ23JZf4Y= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o= diff --git a/server/internal/plugins/manager.go b/server/internal/plugins/manager.go index 5e18abf77..df51b6f8a 100644 --- a/server/internal/plugins/manager.go +++ b/server/internal/plugins/manager.go @@ -13,6 +13,7 @@ import ( "github.com/m1k1o/neko/server/internal/config" "github.com/m1k1o/neko/server/internal/plugins/chat" "github.com/m1k1o/neko/server/internal/plugins/filetransfer" + "github.com/m1k1o/neko/server/internal/plugins/scaletozero" "github.com/m1k1o/neko/server/pkg/types" ) @@ -47,6 +48,7 @@ func New(config *config.Plugins) *ManagerCtx { // add built-in plugins manager.plugins.addPlugin(filetransfer.NewPlugin()) manager.plugins.addPlugin(chat.NewPlugin()) + manager.plugins.addPlugin(scaletozero.NewPlugin()) return manager } diff --git a/server/internal/plugins/scaletozero/config.go b/server/internal/plugins/scaletozero/config.go new file mode 100644 index 000000000..a38a9f2d7 --- /dev/null +++ b/server/internal/plugins/scaletozero/config.go @@ -0,0 +1,23 @@ +package scaletozero + +import ( + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +type Config struct { + Enabled bool +} + +func (Config) Init(cmd *cobra.Command) error { + cmd.PersistentFlags().Bool("scaletozero.enabled", false, "enable scale-to-zero") + if err := viper.BindPFlag("scaletozero.enabled", cmd.PersistentFlags().Lookup("scaletozero.enabled")); err != nil { + return err + } + + return nil +} + +func (c *Config) Set() { + c.Enabled = viper.GetBool("scaletozero.enabled") +} diff --git a/server/internal/plugins/scaletozero/manager.go b/server/internal/plugins/scaletozero/manager.go new file mode 100644 index 000000000..cc80362a9 --- /dev/null +++ b/server/internal/plugins/scaletozero/manager.go @@ -0,0 +1,81 @@ +package scaletozero + +import ( + "context" + "sync" + + "github.com/m1k1o/neko/server/pkg/types" + "github.com/onkernel/kernel-images/server/lib/scaletozero" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +func NewManager( + sessions types.SessionManager, + config *Config, +) *Manager { + logger := log.With().Str("module", "scaletozero").Logger() + + return &Manager{ + logger: logger, + config: config, + sessions: sessions, + ctrl: scaletozero.NewUnikraftCloudController(), + } +} + +type Manager struct { + logger zerolog.Logger + config *Config + sessions types.SessionManager + ctrl scaletozero.Controller + mu sync.Mutex + shutdown bool + pending int +} + +func (m *Manager) Start() error { + if !m.config.Enabled { + return nil + } + m.logger.Info().Msg("scale-to-zero plugin enabled") + + m.sessions.OnConnected(func(session types.Session) { + m.mu.Lock() + defer m.mu.Unlock() + if m.shutdown { + return + } + + m.pending++ + m.logger.Info().Msgf("connection started, disabling scale-to-zero (pending: %d)", m.pending) + m.ctrl.Disable(context.Background()) + }) + + m.sessions.OnDisconnected(func(session types.Session) { + m.mu.Lock() + defer m.mu.Unlock() + if m.shutdown { + return + } + + m.pending-- + m.logger.Info().Msgf("connection started, disabling scale-to-zero (pending: %d)", m.pending) + m.ctrl.Enable(context.Background()) + }) + + return nil +} + +func (m *Manager) Shutdown() error { + m.mu.Lock() + defer m.mu.Unlock() + m.shutdown = true + + m.logger.Info().Msgf("shutdown started, re-enabling scale-to-zero (pending: %d)", m.pending) + for i := 0; i < m.pending; i++ { + m.ctrl.Enable(context.Background()) + } + + return nil +} diff --git a/server/internal/plugins/scaletozero/plugin.go b/server/internal/plugins/scaletozero/plugin.go new file mode 100644 index 000000000..978cc7c40 --- /dev/null +++ b/server/internal/plugins/scaletozero/plugin.go @@ -0,0 +1,33 @@ +package scaletozero + +import ( + "github.com/m1k1o/neko/server/pkg/types" +) + +type Plugin struct { + config *Config + manager *Manager +} + +func NewPlugin() *Plugin { + return &Plugin{ + config: &Config{}, + } +} + +func (p *Plugin) Name() string { + return PluginName +} + +func (p *Plugin) Config() types.PluginConfig { + return p.config +} + +func (p *Plugin) Start(m types.PluginManagers) error { + p.manager = NewManager(m.SessionManager, p.config) + return p.manager.Start() +} + +func (p *Plugin) Shutdown() error { + return p.manager.Shutdown() +} diff --git a/server/internal/plugins/scaletozero/types.go b/server/internal/plugins/scaletozero/types.go new file mode 100644 index 000000000..8ceddf604 --- /dev/null +++ b/server/internal/plugins/scaletozero/types.go @@ -0,0 +1,3 @@ +package scaletozero + +const PluginName = "scaletozero"