mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 03:52:24 -05:00 
			
		
		
		
	[feature] Add instance-stats-randomize config option (#3718)
		
	* [feature] Add `instance-stats-randomize` config option * don't use cache (overkill)
This commit is contained in:
		
					parent
					
						
							
								c47b9bd1d1
							
						
					
				
			
			
				commit
				
					
						a55bd6d2bd
					
				
			
		
					 13 changed files with 183 additions and 10 deletions
				
			
		|  | @ -138,4 +138,15 @@ instance-subscriptions-process-from: "23:00" | ||||||
| # Examples: ["24h", "72h", "12h"] | # Examples: ["24h", "72h", "12h"] | ||||||
| # Default: "24h" (once per day). | # Default: "24h" (once per day). | ||||||
| instance-subscriptions-process-every: "24h" | instance-subscriptions-process-every: "24h" | ||||||
|  | 
 | ||||||
|  | # Bool. Set this to true to randomize stats served at | ||||||
|  | # the /api/v1|v2/instance and /nodeinfo/2.0 endpoints. | ||||||
|  | # | ||||||
|  | # This can be useful when you don't want bots to obtain | ||||||
|  | # reliable information about the amount of users and | ||||||
|  | # statuses on your instance. | ||||||
|  | # | ||||||
|  | # Options: [true, false] | ||||||
|  | # Default: false | ||||||
|  | instance-stats-randomize: false | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
|  | @ -425,6 +425,17 @@ instance-subscriptions-process-from: "23:00" | ||||||
| # Default: "24h" (once per day). | # Default: "24h" (once per day). | ||||||
| instance-subscriptions-process-every: "24h" | instance-subscriptions-process-every: "24h" | ||||||
| 
 | 
 | ||||||
|  | # Bool. Set this to true to randomize stats served at | ||||||
|  | # the /api/v1|v2/instance and /nodeinfo/2.0 endpoints. | ||||||
|  | # | ||||||
|  | # This can be useful when you don't want bots to obtain | ||||||
|  | # reliable information about the amount of users and | ||||||
|  | # statuses on your instance. | ||||||
|  | # | ||||||
|  | # Options: [true, false] | ||||||
|  | # Default: false | ||||||
|  | instance-stats-randomize: false | ||||||
|  | 
 | ||||||
| ########################### | ########################### | ||||||
| ##### ACCOUNTS CONFIG ##### | ##### ACCOUNTS CONFIG ##### | ||||||
| ########################### | ########################### | ||||||
|  |  | ||||||
|  | @ -21,7 +21,9 @@ import ( | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 
 | 
 | ||||||
| 	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" | 	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/config" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/util" | ||||||
| 
 | 
 | ||||||
| 	"github.com/gin-gonic/gin" | 	"github.com/gin-gonic/gin" | ||||||
| ) | ) | ||||||
|  | @ -58,6 +60,12 @@ func (m *Module) InstanceInformationGETHandlerV1(c *gin.Context) { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if config.GetInstanceStatsRandomize() { | ||||||
|  | 		// Replace actual stats with cached randomized ones. | ||||||
|  | 		instance.Stats["user_count"] = util.Ptr(int(instance.RandomStats.TotalUsers)) | ||||||
|  | 		instance.Stats["status_count"] = util.Ptr(int(instance.RandomStats.Statuses)) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	apiutil.JSON(c, http.StatusOK, instance) | 	apiutil.JSON(c, http.StatusOK, instance) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -93,5 +101,10 @@ func (m *Module) InstanceInformationGETHandlerV2(c *gin.Context) { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if config.GetInstanceStatsRandomize() { | ||||||
|  | 		// Replace actual stats with cached randomized ones. | ||||||
|  | 		instance.Usage.Users.ActiveMonth = int(instance.RandomStats.MonthlyActiveUsers) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	apiutil.JSON(c, http.StatusOK, instance) | 	apiutil.JSON(c, http.StatusOK, instance) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -17,7 +17,10 @@ | ||||||
| 
 | 
 | ||||||
| package model | package model | ||||||
| 
 | 
 | ||||||
| import "mime/multipart" | import ( | ||||||
|  | 	"mime/multipart" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
| 
 | 
 | ||||||
| // InstanceSettingsUpdateRequest models an instance update request. | // InstanceSettingsUpdateRequest models an instance update request. | ||||||
| // | // | ||||||
|  | @ -148,3 +151,11 @@ type InstanceConfigurationEmojis struct { | ||||||
| 	// example: 51200 | 	// example: 51200 | ||||||
| 	EmojiSizeLimit int `json:"emoji_size_limit"` | 	EmojiSizeLimit int `json:"emoji_size_limit"` | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // swagger:ignore | ||||||
|  | type RandomStats struct { | ||||||
|  | 	Statuses           int64 | ||||||
|  | 	TotalUsers         int64 | ||||||
|  | 	MonthlyActiveUsers int64 | ||||||
|  | 	Generated          time.Time | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -110,6 +110,13 @@ type InstanceV1 struct { | ||||||
| 	Terms string `json:"terms,omitempty"` | 	Terms string `json:"terms,omitempty"` | ||||||
| 	// Raw (unparsed) version of terms. | 	// Raw (unparsed) version of terms. | ||||||
| 	TermsRaw string `json:"terms_text,omitempty"` | 	TermsRaw string `json:"terms_text,omitempty"` | ||||||
|  | 
 | ||||||
|  | 	// Random stats generated for the instance. | ||||||
|  | 	// Only used if `instance-stats-randomize` is true. | ||||||
|  | 	// Not serialized to the frontend. | ||||||
|  | 	// | ||||||
|  | 	// swagger:ignore | ||||||
|  | 	RandomStats `json:"-"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // InstanceV1URLs models instance-relevant URLs for client application consumption. | // InstanceV1URLs models instance-relevant URLs for client application consumption. | ||||||
|  |  | ||||||
|  | @ -74,6 +74,13 @@ type InstanceV2 struct { | ||||||
| 	Terms string `json:"terms,omitempty"` | 	Terms string `json:"terms,omitempty"` | ||||||
| 	// Raw (unparsed) version of terms. | 	// Raw (unparsed) version of terms. | ||||||
| 	TermsText string `json:"terms_text,omitempty"` | 	TermsText string `json:"terms_text,omitempty"` | ||||||
|  | 
 | ||||||
|  | 	// Random stats generated for the instance. | ||||||
|  | 	// Only used if `instance-stats-randomize` is true. | ||||||
|  | 	// Not serialized to the frontend. | ||||||
|  | 	// | ||||||
|  | 	// swagger:ignore | ||||||
|  | 	RandomStats `json:"-"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Usage data for this instance. | // Usage data for this instance. | ||||||
|  |  | ||||||
|  | @ -90,6 +90,7 @@ type Configuration struct { | ||||||
| 	InstanceLanguages                 language.Languages `name:"instance-languages" usage:"BCP47 language tags for the instance. Used to indicate the preferred languages of instance residents (in order from most-preferred to least-preferred)."` | 	InstanceLanguages                 language.Languages `name:"instance-languages" usage:"BCP47 language tags for the instance. Used to indicate the preferred languages of instance residents (in order from most-preferred to least-preferred)."` | ||||||
| 	InstanceSubscriptionsProcessFrom  string             `name:"instance-subscriptions-process-from" usage:"Time of day from which to start running instance subscriptions processing jobs. Should be in the format 'hh:mm:ss', eg., '15:04:05'."` | 	InstanceSubscriptionsProcessFrom  string             `name:"instance-subscriptions-process-from" usage:"Time of day from which to start running instance subscriptions processing jobs. Should be in the format 'hh:mm:ss', eg., '15:04:05'."` | ||||||
| 	InstanceSubscriptionsProcessEvery time.Duration      `name:"instance-subscriptions-process-every" usage:"Period to elapse between instance subscriptions processing jobs, starting from instance-subscriptions-process-from."` | 	InstanceSubscriptionsProcessEvery time.Duration      `name:"instance-subscriptions-process-every" usage:"Period to elapse between instance subscriptions processing jobs, starting from instance-subscriptions-process-from."` | ||||||
|  | 	InstanceStatsRandomize            bool               `name:"instance-stats-randomize" usage:"Set to true to randomize the stats served at api/v1/instance and api/v2/instance endpoints. Home page stats remain unchanged."` | ||||||
| 
 | 
 | ||||||
| 	AccountsRegistrationOpen bool `name:"accounts-registration-open" usage:"Allow anyone to submit an account signup request. If false, server will be invite-only."` | 	AccountsRegistrationOpen bool `name:"accounts-registration-open" usage:"Allow anyone to submit an account signup request. If false, server will be invite-only."` | ||||||
| 	AccountsReasonRequired   bool `name:"accounts-reason-required" usage:"Do new account signups require a reason to be submitted on registration?"` | 	AccountsReasonRequired   bool `name:"accounts-reason-required" usage:"Do new account signups require a reason to be submitted on registration?"` | ||||||
|  |  | ||||||
|  | @ -92,6 +92,7 @@ func (s *ConfigState) AddServerFlags(cmd *cobra.Command) { | ||||||
| 		cmd.Flags().StringSlice(InstanceLanguagesFlag(), cfg.InstanceLanguages.TagStrs(), fieldtag("InstanceLanguages", "usage")) | 		cmd.Flags().StringSlice(InstanceLanguagesFlag(), cfg.InstanceLanguages.TagStrs(), fieldtag("InstanceLanguages", "usage")) | ||||||
| 		cmd.Flags().String(InstanceSubscriptionsProcessFromFlag(), cfg.InstanceSubscriptionsProcessFrom, fieldtag("InstanceSubscriptionsProcessFrom", "usage")) | 		cmd.Flags().String(InstanceSubscriptionsProcessFromFlag(), cfg.InstanceSubscriptionsProcessFrom, fieldtag("InstanceSubscriptionsProcessFrom", "usage")) | ||||||
| 		cmd.Flags().Duration(InstanceSubscriptionsProcessEveryFlag(), cfg.InstanceSubscriptionsProcessEvery, fieldtag("InstanceSubscriptionsProcessEvery", "usage")) | 		cmd.Flags().Duration(InstanceSubscriptionsProcessEveryFlag(), cfg.InstanceSubscriptionsProcessEvery, fieldtag("InstanceSubscriptionsProcessEvery", "usage")) | ||||||
|  | 		cmd.Flags().Bool(InstanceStatsRandomizeFlag(), cfg.InstanceStatsRandomize, fieldtag("InstanceStatsRandomize", "usage")) | ||||||
| 
 | 
 | ||||||
| 		// Accounts | 		// Accounts | ||||||
| 		cmd.Flags().Bool(AccountsRegistrationOpenFlag(), cfg.AccountsRegistrationOpen, fieldtag("AccountsRegistrationOpen", "usage")) | 		cmd.Flags().Bool(AccountsRegistrationOpenFlag(), cfg.AccountsRegistrationOpen, fieldtag("AccountsRegistrationOpen", "usage")) | ||||||
|  |  | ||||||
|  | @ -1057,6 +1057,31 @@ func SetInstanceSubscriptionsProcessEvery(v time.Duration) { | ||||||
| 	global.SetInstanceSubscriptionsProcessEvery(v) | 	global.SetInstanceSubscriptionsProcessEvery(v) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // GetInstanceStatsRandomize safely fetches the Configuration value for state's 'InstanceStatsRandomize' field | ||||||
|  | func (st *ConfigState) GetInstanceStatsRandomize() (v bool) { | ||||||
|  | 	st.mutex.RLock() | ||||||
|  | 	v = st.config.InstanceStatsRandomize | ||||||
|  | 	st.mutex.RUnlock() | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SetInstanceStatsRandomize safely sets the Configuration value for state's 'InstanceStatsRandomize' field | ||||||
|  | func (st *ConfigState) SetInstanceStatsRandomize(v bool) { | ||||||
|  | 	st.mutex.Lock() | ||||||
|  | 	defer st.mutex.Unlock() | ||||||
|  | 	st.config.InstanceStatsRandomize = v | ||||||
|  | 	st.reloadToViper() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // InstanceStatsRandomizeFlag returns the flag name for the 'InstanceStatsRandomize' field | ||||||
|  | func InstanceStatsRandomizeFlag() string { return "instance-stats-randomize" } | ||||||
|  | 
 | ||||||
|  | // GetInstanceStatsRandomize safely fetches the value for global configuration 'InstanceStatsRandomize' field | ||||||
|  | func GetInstanceStatsRandomize() bool { return global.GetInstanceStatsRandomize() } | ||||||
|  | 
 | ||||||
|  | // SetInstanceStatsRandomize safely sets the value for global configuration 'InstanceStatsRandomize' field | ||||||
|  | func SetInstanceStatsRandomize(v bool) { global.SetInstanceStatsRandomize(v) } | ||||||
|  | 
 | ||||||
| // GetAccountsRegistrationOpen safely fetches the Configuration value for state's 'AccountsRegistrationOpen' field | // GetAccountsRegistrationOpen safely fetches the Configuration value for state's 'AccountsRegistrationOpen' field | ||||||
| func (st *ConfigState) GetAccountsRegistrationOpen() (v bool) { | func (st *ConfigState) GetAccountsRegistrationOpen() (v bool) { | ||||||
| 	st.mutex.RLock() | 	st.mutex.RLock() | ||||||
|  | @ -2699,7 +2724,7 @@ func (st *ConfigState) SetAdvancedRateLimitExceptionsParsed(v []netip.Prefix) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // AdvancedRateLimitExceptionsParsedFlag returns the flag name for the 'AdvancedRateLimitExceptionsParsed' field | // AdvancedRateLimitExceptionsParsedFlag returns the flag name for the 'AdvancedRateLimitExceptionsParsed' field | ||||||
| func AdvancedRateLimitExceptionsParsedFlag() string { return "" } | func AdvancedRateLimitExceptionsParsedFlag() string { return "advanced-rate-limit-exceptions-parsed" } | ||||||
| 
 | 
 | ||||||
| // GetAdvancedRateLimitExceptionsParsed safely fetches the value for global configuration 'AdvancedRateLimitExceptionsParsed' field | // GetAdvancedRateLimitExceptionsParsed safely fetches the value for global configuration 'AdvancedRateLimitExceptionsParsed' field | ||||||
| func GetAdvancedRateLimitExceptionsParsed() []netip.Prefix { | func GetAdvancedRateLimitExceptionsParsed() []netip.Prefix { | ||||||
|  |  | ||||||
|  | @ -65,17 +65,31 @@ func (p *Processor) NodeInfoRelGet(ctx context.Context) (*apimodel.WellKnownResp | ||||||
| 
 | 
 | ||||||
| // NodeInfoGet returns a node info struct in response to a node info request. | // NodeInfoGet returns a node info struct in response to a node info request. | ||||||
| func (p *Processor) NodeInfoGet(ctx context.Context) (*apimodel.Nodeinfo, gtserror.WithCode) { | func (p *Processor) NodeInfoGet(ctx context.Context) (*apimodel.Nodeinfo, gtserror.WithCode) { | ||||||
|  | 	var ( | ||||||
|  | 		userCount int | ||||||
|  | 		postCount int | ||||||
|  | 		err       error | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	if config.GetInstanceStatsRandomize() { | ||||||
|  | 		// Use randomized stats. | ||||||
|  | 		stats := p.converter.RandomStats() | ||||||
|  | 		userCount = int(stats.TotalUsers) | ||||||
|  | 		postCount = int(stats.Statuses) | ||||||
|  | 	} else { | ||||||
|  | 		// Count actual stats. | ||||||
| 		host := config.GetHost() | 		host := config.GetHost() | ||||||
| 
 | 
 | ||||||
| 	userCount, err := p.state.DB.CountInstanceUsers(ctx, host) | 		userCount, err = p.state.DB.CountInstanceUsers(ctx, host) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, gtserror.NewErrorInternalError(err) | 			return nil, gtserror.NewErrorInternalError(err) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 	postCount, err := p.state.DB.CountInstanceStatuses(ctx, host) | 		postCount, err = p.state.DB.CountInstanceStatuses(ctx, host) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, gtserror.NewErrorInternalError(err) | 			return nil, gtserror.NewErrorInternalError(err) | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	return &apimodel.Nodeinfo{ | 	return &apimodel.Nodeinfo{ | ||||||
| 		Version: nodeInfoVersion, | 		Version: nodeInfoVersion, | ||||||
|  |  | ||||||
|  | @ -18,10 +18,17 @@ | ||||||
| package typeutils | package typeutils | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	crand "crypto/rand" | ||||||
|  | 	"math/big" | ||||||
|  | 	"math/rand" | ||||||
| 	"sync" | 	"sync" | ||||||
|  | 	"sync/atomic" | ||||||
|  | 	"time" | ||||||
| 
 | 
 | ||||||
|  | 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/filter/interaction" | 	"github.com/superseriousbusiness/gotosocial/internal/filter/interaction" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/filter/visibility" | 	"github.com/superseriousbusiness/gotosocial/internal/filter/visibility" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/log" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/state" | 	"github.com/superseriousbusiness/gotosocial/internal/state" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -31,6 +38,7 @@ type Converter struct { | ||||||
| 	randAvatars    sync.Map | 	randAvatars    sync.Map | ||||||
| 	visFilter      *visibility.Filter | 	visFilter      *visibility.Filter | ||||||
| 	intFilter      *interaction.Filter | 	intFilter      *interaction.Filter | ||||||
|  | 	randStats      atomic.Pointer[apimodel.RandomStats] | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func NewConverter(state *state.State) *Converter { | func NewConverter(state *state.State) *Converter { | ||||||
|  | @ -41,3 +49,53 @@ func NewConverter(state *state.State) *Converter { | ||||||
| 		intFilter:      interaction.NewFilter(state), | 		intFilter:      interaction.NewFilter(state), | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // RandomStats returns or generates | ||||||
|  | // and returns random instance stats. | ||||||
|  | func (c *Converter) RandomStats() apimodel.RandomStats { | ||||||
|  | 	now := time.Now() | ||||||
|  | 	stats := c.randStats.Load() | ||||||
|  | 	if stats != nil && time.Since(stats.Generated) < time.Hour { | ||||||
|  | 		// Random stats are still | ||||||
|  | 		// fresh (less than 1hr old), | ||||||
|  | 		// so return them as-is. | ||||||
|  | 		return *stats | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Generate new random stats. | ||||||
|  | 	newStats := genRandStats() | ||||||
|  | 	newStats.Generated = now | ||||||
|  | 	c.randStats.Store(&newStats) | ||||||
|  | 	return newStats | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func genRandStats() apimodel.RandomStats { | ||||||
|  | 	const ( | ||||||
|  | 		statusesMax = 10000000 | ||||||
|  | 		usersMax    = 1000000 | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	statusesB, err := crand.Int(crand.Reader, big.NewInt(statusesMax)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		// Only errs if something is buggered with the OS. | ||||||
|  | 		log.Panicf(nil, "error randomly generating statuses count: %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	totalUsersB, err := crand.Int(crand.Reader, big.NewInt(usersMax)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		// Only errs if something is buggered with the OS. | ||||||
|  | 		log.Panicf(nil, "error randomly generating users count: %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Monthly users should only ever | ||||||
|  | 	// be <= 100% of total users. | ||||||
|  | 	totalUsers := totalUsersB.Int64() | ||||||
|  | 	activeRatio := rand.Float64() //nolint | ||||||
|  | 	mau := int64(float64(totalUsers) * activeRatio) | ||||||
|  | 
 | ||||||
|  | 	return apimodel.RandomStats{ | ||||||
|  | 		Statuses:           statusesB.Int64(), | ||||||
|  | 		TotalUsers:         totalUsers, | ||||||
|  | 		MonthlyActiveUsers: mau, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -1745,6 +1745,12 @@ func (c *Converter) InstanceToAPIV1Instance(ctx context.Context, i *gtsmodel.Ins | ||||||
| 	stats["domain_count"] = util.Ptr(domainCount) | 	stats["domain_count"] = util.Ptr(domainCount) | ||||||
| 	instance.Stats = stats | 	instance.Stats = stats | ||||||
| 
 | 
 | ||||||
|  | 	if config.GetInstanceStatsRandomize() { | ||||||
|  | 		// Whack some random stats on the instance | ||||||
|  | 		// to be injected by API handlers. | ||||||
|  | 		instance.RandomStats = c.RandomStats() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	// thumbnail | 	// thumbnail | ||||||
| 	iAccount, err := c.state.DB.GetInstanceAccount(ctx, "") | 	iAccount, err := c.state.DB.GetInstanceAccount(ctx, "") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | @ -1821,6 +1827,12 @@ func (c *Converter) InstanceToAPIV2Instance(ctx context.Context, i *gtsmodel.Ins | ||||||
| 		instance.Debug = util.Ptr(true) | 		instance.Debug = util.Ptr(true) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if config.GetInstanceStatsRandomize() { | ||||||
|  | 		// Whack some random stats on the instance | ||||||
|  | 		// to be injected by API handlers. | ||||||
|  | 		instance.RandomStats = c.RandomStats() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	// thumbnail | 	// thumbnail | ||||||
| 	thumbnail := apimodel.InstanceV2Thumbnail{} | 	thumbnail := apimodel.InstanceV2Thumbnail{} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -118,6 +118,7 @@ EXPECT=$(cat << "EOF" | ||||||
|         "nl", |         "nl", | ||||||
|         "en-GB" |         "en-GB" | ||||||
|     ], |     ], | ||||||
|  |     "instance-stats-randomize": true, | ||||||
|     "instance-subscriptions-process-every": 86400000000000, |     "instance-subscriptions-process-every": 86400000000000, | ||||||
|     "instance-subscriptions-process-from": "23:00", |     "instance-subscriptions-process-from": "23:00", | ||||||
|     "landing-page-user": "admin", |     "landing-page-user": "admin", | ||||||
|  | @ -248,6 +249,7 @@ GTS_INSTANCE_FEDERATION_SPAM_FILTER=true \ | ||||||
| GTS_INSTANCE_DELIVER_TO_SHARED_INBOXES=false \ | GTS_INSTANCE_DELIVER_TO_SHARED_INBOXES=false \ | ||||||
| GTS_INSTANCE_INJECT_MASTODON_VERSION=true \ | GTS_INSTANCE_INJECT_MASTODON_VERSION=true \ | ||||||
| GTS_INSTANCE_LANGUAGES="nl,en-gb" \ | GTS_INSTANCE_LANGUAGES="nl,en-gb" \ | ||||||
|  | GTS_INSTANCE_STATS_RANDOMIZE=true \ | ||||||
| GTS_ACCOUNTS_ALLOW_CUSTOM_CSS=true \ | GTS_ACCOUNTS_ALLOW_CUSTOM_CSS=true \ | ||||||
| GTS_ACCOUNTS_CUSTOM_CSS_LENGTH=5000 \ | GTS_ACCOUNTS_CUSTOM_CSS_LENGTH=5000 \ | ||||||
| GTS_ACCOUNTS_REGISTRATION_OPEN=true \ | GTS_ACCOUNTS_REGISTRATION_OPEN=true \ | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue