Configuring

Sharry’s executable can take one argument – a configuration file. If that is not given, the defaults are used. The config file overrides default values, so only values that differ from the defaults are necessary to specify.

Environment variables can be used as well to override values from the config file. Variable names always start with SHARRY_ and the remainder can be derived from the corresponding config option by replacing period . and dash - by an underscore _, but excluding the root namespace sharry.restserver. For example, the config option sharry.restserver.bind.address would be SHARRY_BIND_ADDRESS as environment variable. A value given as environment variable has priority.

File Format

The format of the configuration files can be HOCON, JSON or whatever the used config library understands. The default values below are in HOCON format, which is recommended, since it allows comments and has some advanced features. Please refer to their documentation for more on this.

The hocon format allows to include environment variables, allowing to mix and match both variants if desired. For example:

mode = "open"
  mode = ${?SHARRY_BACKEND_SIGNUP_MODE}
…

would use the value "open" if the environment varible SHARRY_BACKEND_SIGNUP_MODE is not defined, because it would overwrite the previously defined value.

Important Config Options

The configuration for the REST server is below sharry.restserver.

JDBC

This configures the connection to the database. By default, a H2 database in the current /tmp directory is configured. This will create the database on demand in this directory.

The config looks like this:

sharry.restserver.backend.jdbc {
  url = ...
  user = ...
  password = ...
}

The url is the connection to the database. It must start with jdbc, followed by name of the database. The rest is specific to the database used: it is either a path to a file for H2 or a host/database url for MariaDB and PostgreSQL.

When using H2, the user is sa, the password can be empty and the url must include these options:

;MODE=PostgreSQL;DATABASE_TO_LOWER=TRUE

Examples

PostgreSQL:

url = "jdbc:postgresql://localhost:5432/sharrydb"

MariaDB:

url = "jdbc:mariadb://localhost:3306/sharrydb"

H2

url = "jdbc:h2:///path/to/a/file.db;MODE=PostgreSQL;DATABASE_TO_LOWER=TRUE"

Database Checks

The setting database-domain-checks is used when inspecting errors that happen when uploading files. It allows to translate database error messages into a message that is presented to the end user.

Please see this issue for more information and motivation.

The example provided is this:

sharry.restserver.backend.share {

  # Allows additional database checks to be translated into some
  # meaningful message to the user.
  #
  # This config is used when inspecting database error messages.
  # If the error message from the database contains the defined
  # `native` part, then the server returns a 422 with the error
  # messages given here as `message`.
  #
  # See issue https://github.com/eikek/sharry/issues/255 – the
  # example is a virus check via a postgresql extension "snakeoil".
  database-domain-checks = {
    # Example: This message originates from postgres with an
    # enabled snakeoil extension. This extension allows to virus
    # check byte arrays. It must be setup such that the `bytea`
    # type of the filechunk table is changed to the type
    # `safe_bytea`:
    #
    # CREATE EXTENSION pg_snakeoil;
    # CREATE DOMAIN public.safe_bytea as bytea CHECK (not so_is_infected(value));
    # ALTER TABLE public.filechunk ALTER COLUMN chunkdata TYPE safe_bytea;
    snakeoil = {
      enabled = false
      native = "domain safe_bytea violates check constraint"
      message = "The uploaded file contains a virus!"
    }
  }
}

Example for Snakeoil

The extension snakeoil for PostgreSQL allows to check uploaded binary data for viruses.

In order to use this, you need to change the data type for the binary files. This must be applied after sharry has started at least once to initialize its database!

The following steps must be done manually:

  • install pg_snakeoil - e.g. on ubuntu systems package: postgresql-12-snakeoil
  • execute the following sql commands on the sharry postgres database:
    CREATE EXTENSION pg_snakeoil;
    CREATE DOMAIN public.safe_bytea as bytea check (not so_is_infected(value));
    ALTER TABLE public.filechunk ALTER COLUMN chunkdata TYPE safe_bytea;
    

Then add the above setting into your config file. Test files can be found here.

Files

By default, the files are also stored in the configured database. This works quite well, but you can also choose to store the files somewhere else: either in the local filesystem or in an S3 compatible object storage.

This is configured in the files section:

    # How files are stored.
    files {
      # The id of an enabled store from the `stores` array that should
      # be used.
      default-store = "database"

      # A list of possible file stores. Each entry must have a unique
      # id. The `type` is one of: default-database, filesystem, s3.
      #
      # All stores with enabled=false are
      # removed from the list. The `default-store` must be enabled.
      stores = {
        database =
          { enabled = true
            type = "default-database"
          }

        filesystem =
          { enabled = false
            type = "file-system"
            directory = "/some/directory"
            # If true, empty directories that can be left behind after deleting 
            # a file are removed as well.
            clean-empty-dirs = true
          }

        minio =
          { enabled = false
            type = "s3"
            endpoint = "http://localhost:9000"
            access-key = "username"
            secret-key = "password"
            bucket = "sharry"
          }
      }
      ...
    }

This config section requires to define a file store in stores and then reference the key in default-store. Within stores you can define what kind of storage to use via the type attribute. This can be one of: s3, file-system or default-database. Depending on type more information is required. For example, the filesystem needs the base directory to use, or the above example for Minio requires credentials and a bucket.

Changing file stores

The last part in the files section looks like this:

 # Allows to copy files from one store to the other *before* sharry
 # will be available. It is recommended to set the `enabled` flag to
 # false afterwards and restart sharry.
 #
 # Files are only copied, they are *not* removed from the source
 # store.
 copy-files = {
   enable = false

   # A key in the `backend.files` config identifying the store to
   # copy from.
   source = "database"

   # A key in the `backend.files` config identifying the store to
   # copy the files to.
   target = "minio"

   # How many files to copy in parallel.
   parallel = 2
 }

This allows you to have Sharry copy all files from one store to the other on startup. So to change from database to minio as in the example, set enabled to true and change the default-store to minio (the target store). When starting up sharry it will first copy all files to the minio store before it is available.

Bind

The host and port the http server binds to.

sharry.restserver.bind {
  address = localhost
  port = 9090
}

By default, it binds to localhost and some predefined port.

Base-url

The base url is an important setting that defines the URL where sharry can be reached (the external url). The REST server uses it to create absolute urls and to configure the authenication cookie. These URLs are sent to the client, so they must resolve back to the sharry server. If you see “network error” error messages in the browser, then this setting is probably not correct.

By default it is set to http://localhost:9090. If you leave it at localhost, then sharry uses the request to obtain the real external url dynamically by inspecting http headers and finally falling back to the bind.address|port.

However, if you have a single external url, it is recommended to set this here.

Examples

sharry.restserver.base-url = "https://sharry.example.com"

Registration Options

This defines if and how new users can create accounts. There are 3 options:

  • closed no new user can sign up
  • open new users can sign up
  • invite new users can sign up but require an invitation key
sharry.restserver.backend.signup {
  mode = "open"

  # If mode == 'invite', a password must be provided to generate
  # invitation keys. It must not be empty.
  invite-password = ""

  # If mode == 'invite', this is the period an invitation token is
  # considered valid.
  invite-time = "3 days"
}

The mode invite is intended to open the application only to some users. An admin user can create invitation keys and distribute them to the desired people. While the user must be admin, it is also necessary to provide the invite-password. The idea is that only the person who installs sharry knows this. If it is not set (must be non-empty), then invitation won’t work. New invitation keys can be generated from within the web application or via REST calls (using curl, for example).

curl -X POST -H 'Sharry-Auth: ' -d '{"password":"blabla"}' "http://localhost:7880/api/v1/open/signup/newinvite"

Authentication

The initial authentication will generate an authentication token which is valid for some time. Subsequent calls to secured routes can use this token. The token can be given as a normal http header or via a cookie header.

The following options configure this token:

sharry.restserver.backend.auth {
  server-secret = "hex:caffee" # or "b64:Y2FmZmVlCg=="
  session-valid = "8 minutes"
}

The server-secret is used to sign the token. If multiple REST servers are deployed, all must share the same server secret. Otherwise tokens from one instance are not valid on another instance. The secret can be given as Base64 encoded string or in hex form. Use the prefix hex: and b64:, respectively. If these prefixes are missing, the string’s utf8 bytes are used.

The session-valid deterimens how long a token is valid. This can be just some minutes, the web application obtains new ones periodically. So a rather short time is recommended.

The interval the webapp retrieves a new token can be configured, too. It must be at least 30s below the session-valid time.

sharry.restserver.webapp {
  auth-renewal = "4 minutes"
}

Login Modules

Login modules are used to initially authenticate a user given some credentials. There are some modules that take a username/password pair and hand it to an external service or program for verification. If valid, sharry creates an account transparently. Then there is the oauth setting which supports authentication via OAuth using “OAuth Code Flow”.

All login modules can be enabled/disabled and have an order property that defines the order the login modules are tried. The modules are tried in the specified order until one gives a response.

Fixed

This is a simple login module for bootstrapping. It defines an admin account using the supplied username and password (plain text) from the config file.

fixed {
  enabled = false
  user = "admin"
  password = "admin"
  order = 10
}

It is disabled by default. If the given username doesn’t match the configured username this login module is skipped and the next is tried.

Http

The http login module issues a http request with the username/password pair as payload. The response status code determines valid authentication.

http {
  enabled = false
  url = "http://localhost:1234/auth?user={{user}}&password={{pass}}"
  method = "POST"
  body = ""
  content-type = ""
  order = 20
}

If the method is POST, the body is sent as specified using the given content type. The body and url are processed before as mustache templates, where {{user}} and {{pass}} are replaced by their actual values. For other requests than POST, the body is ignored.

Http Basic

The http-basic login module issues a http request with an Authorization header against some configured url. The header uses the Basic scheme to transport the username/password pair.

http-basic {
  enabled = false
  url = "http://somehost:2345/path"
  method = "GET"
  order = 30
}

If the response is successful (in 2xx), the user is authenticated.

Command

Allows to validate a username/password pair using some external system command. This is the most flexible approach.

command {
  enabled = false
  program = [
    "/path/to/someprogram"
    "{{login}}"
    "{{pass}}"
  ]
  # the return code to consider successful verification
  success = 0
  order = 30
}

The return code of the command is used to determine valid authentication. The program value is an array where the first item is the path to the program and subsequent elements define its arguments.

All arguments are processed as a mustache template and variables {{user}} and {{pass}} are replaced by their actual values.

Internal

The internal login module simply authenticates against the sharry database. If it is disabled, you should disable signup, too, because those user won’t be authenticated.

Proxy

The proxy option allows automatically authenticate users by trusting specific request headers. The configured headers of the login request to open/auth/proxy are read and a user account is created if missing. Be aware that sharry blindly trusts these headers.

proxy {
  enabled = false
  user-header = "X-Valid-User"
  email-header = "X-User-Email"
}

The webapp automatically logs the user in, if the auth configuration only consists of proxy auth and nothing else.

OAuth

There is now an option to authenticate using a external provider supporting the OAuth “code flow”. There are two examples in the config file for Github and Google. I tried to generalise it as much as possible, but (it seems to me) OAuth is not really a protocol, every provider may choose to do it little differently.

The oauth login module can be configured with multiple such providers. Here is an example:

oauth = {
  github = {
    enabled = false
    name = "Github"
    icon = "fab fa-github"
    authorize-url = "https://github.com/login/oauth/authorize"
    token-url = "https://github.com/login/oauth/access_token"
    user-url = "https://api.github.com/user"
    user-id-key = "login"
    scope = ""
    client-id = "<your client id>"
    client-secret = "<your client secret>"
  }
}

Each such entry in the oauth object results in a button on the login screen. The key (github in the above example) is used to refer to this provider as its id.

Here is how it roughly works: If a user clicks this button, it reaches a specific url in sharry. Sharry will read the corresponding config entry and redirect to the provider adding all the necessary details. The user then authenticates at the provider, which redirects back to sharry – so this method only works if sharry is publicly available, obviously. Then sharry does one more request to turn the code from the redirect into a different code. And then it tries to get the account name.

Let’s go through the config values of one entry:

  • enabled: allows to disable this entry without removing it from the file.
  • id: the id that is used in the url behind the button on the login screen. It is also used to amend the account name.
  • name: The name rendered as button text.
  • icon: an icon for the button, included are fa-github, fa-openid, fa-google, fa-user, fa-right-to-bracket
  • authorize-url this is the URL of the provider where sharry redirects to at first, attaching client_id and the redirect uri back to sharry.
  • token-url: The url to the provdier where the response from the authorize-url can be turned into a token.
  • user-url: The url to the provider that retrieves the user information given a token as obtained from token-url.
  • user-id-key: Now it get’s a bit hairy…. The protocol doesn’t define (afaik) a common way how to exchange user data. So google does it different from github. Sharry supports JSON responses only and uses the value of user-id-key to lookup a value in that response structure. For example, the github response is a simple JSON object, where the login name is at field login. The path must evaluate to a string. This value is used for the new account inside sharry. Another value that often works is preferred_username.
  • user-email-key: optional, if present is used to populated the email field of the newly created account.
  • scope: optional, can be empty. Allows to specify the open id scopes to use when initiating the authentication. This can be useful to return custom data from the IDP to be used with user-email-key or user-id-key
  • client-id and client-secret These are provider specific values that you need to obtain there. With github, for example, you register a new “app” which generates these values.

Once sharry gets the account name, it creates a new account (if it not exists already) using the account name from the provider amended with @<id>.

I only tested this with github and google, I would appreciate any information on how it works with other providers.

Cleanup

Sharry has a periodic cleanup job that will delete ‘invalid’ resources. This cleanup job runs in the backend and is triggered by a timer every interval as defined in the cleanup block. It cleans up the following resources:

  • Expired, published shares and their files are removed once the expiration datetime is older than invalid-age defined in the cleanup block.
  • Expired invites are removed based on the invite-time from the signup block.
  • Orphaned files are removed. However, orphaned files should not happen as long as all shares and files are maintained by only sharry and not any external modifications.

Default Config


sharry.restserver {

  # This is the base URL this application is deployed to. This is used
  # to create absolute URLs and to configure the cookie.
  #
  # Note: Currently deploying behind a path is not supported. The URL
  # should not end in a slash.
  base-url = "http://localhost:9090"


  # Where the server binds to.
  bind {
    address = "localhost"
    port = 9090
  }

  file-download {
    # For open range requests, use this amount of data when
    # responding.
    download-chunk-size = "4M"
  }

  # Configures logging
  logging {
    # The format for the log messages. Can be one of:
    # Json, Logfmt, Fancy or Plain
    format = "Fancy"

    # The minimum level to log. From lowest to highest:
    # Trace, Debug, Info, Warn, Error
    minimum-level = "Warn"

    # Override the log level of specific loggers
    levels = {
      "sharry" = "Info"
      "org.flywaydb" = "Info"
      "binny" = "Info"
      "org.http4s" = "Info"
    }
  }

  # The alias-member feature allows to add users to an alias page to
  # automatically make all shares that were uploaded through the
  # corresponding alias available to all members. This allows to
  # search for other users via a http call. If this feature is
  # disabled, the rest call to search other users is disabled and the
  # form element is removed from the ui.
  alias-member-enabled = true

  webapp {
    # This is shown in the top right corner of the web application
    app-name = "Sharry"

    # The icon next to the app-name. Needs to be an URL to an image.
    app-icon = ""

    # The icon next to the app-name when dark mode is enabled.
    app-icon-dark = ""

    # The login and register pages display a logo image, by default
    # the Sharry logo. This can be changed here. It needs to be an URL
    # to an image.
    app-logo = ""

    # The login and register pages display a logo image. This is the
    # one used when dark mode is enabled.
    app-logo-dark = ""

    # This is markdown that is inserted as the footer on each page in
    # the ui. If left empty, a link to the project is rendered.
    app-footer = ""

    # Whether to display the footer on each page in the ui. Set it to
    # false to hide it.
    app-footer-visible = true

    # Chunk size used for one request. The server will re-chunk the
    # stream into smaller chunks. But the client can transfer more in
    # one requests, resulting in faster uploads.
    #
    # You might need to adjust this value depending on your setup. A
    # higher value usually means faster uploads (if the up-link is
    # good enough). It is set rather low by default, because it is a
    # safer default.
    chunk-size = "10M"

    # Number of milliseconds the client should wait before doing a new
    # upload attempt after something failed. The length of the array
    # denotes the number of retries.
    retry-delays = [0, 3000, 6000, 12000, 24000, 48000]

    # The login page can display a welcome message that is readable by
    # everyone. The text is processed as markdown.
    welcome-message = ""

    # The ISO-3166-1 code of the default language to use. If a invalid
    # code is given (or one where no language is available), it falls
    # back to "gb".
    default-language = "gb"

    # The interval a new authentication token is retrieved. This must
    # be at least 30s lower than `backend.auth.session-valid'.
    auth-renewal = "4 minutes"

    # The initial page to go to after logging in. It can be one of the
    # following: home, uploads, share
    initial-page = "home"

    # The value for the validity that is preselected. Only values that
    # are available in the dropdown are possible to specifiy.
    default-validity = 7 days

    # The inital ui theme to use. Can be either 'light' or 'dark'.
    initial-theme = "light"

    # When only OAuth (or only Proxy Auth) is configured and only a
    # single provider, then the weapp automatically redirects to its
    # authentication page skipping the sharry login page. This will
    # also disable the logout button, since sharry is not in charge
    # anyways.
    oauth-auto-redirect = true

    # A custom html snippet that is rendered into the html head
    # section of the main template. If the value is empty, a default
    # section is used for inserting a favicon configuration.
    #
    # The value is first tried to resolve to a file in the local file
    # system. If that is successful, it its content is being inserted
    # as utf8 characters. Otherwise the value given here is rendered
    # as is into the template!
    custom-head = ""
  }

  backend {

    # Authentication is flexible to let Sharry be integrated in other
    # environments.
    auth {

      # The secret for this server that is used to sign the authenicator
      # tokens. You can use base64 or hex strings (prefix with b64: and
      # hex:, respectively)
      server-secret = "hex:caffee"

      # How long an authentication token is valid. The web application
      # will get a new one periodically.
      session-valid = "8 minutes"

      #### Login Modules
      ##
      ## The following settings configure how users are authenticated.
      ## There are several ways possible. The simplest is to
      ## authenticate agains the internal database. But often there is
      ## already a user management component and sharry can be
      ## configured to authenticated against other services.

      # A fixed login module simply checks the username and password
      # agains the information provided here. This only applies if the
      # user matches, otherwise the next login module is tried.
      fixed {
        enabled = false
        user = "admin"
        password = "admin"
        order = 10
      }

      # The http authentication module sends the username and password
      # via a HTTP request and uses the response to indicate success or
      # failure.
      #
      # If the method is POST, the `body' is sent with the request and
      # the `content-type' is used.
      http {
        enabled = false
        url = "http://localhost:1234/auth?user={{user}}&password={{pass}}"
        method = "POST"
        body = ""
        content-type = ""
        order = 20
      }

      # Use HTTP Basic authentication. An Authorization header using
      # the Basic scheme is created and the request is send to the
      # given url. The response body will be ignored, only the status
      # is inspected.
      http-basic {
        enabled = false
        url = "http://somehost:2345/path"
        method = "GET"
        order = 30
      }

      # The command authentication module runs an external command
      # giving it the username and password. The return code indicates
      # success or failure.
      command {
        enabled = false
        program = [
          "/path/to/someprogram"
          "{{user}}"
          "{{pass}}"
        ]
        # the return code to consider successful verification
        success = 0
        order = 40
      }

      # The internal authentication module checks against the internal
      # database.
      internal {
        enabled = true
        order = 50
      }

      # Uses OAuth2 "Code-Flow" for authentication against a
      # configured provider.
      #
      # A provider (like Github, Google, or Microsoft for example) must be
      # configured correctly for this to work. Each element in the array
      # results into a button on the login page.
      #
      # Examples for Github, Google and Microsoft (Azure AD) are provided
      # below. You need to setup an “application” to obtain a client_secret
      # and client_id.
      #
      # Details:
      # - enabled: allows to toggle it on or off
      # - id: a unique id that is part of the url
      # - name: a name that is displayed inside the button on the
      #   login screen
      # - icon: a fontawesome icon name for the button
      # - authorize-url: the url of the provider where the user can
      #   login and grant the permission to retrieve the user name
      # - token-url: the url used to obtain a bearer token using the
      #   response from the authentication above. The response from
      #   the provider must be json or url-form-encdode.
      # - user-url: the url to finalyy retrieve user information –
      #   only JSON responses are supported.
      # - user-id-key: the name of the field in the json response
      #   denoting the user name
      # - user-email-key: the name of the field in the json response
      #   that denotes the users email.
      oauth = {
        "github" = {
          enabled = false
          name = "Github"
          icon = "fab fa-github"
          scope = ""
          authorize-url = "https://github.com/login/oauth/authorize"
          token-url = "https://github.com/login/oauth/access_token"
          user-url = "https://api.github.com/user"
          user-id-key = "login"
          client-id = "<your client id>"
          client-secret = "<your client secret>"
        },
        "google" = {
          enabled = false
          name = "Google"
          icon = "fab fa-google"
          scope = ""
          authorize-url = "https://accounts.google.com/o/oauth2/v2/auth?scope=https://www.googleapis.com/auth/userinfo.profile"
          token-url = "https://oauth2.googleapis.com/token"
          user-url = "https://www.googleapis.com/oauth2/v1/userinfo?alt=json"
          user-id-key = "name"
          client-id = "<your client id>"
          client-secret = "<your client secret>"
        },
        "aad" = {
          enabled = false
          name = "Azure AD"
          icon = "fab fa-microsoft"
          scope = "openid"
          authorize-url = "https://login.microsoftonline.com/<your tenant ID>/oauth2/v2.0/authorize"
          token-url = "https://login.microsoftonline.com/<your tenant ID>/oauth2/v2.0/token"
          user-url = "https://graph.microsoft.com/oidc/userinfo"
          user-id-key = "email"
          user-email-key = "email"
          client-id = "<your client id>"
          client-secret = "<your client secret>"
        }
      }

      # Allows to inspect the request headers for finding already
      # authorized user name/email. If enabled and during login the
      # request contains these headers, they will be used to
      # automatically create accounts.
      proxy {
        enabled = false
        user-header = "X-Valid-User"
        email-header = "X-User-Email"
      }
    }

    # The database connection.
    #
    # By default a H2 file-based database is configured. You can
    # provide a postgresql or mariadb connection here. When using H2
    # use the PostgreSQL compatibility mode.
    jdbc {
      url = "jdbc:h2://"${java.io.tmpdir}"/sharry-demo.db;MODE=PostgreSQL;DATABASE_TO_LOWER=TRUE"
      user = "sa"
      password = ""
    }

    # How files are stored.
    files {
      # The id of an enabled store from the `stores` array that should
      # be used.
      default-store = "database"

      # A list of possible file stores. Each entry must have a unique
      # id. The `type` is one of: default-database, filesystem, s3.
      #
      # All stores with enabled=false are
      # removed from the list. The `default-store` must be enabled.
      stores = {
        database =
          { enabled = true
            type = "default-database"
          }

        filesystem =
          { enabled = false
            type = "file-system"
            directory = "/some/directory"
            clean-empty-dirs = true
          }

        minio =
          { enabled = false
            type = "s3"
            endpoint = "http://localhost:9000"
            access-key = "username"
            secret-key = "password"
            bucket = "sharry"
          }
      }

      # Allows to copy files from one store to the other *before* sharry
      # will be available. It is recommended to set the `enabled` flag to
      # false afterwards and restart sharry.
      #
      # Files are only copied, they are *not* removed from the source
      # store.
      copy-files = {
        enable = false

        # A key in the `backend.files` config identifying the store to
        # copy from.
        source = "database"

        # A key in the `backend.files` config identifying the store to
        # copy the files to.
        target = "minio"

        # How many files to copy in parallel.
        parallel = 2
      }
    }

    # Checksums of uploaded files are computed in the background.
    compute-checksum = {
      # Setting this to false disables computation of checksums completely.
      enable = true

      # How many ids to queue at most. If full, uploading blocks until
      # elemnts are taken off the queue
      capacity = 5000

      # How many checksums to compute in parallel, must be > 0. If 1,
      # they are computed sequentially.
      parallel = 0

      # If true, the `parallel` option above is ignored and it will be
      # set to the number of available cores - 1 (using 1 for single
      # core machines).
      use-default = true
    }

    # Configuration for registering new users at the local database.
    # Accounts registered here are checked via the `internal'
    # authentication plugin as described above.
    signup {

      # The mode defines if new users can signup or not. It can have
      # three values:
      #
      # - open: every new user can sign up
      # - invite: new users can sign up only if they provide a correct
      #   invitation key. Invitation keys can be generated by an admin.
      # - closed: signing up is disabled.
      mode = "open"

      # If mode == 'invite', this is the period an invitation token is
      # considered valid.
      invite-time = "14 days"

      # A password that is required when generating invitation keys.
      # This is more to protect against accidentally creating
      # invitation keys. Generating such keys is only permitted to
      # admin users.
      invite-password = "generate-invite"
    }


    share {
      # When storing binary data use chunks of this size.
      chunk-size = "512K"

      # Maximum size of a share.
      max-size = "1.5G"

      # Maximum validity for uploads
      max-validity = 365 days

      # Allows additional database checks to be translated into some
      # meaningful message to the user.
      #
      # This config is used when inspecting database error messages.
      # If the error message from the database contains the defined
      # `native` part, then the server returns a 422 with the error
      # messages given here as `message`.
      #
      # See issue https://github.com/eikek/sharry/issues/255 – the
      # example is a virus check via a postgresql extension "snakeoil".
      database-domain-checks = {
        # Example: This message originates from postgres with an
        # enabled snakeoil extension. This extension allows to virus
        # check byte arrays. It must be setup such that the `bytea`
        # type of the filechunk table is changed to the type
        # `safe_bytea`:
        #
        # CREATE EXTENSION pg_snakeoil;
        # CREATE DOMAIN public.safe_bytea as bytea CHECK (not so_is_infected(value));
        # ALTER TABLE public.filechunk ALTER COLUMN chunkdata TYPE safe_bytea;
        snakeoil = {
          enabled = false
          native = "domain safe_bytea violates check constraint"
          message = "The uploaded file contains a virus!"
        }
      }
    }

    cleanup {
      # Whether to enable the cleanup job that periodically
      # cleans up published, expired shares and expired invites
      enabled = true

      # The interval for the cleanup job
      interval = 14 days

      # Time of published shares past expiration to get collected by cleanup job
      invalid-age = 7 days
    }

    mail {

      # Enable/Disable the mail feature.
      #
      # If it is disabled, the server will not send mails, including
      # notifications.
      #
      # If enabled, explicit SMTP settings must be provided.
      enabled = false

      # The SMTP settings that are used to sent mails with.
      smtp {
        # Host and port of the SMTP server
        host = "localhost"
        port = 25

        # User credentials to authenticate at the server. If the user
        # is empty, mails are sent without authentication.
        user = ""
        password = ""

        # One of: none, starttls, ssl
        ssl-type = "starttls"

        # In case of self-signed certificates or other problems like
        # that, checking certificates can be disabled.
        check-certificates = true

        # Timeout for mail commands.
        timeout = "10 seconds"

        # The default mail address used for the `From' field.
        #
        # If left empty, the e-mail address of the current user is used.
        default-from = ""

        # When creating mails, the List-Id header is set to this value.
        #
        # This helps identifying these mails in muas. If it is empty,
        # the header is not set.
        list-id = "Sharry"
      }

      templates = {
        download = {
          subject = "Download ready."
          body = """Hello,

there are some files for you to download. Visit this link:

{{{url}}}

{{#password}}
The required password will be sent by other means.
{{/password}}


Greetings,
{{user}} via Sharry
"""
        }

        alias = {
          subject = "Link for Upload"
          body = """Hello,

please use the following link to sent files to me:

{{{url}}}

Greetings,
{{user}} via Sharry
"""
        }

        upload-notify = {
          subject = "[Sharry] Files arrived"
          body = """Hello {{user}},

there have been files uploaded for you via the alias '{{aliasName}}'.
View it here:

{{{url}}}

Greetings,
Sharry
"""
        }
      }
    }
  }
}


Logging

Sharry logs to stdout. This works well, when managed by systemd or similar tools. Logging can be configured with these settings in the config file:

  • logging.minimum-level specifies the log level to control the verbosity. Levels are ordered from: Trace, Debug, Info, Warn and Error
  • logging.format this defines how the logs are formatted. There are two formats for humans: Plain and Fancy. And two more suited for machine consumption: Json and Logfmt. The Json format contains all details, while the others may omit some for readability
  • levels optional logger name to level mappings to override the log level for specific loggers. If not mentioned here, everything is logged with minimum-level.

The default sets some selected loggers to info and the others to warn.