Skip to content

Commit

Permalink
Docker enable (#49)
Browse files Browse the repository at this point in the history
* Accept ENV Vars (Docker compatilibity)

* Initial Docker README

* Fix Traefik inside Details

* Add all settings from ENV except Titles

* remove SafeWriteConfig

* Updated compose example

* Config File wouldn't make sense on Docker. Renamed

* A few tweaks on defaults and docs before merge

* Moved docker below `Dev or build from source`

* Update README.md

Co-authored-by: Rémy Boulanouar <[email protected]>

* Update README.md

Co-authored-by: Rémy Boulanouar <[email protected]>

* Add "See Docker" to "🎮 Use"

---------

Co-authored-by: Rémy Boulanouar <[email protected]>
  • Loading branch information
Helvio88 and DblK committed Nov 22, 2023
1 parent 396e1e3 commit 66299f0
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 68 deletions.
79 changes: 78 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ To proper use this software, here is the checklist:
- [ ] Comment/Uncomment parts in the config according to your needs
- [ ] Games should have in their name `[ID][v0]` to be recognized
- [ ] Games extension should be `nsp` or `nsz`
- [ ] Retrieve binary from [latest release](https://github.com/DblK/tinshop/releases) or [container](https://github.com/DblK/tinshop/pkgs/container/tinshop) or build from source (See [`Dev`](https://github.com/DblK/tinshop/tree/master#-dev-or-build-from-source) section below)
- [ ] Retrieve binary from [latest release](https://github.com/DblK/tinshop/releases) or [container](https://github.com/DblK/tinshop/pkgs/container/tinshop) (See [`Docker`](https://github.com/DblK/tinshop/tree/master#-docker) section below) or build from source (See [`Dev`](https://github.com/DblK/tinshop/tree/master#-dev-or-build-from-source) section below)

Now simply run it and add a shop inside tinfoil with the address setup in `config` (or `http:https://localIp:3000` if not specified).

Expand Down Expand Up @@ -67,6 +67,52 @@ If you want to build `TinShop` from source, please run `go build`.

And then, simply run `./tinshop`.

# 🐋 Docker

To run with [Docker](https://docs.docker.com/engine/install/), you can use this as a starting `cli` example:

`docker run -d --restart=always -e TINSHOP_SOURCES_DIRECTORIES=/games -e TINSHOP_WELCOMEMESSAGE="Welcome to my Tinshop!" -v /local/game/backups:/games -p 3000:3000 ghcr.io/dblk/tinshop:latest`

This will run Tinshop on `http:https://localhost:3000` and persist across reboots!

If `docker compose` is your thing, then start with this example:

```yaml
version: '3.9'
services:
tinshop:
container_name: tinshop
image: ghcr.io/dblk/tinshop:latest
restart: always
ports:
- 3000:3000
environment:
- TINSHOP_SOURCES_DIRECTORIES=/games
- TINSHOP_WELCOMEMESSAGE=Welcome to my Tinshop!
volumes:
- /media/switch:/games
```
All of the settings in the `config.yaml` file are valid Environment Variables. They must be `UPPERCASE` and prefixed by `TINSHOP_`. Nested properties should be prefixed by `_`. Here are a few examples:

| ENV_VAR | `config.yaml` entry | Default Value | Example Value |
|------------------------------|---------------------|--------------------------------|-----------------------------------|
| TINSHOP_HOST | host | `<empty>` | `tinshop.example.com` |
| TINSHOP_PROTOCOL | protocol | `http` | `https` |
| TINSHOP_NAME | name | `TinShop` | `MyShop` |
| TINSHOP_REVERSEPROXY | reverseProxy | `false` | `true` |
| TINSHOP_WELCOMEMESSAGE | welcomeMessage | `Welcome to your own TinShop!` | `Welcome to my shop!` |
| TINSHOP_NOWELCOMEMESSAGE | noWelcomeMessage | `false` | `true` |
| TINSHOP_DEBUG_NFS | debug.nfs | `false` | `true` |
| TINSHOP_DEBUG_NOSECURITY | debug.nosecurity | `false` | `true` |
| TINSHOP_DEBUG_TICKET | debug.ticket | `false` | `true` |
| TINSHOP_NSP_CHECKVERIFIED | nsp.checkVerified | `false` | `true` |
| TINSHOP_SOURCES_DIRECTORIES | sources.directories | `./games` | `/games /path/two /path/three` |
| TINSHOP_SOURCES_NSF | sources.nfs | `null` | `192.168.1.100:/path/to/games` |
| TINSHOP_SECURITY_BANNEDTHEME | sources.bannedTheme | `null` | `THEME1 THEME2 THEME3` |
| TINSHOP_SECURITY_WHITELIST | sources.whitelist | `null` | `NSWID1 NSWID2 NSWID3` |
| TINSHOP_SECURITY_BLACKLIST | sources.blacklist | `null` | `NSWID4 NSWID5 NSWID6` |
| TINSHOP_SECURITY_FORWARDAUTH | sources.forwardAuth | `null` | `https://auth.tinshop.com/switch` |

## 🥍 Want to do cross-build generation?

Wanting to generate all possible os binaries (macOS, linux, windows) with all architectures (arm, amd64)?
Expand Down Expand Up @@ -148,6 +194,37 @@ reverseProxy: true
```

If you want to have HTTPS, ensure `caddy` handle it (it will with Let's Encrypt) and change `https` in the config and remove `:80` in the `Caddyfile` example.

### Example for traefik

To work with [`traefik`](https://traefik.io/), you need to put in your Dynamic Configuration something similar to this:

```yaml
http:
routers:
service: tinshop
rule: Host(`tinshop.example.com`)
entryPoints: websecure # Could be web if not using https

services:
tinshop:
loadBalancer:
servers:
- url: http:https://192.168.1.2:3000
```

and your `config.yaml` as follow:

```yaml
host: tinshop.example.com
protocol: http
port: 3000
reverseProxy: true
```

If you want to have HTTPS, ensure `traefik` can handle it (it will with Let's Encrypt) and use protocol `https` in the config.

For more details on Traefik + Let's Encrypt, [click here](https://doc.traefik.io/traefik/https/acme/).
</details>

## How can I add a `basic auth` to protect my shop?
Expand Down
104 changes: 64 additions & 40 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"net"
"os"
"strconv"
"strings"

"github.com/DblK/tinshop/repository"
"github.com/DblK/tinshop/utils"
Expand All @@ -35,8 +36,8 @@ type nsp struct {
CheckVerified bool `mapstructure:"checkVerified"`
}

// File holds all config information
type File struct {
// Configuration holds all config information
type Configuration struct {
rootShop string
ShopHost string `mapstructure:"host"`
ShopProtocol string `mapstructure:"protocol"`
Expand All @@ -58,18 +59,41 @@ type File struct {

// New returns a new configuration
func New() repository.Config {
return &File{}
return &Configuration{}
}

// LoadConfig handles viper under the hood
func (cfg *File) LoadConfig() {
viper.SetConfigName("config") // name of config file (without extension)
viper.SetConfigType("yaml") // REQUIRED if the config file does not have the extension in the name
viper.AddConfigPath(".") // optionally look for config in the working directory
viper.SetDefault("sources.directories", "./games")
func (cfg *Configuration) LoadConfig() {
viper.SetConfigName("config") // name of config file (without extension)
viper.SetConfigType("yaml") // REQUIRED if the config file does not have the extension in the name
viper.AddConfigPath(".") // optionally look for config in the working directory
viper.SetTypeByDefaultValue(true) // Allows []string to be parsed from Env Vars

viper.SetDefault("host", "")
viper.SetDefault("protocol", "http")
viper.SetDefault("name", "TinShop")
viper.SetDefault("reverseProxy", false)
viper.SetDefault("welcomeMessage", "Welcome to your own TinShop!")
viper.SetDefault("noWelcomeMessage", false)

viper.SetDefault("debug.nfs", false)
viper.SetDefault("debug.noSecurity", false)
viper.SetDefault("debug.ticket", false)

viper.SetDefault("nsp.checkVerified", false)

viper.SetDefault("sources.directories", []string{"./games"})
viper.SetDefault("sources.nfs", []string{})

viper.SetDefault("security.bannedTheme", []string{})
viper.SetDefault("security.whitelist", []string{})
viper.SetDefault("security.blacklist", []string{})
viper.SetDefault("security.forwardAuth", "")

viper.SetEnvPrefix("TINSHOP")
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.AutomaticEnv()

if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
// Config file not found; ignore error if desired
Expand All @@ -89,7 +113,7 @@ func (cfg *File) LoadConfig() {
cfg.configChange()
}

func (cfg *File) configChange() {
func (cfg *Configuration) configChange() {
// Call all before hooks
for _, hook := range cfg.beforeAllHooks {
hook(cfg)
Expand Down Expand Up @@ -121,8 +145,8 @@ func (cfg *File) configChange() {
}
}

func loadAndCompute() *File {
var loadedConfig = &File{}
func loadAndCompute() *Configuration {
var loadedConfig = &Configuration{}
err := viper.Unmarshal(&loadedConfig)

if err != nil {
Expand Down Expand Up @@ -169,7 +193,7 @@ func ComputeDefaultValues(config repository.Config) repository.Config {
rootShop += ":" + strconv.Itoa(config.Port())
}
}
log.Println((rootShop))
log.Println(rootShop)
config.SetRootShop(rootShop)

config.SetShopTemplateData(repository.ShopTemplate{
Expand All @@ -180,153 +204,153 @@ func ComputeDefaultValues(config repository.Config) repository.Config {
}

// AddHook Add hook function on change config
func (cfg *File) AddHook(f func(repository.Config)) {
func (cfg *Configuration) AddHook(f func(repository.Config)) {
cfg.allHooks = append(cfg.allHooks, f)
}

// AddBeforeHook Add hook function before on change config
func (cfg *File) AddBeforeHook(f func(repository.Config)) {
func (cfg *Configuration) AddBeforeHook(f func(repository.Config)) {
cfg.beforeAllHooks = append(cfg.beforeAllHooks, f)
}

// SetRootShop allow to change the root url of the shop
func (cfg *File) SetRootShop(root string) {
func (cfg *Configuration) SetRootShop(root string) {
cfg.rootShop = root
}

// RootShop returns the RootShop url
func (cfg *File) RootShop() string {
func (cfg *Configuration) RootShop() string {
return cfg.rootShop
}

// ReverseProxy returns the ReverseProxy setting
func (cfg *File) ReverseProxy() bool {
func (cfg *Configuration) ReverseProxy() bool {
return cfg.Proxy
}

// WelcomeMessage returns the WelcomeMessage
func (cfg *File) WelcomeMessage() string {
func (cfg *Configuration) WelcomeMessage() string {
return cfg.ShopWelcomeMessage
}

// NoWelcomeMessage returns the NoWelcomeMessage
func (cfg *File) NoWelcomeMessage() bool {
func (cfg *Configuration) NoWelcomeMessage() bool {
return cfg.ShopNoWelcomeMessage
}

// Protocol returns the protocol scheme (http or https)
func (cfg *File) Protocol() string {
func (cfg *Configuration) Protocol() string {
return cfg.ShopProtocol
}

// Host returns the host of the shop
func (cfg *File) Host() string {
func (cfg *Configuration) Host() string {
return cfg.ShopHost
}

// Port returns the port number for outside access
func (cfg *File) Port() int {
func (cfg *Configuration) Port() int {
return cfg.ShopPort
}

// DebugTicket tells if we should display additional log for ticket verification
func (cfg *File) DebugTicket() bool {
func (cfg *Configuration) DebugTicket() bool {
return cfg.Debug.Ticket
}

// DebugNfs tells if we should display additional log for nfs
func (cfg *File) DebugNfs() bool {
func (cfg *Configuration) DebugNfs() bool {
return cfg.Debug.Nfs
}

// DebugNoSecurity returns if we should disable security or not
func (cfg *File) DebugNoSecurity() bool {
func (cfg *Configuration) DebugNoSecurity() bool {
return cfg.Debug.NoSecurity
}

// Directories returns the list of directories sources
func (cfg *File) Directories() []string {
func (cfg *Configuration) Directories() []string {
return cfg.AllSources.Directories
}

// CustomDB returns the list of custom title db
func (cfg *File) CustomDB() map[string]repository.TitleDBEntry {
func (cfg *Configuration) CustomDB() map[string]repository.TitleDBEntry {
return cfg.CustomTitleDB
}

// NfsShares returns the list of nfs sources
func (cfg *File) NfsShares() []string {
func (cfg *Configuration) NfsShares() []string {
return cfg.AllSources.Nfs
}

// Sources returns all available sources
func (cfg *File) Sources() repository.ConfigSources {
func (cfg *Configuration) Sources() repository.ConfigSources {
return cfg.AllSources
}

// ShopTemplateData returns the data needed to render template
func (cfg *File) ShopTemplateData() repository.ShopTemplate {
func (cfg *Configuration) ShopTemplateData() repository.ShopTemplate {
return cfg.shopTemplateData
}

// SetShopTemplateData sets the data for template
func (cfg *File) SetShopTemplateData(data repository.ShopTemplate) {
func (cfg *Configuration) SetShopTemplateData(data repository.ShopTemplate) {
cfg.shopTemplateData = data
}

// ShopTitle returns the name of the shop
func (cfg *File) ShopTitle() string {
func (cfg *Configuration) ShopTitle() string {
return cfg.Name
}

// VerifyNSP tells if we need to verify NSP
func (cfg *File) VerifyNSP() bool {
func (cfg *Configuration) VerifyNSP() bool {
return cfg.NSP.CheckVerified
}

// ForwardAuthURL returns the url of the forward auth
func (cfg *File) ForwardAuthURL() string {
func (cfg *Configuration) ForwardAuthURL() string {
return cfg.Security.ForwardAuth
}

// IsBlacklisted tells if the uid is blacklisted or not
func (cfg *File) IsBlacklisted(uid string) bool {
func (cfg *Configuration) IsBlacklisted(uid string) bool {
if len(cfg.Security.Whitelist) != 0 {
return !cfg.isInWhiteList(uid)
}
return cfg.isInBlackList(uid)
}

// IsWhitelisted tells if the uid is whitelisted or not
func (cfg *File) IsWhitelisted(uid string) bool {
func (cfg *Configuration) IsWhitelisted(uid string) bool {
if len(cfg.Security.Whitelist) == 0 {
return !cfg.isInBlackList(uid)
}
return cfg.isInWhiteList(uid)
}

func (cfg *File) isInBlackList(uid string) bool {
func (cfg *Configuration) isInBlackList(uid string) bool {
idxBlackList := utils.Search(len(cfg.Security.Blacklist), func(index int) bool {
return cfg.Security.Blacklist[index] == uid
})
return idxBlackList != -1
}
func (cfg *File) isInWhiteList(uid string) bool {
func (cfg *Configuration) isInWhiteList(uid string) bool {
idxWhiteList := utils.Search(len(cfg.Security.Whitelist), func(index int) bool {
return cfg.Security.Whitelist[index] == uid
})
return idxWhiteList != -1
}

// IsBannedTheme tells if the theme is banned or not
func (cfg *File) IsBannedTheme(theme string) bool {
func (cfg *Configuration) IsBannedTheme(theme string) bool {
idxBannedTheme := utils.Search(len(cfg.Security.BannedTheme), func(index int) bool {
return cfg.Security.BannedTheme[index] == theme
})
return idxBannedTheme != -1
}

// BannedTheme returns all banned theme
func (cfg *File) BannedTheme() []string {
func (cfg *Configuration) BannedTheme() []string {
return cfg.Security.BannedTheme
}
Loading

0 comments on commit 66299f0

Please sign in to comment.