cmd

package
v1.12.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 21, 2023 License: MIT Imports: 43 Imported by: 0

Documentation

Overview

Package cmd has all top-level commands dispatched by main's flag.Parse The entry point of each command is Execute function

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type AdminGroup

type AdminGroup struct {
	Type   string `long:"type" env:"TYPE" description:"type of admin store" choice:"shared" choice:"rpc" default:"shared"` //nolint
	Shared struct {
		Admins []string `long:"id" env:"ID" description:"admin(s) ids" env-delim:","`
		Email  []string `long:"email" env:"EMAIL" description:"admin emails" env-delim:","`
	} `group:"shared" namespace:"shared" env-namespace:"SHARED"`
	RPC AdminRPCGroup `group:"rpc" namespace:"rpc" env-namespace:"RPC"`
}

AdminGroup defines options group for admin params

type AdminRPCGroup added in v1.11.3

type AdminRPCGroup struct {
	RPCGroup
	SecretPerSite bool `long:"secret_per_site" env:"SECRET_PER_SITE" description:"enable JWT secret retrieval per aud, which is site_id in this case"`
}

AdminRPCGroup defines options for remote admin store

type AppleGroup added in v1.11.3

type AppleGroup struct {
	CID                string `long:"cid" env:"CID" description:"Apple client ID"`
	TID                string `long:"tid" env:"TID" description:"Apple service ID"`
	KID                string `long:"kid" env:"KID" description:"Private key ID"`
	PrivateKeyFilePath string `long:"private-key-filepath" env:"PRIVATE_KEY_FILEPATH" description:"Private key file location" default:"/srv/var/apple.p8"`
}

AppleGroup defines options for Apple auth params

type AuthGroup

type AuthGroup struct {
	CID  string `long:"cid" env:"CID" description:"OAuth client ID"`
	CSEC string `long:"csec" env:"CSEC" description:"OAuth client secret"`
}

AuthGroup defines options group for auth params

type AvatarCommand

type AvatarCommand struct {
	AvatarSrc AvatarGroup `group:"src" namespace:"src"`
	AvatarDst AvatarGroup `group:"dst" namespace:"dst"`

	CommonOpts
	// contains filtered or unexported fields
}

AvatarCommand set of flags and command for avatar migration it converts all avatars from src.type to dst.type. Note: it is possible to run migration for the same types (src = dst) in order to resize all avatars.

func (*AvatarCommand) Execute

func (ac *AvatarCommand) Execute(_ []string) error

Execute runs with AvatarCommand parameters, entry point for "avatar" command

type AvatarGroup

type AvatarGroup struct {
	Type string `long:"type" env:"TYPE" description:"type of avatar storage" choice:"fs" choice:"bolt" choice:"uri" default:"fs"` //nolint
	FS   struct {
		Path string `long:"path" env:"PATH" default:"./var/avatars" description:"avatars location"`
	} `group:"fs" namespace:"fs" env-namespace:"FS"`
	Bolt struct {
		File string `long:"file" env:"FILE" default:"./var/avatars.db" description:"avatars bolt file location"`
	} `group:"bolt" namespace:"bolt" env-namespace:"BOLT"`
	URI    string `long:"uri" env:"URI" default:"./var/avatars" description:"avatars store URI"`
	RszLmt int    `long:"rsz-lmt" env:"RESIZE" default:"0" description:"max image size for resizing avatars on save"`
}

AvatarGroup defines options group for avatar params

type AvatarMigrator

type AvatarMigrator interface {
	Migrate(avatar.Store, avatar.Store) (int, error)
}

AvatarMigrator defines interface for migration

type BackupCommand

type BackupCommand struct {
	ExportPath string `short:"p" long:"path" env:"BACKUP_PATH" default:"./var/backup" description:"export path"`
	ExportFile string `short:"f" long:"file" default:"userbackup-{{.SITE}}-{{.TS}}.gz" description:"file name"`

	SupportCmdOpts
	CommonOpts
}

BackupCommand set of flags and command for export ExportPath used as a separate element to leverage BACKUP_PATH. If ExportFile has a path (i.e. with /) BACKUP_PATH ignored.

func (*BackupCommand) Execute

func (ec *BackupCommand) Execute(_ []string) error

Execute runs export with ExportCommand parameters, entry point for "export" command

type CacheGroup

type CacheGroup struct {
	Type      string `long:"type" env:"TYPE" description:"type of cache" choice:"redis_pub_sub" choice:"mem" choice:"none" default:"mem"` // nolint
	RedisAddr string ``                                                                                                                   /* 157-byte string literal not displayed */
	Max       struct {
		Items int   `long:"items" env:"ITEMS" default:"1000" description:"max cached items"`
		Value int   `long:"value" env:"VALUE" default:"65536" description:"max size of the cached value"`
		Size  int64 `long:"size" env:"SIZE" default:"50000000" description:"max size of total cache"`
	} `group:"max" namespace:"max" env-namespace:"MAX"`
}

CacheGroup defines options group for cache params

type CleanupCommand

type CleanupCommand struct {
	Dry      bool     `long:"dry" description:"dry mode, will not remove comments"`
	From     string   `long:"from" description:"from yyyymmdd"`
	To       string   `long:"to" description:"from yyyymmdd"`
	BadWords []string `short:"w" long:"bword" description:"bad word(s)"`
	BadUsers []string `short:"u" long:"buser" description:"bad user(s)"`
	SetTitle bool     `long:"title" description:"title mode, will not remove comments, but reset titles to page's title'"`

	SupportCmdOpts
	CommonOpts
}

CleanupCommand set of flags and command for cleanup

func (*CleanupCommand) Execute

func (cc *CleanupCommand) Execute(_ []string) error

Execute runs cleanup with CleanupCommand parameters, entry point for "cleanup" command This command uses provided flags to detect and remove junk comments

type CommonOptionsCommander

type CommonOptionsCommander interface {
	SetCommon(commonOpts CommonOpts)
	Execute(args []string) error
	HandleDeprecatedFlags() []DeprecatedFlag
}

CommonOptionsCommander extends flags.Commander with SetCommon All commands should implement this interfaces

type CommonOpts

type CommonOpts struct {
	RemarkURL    string
	SharedSecret string
	Revision     string
}

CommonOpts sets externally from main, shared across all commands

func (*CommonOpts) HandleDeprecatedFlags

func (c *CommonOpts) HandleDeprecatedFlags() []DeprecatedFlag

HandleDeprecatedFlags sets new flags from deprecated and returns their list

func (*CommonOpts) SetCommon

func (c *CommonOpts) SetCommon(commonOpts CommonOpts)

SetCommon satisfies CommonOptionsCommander interface and sets common option fields The method called by main for each command

type DeprecatedFlag

type DeprecatedFlag struct {
	Old       string
	New       string
	Version   string
	Collision bool
}

DeprecatedFlag contains information about deprecated option

type ImageGroup

type ImageGroup struct {
	Type string `long:"type" env:"TYPE" description:"type of storage" choice:"fs" choice:"bolt" choice:"rpc" default:"fs"` // nolint
	FS   struct {
		Path       string `long:"path" env:"PATH" default:"./var/pictures" description:"images location"`
		Staging    string `long:"staging" env:"STAGING" default:"./var/pictures.staging" description:"staging location"`
		Partitions int    `long:"partitions" env:"PARTITIONS" default:"100" description:"partitions (subdirs)"`
	} `group:"fs" namespace:"fs" env-namespace:"FS"`
	Bolt struct {
		File string `long:"file" env:"FILE" default:"./var/pictures.db" description:"images bolt file location"`
	} `group:"bolt" namespace:"bolt" env-namespace:"BOLT"`
	MaxSize      int      `long:"max-size" env:"MAX_SIZE" default:"5000000" description:"max size of image file"`
	ResizeWidth  int      `long:"resize-width" env:"RESIZE_WIDTH" default:"2400" description:"width of a resized image"`
	ResizeHeight int      `long:"resize-height" env:"RESIZE_HEIGHT" default:"900" description:"height of a resized image"`
	RPC          RPCGroup `group:"rpc" namespace:"rpc" env-namespace:"RPC"`
}

ImageGroup defines options group for store pictures

type ImageProxyGroup

type ImageProxyGroup struct {
	HTTP2HTTPS    bool `long:"http2https" env:"HTTP2HTTPS" description:"enable HTTP->HTTPS proxy"`
	CacheExternal bool `long:"cache-external" env:"CACHE_EXTERNAL" description:"enable caching for external images"`
}

ImageProxyGroup defines options group for image proxy

type ImportCommand

type ImportCommand struct {
	InputFile string `short:"f" long:"file" description:"input file name" required:"true"`
	Provider  string `short:"p" long:"provider" default:"disqus" choice:"disqus" choice:"wordpress" choice:"commento" description:"import format"` //nolint

	SupportCmdOpts
	CommonOpts
}

ImportCommand set of flags and command for import

func (*ImportCommand) Execute

func (ic *ImportCommand) Execute(_ []string) error

Execute runs import with ImportCommand parameters, entry point for "import" command

type LoadingCache

type LoadingCache interface {
	Get(key cache.Key, fn func() ([]byte, error)) (data []byte, err error) // load from cache if found or put to cache and return
	Flush(req cache.FlusherRequest)                                        // evict matched records
	Close() error
}

LoadingCache defines interface for caching

type NotifyGroup

type NotifyGroup struct {
	Type []string `` //nolint
	/* 197-byte string literal not displayed */
	Users []string `` //nolint
	/* 142-byte string literal not displayed */
	Admins []string `` //nolint
	/* 177-byte string literal not displayed */
	QueueSize int `long:"queue" env:"QUEUE" description:"size of notification queue" default:"100"`
	Telegram  struct {
		Channel string        `long:"chan" env:"CHAN" description:"the ID of telegram channel for admin notifications"`
		API     string        `long:"api" env:"API" default:"https://api.telegram.org/bot" description:"[deprecated, not used] telegram api prefix"`
		Token   string        `long:"token" env:"TOKEN" description:"[deprecated, use --telegram.token] telegram token"`
		Timeout time.Duration `long:"timeout" env:"TIMEOUT" default:"5s" description:"[deprecated, use --telegram.timeout] telegram timeout"`
	} `group:"telegram" namespace:"telegram" env-namespace:"TELEGRAM"`
	Email struct {
		From                string `long:"from_address" env:"FROM" description:"from email address"`
		VerificationSubject string `long:"verification_subj" env:"VERIFICATION_SUBJ" description:"verification message subject"`
		AdminNotifications  bool   `` /* 137-byte string literal not displayed */
	} `group:"email" namespace:"email" env-namespace:"EMAIL"`
	Slack struct {
		Token   string `long:"token" env:"TOKEN" description:"slack token"`
		Channel string `long:"chan" env:"CHAN" description:"slack channel for admin notifications"`
	} `group:"slack" namespace:"slack" env-namespace:"SLACK"`
	Webhook struct {
		URL      string   `long:"url" env:"URL" description:"webhook URL for admin notifications"`
		Template string   `long:"template" env:"TEMPLATE" description:"webhook authentication template" default:"{\"text\": \"{{.Text}}\"}"`
		Headers  []string `` // env NOTIFY_WEBHOOK_HEADERS split in code bellow to allow , inside ""
		/* 146-byte string literal not displayed */
		Timeout time.Duration `long:"timeout" env:"TIMEOUT" description:"webhook timeout" default:"5s"`
	} `group:"webhook" namespace:"webhook" env-namespace:"WEBHOOK"`
}

NotifyGroup defines options for notification

type RPCGroup

type RPCGroup struct {
	API          string        `long:"api" env:"API" description:"rpc extension api url"`
	TimeOut      time.Duration `long:"timeout" env:"TIMEOUT" default:"5s" description:"http timeout"`
	AuthUser     string        `long:"auth_user" env:"AUTH_USER" description:"basic auth user name"`
	AuthPassword string        `long:"auth_passwd" env:"AUTH_PASSWD" description:"basic auth user password"`
}

RPCGroup defines options for remote modules (plugins)

type RemapCommand

type RemapCommand struct {
	InputFile string `short:"f" long:"file" description:"input file name" required:"true"`

	SupportCmdOpts
	CommonOpts
}

RemapCommand set of flags and command for change linkage between comments to different urls based on given rules (input file)

func (*RemapCommand) Execute

func (rc *RemapCommand) Execute(_ []string) error

Execute runs (re)mapper with RemapCommand parameters, entry point for "remap" command

type RestoreCommand

type RestoreCommand struct {
	ImportPath string `short:"p" long:"path" env:"BACKUP_PATH" default:"./var/backup" description:"export path"`
	ImportFile string `short:"f" long:"file" default:"userbackup-{{.SITE}}-{{.YYYYMMDD}}.gz" description:"file name" required:"true"`

	SupportCmdOpts
	CommonOpts
}

RestoreCommand set of flags and command for restore from backup

func (*RestoreCommand) Execute

func (rc *RestoreCommand) Execute(args []string) error

Execute runs import with RestoreCommand parameters, entry point for "restore" command uses ImportCommand with constructed full file name

type SMTPGroup

type SMTPGroup struct {
	Host      string        `long:"host" env:"HOST" description:"SMTP host"`
	Port      int           `long:"port" env:"PORT" description:"SMTP port"`
	Username  string        `long:"username" env:"USERNAME" description:"SMTP user name"`
	Password  string        `long:"password" env:"PASSWORD" description:"SMTP password"`
	TLS       bool          `long:"tls" env:"TLS" description:"enable TLS"`
	LoginAuth bool          `long:"login_auth" env:"LOGIN_AUTH" description:"enable LOGIN auth instead of PLAIN"`
	StartTLS  bool          `long:"starttls" env:"STARTTLS" description:"enable StartTLS"`
	TimeOut   time.Duration `long:"timeout" env:"TIMEOUT" default:"10s" description:"SMTP TCP connection timeout"`
}

SMTPGroup defines options for SMTP server connection, used in auth and notify modules

type SSLGroup

type SSLGroup struct {
	Type         string `long:"type" env:"TYPE" description:"ssl (auto) support" choice:"none" choice:"static" choice:"auto" default:"none"` //nolint
	Port         int    `long:"port" env:"PORT" description:"port number for https server" default:"8443"`
	Cert         string `long:"cert" env:"CERT" description:"path to the cert.pem file"`
	Key          string `long:"key" env:"KEY" description:"path to the key.pem file"`
	ACMELocation string `` /* 133-byte string literal not displayed */
	ACMEEmail    string `long:"acme-email" env:"ACME_EMAIL" description:"admin email for certificate notifications"`
}

SSLGroup defines options group for server ssl params

type ServerCommand

type ServerCommand struct {
	Store      StoreGroup      `group:"store" namespace:"store" env-namespace:"STORE"`
	Avatar     AvatarGroup     `group:"avatar" namespace:"avatar" env-namespace:"AVATAR"`
	Cache      CacheGroup      `group:"cache" namespace:"cache" env-namespace:"CACHE"`
	Admin      AdminGroup      `group:"admin" namespace:"admin" env-namespace:"ADMIN"`
	Notify     NotifyGroup     `group:"notify" namespace:"notify" env-namespace:"NOTIFY"`
	SMTP       SMTPGroup       `group:"smtp" namespace:"smtp" env-namespace:"SMTP"`
	Telegram   TelegramGroup   `group:"telegram" namespace:"telegram" env-namespace:"TELEGRAM"`
	Image      ImageGroup      `group:"image" namespace:"image" env-namespace:"IMAGE"`
	SSL        SSLGroup        `group:"ssl" namespace:"ssl" env-namespace:"SSL"`
	ImageProxy ImageProxyGroup `group:"image-proxy" namespace:"image-proxy" env-namespace:"IMAGE_PROXY"`

	Sites            []string      `long:"site" env:"SITE" default:"remark" description:"site names" env-delim:","`
	AnonymousVote    bool          `long:"anon-vote" env:"ANON_VOTE" description:"enable anonymous votes (works only with VOTES_IP enabled)"`
	AdminPasswd      string        `long:"admin-passwd" env:"ADMIN_PASSWD" default:"" description:"admin basic auth password"`
	BackupLocation   string        `long:"backup" env:"BACKUP_PATH" default:"./var/backup" description:"backups location"`
	MaxBackupFiles   int           `long:"max-back" env:"MAX_BACKUP_FILES" default:"10" description:"max backups to keep"`
	LegacyImageProxy bool          `long:"img-proxy" env:"IMG_PROXY" description:"[deprecated, use image-proxy.http2https] enable image proxy"`
	MaxCommentSize   int           `long:"max-comment" env:"MAX_COMMENT_SIZE" default:"2048" description:"max comment size"`
	MaxVotes         int           `long:"max-votes" env:"MAX_VOTES" default:"-1" description:"maximum number of votes per comment"`
	RestrictVoteIP   bool          `long:"votes-ip" env:"VOTES_IP" description:"restrict votes from the same ip"`
	DurationVoteIP   time.Duration `long:"votes-ip-time" env:"VOTES_IP_TIME" default:"5m" description:"same ip vote duration"`
	LowScore         int           `long:"low-score" env:"LOW_SCORE" default:"-5" description:"low score threshold"`
	CriticalScore    int           `long:"critical-score" env:"CRITICAL_SCORE" default:"-10" description:"critical score threshold"`
	PositiveScore    bool          `long:"positive-score" env:"POSITIVE_SCORE" description:"enable positive score only"`
	ReadOnlyAge      int           `long:"read-age" env:"READONLY_AGE" default:"0" description:"read-only age of comments, days"`
	EditDuration     time.Duration `long:"edit-time" env:"EDIT_TIME" default:"5m" description:"edit window"`
	AdminEdit        bool          `long:"admin-edit" env:"ADMIN_EDIT" description:"unlimited edit for admins"`
	Port             int           `long:"port" env:"REMARK_PORT" default:"8080" description:"port"`
	Address          string        `long:"address" env:"REMARK_ADDRESS" default:"" description:"listening address"`
	WebRoot          string        `long:"web-root" env:"REMARK_WEB_ROOT" default:"./web" description:"web root directory"`
	UpdateLimit      float64       `long:"update-limit" env:"UPDATE_LIMIT" default:"0.5" description:"updates/sec limit"`
	RestrictedWords  []string      `long:"restricted-words" env:"RESTRICTED_WORDS" description:"words prohibited to use in comments" env-delim:","`
	RestrictedNames  []string      `long:"restricted-names" env:"RESTRICTED_NAMES" description:"names prohibited to use by user" env-delim:","`
	EnableEmoji      bool          `long:"emoji" env:"EMOJI" description:"enable emoji"`
	SimpleView       bool          `long:"simple-view" env:"SIMPLE_VIEW" description:"minimal comment editor mode"`
	ProxyCORS        bool          `long:"proxy-cors" env:"PROXY_CORS" description:"disable internal CORS and delegate it to proxy"`
	AllowedHosts     []string      `long:"allowed-hosts" env:"ALLOWED_HOSTS" description:"limit hosts/sources allowed to embed comments" env-delim:","`
	SubscribersOnly  bool          `long:"subscribers-only" env:"SUBSCRIBERS_ONLY" description:"enable commenting only for Patreon subscribers"`
	DisableSignature bool          `long:"disable-signature" env:"DISABLE_SIGNATURE" description:"disable server signature in headers"`

	Auth struct {
		TTL struct {
			JWT    time.Duration `long:"jwt" env:"JWT" default:"5m" description:"JWT TTL"`
			Cookie time.Duration `long:"cookie" env:"COOKIE" default:"200h" description:"auth cookie TTL"`
		} `group:"ttl" namespace:"ttl" env-namespace:"TTL"`

		SendJWTHeader bool   `long:"send-jwt-header" env:"SEND_JWT_HEADER" description:"send JWT as a header instead of cookie"`
		SameSite      string `` // nolint
		/* 157-byte string literal not displayed */

		Apple     AppleGroup `group:"apple" namespace:"apple" env-namespace:"APPLE" description:"Apple OAuth"`
		Google    AuthGroup  `group:"google" namespace:"google" env-namespace:"GOOGLE" description:"Google OAuth"`
		Github    AuthGroup  `group:"github" namespace:"github" env-namespace:"GITHUB" description:"Github OAuth"`
		Facebook  AuthGroup  `group:"facebook" namespace:"facebook" env-namespace:"FACEBOOK" description:"Facebook OAuth"`
		Microsoft AuthGroup  `group:"microsoft" namespace:"microsoft" env-namespace:"MICROSOFT" description:"Microsoft OAuth"`
		Yandex    AuthGroup  `group:"yandex" namespace:"yandex" env-namespace:"YANDEX" description:"Yandex OAuth"`
		Twitter   AuthGroup  `group:"twitter" namespace:"twitter" env-namespace:"TWITTER" description:"Twitter OAuth"`
		Patreon   AuthGroup  `group:"patreon" namespace:"patreon" env-namespace:"PATREON" description:"Patreon OAuth"`
		Telegram  bool       `long:"telegram" env:"TELEGRAM" description:"Enable Telegram auth (using token from telegram.token)"`
		Dev       bool       `long:"dev" env:"DEV" description:"enable dev (local) oauth2"`
		Anonymous bool       `long:"anon" env:"ANON" description:"enable anonymous login"`
		Email     struct {
			Enable       bool          `long:"enable" env:"ENABLE" description:"enable auth via email"`
			From         string        `long:"from" env:"FROM" description:"from email address"`
			Subject      string        `long:"subj" env:"SUBJ" default:"remark42 confirmation" description:"email's subject"`
			ContentType  string        `long:"content-type" env:"CONTENT_TYPE" default:"text/html" description:"content type"`
			Host         string        `long:"host" env:"HOST" description:"[deprecated, use --smtp.host] SMTP host"`
			Port         int           `long:"port" env:"PORT" description:"[deprecated, use --smtp.port] SMTP password"`
			SMTPPassword string        `long:"passwd" env:"PASSWD" description:"[deprecated, use --smtp.password] SMTP port"`
			SMTPUserName string        `long:"user" env:"USER" description:"[deprecated, use --smtp.username] enable TLS"`
			TLS          bool          `long:"tls" env:"TLS" description:"[deprecated, use --smtp.tls] SMTP TCP connection timeout"`
			TimeOut      time.Duration `long:"timeout" env:"TIMEOUT" default:"10s" description:"[deprecated, use --smtp.timeout] SMTP TCP connection timeout"`
			MsgTemplate  string        `long:"template" env:"TEMPLATE" description:"[deprecated] message template file" default:"email_confirmation_login.html.tmpl"`
		} `group:"email" namespace:"email" env-namespace:"EMAIL"`
	} `group:"auth" namespace:"auth" env-namespace:"AUTH"`

	CommonOpts
	// contains filtered or unexported fields
}

ServerCommand with command line flags and env

func (*ServerCommand) Execute

func (s *ServerCommand) Execute(_ []string) error

Execute is the entry point for "server" command, called by flag parser

func (*ServerCommand) HandleDeprecatedFlags

func (s *ServerCommand) HandleDeprecatedFlags() (result []DeprecatedFlag)

HandleDeprecatedFlags sets new flags from deprecated returns their list. Returned list has DeprecatedFlag.Old and DeprecatedFlag.Version set, and DeprecatedFlag.New is optional (as some entries are removed without substitute). Also it returns flags found by findDeprecatedFlagsCollisions, with DeprecatedFlag.Collision flag set.

type StoreGroup

type StoreGroup struct {
	Type string `long:"type" env:"TYPE" description:"type of storage" choice:"bolt" choice:"rpc" default:"bolt"` // nolint
	Bolt struct {
		Path    string        `long:"path" env:"PATH" default:"./var" description:"parent directory for the bolt files"`
		Timeout time.Duration `long:"timeout" env:"TIMEOUT" default:"30s" description:"bolt timeout"`
	} `group:"bolt" namespace:"bolt" env-namespace:"BOLT"`
	RPC RPCGroup `group:"rpc" namespace:"rpc" env-namespace:"RPC"`
}

StoreGroup defines options group for store params

type SupportCmdOpts added in v1.11.0

type SupportCmdOpts struct {
	Site        string        `short:"s" long:"site" env:"SITE" default:"remark" description:"site name"`
	AdminPasswd string        `long:"admin-passwd" env:"ADMIN_PASSWD" default:"" description:"admin basic auth password"`
	Timeout     time.Duration `long:"timeout" default:"60m" description:"timeout for the command run"`
}

SupportCmdOpts is set of commands shared among similar commands like backup/restore and such. Order of fields defines the help command output order.

type TelegramGroup added in v1.9.0

type TelegramGroup struct {
	Token   string        `long:"token" env:"TOKEN" description:"telegram token (used for auth and telegram notifications)"`
	Timeout time.Duration `long:"timeout" env:"TIMEOUT" default:"5s" description:"telegram timeout"`
}

TelegramGroup defines token for Telegram used in notify and auth modules

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL