Compare commits

...

4 commits

Author SHA1 Message Date
Victor Dyotte ea47d691b3
Merge c8fb4c17f1 into bd1866ad8a 2024-10-07 07:44:27 +08:00
kim bd1866ad8a
update go-ffmpreg to v0.3.1 (pulls in latest wazero too) (#3398) 2024-10-06 20:53:03 +00:00
tobi 02470db5f6
[chore/themes] Tweak colors on new themes (#3397) 2024-10-06 13:05:13 +02:00
vdyotte c8fb4c17f1
Feat: Add global instance CSS customization setting
Allow instance admins to add custom CSS that will affect
every page of their instance.

This is done with a new CustomCSS instance setting that
works pretty much exactly like the Users CustomCSS property.
This custom CSS is then requested for every page load.
User styles/themes take precedence over this CSS.
2024-09-25 00:26:56 -04:00
48 changed files with 396 additions and 243 deletions

View file

@ -167,3 +167,11 @@ Links to the set contact account and/or email address will appear on the footer
The selected **contact user** must be an active (not suspended) admin and/or moderator on the instance. The selected **contact user** must be an active (not suspended) admin and/or moderator on the instance.
If you're on a single-user instance and you give admin privileges to your main account, you can just fill in your own username here; you don't need to make a separate admin account just for this. If you're on a single-user instance and you give admin privileges to your main account, you can just fill in your own username here; you don't need to make a separate admin account just for this.
### Instance Custom CSS
custom CSS allows you to further customize the way your instance looks when visited through a browser.
This custom CSS will be applied to all pages of your instance. Users themes and CSS still take precedence over this customization.
See the [Custom CSS](./custom_css.md) page for some tips on writing custom CSS for your instance.

View file

@ -1550,6 +1550,10 @@ definitions:
$ref: '#/definitions/instanceV1Configuration' $ref: '#/definitions/instanceV1Configuration'
contact_account: contact_account:
$ref: '#/definitions/account' $ref: '#/definitions/account'
custom_css:
description: Custom CSS for the instance.
type: string
x-go-name: CustomCSS
debug: debug:
description: Whether or not instance is running in DEBUG mode. Omitted if false. description: Whether or not instance is running in DEBUG mode. Omitted if false.
type: boolean type: boolean
@ -1730,6 +1734,10 @@ definitions:
$ref: '#/definitions/instanceV2Configuration' $ref: '#/definitions/instanceV2Configuration'
contact: contact:
$ref: '#/definitions/instanceV2Contact' $ref: '#/definitions/instanceV2Contact'
custom_css:
description: Instance Custom Css
type: string
x-go-name: CustomCSS
debug: debug:
description: Whether or not instance is running in DEBUG mode. Omitted if false. description: Whether or not instance is running in DEBUG mode. Omitted if false.
type: boolean type: boolean

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

After

Width:  |  Height:  |  Size: 229 KiB

4
go.mod
View file

@ -12,7 +12,7 @@ require (
codeberg.org/gruf/go-debug v1.3.0 codeberg.org/gruf/go-debug v1.3.0
codeberg.org/gruf/go-errors/v2 v2.3.2 codeberg.org/gruf/go-errors/v2 v2.3.2
codeberg.org/gruf/go-fastcopy v1.1.3 codeberg.org/gruf/go-fastcopy v1.1.3
codeberg.org/gruf/go-ffmpreg v0.2.6 codeberg.org/gruf/go-ffmpreg v0.3.1
codeberg.org/gruf/go-iotools v0.0.0-20240710125620-934ae9c654cf codeberg.org/gruf/go-iotools v0.0.0-20240710125620-934ae9c654cf
codeberg.org/gruf/go-kv v1.6.5 codeberg.org/gruf/go-kv v1.6.5
codeberg.org/gruf/go-list v0.0.0-20240425093752-494db03d641f codeberg.org/gruf/go-list v0.0.0-20240425093752-494db03d641f
@ -55,7 +55,7 @@ require (
github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8 github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8
github.com/tdewolff/minify/v2 v2.20.37 github.com/tdewolff/minify/v2 v2.20.37
github.com/technologize/otel-go-contrib v1.1.1 github.com/technologize/otel-go-contrib v1.1.1
github.com/tetratelabs/wazero v1.8.0 github.com/tetratelabs/wazero v1.8.1
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
github.com/ulule/limiter/v3 v3.11.2 github.com/ulule/limiter/v3 v3.11.2
github.com/uptrace/bun v1.2.1 github.com/uptrace/bun v1.2.1

8
go.sum
View file

@ -46,8 +46,8 @@ codeberg.org/gruf/go-fastcopy v1.1.3 h1:Jo9VTQjI6KYimlw25PPc7YLA3Xm+XMQhaHwKnM7x
codeberg.org/gruf/go-fastcopy v1.1.3/go.mod h1:GDDYR0Cnb3U/AIfGM3983V/L+GN+vuwVMvrmVABo21s= codeberg.org/gruf/go-fastcopy v1.1.3/go.mod h1:GDDYR0Cnb3U/AIfGM3983V/L+GN+vuwVMvrmVABo21s=
codeberg.org/gruf/go-fastpath/v2 v2.0.0 h1:iAS9GZahFhyWEH0KLhFEJR+txx1ZhMXxYzu2q5Qo9c0= codeberg.org/gruf/go-fastpath/v2 v2.0.0 h1:iAS9GZahFhyWEH0KLhFEJR+txx1ZhMXxYzu2q5Qo9c0=
codeberg.org/gruf/go-fastpath/v2 v2.0.0/go.mod h1:3pPqu5nZjpbRrOqvLyAK7puS1OfEtQvjd6342Cwz56Q= codeberg.org/gruf/go-fastpath/v2 v2.0.0/go.mod h1:3pPqu5nZjpbRrOqvLyAK7puS1OfEtQvjd6342Cwz56Q=
codeberg.org/gruf/go-ffmpreg v0.2.6 h1:OHlTOF+62/b+VeM3Svg7praweU/NECRIsuhilZLFaO4= codeberg.org/gruf/go-ffmpreg v0.3.1 h1:5qE6sHQbLCbQ4RO7ZL4OKZBN4ViAYfDm9ExT8N0ZE7s=
codeberg.org/gruf/go-ffmpreg v0.2.6/go.mod h1:sViRI0BYK2B8PJw4BrOg7DquPD71mZjDfffRAFcDtvk= codeberg.org/gruf/go-ffmpreg v0.3.1/go.mod h1:Ar5nbt3tB2Wr0uoaqV3wDBNwAx+H+AB/mV7Kw7NlZTI=
codeberg.org/gruf/go-iotools v0.0.0-20240710125620-934ae9c654cf h1:84s/ii8N6lYlskZjHH+DG6jyia8w2mXMZlRwFn8Gs3A= codeberg.org/gruf/go-iotools v0.0.0-20240710125620-934ae9c654cf h1:84s/ii8N6lYlskZjHH+DG6jyia8w2mXMZlRwFn8Gs3A=
codeberg.org/gruf/go-iotools v0.0.0-20240710125620-934ae9c654cf/go.mod h1:zZAICsp5rY7+hxnws2V0ePrWxE0Z2Z/KXcN3p/RQCfk= codeberg.org/gruf/go-iotools v0.0.0-20240710125620-934ae9c654cf/go.mod h1:zZAICsp5rY7+hxnws2V0ePrWxE0Z2Z/KXcN3p/RQCfk=
codeberg.org/gruf/go-kv v1.6.5 h1:ttPf0NA8F79pDqBttSudPTVCZmGncumeNIxmeM9ztz0= codeberg.org/gruf/go-kv v1.6.5 h1:ttPf0NA8F79pDqBttSudPTVCZmGncumeNIxmeM9ztz0=
@ -548,8 +548,8 @@ github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
github.com/technologize/otel-go-contrib v1.1.1 h1:wZH9aSPNWZWIkEh3vfaKfMb15AJ80jJ1aVj/4GZdqIw= github.com/technologize/otel-go-contrib v1.1.1 h1:wZH9aSPNWZWIkEh3vfaKfMb15AJ80jJ1aVj/4GZdqIw=
github.com/technologize/otel-go-contrib v1.1.1/go.mod h1:dCN/wj2WyUO8aFZFdIN+6tfJHImjTML/8r2YVYAy3So= github.com/technologize/otel-go-contrib v1.1.1/go.mod h1:dCN/wj2WyUO8aFZFdIN+6tfJHImjTML/8r2YVYAy3So=
github.com/tetratelabs/wazero v1.8.0 h1:iEKu0d4c2Pd+QSRieYbnQC9yiFlMS9D+Jr0LsRmcF4g= github.com/tetratelabs/wazero v1.8.1 h1:NrcgVbWfkWvVc4UtT4LRLDf91PsOzDzefMdwhLfA550=
github.com/tetratelabs/wazero v1.8.0/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs= github.com/tetratelabs/wazero v1.8.1/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs=
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274 h1:G6Z6HvJuPjG6XfNGi/feOATzeJrfgTNJY+rGrHbA04E= github.com/tidwall/btree v0.0.0-20191029221954-400434d76274 h1:G6Z6HvJuPjG6XfNGi/feOATzeJrfgTNJY+rGrHbA04E=
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8= github.com/tidwall/btree v0.0.0-20191029221954-400434d76274/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8=
github.com/tidwall/buntdb v1.1.2 h1:noCrqQXL9EKMtcdwJcmuVKSEjqu1ua99RHHgbLTEHRo= github.com/tidwall/buntdb v1.1.2 h1:noCrqQXL9EKMtcdwJcmuVKSEjqu1ua99RHHgbLTEHRo=

View file

@ -175,6 +175,7 @@ func validateInstanceUpdate(form *apimodel.InstanceSettingsUpdateRequest) error
form.ContactEmail == nil && form.ContactEmail == nil &&
form.ShortDescription == nil && form.ShortDescription == nil &&
form.Description == nil && form.Description == nil &&
form.CustomCSS == nil &&
form.Terms == nil && form.Terms == nil &&
form.Avatar == nil && form.Avatar == nil &&
form.AvatarDescription == nil && form.AvatarDescription == nil &&

View file

@ -33,6 +33,8 @@ type InstanceSettingsUpdateRequest struct {
ShortDescription *string `form:"short_description" json:"short_description" xml:"short_description"` ShortDescription *string `form:"short_description" json:"short_description" xml:"short_description"`
// Longer description of the instance, max 5,000 chars. HTML formatting accepted. // Longer description of the instance, max 5,000 chars. HTML formatting accepted.
Description *string `form:"description" json:"description" xml:"description"` Description *string `form:"description" json:"description" xml:"description"`
// Custom CSS for the instance.
CustomCSS *string `form:"custom_css" json:"custom_css,omitempty" xml:"custom_css"`
// Terms and conditions of the instance, max 5,000 chars. HTML formatting accepted. // Terms and conditions of the instance, max 5,000 chars. HTML formatting accepted.
Terms *string `form:"terms" json:"terms" xml:"terms"` Terms *string `form:"terms" json:"terms" xml:"terms"`
// Image to use as the instance thumbnail. // Image to use as the instance thumbnail.

View file

@ -38,6 +38,8 @@ type InstanceV1 struct {
// //
// This should be displayed on the 'about' page for an instance. // This should be displayed on the 'about' page for an instance.
Description string `json:"description"` Description string `json:"description"`
// Custom CSS for the instance.
CustomCSS string `json:"custom_css,omitempty"`
// Raw (unparsed) version of description. // Raw (unparsed) version of description.
DescriptionText string `json:"description_text,omitempty"` DescriptionText string `json:"description_text,omitempty"`
// A shorter description of the instance. // A shorter description of the instance.

View file

@ -53,6 +53,8 @@ type InstanceV2 struct {
Description string `json:"description"` Description string `json:"description"`
// Raw (unparsed) version of description. // Raw (unparsed) version of description.
DescriptionText string `json:"description_text,omitempty"` DescriptionText string `json:"description_text,omitempty"`
// Instance Custom Css
CustomCSS string `json:"custom_css,omitempty"`
// Basic anonymous usage data for this instance. // Basic anonymous usage data for this instance.
Usage InstanceV2Usage `json:"usage"` Usage InstanceV2Usage `json:"usage"`
// An image used to represent this instance. // An image used to represent this instance.

View file

@ -0,0 +1,44 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package migrations
import (
"context"
"strings"
"github.com/uptrace/bun"
)
func init() {
up := func(ctx context.Context, db *bun.DB) error {
_, err := db.ExecContext(ctx, "ALTER TABLE ? ADD COLUMN ? TEXT", bun.Ident("instances"), bun.Ident("custom_css"))
if err != nil && !(strings.Contains(err.Error(), "already exists") || strings.Contains(err.Error(), "duplicate column name") || strings.Contains(err.Error(), "SQLSTATE 42701")) {
return err
}
return nil
}
down := func(ctx context.Context, db *bun.DB) error {
_, err := db.ExecContext(ctx, "ALTER TABLE ? DROP COLUMN ?", bun.Ident("instances"), bun.Ident("custom_css"))
return err
}
if err := Migrations.Register(up, down); err != nil {
panic(err)
}
}

View file

@ -34,6 +34,7 @@ type Instance struct {
ShortDescriptionText string `bun:""` // Raw text version of short description (before parsing). ShortDescriptionText string `bun:""` // Raw text version of short description (before parsing).
Description string `bun:""` // Longer description of this instance. Description string `bun:""` // Longer description of this instance.
DescriptionText string `bun:""` // Raw text version of long description (before parsing). DescriptionText string `bun:""` // Raw text version of long description (before parsing).
CustomCSS string `bun:",nullzero"` // Custom CSS for the instance.
Terms string `bun:""` // Terms and conditions of this instance. Terms string `bun:""` // Terms and conditions of this instance.
TermsText string `bun:""` // Raw text version of terms (before parsing). TermsText string `bun:""` // Raw text version of terms (before parsing).
ContactEmail string `bun:""` // Contact email address for this instance ContactEmail string `bun:""` // Contact email address for this instance

View file

@ -20,6 +20,7 @@
import ( import (
"context" "context"
"codeberg.org/gruf/go-ffmpreg/wasm"
"github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero"
) )
@ -65,6 +66,6 @@ func (r *runner) Run(ctx context.Context, cmod wazero.CompiledModule, args Args)
// Release slot back to pool on end. // Release slot back to pool on end.
defer func() { r.pool <- struct{}{} }() defer func() { r.pool <- struct{}{} }()
// Pass to main module runner. // Pass to main module runner function.
return run(ctx, cmod, args) return wasm.Run(ctx, runtime, cmod, args)
} }

View file

@ -19,20 +19,18 @@
import ( import (
"context" "context"
"io"
"os" "os"
ffmpeglib "codeberg.org/gruf/go-ffmpreg/embed/ffmpeg" ffmpeglib "codeberg.org/gruf/go-ffmpreg/embed/ffmpeg"
ffprobelib "codeberg.org/gruf/go-ffmpreg/embed/ffprobe" ffprobelib "codeberg.org/gruf/go-ffmpreg/embed/ffprobe"
"codeberg.org/gruf/go-ffmpreg/wasm"
"github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
"github.com/tetratelabs/wazero/sys"
) )
// Use all core features required by ffmpeg / ffprobe // Use all core features required by ffmpeg / ffprobe
// (these should be the same but we OR just in case). // (these should be the same but we OR just in case).
const corefeatures = ffprobelib.CoreFeatures | const corefeatures = wasm.CoreFeatures
ffmpeglib.CoreFeatures
var ( var (
// shared WASM runtime instance. // shared WASM runtime instance.
@ -47,65 +45,7 @@
// configuration options to run an instance // configuration options to run an instance
// of a compiled WebAssembly module that is // of a compiled WebAssembly module that is
// run in a typical CLI manner. // run in a typical CLI manner.
type Args struct { type Args = wasm.Args
// Optional further module configuration function.
// (e.g. to mount filesystem dir, set env vars, etc).
Config func(wazero.ModuleConfig) wazero.ModuleConfig
// Standard FDs.
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
// CLI args.
Args []string
}
// run will run the given compiled
// WebAssembly module using given args,
// using the global wazero runtime.
func run(
ctx context.Context,
cmod wazero.CompiledModule,
args Args,
) (
uint32, // exit code
error,
) {
// Prefix module name as argv0 to args.
cargs := make([]string, len(args.Args)+1)
copy(cargs[1:], args.Args)
cargs[0] = cmod.Name()
// Create base module config.
modcfg := wazero.NewModuleConfig()
modcfg = modcfg.WithArgs(cargs...)
modcfg = modcfg.WithStdin(args.Stdin)
modcfg = modcfg.WithStdout(args.Stdout)
modcfg = modcfg.WithStderr(args.Stderr)
if args.Config != nil {
// Pass through config fn.
modcfg = args.Config(modcfg)
}
// Instantiate the module from precompiled wasm module data.
mod, err := runtime.InstantiateModule(ctx, cmod, modcfg)
if mod != nil {
// Ensure closed.
_ = mod.Close(ctx)
}
// Try extract exit code.
switch err := err.(type) {
case *sys.ExitError:
return err.ExitCode(), nil
default:
return 0, err
}
}
// compileFfmpeg ensures the ffmpeg WebAssembly has been // compileFfmpeg ensures the ffmpeg WebAssembly has been
// pre-compiled into memory. If already compiled is a no-op. // pre-compiled into memory. If already compiled is a no-op.

View file

@ -227,6 +227,17 @@ func (p *Processor) InstancePatch(ctx context.Context, form *apimodel.InstanceSe
columns = append(columns, []string{"description", "description_text"}...) columns = append(columns, []string{"description", "description_text"}...)
} }
// validate & update site custom css if it's set on the form
if form.CustomCSS != nil {
customCSS := *form.CustomCSS
if err := validate.InstanceCustomCSS(customCSS); err != nil {
return nil, gtserror.NewErrorBadRequest(err, err.Error())
}
instance.CustomCSS = text.SanitizeToPlaintext(customCSS)
columns = append(columns, []string{"custom_css"}...)
}
// Validate & update site // Validate & update site
// terms if set on the form. // terms if set on the form.
if form.Terms != nil { if form.Terms != nil {

View file

@ -1523,6 +1523,7 @@ func (c *Converter) InstanceToAPIV1Instance(ctx context.Context, i *gtsmodel.Ins
Title: i.Title, Title: i.Title,
Description: i.Description, Description: i.Description,
DescriptionText: i.DescriptionText, DescriptionText: i.DescriptionText,
CustomCSS: i.CustomCSS,
ShortDescription: i.ShortDescription, ShortDescription: i.ShortDescription,
ShortDescriptionText: i.ShortDescriptionText, ShortDescriptionText: i.ShortDescriptionText,
Email: i.ContactEmail, Email: i.ContactEmail,
@ -1644,6 +1645,7 @@ func (c *Converter) InstanceToAPIV2Instance(ctx context.Context, i *gtsmodel.Ins
SourceURL: instanceSourceURL, SourceURL: instanceSourceURL,
Description: i.Description, Description: i.Description,
DescriptionText: i.DescriptionText, DescriptionText: i.DescriptionText,
CustomCSS: i.CustomCSS,
Usage: apimodel.InstanceV2Usage{}, // todo: not implemented Usage: apimodel.InstanceV2Usage{}, // todo: not implemented
Languages: config.GetInstanceLanguages().TagStrs(), Languages: config.GetInstanceLanguages().TagStrs(),
Rules: c.InstanceRulesToAPIRules(i.Rules), Rules: c.InstanceRulesToAPIRules(i.Rules),

View file

@ -189,6 +189,16 @@ func CustomCSS(customCSS string) error {
return nil return nil
} }
func InstanceCustomCSS(customCSS string) error {
maximumCustomCSSLength := config.GetAccountsCustomCSSLength()
if length := len([]rune(customCSS)); length > maximumCustomCSSLength {
return fmt.Errorf("custom_css must be less than %d characters, but submitted custom_css was %d characters", maximumCustomCSSLength, length)
}
return nil
}
// EmojiShortcode just runs the given shortcode through the regular expression // EmojiShortcode just runs the given shortcode through the regular expression
// for emoji shortcodes, to figure out whether it's a valid shortcode, ie., 2-30 characters, // for emoji shortcodes, to figure out whether it's a valid shortcode, ie., 2-30 characters,
// a-zA-Z, numbers, and underscores. // a-zA-Z, numbers, and underscores.

View file

@ -54,7 +54,7 @@ func (m *Module) aboutGETHandler(c *gin.Context) {
Template: "about.tmpl", Template: "about.tmpl",
Instance: instance, Instance: instance,
OGMeta: apiutil.OGBase(instance), OGMeta: apiutil.OGBase(instance),
Stylesheets: []string{cssAbout}, Stylesheets: []string{cssAbout, instanceCustomCSSPath},
Extra: map[string]any{ Extra: map[string]any{
"showStrap": true, "showStrap": true,
"blocklistExposed": config.GetInstanceExposeSuspendedWeb(), "blocklistExposed": config.GetInstanceExposeSuspendedWeb(),

View file

@ -127,8 +127,9 @@ func (m *Module) confirmEmailPOSTHandler(c *gin.Context) {
// Serve page informing user that their // Serve page informing user that their
// email address is now confirmed. // email address is now confirmed.
page := apiutil.WebPage{ page := apiutil.WebPage{
Template: "confirmed_email.tmpl", Template: "confirmed_email.tmpl",
Instance: instance, Instance: instance,
Stylesheets: []string{instanceCustomCSSPath},
Extra: map[string]any{ Extra: map[string]any{
"email": user.Email, "email": user.Email,
"username": user.Account.Username, "username": user.Account.Username,

View file

@ -55,3 +55,22 @@ func (m *Module) customCSSGETHandler(c *gin.Context) {
c.Header(cacheControlHeader, cacheControlNoCache) c.Header(cacheControlHeader, cacheControlNoCache)
c.Data(http.StatusOK, textCSSUTF8, []byte(customCSS)) c.Data(http.StatusOK, textCSSUTF8, []byte(customCSS))
} }
func (m *Module) instanceCustomCSSGETHandler(c *gin.Context) {
if _, err := apiutil.NegotiateAccept(c, apiutil.TextCSS); err != nil {
apiutil.WebErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
return
}
instanceV1, errWithCode := m.processor.InstanceGetV1(c.Request.Context())
if errWithCode != nil {
apiutil.WebErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
return
}
instanceCustomCSS := instanceV1.CustomCSS
c.Header(cacheControlHeader, cacheControlNoCache)
c.Data(http.StatusOK, textCSSUTF8, []byte(instanceCustomCSS))
}

View file

@ -67,7 +67,7 @@ func (m *Module) domainBlockListGETHandler(c *gin.Context) {
Template: "domain-blocklist.tmpl", Template: "domain-blocklist.tmpl",
Instance: instance, Instance: instance,
OGMeta: apiutil.OGBase(instance), OGMeta: apiutil.OGBase(instance),
Stylesheets: []string{cssFA}, Stylesheets: []string{cssFA, instanceCustomCSSPath},
Javascript: []string{jsFrontend}, Javascript: []string{jsFrontend},
Extra: map[string]any{"blocklist": domainBlocks}, Extra: map[string]any{"blocklist": domainBlocks},
} }

View file

@ -59,7 +59,7 @@ func (m *Module) indexHandler(c *gin.Context) {
Template: "index.tmpl", Template: "index.tmpl",
Instance: instance, Instance: instance,
OGMeta: apiutil.OGBase(instance), OGMeta: apiutil.OGBase(instance),
Stylesheets: []string{cssAbout, cssIndex}, Stylesheets: []string{cssAbout, cssIndex, instanceCustomCSSPath},
Extra: map[string]any{"showStrap": true}, Extra: map[string]any{"showStrap": true},
} }

View file

@ -132,7 +132,7 @@ func (m *Module) profileGETHandler(c *gin.Context) {
} }
// Prepare stylesheets for profile. // Prepare stylesheets for profile.
stylesheets := make([]string, 0, 6) stylesheets := make([]string, 0, 7)
// Basic profile stylesheets. // Basic profile stylesheets.
stylesheets = append( stylesheets = append(
@ -142,6 +142,7 @@ func (m *Module) profileGETHandler(c *gin.Context) {
cssStatus, cssStatus,
cssThread, cssThread,
cssProfile, cssProfile,
instanceCustomCSSPath,
}..., }...,
) )

View file

@ -53,6 +53,7 @@ func (m *Module) SettingsPanelHandler(c *gin.Context) {
cssProfile, // Used for rendering stub/fake profiles. cssProfile, // Used for rendering stub/fake profiles.
cssStatus, // Used for rendering stub/fake statuses. cssStatus, // Used for rendering stub/fake statuses.
cssSettings, cssSettings,
instanceCustomCSSPath,
}, },
Javascript: []string{jsSettings}, Javascript: []string{jsSettings},
} }

View file

@ -126,9 +126,10 @@ func (m *Module) signupPOSTHandler(c *gin.Context) {
// Serve a page informing the // Serve a page informing the
// user that they've signed up. // user that they've signed up.
page := apiutil.WebPage{ page := apiutil.WebPage{
Template: "signed-up.tmpl", Template: "signed-up.tmpl",
Instance: instance, Instance: instance,
OGMeta: apiutil.OGBase(instance), Stylesheets: []string{instanceCustomCSSPath},
OGMeta: apiutil.OGBase(instance),
Extra: map[string]any{ Extra: map[string]any{
"email": user.UnconfirmedEmail, "email": user.UnconfirmedEmail,
"username": user.Account.Username, "username": user.Account.Username,

View file

@ -59,7 +59,7 @@ func (m *Module) tagGETHandler(c *gin.Context) {
Template: "tag.tmpl", Template: "tag.tmpl",
Instance: instance, Instance: instance,
OGMeta: apiutil.OGBase(instance), OGMeta: apiutil.OGBase(instance),
Stylesheets: []string{cssFA, cssThread, cssTag}, Stylesheets: []string{cssFA, cssThread, cssTag, instanceCustomCSSPath},
Extra: map[string]any{"tagName": tagName}, Extra: map[string]any{"tagName": tagName},
} }

View file

@ -115,7 +115,7 @@ func (m *Module) threadGETHandler(c *gin.Context) {
} }
// Prepare stylesheets for thread. // Prepare stylesheets for thread.
stylesheets := make([]string, 0, 5) stylesheets := make([]string, 0, 6)
// Basic thread stylesheets. // Basic thread stylesheets.
stylesheets = append( stylesheets = append(
@ -131,6 +131,7 @@ func (m *Module) threadGETHandler(c *gin.Context) {
if theme := targetAccount.Theme; theme != "" { if theme := targetAccount.Theme; theme != "" {
stylesheets = append( stylesheets = append(
stylesheets, stylesheets,
instanceCustomCSSPath,
themesPathPrefix+"/"+theme, themesPathPrefix+"/"+theme,
) )
} }

View file

@ -36,20 +36,21 @@
) )
const ( const (
confirmEmailPath = "/" + uris.ConfirmEmailPath confirmEmailPath = "/" + uris.ConfirmEmailPath
profileGroupPath = "/@:username" profileGroupPath = "/@:username"
statusPath = "/statuses/:" + apiutil.WebStatusIDKey // leave out the '/@:username' prefix as this will be served within the profile group statusPath = "/statuses/:" + apiutil.WebStatusIDKey // leave out the '/@:username' prefix as this will be served within the profile group
tagsPath = "/tags/:" + apiutil.TagNameKey tagsPath = "/tags/:" + apiutil.TagNameKey
customCSSPath = profileGroupPath + "/custom.css" customCSSPath = profileGroupPath + "/custom.css"
rssFeedPath = profileGroupPath + "/feed.rss" instanceCustomCSSPath = "/custom.css"
assetsPathPrefix = "/assets" rssFeedPath = profileGroupPath + "/feed.rss"
distPathPrefix = assetsPathPrefix + "/dist" assetsPathPrefix = "/assets"
themesPathPrefix = assetsPathPrefix + "/themes" distPathPrefix = assetsPathPrefix + "/dist"
settingsPathPrefix = "/settings" themesPathPrefix = assetsPathPrefix + "/themes"
settingsPanelGlob = settingsPathPrefix + "/*panel" settingsPathPrefix = "/settings"
userPanelPath = settingsPathPrefix + "/user" settingsPanelGlob = settingsPathPrefix + "/*panel"
adminPanelPath = settingsPathPrefix + "/admin" userPanelPath = settingsPathPrefix + "/user"
signupPath = "/signup" adminPanelPath = settingsPathPrefix + "/admin"
signupPath = "/signup"
cacheControlHeader = "Cache-Control" // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control cacheControlHeader = "Cache-Control" // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
cacheControlNoCache = "no-cache" // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#response_directives cacheControlNoCache = "no-cache" // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#response_directives
@ -114,6 +115,7 @@ func (m *Module) Route(r *router.Router, mi ...gin.HandlerFunc) {
r.AttachHandler(http.MethodGet, settingsPathPrefix, m.SettingsPanelHandler) r.AttachHandler(http.MethodGet, settingsPathPrefix, m.SettingsPanelHandler)
r.AttachHandler(http.MethodGet, settingsPanelGlob, m.SettingsPanelHandler) r.AttachHandler(http.MethodGet, settingsPanelGlob, m.SettingsPanelHandler)
r.AttachHandler(http.MethodGet, customCSSPath, m.customCSSGETHandler) r.AttachHandler(http.MethodGet, customCSSPath, m.customCSSGETHandler)
r.AttachHandler(http.MethodGet, instanceCustomCSSPath, m.instanceCustomCSSGETHandler)
r.AttachHandler(http.MethodGet, rssFeedPath, m.rssFeedGETHandler) r.AttachHandler(http.MethodGet, rssFeedPath, m.rssFeedGETHandler)
r.AttachHandler(http.MethodGet, confirmEmailPath, m.confirmEmailGETHandler) r.AttachHandler(http.MethodGet, confirmEmailPath, m.confirmEmailGETHandler)
r.AttachHandler(http.MethodPost, confirmEmailPath, m.confirmEmailPOSTHandler) r.AttachHandler(http.MethodPost, confirmEmailPath, m.confirmEmailPOSTHandler)

Binary file not shown.

View file

@ -3,8 +3,6 @@
import ( import (
_ "embed" _ "embed"
"os" "os"
"github.com/tetratelabs/wazero/api"
) )
func init() { func init() {
@ -23,14 +21,5 @@ func init() {
} }
} }
// CoreFeatures is the WebAssembly Core specification
// features this embedded binary was compiled with.
const CoreFeatures = api.CoreFeatureSIMD |
api.CoreFeatureBulkMemoryOperations |
api.CoreFeatureNonTrappingFloatToIntConversion |
api.CoreFeatureMutableGlobal |
api.CoreFeatureReferenceTypes |
api.CoreFeatureSignExtensionOps
//go:embed ffmpeg.wasm //go:embed ffmpeg.wasm
var B []byte var B []byte

Binary file not shown.

View file

@ -3,8 +3,6 @@
import ( import (
_ "embed" _ "embed"
"os" "os"
"github.com/tetratelabs/wazero/api"
) )
func init() { func init() {
@ -23,14 +21,5 @@ func init() {
} }
} }
// CoreFeatures is the WebAssembly Core specification
// features this embedded binary was compiled with.
const CoreFeatures = api.CoreFeatureSIMD |
api.CoreFeatureBulkMemoryOperations |
api.CoreFeatureNonTrappingFloatToIntConversion |
api.CoreFeatureMutableGlobal |
api.CoreFeatureReferenceTypes |
api.CoreFeatureSignExtensionOps
//go:embed ffprobe.wasm //go:embed ffprobe.wasm
var B []byte var B []byte

89
vendor/codeberg.org/gruf/go-ffmpreg/wasm/instance.go generated vendored Normal file
View file

@ -0,0 +1,89 @@
package wasm
import (
"context"
"io"
"unsafe"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/sys"
)
// CoreFeatures are the WebAssembly Core specification
// features our embedded binaries are compiled with.
const CoreFeatures = api.CoreFeatureSIMD |
api.CoreFeatureBulkMemoryOperations |
api.CoreFeatureNonTrappingFloatToIntConversion |
api.CoreFeatureMutableGlobal |
api.CoreFeatureReferenceTypes |
api.CoreFeatureSignExtensionOps
// Args encompasses a common set of
// configuration options often passed to
// wazero.Runtime on module instantiation.
type Args struct {
// Optional further module configuration function.
// (e.g. to mount filesystem dir, set env vars, etc).
Config func(wazero.ModuleConfig) wazero.ModuleConfig
// Standard FDs.
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
// CLI args.
Args []string
}
// Run will run given compiled WebAssembly module
// within the given runtime, with given arguments.
// Returns the exit code, or error.
func Run(
ctx context.Context,
runtime wazero.Runtime,
module wazero.CompiledModule,
args Args,
) (rc uint32, err error) {
// Prefix arguments with module name.
cargs := make([]string, len(args.Args)+1)
cargs[0] = module.Name()
copy(cargs[1:], args.Args)
// Prepare new module configuration.
modcfg := wazero.NewModuleConfig()
modcfg = modcfg.WithArgs(cargs...)
modcfg = modcfg.WithStdin(args.Stdin)
modcfg = modcfg.WithStdout(args.Stdout)
modcfg = modcfg.WithStderr(args.Stderr)
if args.Config != nil {
// Pass through config fn.
modcfg = args.Config(modcfg)
}
// Instantiate the module from precompiled wasm module data.
mod, err := runtime.InstantiateModule(ctx, module, modcfg)
if !isNil(mod) {
// Ensure closed.
_ = mod.Close(ctx)
}
// Try extract exit code.
switch err := err.(type) {
case *sys.ExitError:
return err.ExitCode(), nil
default:
return 0, err
}
}
// isNil will safely check if 'v' is nil without
// dealing with weird Go interface nil bullshit.
func isNil(i interface{}) bool {
type eface struct{ Type, Data unsafe.Pointer }
return (*(*eface)(unsafe.Pointer(&i))).Data == nil
}

View file

@ -35,6 +35,8 @@ type LinearMemory interface {
// Notes: // Notes:
// - To back a shared memory, Reallocate can't change the address of the // - To back a shared memory, Reallocate can't change the address of the
// backing []byte (only its length/capacity may change). // backing []byte (only its length/capacity may change).
// - Reallocate may return nil if fails to grow the LinearMemory. This
// condition may or may not be handled gracefully by the Wasm module.
Reallocate(size uint64) []byte Reallocate(size uint64) []byte
// Free the backing memory buffer. // Free the backing memory buffer.
Free() Free()

View file

@ -1,6 +1,9 @@
package descriptor package descriptor
import "math/bits" import (
"math/bits"
"slices"
)
// Table is a data structure mapping 32 bit descriptor to items. // Table is a data structure mapping 32 bit descriptor to items.
// //
@ -37,23 +40,13 @@ func (t *Table[Key, Item]) Len() (n int) {
return n return n
} }
// grow ensures that t has enough room for n items, potentially reallocating the // grow grows the table by n * 64 items.
// internal buffers if their capacity was too small to hold this many items.
func (t *Table[Key, Item]) grow(n int) { func (t *Table[Key, Item]) grow(n int) {
// Round up to a multiple of 64 since this is the smallest increment due to total := len(t.masks) + n
// using 64 bits masks. t.masks = slices.Grow(t.masks, n)[:total]
n = (n*64 + 63) / 64
if n > len(t.masks) { total = len(t.items) + n*64
masks := make([]uint64, n) t.items = slices.Grow(t.items, n*64)[:total]
copy(masks, t.masks)
items := make([]Item, n*64)
copy(items, t.items)
t.masks = masks
t.items = items
}
} }
// Insert inserts the given item to the table, returning the key that it is // Insert inserts the given item to the table, returning the key that it is
@ -78,13 +71,9 @@ func (t *Table[Key, Item]) Insert(item Item) (key Key, ok bool) {
} }
} }
// No free slot found, grow the table and retry.
offset = len(t.masks) offset = len(t.masks)
n := 2 * len(t.masks) t.grow(1)
if n == 0 {
n = 1
}
t.grow(n)
goto insert goto insert
} }
@ -109,10 +98,10 @@ func (t *Table[Key, Item]) InsertAt(item Item, key Key) bool {
if key < 0 { if key < 0 {
return false return false
} }
if diff := int(key) - t.Len(); diff > 0 { index := uint(key) / 64
if diff := int(index) - len(t.masks) + 1; diff > 0 {
t.grow(diff) t.grow(diff)
} }
index := uint(key) / 64
shift := uint(key) % 64 shift := uint(key) % 64
t.masks[index] |= 1 << shift t.masks[index] |= 1 << shift
t.items[key] = item t.items[key] = item
@ -124,7 +113,8 @@ func (t *Table[Key, Item]) Delete(key Key) {
if key < 0 { // invalid key if key < 0 { // invalid key
return return
} }
if index, shift := key/64, key%64; int(index) < len(t.masks) { if index := uint(key) / 64; int(index) < len(t.masks) {
shift := uint(key) % 64
mask := t.masks[index] mask := t.masks[index]
if (mask & (1 << shift)) != 0 { if (mask & (1 << shift)) != 0 {
var zero Item var zero Item

View file

@ -487,7 +487,7 @@ func (e *engine) setLabelAddress(op *uint64, label label, labelAddressResolution
} }
// ResolveImportedFunction implements wasm.ModuleEngine. // ResolveImportedFunction implements wasm.ModuleEngine.
func (e *moduleEngine) ResolveImportedFunction(index, indexInImportedModule wasm.Index, importedModuleEngine wasm.ModuleEngine) { func (e *moduleEngine) ResolveImportedFunction(index, descFunc, indexInImportedModule wasm.Index, importedModuleEngine wasm.ModuleEngine) {
imported := importedModuleEngine.(*moduleEngine) imported := importedModuleEngine.(*moduleEngine)
e.functions[index] = imported.functions[indexInImportedModule] e.functions[index] = imported.functions[indexInImportedModule]
} }

View file

@ -237,7 +237,7 @@ func (m *moduleEngine) putLocalMemory() {
} }
// ResolveImportedFunction implements wasm.ModuleEngine. // ResolveImportedFunction implements wasm.ModuleEngine.
func (m *moduleEngine) ResolveImportedFunction(index, indexInImportedModule wasm.Index, importedModuleEngine wasm.ModuleEngine) { func (m *moduleEngine) ResolveImportedFunction(index, descFunc, indexInImportedModule wasm.Index, importedModuleEngine wasm.ModuleEngine) {
executableOffset, moduleCtxOffset, typeIDOffset := m.parent.offsets.ImportedFunctionOffset(index) executableOffset, moduleCtxOffset, typeIDOffset := m.parent.offsets.ImportedFunctionOffset(index)
importedME := importedModuleEngine.(*moduleEngine) importedME := importedModuleEngine.(*moduleEngine)
@ -245,12 +245,12 @@ func (m *moduleEngine) ResolveImportedFunction(index, indexInImportedModule wasm
indexInImportedModule -= wasm.Index(len(importedME.importedFunctions)) indexInImportedModule -= wasm.Index(len(importedME.importedFunctions))
} else { } else {
imported := &importedME.importedFunctions[indexInImportedModule] imported := &importedME.importedFunctions[indexInImportedModule]
m.ResolveImportedFunction(index, imported.indexInModule, imported.me) m.ResolveImportedFunction(index, descFunc, imported.indexInModule, imported.me)
return // Recursively resolve the imported function. return // Recursively resolve the imported function.
} }
offset := importedME.parent.functionOffsets[indexInImportedModule] offset := importedME.parent.functionOffsets[indexInImportedModule]
typeID := getTypeIDOf(indexInImportedModule, importedME.module) typeID := m.module.TypeIDs[descFunc]
executable := &importedME.parent.executable[offset] executable := &importedME.parent.executable[offset]
// Write functionInstance. // Write functionInstance.
binary.LittleEndian.PutUint64(m.opaque[executableOffset:], uint64(uintptr(unsafe.Pointer(executable)))) binary.LittleEndian.PutUint64(m.opaque[executableOffset:], uint64(uintptr(unsafe.Pointer(executable))))
@ -261,28 +261,6 @@ func (m *moduleEngine) ResolveImportedFunction(index, indexInImportedModule wasm
m.importedFunctions[index] = importedFunction{me: importedME, indexInModule: indexInImportedModule} m.importedFunctions[index] = importedFunction{me: importedME, indexInModule: indexInImportedModule}
} }
func getTypeIDOf(funcIndex wasm.Index, m *wasm.ModuleInstance) wasm.FunctionTypeID {
source := m.Source
var typeIndex wasm.Index
if funcIndex >= source.ImportFunctionCount {
funcIndex -= source.ImportFunctionCount
typeIndex = source.FunctionSection[funcIndex]
} else {
var cnt wasm.Index
for i := range source.ImportSection {
if source.ImportSection[i].Type == wasm.ExternTypeFunc {
if cnt == funcIndex {
typeIndex = source.ImportSection[i].DescFunc
break
}
cnt++
}
}
}
return m.TypeIDs[typeIndex]
}
// ResolveImportedMemory implements wasm.ModuleEngine. // ResolveImportedMemory implements wasm.ModuleEngine.
func (m *moduleEngine) ResolveImportedMemory(importedModuleEngine wasm.ModuleEngine) { func (m *moduleEngine) ResolveImportedMemory(importedModuleEngine wasm.ModuleEngine) {
importedME := importedModuleEngine.(*moduleEngine) importedME := importedModuleEngine.(*moduleEngine)

View file

@ -5,6 +5,7 @@
import ( import (
"io/fs" "io/fs"
"os" "os"
"path"
experimentalsys "github.com/tetratelabs/wazero/experimental/sys" experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
) )
@ -34,6 +35,11 @@ func (d *dirFS) Chmod(path string, perm fs.FileMode) experimentalsys.Errno {
// Symlink implements the same method as documented on sys.FS // Symlink implements the same method as documented on sys.FS
func (d *dirFS) Symlink(oldName, link string) experimentalsys.Errno { func (d *dirFS) Symlink(oldName, link string) experimentalsys.Errno {
// Creating a symlink with an absolute path string fails with a "not permitted" error.
// https://github.com/WebAssembly/wasi-filesystem/blob/v0.2.0/path-resolution.md#symlinks
if path.IsAbs(oldName) {
return experimentalsys.EPERM
}
// Note: do not resolve `oldName` relative to this dirFS. The link result is always resolved // Note: do not resolve `oldName` relative to this dirFS. The link result is always resolved
// when dereference the `link` on its usage (e.g. readlink, read, etc). // when dereference the `link` on its usage (e.g. readlink, read, etc).
// https://github.com/bytecodealliance/cap-std/blob/v1.0.4/cap-std/src/fs/dir.rs#L404-L409 // https://github.com/bytecodealliance/cap-std/blob/v1.0.4/cap-std/src/fs/dir.rs#L404-L409

View file

@ -269,7 +269,7 @@ func (f *fsFile) Readdir(n int) (dirents []experimentalsys.Dirent, errno experim
if f.reopenDir { // re-open the directory if needed. if f.reopenDir { // re-open the directory if needed.
f.reopenDir = false f.reopenDir = false
if errno = adjustReaddirErr(f, f.closed, f.reopen()); errno != 0 { if errno = adjustReaddirErr(f, f.closed, f.rewindDir()); errno != 0 {
return return
} }
} }
@ -418,19 +418,25 @@ func seek(s io.Seeker, offset int64, whence int) (int64, experimentalsys.Errno)
return newOffset, experimentalsys.UnwrapOSError(err) return newOffset, experimentalsys.UnwrapOSError(err)
} }
// reopenFile allows re-opening a file for reasons such as applying flags or func (f *fsFile) rewindDir() experimentalsys.Errno {
// directory iteration. // Reopen the directory to rewind it.
type reopenFile func() experimentalsys.Errno file, err := f.fs.Open(f.name)
if err != nil {
// compile-time check to ensure fsFile.reopen implements reopenFile. return experimentalsys.UnwrapOSError(err)
var _ reopenFile = (*fsFile)(nil).reopen }
fi, err := file.Stat()
// reopen implements the same method as documented on reopenFile. if err != nil {
func (f *fsFile) reopen() experimentalsys.Errno { return experimentalsys.UnwrapOSError(err)
_ = f.close() }
var err error // Can't check if it's still the same file,
f.file, err = f.fs.Open(f.name) // but is it still a directory, at least?
return experimentalsys.UnwrapOSError(err) if !fi.IsDir() {
return experimentalsys.ENOTDIR
}
// Only update f on success.
_ = f.file.Close()
f.file = file
return 0
} }
// readdirFile allows masking the `Readdir` function on os.File. // readdirFile allows masking the `Readdir` function on os.File.

View file

@ -83,21 +83,12 @@ func (f *osFile) SetAppend(enable bool) (errno experimentalsys.Errno) {
f.flag &= ^experimentalsys.O_APPEND f.flag &= ^experimentalsys.O_APPEND
} }
// Clear any create or trunc flag, as we are re-opening, not re-creating. // appendMode cannot be changed later, so we have to re-open the file
f.flag &= ^(experimentalsys.O_CREAT | experimentalsys.O_TRUNC) // https://github.com/golang/go/blob/go1.23/src/os/file_unix.go#L60
// appendMode (bool) cannot be changed later, so we have to re-open the
// file. https://github.com/golang/go/blob/go1.20/src/os/file_unix.go#L60
return fileError(f, f.closed, f.reopen()) return fileError(f, f.closed, f.reopen())
} }
// compile-time check to ensure osFile.reopen implements reopenFile.
var _ reopenFile = (*osFile)(nil).reopen
func (f *osFile) reopen() (errno experimentalsys.Errno) { func (f *osFile) reopen() (errno experimentalsys.Errno) {
// Clear any create flag, as we are re-opening, not re-creating.
f.flag &= ^experimentalsys.O_CREAT
var ( var (
isDir bool isDir bool
offset int64 offset int64
@ -116,22 +107,47 @@ func (f *osFile) reopen() (errno experimentalsys.Errno) {
} }
} }
_ = f.close() // Clear any create or trunc flag, as we are re-opening, not re-creating.
f.file, errno = OpenFile(f.path, f.flag, f.perm) flag := f.flag &^ (experimentalsys.O_CREAT | experimentalsys.O_TRUNC)
file, errno := OpenFile(f.path, flag, f.perm)
if errno != 0 {
return errno
}
errno = f.checkSameFile(file)
if errno != 0 { if errno != 0 {
return errno return errno
} }
if !isDir { if !isDir {
_, err = f.file.Seek(offset, io.SeekStart) _, err = file.Seek(offset, io.SeekStart)
if err != nil { if err != nil {
_ = file.Close()
return experimentalsys.UnwrapOSError(err) return experimentalsys.UnwrapOSError(err)
} }
} }
// Only update f on success.
_ = f.file.Close()
f.file = file
f.fd = file.Fd()
return 0 return 0
} }
func (f *osFile) checkSameFile(osf *os.File) experimentalsys.Errno {
fi1, err := f.file.Stat()
if err != nil {
return experimentalsys.UnwrapOSError(err)
}
fi2, err := osf.Stat()
if err != nil {
return experimentalsys.UnwrapOSError(err)
}
if os.SameFile(fi1, fi2) {
return 0
}
return experimentalsys.ENOENT
}
// IsNonblock implements the same method as documented on fsapi.File // IsNonblock implements the same method as documented on fsapi.File
func (f *osFile) IsNonblock() bool { func (f *osFile) IsNonblock() bool {
return isNonblock(f) return isNonblock(f)

View file

@ -44,9 +44,10 @@ type ModuleEngine interface {
// ResolveImportedFunction is used to add imported functions needed to make this ModuleEngine fully functional. // ResolveImportedFunction is used to add imported functions needed to make this ModuleEngine fully functional.
// - `index` is the function Index of this imported function. // - `index` is the function Index of this imported function.
// - `descFunc` is the type Index in Module.TypeSection of this imported function. It corresponds to Import.DescFunc.
// - `indexInImportedModule` is the function Index of the imported function in the imported module. // - `indexInImportedModule` is the function Index of the imported function in the imported module.
// - `importedModuleEngine` is the ModuleEngine for the imported ModuleInstance. // - `importedModuleEngine` is the ModuleEngine for the imported ModuleInstance.
ResolveImportedFunction(index, indexInImportedModule Index, importedModuleEngine ModuleEngine) ResolveImportedFunction(index, descFunc, indexInImportedModule Index, importedModuleEngine ModuleEngine)
// ResolveImportedMemory is called when this module imports a memory from another module. // ResolveImportedMemory is called when this module imports a memory from another module.
ResolveImportedMemory(importedModuleEngine ModuleEngine) ResolveImportedMemory(importedModuleEngine ModuleEngine)

View file

@ -77,6 +77,7 @@ func NewMemoryInstance(memSec *Memory, allocator experimental.MemoryAllocator, m
if allocator != nil { if allocator != nil {
expBuffer = allocator.Allocate(capBytes, maxBytes) expBuffer = allocator.Allocate(capBytes, maxBytes)
buffer = expBuffer.Reallocate(minBytes) buffer = expBuffer.Reallocate(minBytes)
_ = buffer[:minBytes] // Bounds check that the minimum was allocated.
} else if memSec.IsShared { } else if memSec.IsShared {
// Shared memory needs a fixed buffer, so allocate with the maximum size. // Shared memory needs a fixed buffer, so allocate with the maximum size.
// //
@ -238,12 +239,15 @@ func (m *MemoryInstance) Grow(delta uint32) (result uint32, ok bool) {
return currentPages, true return currentPages, true
} }
// If exceeds the max of memory size, we push -1 according to the spec.
newPages := currentPages + delta newPages := currentPages + delta
if newPages > m.Max || int32(delta) < 0 { if newPages > m.Max || int32(delta) < 0 {
return 0, false return 0, false
} else if m.expBuffer != nil { } else if m.expBuffer != nil {
buffer := m.expBuffer.Reallocate(MemoryPagesToBytesNum(newPages)) buffer := m.expBuffer.Reallocate(MemoryPagesToBytesNum(newPages))
if buffer == nil {
// Allocator failed to grow.
return 0, false
}
if m.Shared { if m.Shared {
if unsafe.SliceData(buffer) != unsafe.SliceData(m.Buffer) { if unsafe.SliceData(buffer) != unsafe.SliceData(m.Buffer) {
panic("shared memory cannot move, this is a bug in the memory allocator") panic("shared memory cannot move, this is a bug in the memory allocator")

View file

@ -446,7 +446,7 @@ func (m *ModuleInstance) resolveImports(ctx context.Context, module *Module) (er
return return
} }
m.Engine.ResolveImportedFunction(i.IndexPerType, imported.Index, importedModule.Engine) m.Engine.ResolveImportedFunction(i.IndexPerType, i.DescFunc, imported.Index, importedModule.Engine)
case ExternTypeTable: case ExternTypeTable:
expected := i.DescTable expected := i.DescTable
importedTable := importedModule.Tables[imported.Index] importedTable := importedModule.Tables[imported.Index]

View file

@ -29,9 +29,7 @@
// # Notes // # Notes
// //
// - This is used for WebAssembly ABI emulating the POSIX `stat` system call. // - This is used for WebAssembly ABI emulating the POSIX `stat` system call.
// Fields included are required for WebAssembly ABI including wasip1 // See https://pubs.opengroup.org/onlinepubs/9699919799/functions/stat.html
// (a.k.a. wasix) and wasi-filesystem (a.k.a. wasip2). See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/stat.html
// - Fields here are required for WebAssembly ABI including wasip1 // - Fields here are required for WebAssembly ABI including wasip1
// (a.k.a. wasix) and wasi-filesystem (a.k.a. wasip2). // (a.k.a. wasix) and wasi-filesystem (a.k.a. wasip2).
// - This isn't the same as syscall.Stat_t because wazero supports Windows, // - This isn't the same as syscall.Stat_t because wazero supports Windows,

5
vendor/modules.txt vendored
View file

@ -24,10 +24,11 @@ codeberg.org/gruf/go-fastcopy
# codeberg.org/gruf/go-fastpath/v2 v2.0.0 # codeberg.org/gruf/go-fastpath/v2 v2.0.0
## explicit; go 1.14 ## explicit; go 1.14
codeberg.org/gruf/go-fastpath/v2 codeberg.org/gruf/go-fastpath/v2
# codeberg.org/gruf/go-ffmpreg v0.2.6 # codeberg.org/gruf/go-ffmpreg v0.3.1
## explicit; go 1.22.0 ## explicit; go 1.22.0
codeberg.org/gruf/go-ffmpreg/embed/ffmpeg codeberg.org/gruf/go-ffmpreg/embed/ffmpeg
codeberg.org/gruf/go-ffmpreg/embed/ffprobe codeberg.org/gruf/go-ffmpreg/embed/ffprobe
codeberg.org/gruf/go-ffmpreg/wasm
# codeberg.org/gruf/go-iotools v0.0.0-20240710125620-934ae9c654cf # codeberg.org/gruf/go-iotools v0.0.0-20240710125620-934ae9c654cf
## explicit; go 1.21 ## explicit; go 1.21
codeberg.org/gruf/go-iotools codeberg.org/gruf/go-iotools
@ -844,7 +845,7 @@ github.com/tdewolff/parse/v2/strconv
# github.com/technologize/otel-go-contrib v1.1.1 # github.com/technologize/otel-go-contrib v1.1.1
## explicit; go 1.17 ## explicit; go 1.17
github.com/technologize/otel-go-contrib/otelginmetrics github.com/technologize/otel-go-contrib/otelginmetrics
# github.com/tetratelabs/wazero v1.8.0 # github.com/tetratelabs/wazero v1.8.1
## explicit; go 1.21 ## explicit; go 1.21
github.com/tetratelabs/wazero github.com/tetratelabs/wazero
github.com/tetratelabs/wazero/api github.com/tetratelabs/wazero/api

View file

@ -30,14 +30,10 @@
/* Buttons */ /* Buttons */
--bloodshot: linear-gradient( --bloodshot: linear-gradient(
var(--blood-red) 0%, var(--bright-red),
var(--feral-orange) 2%, var(--blood-red),
var(--bright-red) 5%, var(--blood-red),
var(--blood-red) 40%, var(--bright-red)
var(--blood-red) 60%,
var(--bright-red) 95%,
var(--feral-orange) 98%,
var(--blood-red) 100%
); );
--button-bg: var(--bloodshot); --button-bg: var(--bloodshot);
--button-fg: var(--bleached-bone); --button-fg: var(--bleached-bone);

View file

@ -40,31 +40,31 @@
--orange2: var(--red); --orange2: var(--red);
/* Restyle basic colors to use Solarized */ /* Restyle basic colors to use Solarized */
--white1: var(--base02); --white1: var(--base03);
--white2: var(--base03); --white2: var(--base02);
--blue2: var(--base0); --blue2: var(--base1);
--blue3: var(--base1); --blue3: var(--base1);
/* Basic page styling (background + foreground) */ /* Basic page styling (background + foreground) */
--bg: var(--base03); --bg: var(--base02);
--bg-accent: var(--base02); --bg-accent: var(--base02);
--fg-reduced: var(--base0); --fg-reduced: var(--base0);
--fg: var(--base1); --fg: var(--base0);
/* Profile page styling */ /* Profile page styling */
--profile-bg: var(--white2); --profile-bg: var(--white2);
/* Solarize statuses */ /* Solarize statuses */
--status-bg: var(--white1); --status-bg: var(--base03);
--status-focus-bg: var(--white1); --status-focus-bg: var(--base03);
--status-info-bg: var(--white2); --status-info-bg: var(--base02);
--status-focus-info-bg: var(--white2); --status-focus-info-bg: var(--base02);
/* Used around statuses + other items */ /* Used around statuses + other items */
--boxshadow-border: 0.1rem solid var(--base01); --boxshadow-border: 0.15rem solid var(--base01);
--plyr-video-control-color: var(--fg); --plyr-video-control-color: var(--base1);
--plyr-video-control-color-hover: var(--fg-reduced); --plyr-video-control-color-hover: var(--base03);
} }
@font-face { @font-face {
@ -83,6 +83,7 @@ html, body {
/* Column headers */ /* Column headers */
.col-header { .col-header {
border: var(--boxshadow-border); border: var(--boxshadow-border);
color: var(--base1);
} }
.profile .about-user .col-header { .profile .about-user .col-header {
@ -90,20 +91,32 @@ html, body {
margin-bottom: 0; margin-bottom: 0;
} }
/* Instance title color */
.page-header a h1 {
color: var(--base1);
}
/* Header card */ /* Header card */
.profile .profile-header { .profile .profile-header {
border: var(--boxshadow-border); border: var(--boxshadow-border);
background: var(--base03);
} }
/* Fiddle around with borders on about sections */ /* Fiddle around with borders on about sections */
.profile .about-user .fields, .profile .about-user .fields,
.profile .about-user .bio, .profile .about-user .bio,
.profile .about-user .accountstats { .profile .about-user .accountstats {
background: var(--base03);
border-left: var(--boxshadow-border); border-left: var(--boxshadow-border);
border-right: var(--boxshadow-border); border-right: var(--boxshadow-border);
} }
.profile .about-user .accountstats { .profile .about-user .accountstats {
border-bottom: var(--boxshadow-border); border-bottom: var(--boxshadow-border);
color: var(--base0);
}
.profile .about-user .fields {
padding-top: 0;
} }
/* Profile fields */ /* Profile fields */
@ -117,10 +130,11 @@ html, body {
/* Status media */ /* Status media */
.status .media .media-wrapper { .status .media .media-wrapper {
border: var(--boxshadow-border); border: var(--boxshadow-border);
background: var(--base02);
} }
.status .media .media-wrapper details .unknown-attachment .placeholder { .status .media .media-wrapper details .unknown-attachment .placeholder {
color: var(--base01); color: var(--base1);
border: 0.2rem dashed var(--base01); border: 0.2rem dashed var(--base1);
} }
.status .media .media-wrapper details video.plyr-video { .status .media .media-wrapper details video.plyr-video {
background: transparent; background: transparent;
@ -128,10 +142,11 @@ html, body {
/* Status polls */ /* Status polls */
.status .text .poll { .status .text .poll {
background-color: var(--white2); background-color: var(--base03);
border: var(--boxshadow-border);
} }
.status .text .poll .poll-info { .status .text .poll .poll-info {
background-color: var(--white1); background-color: var(--base03);
} }
/* Code snippets */ /* Code snippets */
@ -153,23 +168,24 @@ button, .button,
} }
.button { .button {
color: var(--base0);
background: var(--base03);
border: var(--boxshadow-border);
}
.button:hover {
color: var(--base1); color: var(--base1);
background: var(--base02); background: var(--base02);
border: var(--boxshadow-border); border: var(--boxshadow-border);
} }
/* Ensure role badge readable */ .button:hover {
.profile .profile-header .basic-info .namerole .role.admin {
color: var(--base0); color: var(--base0);
} background: var(--base03);
/* Distinguish bot icon from background */
.profile .profile-header .basic-info .namerole .bot-username-wrapper .bot-legend-wrapper {
border: var(--boxshadow-border); border: var(--boxshadow-border);
} }
/* Ensure role badge readable */
.profile .profile-header .basic-info .namerole .role.admin {
color: var(--base1);
}
/* Distinguish bot icon */
.profile .profile-header .basic-info .namerole .bot-username-wrapper .bot-legend-wrapper {
border: var(--boxshadow-border);
color: var(--base1);
}

View file

@ -25,6 +25,7 @@ export interface InstanceV1 {
description_text?: string; description_text?: string;
short_description: string; short_description: string;
short_description_text?: string; short_description_text?: string;
custom_css: string;
email: string; email: string;
version: string; version: string;
debug?: boolean; debug?: boolean;

View file

@ -66,6 +66,10 @@ function InstanceSettingsForm({ data: instance }: InstanceSettingsFormProps) {
valueSelector: (s: InstanceV1) => s.description_text, valueSelector: (s: InstanceV1) => s.description_text,
validator: (val: string) => val.length <= descLimit ? "" : `Instance description is ${val.length} characters; must be ${descLimit} characters or less` validator: (val: string) => val.length <= descLimit ? "" : `Instance description is ${val.length} characters; must be ${descLimit} characters or less`
}), }),
customCSS: useTextInput("custom_css", {
source: instance,
valueSelector: (s: InstanceV1) => s.custom_css
}),
terms: useTextInput("terms", { terms: useTextInput("terms", {
source: instance, source: instance,
// Select "raw" text version of parsed field for editing. // Select "raw" text version of parsed field for editing.
@ -191,6 +195,15 @@ function InstanceSettingsForm({ data: instance }: InstanceSettingsFormProps) {
type="email" type="email"
/> />
<TextArea
field={form.customCSS}
label={"Custom CSS"}
className="monospace"
rows={8}
autoCapitalize="none"
spellCheck="false"
/>
<MutationButton label="Save" result={result} disabled={false} /> <MutationButton label="Save" result={result} disabled={false} />
</form> </form>
); );