mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-10-28 23:52:25 -05:00
[feature] configurable maximum thumbnail dimensions (#4258)
- adds configuration for thumbnail maximum dimensions with warning on exceeding recommendations - moves the media configuration vars into their own sub-struct - replaces the configuration flag funcs with simple string consts Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4258 Reviewed-by: tobi <kipvandenbos@noreply.codeberg.org> Co-authored-by: kim <grufwub@gmail.com> Co-committed-by: kim <grufwub@gmail.com>
This commit is contained in:
parent
1dc79c9586
commit
d7f967cbb5
17 changed files with 902 additions and 1099 deletions
|
|
@ -620,14 +620,14 @@ func parseClientRanges() (
|
||||||
|
|
||||||
allowIPs := config.GetHTTPClientAllowIPs()
|
allowIPs := config.GetHTTPClientAllowIPs()
|
||||||
allowRanges := make([]netip.Prefix, len(allowIPs))
|
allowRanges := make([]netip.Prefix, len(allowIPs))
|
||||||
allowFlag := config.HTTPClientAllowIPsFlag()
|
allowFlag := config.HTTPClientAllowIPsFlag
|
||||||
if err := parseF(allowIPs, allowRanges, allowFlag); err != nil {
|
if err := parseF(allowIPs, allowRanges, allowFlag); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
blockIPs := config.GetHTTPClientBlockIPs()
|
blockIPs := config.GetHTTPClientBlockIPs()
|
||||||
blockRanges := make([]netip.Prefix, len(blockIPs))
|
blockRanges := make([]netip.Prefix, len(blockIPs))
|
||||||
blockFlag := config.HTTPClientBlockIPsFlag()
|
blockFlag := config.HTTPClientBlockIPsFlag
|
||||||
if err := parseF(blockIPs, blockRanges, blockFlag); err != nil {
|
if err := parseF(blockIPs, blockRanges, blockFlag); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -629,6 +629,13 @@ media-video-size-hint: 40MiB
|
||||||
# Default: 40MiB (41943040 bytes)
|
# Default: 40MiB (41943040 bytes)
|
||||||
media-remote-max-size: 40MiB
|
media-remote-max-size: 40MiB
|
||||||
|
|
||||||
|
# Int. Max size in pixels of any one dimension of
|
||||||
|
# a thumbnail (as input media ratio is preserved).
|
||||||
|
#
|
||||||
|
# Examples: [256, 512, 1024]
|
||||||
|
# Default: 512
|
||||||
|
media-thumb-max-pixels: 512
|
||||||
|
|
||||||
# Int. Minimum amount of characters required as an image or video description.
|
# Int. Minimum amount of characters required as an image or video description.
|
||||||
# Examples: [500, 1000, 1500]
|
# Examples: [500, 1000, 1500]
|
||||||
# Default: 0 (not required)
|
# Default: 0 (not required)
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ func NewCookiePolicy() CookiePolicy {
|
||||||
case "lax":
|
case "lax":
|
||||||
sameSite = http.SameSiteLaxMode
|
sameSite = http.SameSiteLaxMode
|
||||||
default:
|
default:
|
||||||
log.Warnf(nil, "%s set to %s which is not recognized, defaulting to 'lax'", config.AdvancedCookiesSamesiteFlag(), s)
|
log.Warnf(nil, "%s set to %s which is not recognized, defaulting to 'lax'", config.AdvancedCookiesSamesiteFlag, s)
|
||||||
sameSite = http.SameSiteLaxMode
|
sameSite = http.SameSiteLaxMode
|
||||||
}
|
}
|
||||||
return CookiePolicy{
|
return CookiePolicy{
|
||||||
|
|
|
||||||
|
|
@ -113,19 +113,6 @@ type Configuration struct {
|
||||||
AccountsCustomCSSLength int `name:"accounts-custom-css-length" usage:"Maximum permitted length (characters) of custom CSS for accounts."`
|
AccountsCustomCSSLength int `name:"accounts-custom-css-length" usage:"Maximum permitted length (characters) of custom CSS for accounts."`
|
||||||
AccountsMaxProfileFields int `name:"accounts-max-profile-fields" usage:"Maximum number of profile fields allowed for each account."`
|
AccountsMaxProfileFields int `name:"accounts-max-profile-fields" usage:"Maximum number of profile fields allowed for each account."`
|
||||||
|
|
||||||
MediaDescriptionMinChars int `name:"media-description-min-chars" usage:"Min required chars for an image description"`
|
|
||||||
MediaDescriptionMaxChars int `name:"media-description-max-chars" usage:"Max permitted chars for an image description"`
|
|
||||||
MediaRemoteCacheDays int `name:"media-remote-cache-days" usage:"Number of days to locally cache media from remote instances. If set to 0, remote media will be kept indefinitely."`
|
|
||||||
MediaEmojiLocalMaxSize bytesize.Size `name:"media-emoji-local-max-size" usage:"Max size in bytes of emojis uploaded to this instance via the admin API."`
|
|
||||||
MediaEmojiRemoteMaxSize bytesize.Size `name:"media-emoji-remote-max-size" usage:"Max size in bytes of emojis to download from other instances."`
|
|
||||||
MediaImageSizeHint bytesize.Size `name:"media-image-size-hint" usage:"Size in bytes of max image size referred to on /api/v_/instance endpoints (else, local max size)"`
|
|
||||||
MediaVideoSizeHint bytesize.Size `name:"media-video-size-hint" usage:"Size in bytes of max video size referred to on /api/v_/instance endpoints (else, local max size)"`
|
|
||||||
MediaLocalMaxSize bytesize.Size `name:"media-local-max-size" usage:"Max size in bytes of media uploaded to this instance via API"`
|
|
||||||
MediaRemoteMaxSize bytesize.Size `name:"media-remote-max-size" usage:"Max size in bytes of media to download from other instances"`
|
|
||||||
MediaCleanupFrom string `name:"media-cleanup-from" usage:"Time of day from which to start running media cleanup/prune jobs. Should be in the format 'hh:mm:ss', eg., '15:04:05'."`
|
|
||||||
MediaCleanupEvery time.Duration `name:"media-cleanup-every" usage:"Period to elapse between cleanups, starting from media-cleanup-at."`
|
|
||||||
MediaFfmpegPoolSize int `name:"media-ffmpeg-pool-size" usage:"Number of instances of the embedded ffmpeg WASM binary to add to the media processing pool. 0 or less uses GOMAXPROCS."`
|
|
||||||
|
|
||||||
StorageBackend string `name:"storage-backend" usage:"Storage backend to use for media attachments"`
|
StorageBackend string `name:"storage-backend" usage:"Storage backend to use for media attachments"`
|
||||||
StorageLocalBasePath string `name:"storage-local-base-path" usage:"Full path to an already-created directory where gts should store/retrieve media files. Subfolders will be created within this dir."`
|
StorageLocalBasePath string `name:"storage-local-base-path" usage:"Full path to an already-created directory where gts should store/retrieve media files. Subfolders will be created within this dir."`
|
||||||
StorageS3Endpoint string `name:"storage-s3-endpoint" usage:"S3 Endpoint URL (e.g 'minio.example.org:9000')"`
|
StorageS3Endpoint string `name:"storage-s3-endpoint" usage:"S3 Endpoint URL (e.g 'minio.example.org:9000')"`
|
||||||
|
|
@ -181,6 +168,9 @@ type Configuration struct {
|
||||||
// HTTPClient configuration vars.
|
// HTTPClient configuration vars.
|
||||||
HTTPClient HTTPClientConfiguration `name:"http-client"`
|
HTTPClient HTTPClientConfiguration `name:"http-client"`
|
||||||
|
|
||||||
|
// Media configuration vars.
|
||||||
|
Media MediaConfiguration `name:"media"`
|
||||||
|
|
||||||
// Cache configuration vars.
|
// Cache configuration vars.
|
||||||
Cache CacheConfiguration `name:"cache"`
|
Cache CacheConfiguration `name:"cache"`
|
||||||
|
|
||||||
|
|
@ -202,6 +192,22 @@ type HTTPClientConfiguration struct {
|
||||||
InsecureOutgoing bool `name:"insecure-outgoing"`
|
InsecureOutgoing bool `name:"insecure-outgoing"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MediaConfiguration struct {
|
||||||
|
DescriptionMinChars int `name:"description-min-chars" usage:"Min required chars for an image description"`
|
||||||
|
DescriptionMaxChars int `name:"description-max-chars" usage:"Max permitted chars for an image description"`
|
||||||
|
RemoteCacheDays int `name:"remote-cache-days" usage:"Number of days to locally cache media from remote instances. If set to 0, remote media will be kept indefinitely."`
|
||||||
|
EmojiLocalMaxSize bytesize.Size `name:"emoji-local-max-size" usage:"Max size in bytes of emojis uploaded to this instance via the admin API."`
|
||||||
|
EmojiRemoteMaxSize bytesize.Size `name:"emoji-remote-max-size" usage:"Max size in bytes of emojis to download from other instances."`
|
||||||
|
ImageSizeHint bytesize.Size `name:"image-size-hint" usage:"Size in bytes of max image size referred to on /api/v_/instance endpoints (else, local max size)"`
|
||||||
|
VideoSizeHint bytesize.Size `name:"video-size-hint" usage:"Size in bytes of max video size referred to on /api/v_/instance endpoints (else, local max size)"`
|
||||||
|
LocalMaxSize bytesize.Size `name:"local-max-size" usage:"Max size in bytes of media uploaded to this instance via API"`
|
||||||
|
RemoteMaxSize bytesize.Size `name:"remote-max-size" usage:"Max size in bytes of media to download from other instances"`
|
||||||
|
CleanupFrom string `name:"cleanup-from" usage:"Time of day from which to start running media cleanup/prune jobs. Should be in the format 'hh:mm:ss', eg., '15:04:05'."`
|
||||||
|
CleanupEvery time.Duration `name:"cleanup-every" usage:"Period to elapse between cleanups, starting from media-cleanup-at."`
|
||||||
|
FfmpegPoolSize int `name:"ffmpeg-pool-size" usage:"Number of instances of the embedded ffmpeg WASM binary to add to the media processing pool. 0 or less uses GOMAXPROCS."`
|
||||||
|
ThumbMaxPixels int `name:"thumb-max-pixels" usage:"Max size in pixels of any one dimension of a thumbnail (as input media ratio is preserved)."`
|
||||||
|
}
|
||||||
|
|
||||||
type CacheConfiguration struct {
|
type CacheConfiguration struct {
|
||||||
MemoryTarget bytesize.Size `name:"memory-target"`
|
MemoryTarget bytesize.Size `name:"memory-target"`
|
||||||
AccountMemRatio float64 `name:"account-mem-ratio"`
|
AccountMemRatio float64 `name:"account-mem-ratio"`
|
||||||
|
|
|
||||||
|
|
@ -78,16 +78,19 @@ var Defaults = Configuration{
|
||||||
AccountsCustomCSSLength: 10000,
|
AccountsCustomCSSLength: 10000,
|
||||||
AccountsMaxProfileFields: 6,
|
AccountsMaxProfileFields: 6,
|
||||||
|
|
||||||
MediaDescriptionMinChars: 0,
|
Media: MediaConfiguration{
|
||||||
MediaDescriptionMaxChars: 1500,
|
DescriptionMinChars: 0,
|
||||||
MediaRemoteCacheDays: 7,
|
DescriptionMaxChars: 1500,
|
||||||
MediaLocalMaxSize: 40 * bytesize.MiB,
|
RemoteCacheDays: 7,
|
||||||
MediaRemoteMaxSize: 40 * bytesize.MiB,
|
LocalMaxSize: 40 * bytesize.MiB,
|
||||||
MediaEmojiLocalMaxSize: 50 * bytesize.KiB,
|
RemoteMaxSize: 40 * bytesize.MiB,
|
||||||
MediaEmojiRemoteMaxSize: 100 * bytesize.KiB,
|
EmojiLocalMaxSize: 50 * bytesize.KiB,
|
||||||
MediaCleanupFrom: "00:00", // Midnight.
|
EmojiRemoteMaxSize: 100 * bytesize.KiB,
|
||||||
MediaCleanupEvery: 24 * time.Hour, // 1/day.
|
CleanupFrom: "00:00", // Midnight.
|
||||||
MediaFfmpegPoolSize: 1,
|
CleanupEvery: 24 * time.Hour, // 1/day.
|
||||||
|
FfmpegPoolSize: 1,
|
||||||
|
ThumbMaxPixels: 512,
|
||||||
|
},
|
||||||
|
|
||||||
StorageBackend: "local",
|
StorageBackend: "local",
|
||||||
StorageLocalBasePath: "/gotosocial/storage",
|
StorageLocalBasePath: "/gotosocial/storage",
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import (
|
||||||
|
|
||||||
// AddAdminAccount attaches flags pertaining to admin account actions.
|
// AddAdminAccount attaches flags pertaining to admin account actions.
|
||||||
func AddAdminAccount(cmd *cobra.Command) {
|
func AddAdminAccount(cmd *cobra.Command) {
|
||||||
name := AdminAccountUsernameFlag()
|
name := AdminAccountUsernameFlag
|
||||||
usage := fieldtag("AdminAccountUsername", "usage")
|
usage := fieldtag("AdminAccountUsername", "usage")
|
||||||
cmd.Flags().String(name, "", usage) // REQUIRED
|
cmd.Flags().String(name, "", usage) // REQUIRED
|
||||||
if err := cmd.MarkFlagRequired(name); err != nil {
|
if err := cmd.MarkFlagRequired(name); err != nil {
|
||||||
|
|
@ -35,7 +35,7 @@ func AddAdminAccount(cmd *cobra.Command) {
|
||||||
|
|
||||||
// AddAdminAccountPassword attaches flags pertaining to admin account password reset.
|
// AddAdminAccountPassword attaches flags pertaining to admin account password reset.
|
||||||
func AddAdminAccountPassword(cmd *cobra.Command) {
|
func AddAdminAccountPassword(cmd *cobra.Command) {
|
||||||
name := AdminAccountPasswordFlag()
|
name := AdminAccountPasswordFlag
|
||||||
usage := fieldtag("AdminAccountPassword", "usage")
|
usage := fieldtag("AdminAccountPassword", "usage")
|
||||||
cmd.Flags().String(name, "", usage) // REQUIRED
|
cmd.Flags().String(name, "", usage) // REQUIRED
|
||||||
if err := cmd.MarkFlagRequired(name); err != nil {
|
if err := cmd.MarkFlagRequired(name); err != nil {
|
||||||
|
|
@ -49,7 +49,7 @@ func AddAdminAccountCreate(cmd *cobra.Command) {
|
||||||
AddAdminAccount(cmd)
|
AddAdminAccount(cmd)
|
||||||
AddAdminAccountPassword(cmd)
|
AddAdminAccountPassword(cmd)
|
||||||
|
|
||||||
name := AdminAccountEmailFlag()
|
name := AdminAccountEmailFlag
|
||||||
usage := fieldtag("AdminAccountEmail", "usage")
|
usage := fieldtag("AdminAccountEmail", "usage")
|
||||||
cmd.Flags().String(name, "", usage) // REQUIRED
|
cmd.Flags().String(name, "", usage) // REQUIRED
|
||||||
if err := cmd.MarkFlagRequired(name); err != nil {
|
if err := cmd.MarkFlagRequired(name); err != nil {
|
||||||
|
|
@ -59,7 +59,7 @@ func AddAdminAccountCreate(cmd *cobra.Command) {
|
||||||
|
|
||||||
// AddAdminTrans attaches flags pertaining to import/export commands.
|
// AddAdminTrans attaches flags pertaining to import/export commands.
|
||||||
func AddAdminTrans(cmd *cobra.Command) {
|
func AddAdminTrans(cmd *cobra.Command) {
|
||||||
name := AdminTransPathFlag()
|
name := AdminTransPathFlag
|
||||||
usage := fieldtag("AdminTransPath", "usage")
|
usage := fieldtag("AdminTransPath", "usage")
|
||||||
cmd.Flags().String(name, "", usage) // REQUIRED
|
cmd.Flags().String(name, "", usage) // REQUIRED
|
||||||
if err := cmd.MarkFlagRequired(name); err != nil {
|
if err := cmd.MarkFlagRequired(name); err != nil {
|
||||||
|
|
@ -69,18 +69,18 @@ func AddAdminTrans(cmd *cobra.Command) {
|
||||||
|
|
||||||
// AddAdminMediaList attaches flags pertaining to media list commands.
|
// AddAdminMediaList attaches flags pertaining to media list commands.
|
||||||
func AddAdminMediaList(cmd *cobra.Command) {
|
func AddAdminMediaList(cmd *cobra.Command) {
|
||||||
localOnly := AdminMediaListLocalOnlyFlag()
|
localOnly := AdminMediaListLocalOnlyFlag
|
||||||
localOnlyUsage := fieldtag("AdminMediaListLocalOnly", "usage")
|
localOnlyUsage := fieldtag("AdminMediaListLocalOnly", "usage")
|
||||||
cmd.Flags().Bool(localOnly, false, localOnlyUsage)
|
cmd.Flags().Bool(localOnly, false, localOnlyUsage)
|
||||||
|
|
||||||
remoteOnly := AdminMediaListRemoteOnlyFlag()
|
remoteOnly := AdminMediaListRemoteOnlyFlag
|
||||||
remoteOnlyUsage := fieldtag("AdminMediaListRemoteOnly", "usage")
|
remoteOnlyUsage := fieldtag("AdminMediaListRemoteOnly", "usage")
|
||||||
cmd.Flags().Bool(remoteOnly, false, remoteOnlyUsage)
|
cmd.Flags().Bool(remoteOnly, false, remoteOnlyUsage)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddAdminMediaPrune attaches flags pertaining to media storage prune commands.
|
// AddAdminMediaPrune attaches flags pertaining to media storage prune commands.
|
||||||
func AddAdminMediaPrune(cmd *cobra.Command) {
|
func AddAdminMediaPrune(cmd *cobra.Command) {
|
||||||
name := AdminMediaPruneDryRunFlag()
|
name := AdminMediaPruneDryRunFlag
|
||||||
usage := fieldtag("AdminMediaPruneDryRun", "usage")
|
usage := fieldtag("AdminMediaPruneDryRun", "usage")
|
||||||
cmd.Flags().Bool(name, true, usage)
|
cmd.Flags().Bool(name, true, usage)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,7 @@ func main() {
|
||||||
fprintf(output, "\t\"github.com/spf13/cast\"\n")
|
fprintf(output, "\t\"github.com/spf13/cast\"\n")
|
||||||
fprintf(output, ")\n")
|
fprintf(output, ")\n")
|
||||||
fprintf(output, "\n")
|
fprintf(output, "\n")
|
||||||
|
generateFlagConsts(output, fields)
|
||||||
generateFlagRegistering(output, fields)
|
generateFlagRegistering(output, fields)
|
||||||
generateMapMarshaler(output, fields)
|
generateMapMarshaler(output, fields)
|
||||||
generateMapUnmarshaler(output, fields)
|
generateMapUnmarshaler(output, fields)
|
||||||
|
|
@ -200,14 +201,14 @@ func loadConfigFields(pathPrefixes, flagPrefixes []string, t reflect.Type) []Con
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// func generateFlagConsts(out io.Writer, fields []ConfigField) {
|
func generateFlagConsts(out io.Writer, fields []ConfigField) {
|
||||||
// fprintf(out, "const (\n")
|
fprintf(out, "const (\n")
|
||||||
// for _, field := range fields {
|
for _, field := range fields {
|
||||||
// name := strings.ReplaceAll(field.Path, ".", "")
|
name := strings.ReplaceAll(field.Path, ".", "")
|
||||||
// fprintf(out, "\t%sFlag = \"%s\"\n", name, field.Flag())
|
fprintf(out, "\t%sFlag = \"%s\"\n", name, field.Flag())
|
||||||
// }
|
}
|
||||||
// fprintf(out, ")\n\n")
|
fprintf(out, ")\n\n")
|
||||||
// }
|
}
|
||||||
|
|
||||||
func generateFlagRegistering(out io.Writer, fields []ConfigField) {
|
func generateFlagRegistering(out io.Writer, fields []ConfigField) {
|
||||||
fprintf(out, "func (cfg *Configuration) RegisterFlags(flags *pflag.FlagSet) {\n")
|
fprintf(out, "func (cfg *Configuration) RegisterFlags(flags *pflag.FlagSet) {\n")
|
||||||
|
|
@ -461,9 +462,6 @@ func generateGetSetters(out io.Writer, fields []ConfigField) {
|
||||||
"config.", "",
|
"config.", "",
|
||||||
)
|
)
|
||||||
|
|
||||||
fprintf(out, "// %sFlag returns the flag name for the '%s' field\n", name, field.Path)
|
|
||||||
fprintf(out, "func %sFlag() string { return \"%s\" }\n\n", name, field.Flag())
|
|
||||||
|
|
||||||
// ConfigState structure helper methods
|
// ConfigState structure helper methods
|
||||||
fprintf(out, "// Get%s safely fetches the Configuration value for state's '%s' field\n", name, field.Path)
|
fprintf(out, "// Get%s safely fetches the Configuration value for state's '%s' field\n", name, field.Path)
|
||||||
fprintf(out, "func (st *ConfigState) Get%s() (v %s) {\n", name, fieldType)
|
fprintf(out, "func (st *ConfigState) Get%s() (v %s) {\n", name, fieldType)
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -42,7 +42,7 @@ func Validate() error {
|
||||||
// `host`
|
// `host`
|
||||||
host := GetHost()
|
host := GetHost()
|
||||||
if host == "" {
|
if host == "" {
|
||||||
errf("%s must be set", HostFlag())
|
errf("%s must be set", HostFlag)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If `account-domain` and `host`
|
// If `account-domain` and `host`
|
||||||
|
|
@ -55,10 +55,8 @@ func Validate() error {
|
||||||
// back by setting it to `host`.
|
// back by setting it to `host`.
|
||||||
SetAccountDomain(GetHost())
|
SetAccountDomain(GetHost())
|
||||||
} else if !dns.IsSubDomain(ad, host) {
|
} else if !dns.IsSubDomain(ad, host) {
|
||||||
errf(
|
errf("%s %s is not a valid subdomain of %s %s",
|
||||||
"%s %s is not a valid subdomain of %s %s",
|
AccountDomainFlag, ad, HostFlag, host)
|
||||||
AccountDomainFlag(), ad, HostFlag(), host,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,20 +66,15 @@ func Validate() error {
|
||||||
// No problem.
|
// No problem.
|
||||||
|
|
||||||
case "http":
|
case "http":
|
||||||
log.Warnf(
|
log.Warnf(nil, "%s was set to 'http'; this should *only* be used for debugging and tests!",
|
||||||
nil,
|
ProtocolFlag)
|
||||||
"%s was set to 'http'; this should *only* be used for debugging and tests!",
|
|
||||||
ProtocolFlag(),
|
|
||||||
)
|
|
||||||
|
|
||||||
case "":
|
case "":
|
||||||
errf("%s must be set", ProtocolFlag())
|
errf("%s must be set", ProtocolFlag)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
errf(
|
errf("%s must be set to either http or https, provided value was %s",
|
||||||
"%s must be set to either http or https, provided value was %s",
|
ProtocolFlag, proto)
|
||||||
ProtocolFlag(), proto,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// `federation-mode` should be
|
// `federation-mode` should be
|
||||||
|
|
@ -91,22 +84,19 @@ func Validate() error {
|
||||||
// No problem.
|
// No problem.
|
||||||
|
|
||||||
case "":
|
case "":
|
||||||
errf("%s must be set", InstanceFederationModeFlag())
|
errf("%s must be set", InstanceFederationModeFlag)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
errf(
|
errf("%s must be set to either blocklist or allowlist, provided value was %s",
|
||||||
"%s must be set to either blocklist or allowlist, provided value was %s",
|
InstanceFederationModeFlag, fediMode)
|
||||||
InstanceFederationModeFlag(), fediMode,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse `instance-languages`, and
|
// Parse `instance-languages`, and
|
||||||
// set enriched version into config.
|
// set enriched version into config.
|
||||||
parsedLangs, err := language.InitLangs(GetInstanceLanguages().TagStrs())
|
parsedLangs, err := language.InitLangs(GetInstanceLanguages().TagStrs())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errf(
|
errf("%s could not be parsed as an array of valid BCP47 language tags: %v",
|
||||||
"%s could not be parsed as an array of valid BCP47 language tags: %v",
|
InstanceLanguagesFlag, err,
|
||||||
InstanceLanguagesFlag(), err,
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// Parsed successfully, put enriched
|
// Parsed successfully, put enriched
|
||||||
|
|
@ -121,37 +111,30 @@ func Validate() error {
|
||||||
// No problem.
|
// No problem.
|
||||||
|
|
||||||
default:
|
default:
|
||||||
errf(
|
errf("%s must be set to empty string, zero, serve, or baffle, provided value was %s",
|
||||||
"%s must be set to empty string, zero, serve, or baffle, provided value was %s",
|
InstanceFederationModeFlag, statsMode,
|
||||||
InstanceFederationModeFlag(), statsMode,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// `web-assets-base-dir`.
|
// `web-assets-base-dir`.
|
||||||
webAssetsBaseDir := GetWebAssetBaseDir()
|
webAssetsBaseDir := GetWebAssetBaseDir()
|
||||||
if webAssetsBaseDir == "" {
|
if webAssetsBaseDir == "" {
|
||||||
errf("%s must be set", WebAssetBaseDirFlag())
|
errf("%s must be set", WebAssetBaseDirFlag)
|
||||||
}
|
}
|
||||||
|
|
||||||
// `storage-s3-redirect-url`
|
// `storage-s3-redirect-url`
|
||||||
if s3RedirectURL := GetStorageS3RedirectURL(); s3RedirectURL != "" {
|
if s3RedirectURL := GetStorageS3RedirectURL(); s3RedirectURL != "" {
|
||||||
if strings.HasSuffix(s3RedirectURL, "/") {
|
if strings.HasSuffix(s3RedirectURL, "/") {
|
||||||
errf(
|
errf("%s must not end with a trailing slash",
|
||||||
"%s must not end with a trailing slash",
|
StorageS3RedirectURLFlag)
|
||||||
StorageS3RedirectURLFlag(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if url, err := url.Parse(s3RedirectURL); err != nil {
|
if url, err := url.Parse(s3RedirectURL); err != nil {
|
||||||
errf(
|
errf("%s invalid: %w",
|
||||||
"%s invalid: %w",
|
StorageS3RedirectURLFlag, err)
|
||||||
StorageS3RedirectURLFlag(), err,
|
|
||||||
)
|
|
||||||
} else if url.Scheme != "https" && url.Scheme != "http" {
|
} else if url.Scheme != "https" && url.Scheme != "http" {
|
||||||
errf(
|
errf("%s scheme must be https or http",
|
||||||
"%s scheme must be https or http",
|
StorageS3RedirectURLFlag)
|
||||||
StorageS3RedirectURLFlag(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -161,32 +144,42 @@ func Validate() error {
|
||||||
// and if using custom certs then all relevant
|
// and if using custom certs then all relevant
|
||||||
// values must be provided.
|
// values must be provided.
|
||||||
var (
|
var (
|
||||||
tlsChain = GetTLSCertificateChain()
|
tlsChain = GetTLSCertificateChain()
|
||||||
tlsKey = GetTLSCertificateKey()
|
tlsKey = GetTLSCertificateKey()
|
||||||
tlsChainFlag = TLSCertificateChainFlag()
|
|
||||||
tlsKeyFlag = TLSCertificateKeyFlag()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if GetLetsEncryptEnabled() && (tlsChain != "" || tlsKey != "") {
|
if GetLetsEncryptEnabled() && (tlsChain != "" || tlsKey != "") {
|
||||||
errf(
|
errf("%s cannot be true when %s and/or %s are also set",
|
||||||
"%s cannot be true when %s and/or %s are also set",
|
LetsEncryptEnabledFlag, TLSCertificateChainFlag, TLSCertificateKeyFlag)
|
||||||
LetsEncryptEnabledFlag(), tlsChainFlag, tlsKeyFlag,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tlsChain != "" && tlsKey == "") || (tlsChain == "" && tlsKey != "") {
|
if (tlsChain != "" && tlsKey == "") || (tlsChain == "" && tlsKey != "") {
|
||||||
errf(
|
errf("%s and %s need to both be set or unset",
|
||||||
"%s and %s need to both be set or unset",
|
TLSCertificateChainFlag, TLSCertificateKeyFlag)
|
||||||
tlsChainFlag, tlsKeyFlag,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// http-client.insecure-outgoing
|
// http-client.insecure-outgoing
|
||||||
if GetHTTPClientInsecureOutgoing() {
|
if GetHTTPClientInsecureOutgoing() {
|
||||||
log.Warn(nil, "http-client.insecure-outgoing was set to TRUE. "+
|
log.Warnf(nil, "%s was set to TRUE. "+
|
||||||
"*****THIS SHOULD BE USED FOR TESTING ONLY, IF YOU TURN THIS ON WHILE "+
|
"*****THIS SHOULD BE USED FOR TESTING ONLY, IF YOU TURN THIS ON WHILE "+
|
||||||
"IF IN DOUBT, STOP YOUR SERVER *NOW* AND ADJUST YOUR CONFIGURATION!*****",
|
"IF IN DOUBT, STOP YOUR SERVER *NOW* AND ADJUST YOUR CONFIGURATION!*****",
|
||||||
)
|
HTTPClientInsecureOutgoingFlag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// thumb size recommendations,
|
||||||
|
// beyond which we log.Warn().
|
||||||
|
const minThumb = 32
|
||||||
|
const minThumbRecc = 256
|
||||||
|
const maxThumbRecc = 1024
|
||||||
|
|
||||||
|
// Get and check configured max thumb size.
|
||||||
|
switch max := GetMediaThumbMaxPixels(); {
|
||||||
|
case max < minThumb:
|
||||||
|
errf("%s < 32 is not a useable thumbsize", MediaThumbMaxPixelsFlag, max)
|
||||||
|
case max < minThumbRecc:
|
||||||
|
log.Warnf(nil, "%s smaller than min recommended thumbsize %d", MediaThumbMaxPixelsFlag, minThumbRecc)
|
||||||
|
case max > maxThumbRecc:
|
||||||
|
log.Warnf(nil, "%s larger than max recommended thumbsize %d", MediaThumbMaxPixelsFlag, maxThumbRecc)
|
||||||
}
|
}
|
||||||
|
|
||||||
return errs.Combine()
|
return errs.Combine()
|
||||||
|
|
|
||||||
|
|
@ -376,7 +376,7 @@ func sqliteConn(ctx context.Context) (*sql.DB, func() schema.Dialect, error) {
|
||||||
// validate db address has actually been set
|
// validate db address has actually been set
|
||||||
address := config.GetDbAddress()
|
address := config.GetDbAddress()
|
||||||
if address == "" {
|
if address == "" {
|
||||||
return nil, nil, fmt.Errorf("'%s' was not set when attempting to start sqlite", config.DbAddressFlag())
|
return nil, nil, fmt.Errorf("'%s' was not set when attempting to start sqlite", config.DbAddressFlag)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build SQLite connection address with prefs.
|
// Build SQLite connection address with prefs.
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ func init() {
|
||||||
|
|
||||||
storageBasePath := config.GetStorageLocalBasePath()
|
storageBasePath := config.GetStorageLocalBasePath()
|
||||||
if storageBasePath == "" {
|
if storageBasePath == "" {
|
||||||
return fmt.Errorf("%s must be set to do storage migration", config.StorageLocalBasePathFlag())
|
return fmt.Errorf("%s must be set to do storage migration", config.StorageLocalBasePathFlag)
|
||||||
}
|
}
|
||||||
|
|
||||||
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
|
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"codeberg.org/gruf/go-kv"
|
"codeberg.org/gruf/go-kv"
|
||||||
"codeberg.org/gruf/go-runners"
|
"codeberg.org/gruf/go-runners"
|
||||||
|
|
||||||
|
"code.superseriousbusiness.org/gotosocial/internal/config"
|
||||||
"code.superseriousbusiness.org/gotosocial/internal/gtserror"
|
"code.superseriousbusiness.org/gotosocial/internal/gtserror"
|
||||||
"code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
|
"code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
|
||||||
"code.superseriousbusiness.org/gotosocial/internal/log"
|
"code.superseriousbusiness.org/gotosocial/internal/log"
|
||||||
|
|
@ -225,6 +226,7 @@ func (p *ProcessingMedia) store(ctx context.Context) error {
|
||||||
if width > 0 && height > 0 {
|
if width > 0 && height > 0 {
|
||||||
// Determine thumbnail dimens to use.
|
// Determine thumbnail dimens to use.
|
||||||
thumbWidth, thumbHeight := thumbSize(
|
thumbWidth, thumbHeight := thumbSize(
|
||||||
|
config.GetMediaThumbMaxPixels(),
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
aspect,
|
aspect,
|
||||||
|
|
|
||||||
|
|
@ -33,37 +33,33 @@ import (
|
||||||
"golang.org/x/image/webp"
|
"golang.org/x/image/webp"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
maxThumbWidth = 512
|
|
||||||
maxThumbHeight = 512
|
|
||||||
)
|
|
||||||
|
|
||||||
// thumbSize returns the dimensions to use for an input
|
// thumbSize returns the dimensions to use for an input
|
||||||
// image of given width / height, for its outgoing thumbnail.
|
// image of given width / height, for its outgoing thumbnail.
|
||||||
// This attempts to maintains the original image aspect ratio.
|
// This attempts to maintains the original image aspect ratio.
|
||||||
func thumbSize(width, height int, aspect float32) (int, int) {
|
func thumbSize(max, width, height int, aspect float32) (int, int) {
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
// Simplest case, within bounds!
|
// Simplest case, within bounds!
|
||||||
case width < maxThumbWidth &&
|
case width < max && height < max:
|
||||||
height < maxThumbHeight:
|
|
||||||
return width, height
|
return width, height
|
||||||
|
|
||||||
// Width is larger side.
|
// Width is larger side.
|
||||||
case width > height:
|
case width > height:
|
||||||
|
|
||||||
// i.e. height = newWidth * (height / width)
|
// i.e. height = newWidth * (height / width)
|
||||||
height = int(float32(maxThumbWidth) / aspect)
|
height = int(float32(max) / aspect)
|
||||||
return maxThumbWidth, height
|
return max, height
|
||||||
|
|
||||||
// Height is larger side.
|
// Height is larger side.
|
||||||
case height > width:
|
case height > width:
|
||||||
|
|
||||||
// i.e. width = newHeight * (width / height)
|
// i.e. width = newHeight * (width / height)
|
||||||
width = int(float32(maxThumbHeight) * aspect)
|
width = int(float32(max) * aspect)
|
||||||
return width, maxThumbHeight
|
return width, max
|
||||||
|
|
||||||
// Square.
|
// Square.
|
||||||
default:
|
default:
|
||||||
return maxThumbWidth, maxThumbHeight
|
return max, max
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ func LoadTemplates(engine *gin.Engine) error {
|
||||||
if templateBaseDir == "" {
|
if templateBaseDir == "" {
|
||||||
return gtserror.Newf(
|
return gtserror.Newf(
|
||||||
"%s cannot be empty and must be a relative or absolute path",
|
"%s cannot be empty and must be a relative or absolute path",
|
||||||
config.WebTemplateBaseDirFlag(),
|
config.WebTemplateBaseDirFlag,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -326,7 +326,7 @@ func NewS3Storage() (*Driver, error) {
|
||||||
case "path":
|
case "path":
|
||||||
bucketLookup = minio.BucketLookupPath
|
bucketLookup = minio.BucketLookupPath
|
||||||
default:
|
default:
|
||||||
log.Warnf(nil, "%s set to %s which is not recognized, defaulting to 'auto'", config.StorageS3BucketLookupFlag(), s)
|
log.Warnf(nil, "%s set to %s which is not recognized, defaulting to 'auto'", config.StorageS3BucketLookupFlag, s)
|
||||||
bucketLookup = minio.BucketLookupAuto
|
bucketLookup = minio.BucketLookupAuto
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -148,6 +148,7 @@ EXPECT=$(cat << "EOF"
|
||||||
"media-local-max-size": "420B",
|
"media-local-max-size": "420B",
|
||||||
"media-remote-cache-days": 30,
|
"media-remote-cache-days": 30,
|
||||||
"media-remote-max-size": "420B",
|
"media-remote-max-size": "420B",
|
||||||
|
"media-thumb-max-pixels": 42069,
|
||||||
"media-video-size-hint": "40.0MiB",
|
"media-video-size-hint": "40.0MiB",
|
||||||
"metrics-enabled": false,
|
"metrics-enabled": false,
|
||||||
"oidc-admin-groups": [
|
"oidc-admin-groups": [
|
||||||
|
|
@ -272,6 +273,7 @@ GTS_MEDIA_EMOJI_LOCAL_MAX_SIZE=420 \
|
||||||
GTS_MEDIA_EMOJI_REMOTE_MAX_SIZE=420 \
|
GTS_MEDIA_EMOJI_REMOTE_MAX_SIZE=420 \
|
||||||
GTS_MEDIA_FFMPEG_POOL_SIZE=8 \
|
GTS_MEDIA_FFMPEG_POOL_SIZE=8 \
|
||||||
GTS_MEDIA_VIDEO_SIZE_HINT='40MiB' \
|
GTS_MEDIA_VIDEO_SIZE_HINT='40MiB' \
|
||||||
|
GTS_MEDIA_THUMB_MAX_PIXELS=42069 \
|
||||||
GTS_METRICS_ENABLED=false \
|
GTS_METRICS_ENABLED=false \
|
||||||
GTS_STORAGE_BACKEND='local' \
|
GTS_STORAGE_BACKEND='local' \
|
||||||
GTS_STORAGE_LOCAL_BASE_PATH='/root/store' \
|
GTS_STORAGE_LOCAL_BASE_PATH='/root/store' \
|
||||||
|
|
|
||||||
|
|
@ -114,15 +114,18 @@ func testDefaults() config.Configuration {
|
||||||
AccountsCustomCSSLength: 10000,
|
AccountsCustomCSSLength: 10000,
|
||||||
AccountsMaxProfileFields: 8,
|
AccountsMaxProfileFields: 8,
|
||||||
|
|
||||||
MediaDescriptionMinChars: 0,
|
Media: config.MediaConfiguration{
|
||||||
MediaDescriptionMaxChars: 500,
|
DescriptionMinChars: 0,
|
||||||
MediaRemoteCacheDays: 7,
|
DescriptionMaxChars: 500,
|
||||||
MediaLocalMaxSize: 40 * bytesize.MiB,
|
RemoteCacheDays: 7,
|
||||||
MediaRemoteMaxSize: 40 * bytesize.MiB,
|
LocalMaxSize: 40 * bytesize.MiB,
|
||||||
MediaEmojiLocalMaxSize: 51200, // 50KiB
|
RemoteMaxSize: 40 * bytesize.MiB,
|
||||||
MediaEmojiRemoteMaxSize: 102400, // 100KiB
|
EmojiLocalMaxSize: 51200, // 50KiB
|
||||||
MediaCleanupFrom: "00:00", // midnight.
|
EmojiRemoteMaxSize: 102400, // 100KiB
|
||||||
MediaCleanupEvery: 24 * time.Hour, // 1/day.
|
CleanupFrom: "00:00", // midnight.
|
||||||
|
CleanupEvery: 24 * time.Hour, // 1/day.
|
||||||
|
ThumbMaxPixels: 512,
|
||||||
|
},
|
||||||
|
|
||||||
// the testrig only uses in-memory storage, so we can
|
// the testrig only uses in-memory storage, so we can
|
||||||
// safely set this value to 'test' to avoid running storage
|
// safely set this value to 'test' to avoid running storage
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue