feat: use cobra to provide subcommands, move sources to lib (#506)

- Use cobra in order to provide subcommands `serve` and `db`.
  - Subdir `cmd` is removed.
  - Subdir `cli` is created, which is a standard cobra structure.
- Sources related to the core are moved to subdir `lib`.
- #497 and #504 are merged.
- Deprecated flags are added. See https://github.com/filebrowser/filebrowser/pull/497#discussion_r209428120.
- [`viper.BindPFlags`](https://godoc.org/github.com/spf13/viper#BindPFlags) is used in order to reduce the verbosity in `serve.go`.
pull/494/head
1138-4EB 2018-08-21 22:42:03 +01:00
parent b1eb90767d
commit 69a3f853bd
35 changed files with 535 additions and 840 deletions

View File

@ -17,8 +17,7 @@ stages:
- release - release
cache: cache:
directories: directories:
- vendor - lib/rice-box.go
- rice-box.go
jobs: jobs:
include: include:
- stage: lint - stage: lint

View File

@ -2,9 +2,11 @@
"port": 80, "port": 80,
"address": "", "address": "",
"database": "/database.db", "database": "/database.db",
"defaults": {
"scope": "/srv", "scope": "/srv",
"allowCommands": true, "allowCommands": true,
"allowEdit": true, "allowEdit": true,
"allowNew": true, "allowNew": true,
"commands": [] "commands": []
} }
}

395
Gopkg.lock generated
View File

@ -1,395 +0,0 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
digest = "1:af3215cd158218c92f0706bb32f67b0ce3d37da4c4d135e7ed5e2dc36a90fca5"
name = "github.com/BurntSushi/toml"
packages = ["."]
pruneopts = "UT"
revision = "a368813c5e648fee92e5f6c30e3944ff9d5e8895"
[[projects]]
branch = "master"
digest = "1:65796e5fdb94d94bc0ee6bc422aa3541dbe69ed4da275cb5a27f8fa141f4e35c"
name = "github.com/GeertJohan/go.rice"
packages = [
".",
"embedded",
]
pruneopts = "UT"
revision = "c02ca9a983da5807ddf7d796784928f5be4afd09"
[[projects]]
digest = "1:9ede7940cd19ac5d92896381abac71954feb57608b617152e48112c3dc667e87"
name = "github.com/asdine/storm"
packages = [
".",
"codec",
"codec/json",
"index",
"internal",
"q",
]
pruneopts = "UT"
revision = "bda68dab90fc908ee5dbccb36400edf4f54972d6"
version = "v2.1.1"
[[projects]]
digest = "1:68eae87f8e5391e83ed2f7568e727709c598672f9c8390460241dcc1c2db2d34"
name = "github.com/chaseadamsio/goorgeous"
packages = ["."]
pruneopts = "UT"
revision = "dcf1ef873b8987bf12596fe6951c48347986eb2f"
version = "v1.1.0"
[[projects]]
digest = "1:c28625428387b63dd7154eb857f51e700465cfbf7c06f619e71f2da33cefe47e"
name = "github.com/coreos/bbolt"
packages = ["."]
pruneopts = "UT"
revision = "583e8937c61f1af6513608ccc75c97b6abdf4ff9"
version = "v1.3.0"
[[projects]]
branch = "master"
digest = "1:5fd5c4d4282935b7a575299494f2c09e9d2cacded7815c83aff7c1602aff3154"
name = "github.com/daaku/go.zipexe"
packages = ["."]
pruneopts = "UT"
revision = "a5fe2436ffcb3236e175e5149162b41cd28bd27d"
[[projects]]
digest = "1:bb2130e54ac8ea3ff78312ca365ce3b8a23423ed362530551bdceaccfa4d8e77"
name = "github.com/dgrijalva/jwt-go"
packages = [
".",
"request",
]
pruneopts = "UT"
revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e"
version = "v3.2.0"
[[projects]]
branch = "master"
digest = "1:988cfde37f089c50ba1ac8a1032c3f81d2ac0359e1872093b946a30139665eb6"
name = "github.com/dsnet/compress"
packages = [
".",
"bzip2",
"bzip2/internal/sais",
"internal",
"internal/errors",
"internal/prefix",
]
pruneopts = "UT"
revision = "cc9eb1d7ad760af14e8f918698f745e80377af4f"
[[projects]]
branch = "master"
digest = "1:50a46ab1d5edbbdd55125b4d37f1bf503d0807c26461f9ad7b358d6006641d09"
name = "github.com/flynn/go-shlex"
packages = ["."]
pruneopts = "UT"
revision = "3f9db97f856818214da2e1057f8ad84803971cff"
[[projects]]
digest = "1:abeb38ade3f32a92943e5be54f55ed6d6e3b6602761d74b4aab4c9dd45c18abd"
name = "github.com/fsnotify/fsnotify"
packages = ["."]
pruneopts = "UT"
revision = "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9"
version = "v1.4.7"
[[projects]]
digest = "1:402c1a5e502d338e285ade7820bcd7bc6d860a262e1809eee5b1c34ef7692627"
name = "github.com/gohugoio/hugo"
packages = ["parser"]
pruneopts = "UT"
revision = "25e88ccabe9b04c42ffb43528c86743f623fac46"
version = "v0.36.1"
[[projects]]
branch = "master"
digest = "1:4a0c6bb4805508a6287675fac876be2ac1182539ca8a32468d8128882e9d5009"
name = "github.com/golang/snappy"
packages = ["."]
pruneopts = "UT"
revision = "2e65f85255dbc3072edf28d6b5b8efc472979f5a"
[[projects]]
digest = "1:43dd08a10854b2056e615d1b1d22ac94559d822e1f8b6fcc92c1a1057e85188e"
name = "github.com/gorilla/websocket"
packages = ["."]
pruneopts = "UT"
revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b"
version = "v1.2.0"
[[projects]]
branch = "master"
digest = "1:837e92fe1b970f26d25d35c755106d86ba45717d11374c4f344654baa4add7b3"
name = "github.com/hacdias/fileutils"
packages = ["."]
pruneopts = "UT"
revision = "76b1c6ab906773727a1ce2f7fb22830685166f85"
[[projects]]
branch = "master"
digest = "1:a6c3d669d54b94899aa207bb3da0a38f4d3054e396a80cae2ed7634e2843c1ba"
name = "github.com/hacdias/varutils"
packages = ["."]
pruneopts = "UT"
revision = "82d3b57f667a756cfc4b1535951b46878881f3e1"
[[projects]]
branch = "master"
digest = "1:a361611b8c8c75a1091f00027767f7779b29cb37c456a71b8f2604c88057ab40"
name = "github.com/hashicorp/hcl"
packages = [
".",
"hcl/ast",
"hcl/parser",
"hcl/printer",
"hcl/scanner",
"hcl/strconv",
"hcl/token",
"json/parser",
"json/scanner",
"json/token",
]
pruneopts = "UT"
revision = "ef8a98b0bbce4a65b5aa4c368430a80ddc533168"
[[projects]]
branch = "master"
digest = "1:caf6db28595425c0e0f2301a00257d11712f65c1878e12cffc42f6b9a9cf3f23"
name = "github.com/kardianos/osext"
packages = ["."]
pruneopts = "UT"
revision = "ae77be60afb1dcacde03767a8c37337fad28ac14"
[[projects]]
digest = "1:c568d7727aa262c32bdf8a3f7db83614f7af0ed661474b24588de635c20024c7"
name = "github.com/magiconair/properties"
packages = ["."]
pruneopts = "UT"
revision = "c2353362d570a7bfa228149c62842019201cfb71"
version = "v1.8.0"
[[projects]]
branch = "master"
digest = "1:4bceeb937bfdd1fc2c545c685c43fdad909d78508b308b318e79db6b8fd6c792"
name = "github.com/maruel/natural"
packages = ["."]
pruneopts = "UT"
revision = "dbcb3e2e8cf10eb839718ba666ef1e21b1c8b847"
[[projects]]
digest = "1:f17bcb6a3694fe34b29fa9f0d93e1984450f6854b66c2df49524ba9541f9952d"
name = "github.com/mholt/archiver"
packages = ["."]
pruneopts = "UT"
revision = "26cf5bb32d07aa4e8d0de15f56ce516f4641d7df"
[[projects]]
digest = "1:fa577f7189e08bb551a16f655bb53ef27e1bebf0280e523f651eeced0cd98514"
name = "github.com/mholt/caddy"
packages = [
".",
"caddyfile",
]
pruneopts = "UT"
revision = "2922d09bef3c504dde66bc12f7441668fcef6a20"
version = "v0.10.14"
[[projects]]
branch = "master"
digest = "1:5ab79470a1d0fb19b041a624415612f8236b3c06070161a910562f2b2d064355"
name = "github.com/mitchellh/mapstructure"
packages = ["."]
pruneopts = "UT"
revision = "f15292f7a699fcc1a38a80977f80a046874ba8ac"
[[projects]]
branch = "master"
digest = "1:3bdb4203c03569a564d6a4bd54d84315575cebb2d76471f8676f8ee8c402005e"
name = "github.com/nwaples/rardecode"
packages = ["."]
pruneopts = "UT"
revision = "e06696f847aeda6f39a8f0b7cdff193b7690aef6"
[[projects]]
digest = "1:95741de3af260a92cc5c7f3f3061e85273f5a81b5db20d4bd68da74bd521675e"
name = "github.com/pelletier/go-toml"
packages = ["."]
pruneopts = "UT"
revision = "c01d1270ff3e442a8a57cddc1c92dc1138598194"
version = "v1.2.0"
[[projects]]
digest = "1:29803f52611cbcc1dfe55b456e9fdac362af7248b3d29d7ea1bec0a12e71dff4"
name = "github.com/pierrec/lz4"
packages = [
".",
"internal/xxh32",
]
pruneopts = "UT"
revision = "1958fd8fff7f115e79725b1288e0b878b3e06b00"
version = "v2.0.3"
[[projects]]
digest = "1:ed615c5430ecabbb0fb7629a182da65ecee6523900ac1ac932520860878ffcad"
name = "github.com/robfig/cron"
packages = ["."]
pruneopts = "UT"
revision = "b41be1df696709bb6395fe435af20370037c0b4c"
version = "v1.1"
[[projects]]
digest = "1:8bc629776d035c003c7814d4369521afe67fdb8efc4b5f66540d29343b98cf23"
name = "github.com/russross/blackfriday"
packages = ["."]
pruneopts = "UT"
revision = "55d61fa8aa702f59229e6cff85793c22e580eaf5"
version = "v1.5.1"
[[projects]]
branch = "master"
digest = "1:def689e73e9252f6f7fe66834a76751a41b767e03daab299e607e7226c58a855"
name = "github.com/shurcooL/sanitized_anchor_name"
packages = ["."]
pruneopts = "UT"
revision = "86672fcb3f950f35f2e675df2240550f2a50762f"
[[projects]]
digest = "1:bd1ae00087d17c5a748660b8e89e1043e1e5479d0fea743352cda2f8dd8c4f84"
name = "github.com/spf13/afero"
packages = [
".",
"mem",
]
pruneopts = "UT"
revision = "787d034dfe70e44075ccc060d346146ef53270ad"
version = "v1.1.1"
[[projects]]
digest = "1:516e71bed754268937f57d4ecb190e01958452336fa73dbac880894164e91c1f"
name = "github.com/spf13/cast"
packages = ["."]
pruneopts = "UT"
revision = "8965335b8c7107321228e3e3702cab9832751bac"
version = "v1.2.0"
[[projects]]
branch = "master"
digest = "1:080e5f630945ad754f4b920e60b4d3095ba0237ebf88dc462eb28002932e3805"
name = "github.com/spf13/jwalterweatherman"
packages = ["."]
pruneopts = "UT"
revision = "7c0cea34c8ece3fbeb2b27ab9b59511d360fb394"
[[projects]]
digest = "1:9424f440bba8f7508b69414634aef3b2b3a877e522d8a4624692412805407bb7"
name = "github.com/spf13/pflag"
packages = ["."]
pruneopts = "UT"
revision = "583c0c0531f06d5278b7d917446061adc344b5cd"
version = "v1.0.1"
[[projects]]
digest = "1:4fc8a61287ccfb4286e1ca5ad2ce3b0b301d746053bf44ac38cf34e40ae10372"
name = "github.com/spf13/viper"
packages = ["."]
pruneopts = "UT"
revision = "907c19d40d9a6c9bb55f040ff4ae45271a4754b9"
version = "v1.1.0"
[[projects]]
digest = "1:4aeb3860275fa1fd60cccfb5a6ef85da438bf17402e1e84412ade4d4b55066a0"
name = "github.com/ulikunitz/xz"
packages = [
".",
"internal/hash",
"internal/xlog",
"lzma",
]
pruneopts = "UT"
revision = "0c6b41e72360850ca4f98dc341fd999726ea007f"
version = "v0.5.4"
[[projects]]
branch = "master"
digest = "1:1ecf2a49df33be51e757d0033d5d51d5f784f35f68e5a38f797b2d3f03357d71"
name = "golang.org/x/crypto"
packages = [
"bcrypt",
"blowfish",
]
pruneopts = "UT"
revision = "de0752318171da717af4ce24d0a2e8626afaeb11"
[[projects]]
branch = "master"
digest = "1:76b5ca88193bf744f729c2fde12151b24364ffaca3fd092069d9ca2ea6e1f999"
name = "golang.org/x/sys"
packages = ["unix"]
pruneopts = "UT"
revision = "904bdc257025c7b3f43c19360ad3ab85783fad78"
[[projects]]
digest = "1:8029e9743749d4be5bc9f7d42ea1659471767860f0cdc34d37c3111bd308a295"
name = "golang.org/x/text"
packages = [
"internal/gen",
"internal/triegen",
"internal/ucd",
"transform",
"unicode/cldr",
"unicode/norm",
]
pruneopts = "UT"
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
version = "v0.3.0"
[[projects]]
digest = "1:c805e517269b0ba4c21ded5836019ed7d16953d4026cb7d00041d039c7906be9"
name = "gopkg.in/natefinch/lumberjack.v2"
packages = ["."]
pruneopts = "UT"
revision = "a96e63847dc3c67d17befa69c303767e2f84e54f"
version = "v2.1"
[[projects]]
branch = "v2"
digest = "1:342378ac4dcb378a5448dd723f0784ae519383532f5e70ade24132c4c8693202"
name = "gopkg.in/yaml.v2"
packages = ["."]
pruneopts = "UT"
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/GeertJohan/go.rice",
"github.com/GeertJohan/go.rice/embedded",
"github.com/asdine/storm",
"github.com/asdine/storm/q",
"github.com/dgrijalva/jwt-go",
"github.com/dgrijalva/jwt-go/request",
"github.com/gohugoio/hugo/parser",
"github.com/gorilla/websocket",
"github.com/hacdias/fileutils",
"github.com/hacdias/varutils",
"github.com/maruel/natural",
"github.com/mholt/archiver",
"github.com/mholt/caddy",
"github.com/mitchellh/mapstructure",
"github.com/robfig/cron",
"github.com/spf13/pflag",
"github.com/spf13/viper",
"golang.org/x/crypto/bcrypt",
"gopkg.in/natefinch/lumberjack.v2",
]
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -1,74 +0,0 @@
[[constraint]]
branch = "master"
name = "github.com/GeertJohan/go.rice"
[[constraint]]
name = "github.com/asdine/storm"
version = "2.0.2"
[[constraint]]
name = "github.com/dgrijalva/jwt-go"
version = "3.1.0"
[[constraint]]
name = "github.com/gohugoio/hugo"
version = "0.36.1"
[[constraint]]
name = "github.com/gorilla/websocket"
version = "1.2.0"
[[constraint]]
branch = "master"
name = "github.com/hacdias/fileutils"
[[constraint]]
branch = "master"
name = "github.com/hacdias/varutils"
[[constraint]]
name = "github.com/mholt/archiver"
# TODO: switch to version when it's available
# this is for Archiver.Write() which was introduced in 548c791
revision = "26cf5bb32d07aa4e8d0de15f56ce516f4641d7df"
# version = "2.0.0"
[[constraint]]
name = "github.com/mholt/caddy"
version = "0.10.11"
[[constraint]]
branch = "master"
name = "github.com/mitchellh/mapstructure"
[[constraint]]
name = "github.com/robfig/cron"
version = "1.0.0"
[[constraint]]
name = "github.com/spf13/pflag"
version = "1.0.0"
[[constraint]]
name = "github.com/spf13/viper"
version = "1.0.0"
[[constraint]]
branch = "master"
name = "golang.org/x/crypto"
[[constraint]]
name = "gopkg.in/natefinch/lumberjack.v2"
version = "2.1.0"
[[constraint]]
branch = "master"
name = "github.com/maruel/natural"
[[override]]
name = "github.com/russross/blackfriday"
version = "^1.0.0"
[prune]
go-tests = true
unused-packages = true

View File

@ -2,22 +2,19 @@
set -e set -e
cd $(dirname $0)/.. cd $(dirname $0)/../cli
dep ensure -vendor-only go get -v ./...
if [ "$COMMIT_SHA" != "" ]; then if [ "$COMMIT_SHA" != "" ]; then
echo "Set version to ($COMMIT_SHA)" echo "Set version to ($COMMIT_SHA)"
sed -i.bak "s|(untracked)|($COMMIT_SHA)|g" filebrowser.go sed -i.bak "s|(untracked)|($COMMIT_SHA)|g" ../lib/filebrowser.go
fi fi
echo "Build cmd/filebrowser" echo "Build cmd/filebrowser"
cd cmd/filebrowser CGO_ENABLED=0 go build -a -o filebrowser
CGO_ENABLED=0 go build -a
cd ../..
cp cmd/filebrowser/filebrowser ./
if [ "$COMMIT_SHA" != "" ]; then if [ "$COMMIT_SHA" != "" ]; then
echo "Reset version to (untracked)" echo "Reset version to (untracked)"
sed -i "s|($COMMIT_SHA)|(untracked)|g" filebrowser.go sed -i "s|($COMMIT_SHA)|(untracked)|g" ../lib/filebrowser.go
fi fi

View File

@ -2,8 +2,8 @@
cd $(dirname $0)/.. cd $(dirname $0)/..
if [ -d "rice-box.go" ]; then if [ -d lib/"rice-box.go" ]; then
rm -rf rice-box.go rm -rf lib/rice-box.go
fi fi
if [ "$USE_DOCKER" != "" ]; then if [ "$USE_DOCKER" != "" ]; then
@ -41,9 +41,8 @@ if [ "$USE_DOCKER" != "" ]; then
for d in "dist/" "node_modules/"; do for d in "dist/" "node_modules/"; do
docker cp filebrowser-tmp:/$WDIR/frontend/$d frontend docker cp filebrowser-tmp:/$WDIR/frontend/$d frontend
done done
for d in "vendor/" "rice-box.go" "filebrowser"; do docker cp filebrowser-tmp:/$WDIR/cli/filebrowser ./filebrowser
docker cp filebrowser-tmp:/$WDIR/$d ./ docker cp filebrowser-tmp:/$WDIR/lib/rice-box.go ./lib/rice-box.go
done
fi fi
docker rm -f filebrowser-tmp docker rm -f filebrowser-tmp
else else

View File

@ -19,4 +19,5 @@ if ! [ -x "$(command -v rice)" ]; then
fi fi
# Embed the assets using rice # Embed the assets using rice
cd lib
rice embed-go rice embed-go

View File

@ -11,8 +11,8 @@ openssl aes-256-cbc -K $encrypted_9ca81b5594f5_key -iv $encrypted_9ca81b5594f5_i
git clone git@github.com:filebrowser/caddy caddy git clone git@github.com:filebrowser/caddy caddy
cd caddy cd caddy
cp ../../rice-box.go assets/ cp ../../lib/rice-box.go assets/
sed -i 's/package filebrowser/package assets/g' assets/rice-box.go sed -i 's/package lib/package assets/g' assets/rice-box.go
git checkout -b update-rice-box origin/master git checkout -b update-rice-box origin/master
git config --local user.name "Filebrowser Bot" git config --local user.name "Filebrowser Bot"
git config --local user.email "FilebrowserBot@users.noreply.github.com" git config --local user.email "FilebrowserBot@users.noreply.github.com"

View File

@ -8,7 +8,7 @@ dolint='gometalinter --exclude="rice-box.go" --exclude="vendor" --deadline=300s
if [ "$USE_DOCKER" != "" ]; then if [ "$USE_DOCKER" != "" ]; then
docker run --rm -itv $(pwd):/src filebrowser/dev sh -c "\ docker run --rm -itv $(pwd):/src filebrowser/dev sh -c "\
cp -r /src/. ./ && dep ensure -v -vendor-only && \ cp -r /src/. ./ && cd cli && go get -v ./... && \
CGO_ENABLED=0 $dolint" CGO_ENABLED=0 $dolint"
else else
$dolint $dolint

24
cli/cmd/db.go Normal file
View File

@ -0,0 +1,24 @@
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
// dbCmd represents the db command
var dbCmd = &cobra.Command{
Use: "db",
Version: rootCmd.Version,
Aliases: []string{"database"},
Short: "Manage a filebrowser database",
Long: `This is a CLI tool to ease the management of
filebrowser database files.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("db called. Command not implemented, yet.")
},
}
func init() {
rootCmd.AddCommand(dbCmd)
}

75
cli/cmd/root.go Normal file
View File

@ -0,0 +1,75 @@
package cmd
import (
"log"
"strings"
homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/cobra"
v "github.com/spf13/viper"
)
var cfgFile string
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "filebrowser",
Version: "(untracked)",
Aliases: []string{"serve"},
Short: "A stylish web-based file manager",
Long: `Command 'serve' is the default. Filebrowser is started
with the provided envvars, flags and/or config file. For example:
filebrowser -c config.json -p 80 -s ./srv
File Browser is a static binary composed of a golang backend and
a Vue.js frontend to create, edit, copy, move, download your files
easily, everywhere, every time.`,
// Run: func(cmd *cobra.Command, args []string) {},
}
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
checkRootAlias()
if err := rootCmd.Execute(); err != nil {
panic(err)
}
}
func init() {
cobra.OnInitialize(initConfig)
rootCmd.SetVersionTemplate("File Browser {{printf \"version %s\" .Version}}\n")
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file (defaults are './.filebrowser[ext]', '$HOME/.filebrowser[ext]' or '/etc/filebrowser/.filebrowser[ext]')")
}
// initConfig reads in config file and ENV variables if set.
func initConfig() {
if cfgFile == "" {
// Find home directory.
home, err := homedir.Dir()
if err != nil {
panic(err)
}
v.AddConfigPath(".")
v.AddConfigPath(home)
v.AddConfigPath("/etc/filebrowser/")
v.SetConfigName(".filebrowser")
} else {
// Use config file from the flag.
v.SetConfigFile(cfgFile)
}
v.SetEnvPrefix("FB")
v.AutomaticEnv()
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
if err := v.ReadInConfig(); err != nil {
if _, ok := err.(v.ConfigParseError); ok {
panic(err)
}
} else {
log.Println("Using config file:", v.ConfigFileUsed())
}
}

46
cli/cmd/rootalias.go Normal file
View File

@ -0,0 +1,46 @@
package cmd
import (
"log"
"os"
)
// checkRootAlias compares the first argument provided in the CLI with a list of
// subcmds and aliases. If no match is found, the first alias of rootCmd is added.
func checkRootAlias() {
l := len(rootCmd.Aliases)
if l == 0 {
return
}
if l > 1 {
log.Printf("rootCmd.Aliases should contain a single string. '%s' is used.\n", rootCmd.Aliases[0])
}
if len(os.Args) > 1 {
for _, v := range append(nonRootSubCmds(), []string{"--help", "--version"}...) {
if os.Args[1] == v {
return
}
}
}
os.Args = append([]string{os.Args[0], rootCmd.Aliases[0]}, os.Args[1:]...)
}
// nonRootSubCmds traverses the list of subcommands of rootCmd and returns a string
// slice containing the names and aliases of all the subcmds, except the one defined
// in the Aliases field of rootCmd.
func nonRootSubCmds() (l []string) {
for _, c := range rootCmd.Commands() {
isAlias := false
for _, a := range append(c.Aliases, c.Name()) {
if a == rootCmd.Aliases[0] {
isAlias = true
break
}
}
if !isAlias {
l = append(l, c.Name())
l = append(l, c.Aliases...)
}
}
return
}

111
cli/cmd/serve.go Normal file
View File

@ -0,0 +1,111 @@
package cmd
import (
filebrowser "github.com/filebrowser/filebrowser/lib"
"github.com/spf13/cobra"
v "github.com/spf13/viper"
)
// serveCmd represents the serve command
var serveCmd = &cobra.Command{
Use: "serve",
Version: rootCmd.Version,
Aliases: []string{"server"},
Short: "Start filebrowser service",
Long: rootCmd.Long,
Run: func(cmd *cobra.Command, args []string) {
Serve()
},
Args: cobra.NoArgs,
}
func init() {
rootCmd.AddCommand(serveCmd)
f := serveCmd.PersistentFlags()
flag := func(k string, i interface{}, u string) {
switch y := i.(type) {
case bool:
f.Bool(k, y, u)
case int:
f.Int(k, y, u)
case string:
f.String(k, y, u)
}
v.SetDefault(k, i)
}
flagP := func(k, p string, i interface{}, u string) {
switch y := i.(type) {
case bool:
f.BoolP(k, p, y, u)
case int:
f.IntP(k, p, y, u)
case string:
f.StringP(k, p, y, u)
}
v.SetDefault(k, i)
}
deprecated := func(k string, i interface{}, u, m string) {
switch y := i.(type) {
case bool:
f.Bool(k, y, u)
case int:
f.Int(k, y, u)
case string:
f.String(k, y, u)
}
f.MarkDeprecated(k, m)
}
// Global settings
flagP("port", "p", 0, "HTTP Port (default is random)")
flagP("address", "a", "", "Address to listen to (default is all of them)")
flagP("database", "d", "./filebrowser.db", "Database file")
flagP("log", "l", "stdout", "Errors logger; can use 'stdout', 'stderr' or file")
flagP("baseurl", "b", "", "Base URL")
flag("prefixurl", "", "Prefix URL")
flag("staticgen", "", "Static Generator you want to enable")
// User default settings
f.String("defaults.commands", "git svn hg", "Default commands option for new users")
v.SetDefault("defaults.commands", []string{"git", "svn", "hg"})
flagP("defaults.scope", "s", ".", "Default scope option for new users")
flag("defaults.viewMode", filebrowser.MosaicViewMode, "Default view mode for new users")
flag("defaults.allowCommands", true, "Default allow commands option for new users")
flag("defaults.allowEdit", true, "Default allow edit option for new users")
flag("defaults.allowNew", true, "Default allow new option for new users")
flag("defaults.allowPublish", true, "Default allow publish option for new users")
flag("defaults.locale", "", "Default locale for new users, set it empty to enable auto detect from browser")
// Recaptcha settings
flag("recaptcha.host", "https://www.google.com", "Use another host for ReCAPTCHA. recaptcha.net might be useful in China")
flag("recaptcha.key", "", "ReCaptcha site key")
flag("recaptcha.secret", "", "ReCaptcha secret")
// Auth settings
flag("auth.method", "default", "Switch between 'none', 'default' and 'proxy' authentication")
flag("auth.header", "X-Forwarded-User", "The header name used for proxy authentication")
// Bind the full flag set to the configuration
if err := v.BindPFlags(f); err != nil {
panic(err)
}
// Deprecated flags
deprecated("no-auth", false, "Disables authentication", "use --auth.method='none' instead")
deprecated("alternative-recaptcha", false, "Use recaptcha.net for serving and handling, useful in China", "use --recaptcha.host instead")
deprecated("recaptcha-key", "", "ReCaptcha site key", "use --recaptcha.key instead")
deprecated("recaptcha-secret", "", "ReCaptcha secret", "use --recaptcha.secret instead")
deprecated("scope", ".", "Default scope option for new users", "use --defaults.scope instead")
deprecated("commands", "git svn hg", "Default commands option for new users", "use --defaults.commands instead")
deprecated("view-mode", "mosaic", "Default view mode for new users", "use --defaults.viewMode instead")
deprecated("locale", "", "Default locale for new users, set it empty to enable auto detect from browser", "use --defaults.locale instead")
deprecated("allow-commands", true, "Default allow commands option for new users", "use --defaults.allowCommands instead")
deprecated("allow-edit", true, "Default allow edit option for new users", "use --defaults.allowEdit instead")
deprecated("allow-publish", true, "Default allow publish option for new users", "use --defaults.allowPublish instead")
deprecated("allow-new", true, "Default allow new option for new users", "use --defaults.allowNew instead")
}

150
cli/cmd/server.go Normal file
View File

@ -0,0 +1,150 @@
package cmd
import (
"io/ioutil"
"log"
"net"
"net/http"
"os"
"path/filepath"
"github.com/asdine/storm"
filebrowser "github.com/filebrowser/filebrowser/lib"
"github.com/filebrowser/filebrowser/lib/bolt"
h "github.com/filebrowser/filebrowser/lib/http"
"github.com/filebrowser/filebrowser/lib/staticgen"
"github.com/hacdias/fileutils"
"github.com/spf13/viper"
"gopkg.in/natefinch/lumberjack.v2"
)
func Serve() {
// Set up process log before anything bad happens.
switch l := viper.GetString("log"); l {
case "stdout":
log.SetOutput(os.Stdout)
case "stderr":
log.SetOutput(os.Stderr)
case "":
log.SetOutput(ioutil.Discard)
default:
log.SetOutput(&lumberjack.Logger{
Filename: l,
MaxSize: 100,
MaxAge: 14,
MaxBackups: 10,
})
}
// Validate the provided config before moving forward
{
// Map of valid authentication methods, containing a boolean value to indicate the need of Auth.Header
validMethods := make(map[string]bool)
validMethods["none"] = false
validMethods["default"] = false
validMethods["proxy"] = true
m := viper.GetString("auth.method")
b, ok := validMethods[m]
if !ok {
log.Fatal("The property 'auth.method' needs to be set to 'none', 'default' or 'proxy'.")
}
if b {
if viper.GetString("auth.header") == "" {
log.Fatal("The 'auth.header' needs to be specified when '", m, "' authentication is used.")
}
log.Println("[WARN] Filebrowser authentication is configured to '", m, "' authentication. This can cause a huge security issue if the infrastructure is not configured correctly.")
}
}
// Builds the address and a listener.
laddr := viper.GetString("address") + ":" + viper.GetString("port")
listener, err := net.Listen("tcp", laddr)
if err != nil {
log.Fatal(err)
}
// Tell the user the port in which is listening.
log.Println("Listening on", listener.Addr().String())
// Starts the server.
if err := http.Serve(listener, handler()); err != nil {
log.Fatal(err)
}
}
func handler() http.Handler {
db, err := storm.Open(viper.GetString("database"))
if err != nil {
log.Fatal(err)
}
fb := &filebrowser.FileBrowser{
Auth: &filebrowser.Auth{
Method: viper.GetString("auth.method"),
Header: viper.GetString("auth.header"),
},
ReCaptcha: &filebrowser.ReCaptcha{
Host: viper.GetString("recaptcha.host"),
Key: viper.GetString("recaptcha.key"),
Secret: viper.GetString("recaptcha.secret"),
},
DefaultUser: &filebrowser.User{
AllowCommands: viper.GetBool("defaults.allowCommands"),
AllowEdit: viper.GetBool("defaults.allowEdit"),
AllowNew: viper.GetBool("defaults.allowNew"),
AllowPublish: viper.GetBool("defaults.allowPublish"),
Commands: viper.GetStringSlice("defaults.commands"),
Rules: []*filebrowser.Rule{},
Locale: viper.GetString("defaults.locale"),
CSS: "",
Scope: viper.GetString("defaults.scope"),
FileSystem: fileutils.Dir(viper.GetString("defaults.scope")),
ViewMode: viper.GetString("defaults.viewMode"),
},
Store: &filebrowser.Store{
Config: bolt.ConfigStore{DB: db},
Users: bolt.UsersStore{DB: db},
Share: bolt.ShareStore{DB: db},
},
NewFS: func(scope string) filebrowser.FileSystem {
return fileutils.Dir(scope)
},
}
fb.SetBaseURL(viper.GetString("baseurl"))
fb.SetPrefixURL(viper.GetString("prefixurl"))
err = fb.Setup()
if err != nil {
log.Fatal(err)
}
switch viper.GetString("staticgen") {
case "hugo":
hugo := &staticgen.Hugo{
Root: viper.GetString("Scope"),
Public: filepath.Join(viper.GetString("Scope"), "public"),
Args: []string{},
CleanPublic: true,
}
if err = fb.Attach(hugo); err != nil {
log.Fatal(err)
}
case "jekyll":
jekyll := &staticgen.Jekyll{
Root: viper.GetString("Scope"),
Public: filepath.Join(viper.GetString("Scope"), "_site"),
Args: []string{"build"},
CleanPublic: true,
}
if err = fb.Attach(jekyll); err != nil {
log.Fatal(err)
}
}
return h.Handler(fb)
}

26
cli/cmd/version.go Normal file
View File

@ -0,0 +1,26 @@
package cmd
import (
"text/template"
"github.com/spf13/cobra"
)
var versionCmd = &cobra.Command{
Use: "version",
Short: "Print the version number of File Browser",
Long: `All software has versions. This is File Browser's`,
Run: func(cmd *cobra.Command, args []string) {
// https://github.com/spf13/cobra/issues/724
t := template.New("version")
template.Must(t.Parse(rootCmd.VersionTemplate()))
err := t.Execute(rootCmd.OutOrStdout(), rootCmd)
if err != nil {
rootCmd.Println(err)
}
},
}
func init() {
rootCmd.AddCommand(versionCmd)
}

7
cli/main.go Normal file
View File

@ -0,0 +1,7 @@
package main
import "github.com/filebrowser/filebrowser/cli/cmd"
func main() {
cmd.Execute()
}

View File

@ -1,279 +0,0 @@
package main
import (
"fmt"
"github.com/asdine/storm"
"github.com/filebrowser/filebrowser"
"github.com/filebrowser/filebrowser/bolt"
h "github.com/filebrowser/filebrowser/http"
"github.com/filebrowser/filebrowser/staticgen"
"github.com/hacdias/fileutils"
flag "github.com/spf13/pflag"
"github.com/spf13/viper"
"gopkg.in/natefinch/lumberjack.v2"
"io/ioutil"
"log"
"net"
"net/http"
"os"
"path/filepath"
"strings")
var (
addr string
config string
database string
scope string
commands string
logfile string
staticg string
locale string
baseurl string
prefixurl string
viewMode string
recaptchakey string
recaptchasecret string
port int
auth struct {
method string
loginHeader string
}
noAuth bool
allowCommands bool
allowEdit bool
allowNew bool
allowPublish bool
showVer bool
alterRecaptcha bool
)
func init() {
flag.StringVarP(&config, "config", "c", "", "Configuration file")
flag.IntVarP(&port, "port", "p", 0, "HTTP Port (default is random)")
flag.StringVarP(&addr, "address", "a", "", "Address to listen to (default is all of them)")
flag.StringVarP(&database, "database", "d", "./filebrowser.db", "Database file")
flag.StringVarP(&logfile, "log", "l", "stdout", "Errors logger; can use 'stdout', 'stderr' or file")
flag.StringVarP(&scope, "scope", "s", ".", "Default scope option for new users")
flag.StringVarP(&baseurl, "baseurl", "b", "", "Base URL")
flag.StringVar(&commands, "commands", "git svn hg", "Default commands option for new users")
flag.StringVar(&prefixurl, "prefixurl", "", "Prefix URL")
flag.StringVar(&viewMode, "view-mode", "mosaic", "Default view mode for new users")
flag.StringVar(&recaptchakey, "recaptcha-key", "", "ReCaptcha site key")
flag.StringVar(&recaptchasecret, "recaptcha-secret", "", "ReCaptcha secret")
flag.BoolVar(&allowCommands, "allow-commands", true, "Default allow commands option for new users")
flag.BoolVar(&allowEdit, "allow-edit", true, "Default allow edit option for new users")
flag.BoolVar(&allowPublish, "allow-publish", true, "Default allow publish option for new users")
flag.StringVar(&auth.method, "auth.method", "default", "Switch between 'none', 'default' and 'proxy' authentication.")
flag.StringVar(&auth.loginHeader, "auth.loginHeader", "X-Forwarded-User", "The header name used for proxy authentication.")
flag.BoolVar(&allowNew, "allow-new", true, "Default allow new option for new users")
flag.BoolVar(&noAuth, "no-auth", false, "Disables authentication")
flag.BoolVar(&alterRecaptcha, "alternative-recaptcha", false, "Use recaptcha.net for serving and handling, useful in China")
flag.StringVar(&locale, "locale", "", "Default locale for new users, set it empty to enable auto detect from browser")
flag.StringVar(&staticg, "staticgen", "", "Static Generator you want to enable")
flag.BoolVarP(&showVer, "version", "v", false, "Show version")
}
func setupViper() {
viper.SetDefault("Address", "")
viper.SetDefault("Port", "0")
viper.SetDefault("Database", "./filebrowser.db")
viper.SetDefault("Scope", ".")
viper.SetDefault("Logger", "stdout")
viper.SetDefault("Commands", []string{"git", "svn", "hg"})
viper.SetDefault("AllowCommmands", true)
viper.SetDefault("AllowEdit", true)
viper.SetDefault("AllowNew", true)
viper.SetDefault("AllowPublish", true)
viper.SetDefault("StaticGen", "")
viper.SetDefault("Locale", "")
viper.SetDefault("AuthMethod", "default")
viper.SetDefault("LoginHeader", "X-Fowarded-User")
viper.SetDefault("NoAuth", false)
viper.SetDefault("BaseURL", "")
viper.SetDefault("PrefixURL", "")
viper.SetDefault("ViewMode", filebrowser.MosaicViewMode)
viper.SetDefault("AlternativeRecaptcha", false)
viper.SetDefault("ReCaptchaKey", "")
viper.SetDefault("ReCaptchaSecret", "")
viper.BindPFlag("Port", flag.Lookup("port"))
viper.BindPFlag("Address", flag.Lookup("address"))
viper.BindPFlag("Database", flag.Lookup("database"))
viper.BindPFlag("Scope", flag.Lookup("scope"))
viper.BindPFlag("Logger", flag.Lookup("log"))
viper.BindPFlag("Commands", flag.Lookup("commands"))
viper.BindPFlag("AllowCommands", flag.Lookup("allow-commands"))
viper.BindPFlag("AllowEdit", flag.Lookup("allow-edit"))
viper.BindPFlag("AllowNew", flag.Lookup("allow-new"))
viper.BindPFlag("AllowPublish", flag.Lookup("allow-publish"))
viper.BindPFlag("Locale", flag.Lookup("locale"))
viper.BindPFlag("StaticGen", flag.Lookup("staticgen"))
viper.BindPFlag("AuthMethod", flag.Lookup("auth.method"))
viper.BindPFlag("LoginHeader", flag.Lookup("auth.loginHeader"))
viper.BindPFlag("NoAuth", flag.Lookup("no-auth"))
viper.BindPFlag("BaseURL", flag.Lookup("baseurl"))
viper.BindPFlag("PrefixURL", flag.Lookup("prefixurl"))
viper.BindPFlag("ViewMode", flag.Lookup("view-mode"))
viper.BindPFlag("AlternativeRecaptcha", flag.Lookup("alternative-recaptcha"))
viper.BindPFlag("ReCaptchaKey", flag.Lookup("recaptcha-key"))
viper.BindPFlag("ReCaptchaSecret", flag.Lookup("recaptcha-secret"))
}
func printVersion() {
fmt.Println("filebrowser version", filebrowser.Version)
os.Exit(0)
}
func initConfig() {
// Add a configuration file if set.
if config != "" {
cfg := strings.TrimSuffix(config, filepath.Ext(config))
if dir := filepath.Dir(cfg); dir != "" {
viper.AddConfigPath(dir)
cfg = strings.TrimPrefix(cfg, dir)
}
viper.SetConfigName(cfg)
} else {
viper.SetConfigName("filebrowser")
viper.AddConfigPath(".")
}
// Read configuration from a file if exists.
err := viper.ReadInConfig()
if err != nil {
if _, ok := err.(viper.ConfigParseError); ok {
panic(err)
}
}
}
func main() {
setupViper()
flag.Parse()
if showVer {
printVersion()
}
initConfig();
// Set up process log before anything bad happens.
switch viper.GetString("Logger") {
case "stdout":
log.SetOutput(os.Stdout)
case "stderr":
log.SetOutput(os.Stderr)
case "":
log.SetOutput(ioutil.Discard)
default:
log.SetOutput(&lumberjack.Logger{
Filename: logfile,
MaxSize: 100,
MaxAge: 14,
MaxBackups: 10,
})
}
// Validate the provided config before moving forward
if viper.GetString("AuthMethod") != "none" && viper.GetString("AuthMethod") != "default" && viper.GetString("AuthMethod") != "proxy" {
log.Fatal("The property 'auth.method' needs to be set to 'default' or 'proxy'.")
}
if viper.GetString("AuthMethod") == "proxy" {
if viper.GetString("LoginHeader") == "" {
log.Fatal("The 'loginHeader' needs to be specified when 'proxy' authentication is used.")
}
log.Println("[WARN] Filebrowser authentication is configured to 'proxy' authentication. This can cause a huge security issue if the infrastructure is not configured correctly.")
}
// Builds the address and a listener.
laddr := viper.GetString("Address") + ":" + viper.GetString("Port")
listener, err := net.Listen("tcp", laddr)
if err != nil {
log.Fatal(err)
}
// Tell the user the port in which is listening.
fmt.Println("Listening on", listener.Addr().String())
// Starts the server.
if err := http.Serve(listener, handler()); err != nil {
log.Fatal(err)
}
}
func handler() http.Handler {
db, err := storm.Open(viper.GetString("Database"))
if err != nil {
log.Fatal(err)
}
recaptchaHost := "https://www.google.com"
if viper.GetBool("AlternativeRecaptcha") {
recaptchaHost = "https://recaptcha.net"
}
fm := &filebrowser.FileBrowser{
AuthMethod: viper.GetString("AuthMethod"),
LoginHeader: viper.GetString("LoginHeader"),
NoAuth: viper.GetBool("NoAuth"),
BaseURL: viper.GetString("BaseURL"),
PrefixURL: viper.GetString("PrefixURL"),
ReCaptchaHost: recaptchaHost,
ReCaptchaKey: viper.GetString("ReCaptchaKey"),
ReCaptchaSecret: viper.GetString("ReCaptchaSecret"),
DefaultUser: &filebrowser.User{
AllowCommands: viper.GetBool("AllowCommands"),
AllowEdit: viper.GetBool("AllowEdit"),
AllowNew: viper.GetBool("AllowNew"),
AllowPublish: viper.GetBool("AllowPublish"),
Commands: viper.GetStringSlice("Commands"),
Rules: []*filebrowser.Rule{},
Locale: viper.GetString("Locale"),
CSS: "",
Scope: viper.GetString("Scope"),
FileSystem: fileutils.Dir(viper.GetString("Scope")),
ViewMode: viper.GetString("ViewMode"),
},
Store: &filebrowser.Store{
Config: bolt.ConfigStore{DB: db},
Users: bolt.UsersStore{DB: db},
Share: bolt.ShareStore{DB: db},
},
NewFS: func(scope string) filebrowser.FileSystem {
return fileutils.Dir(scope)
},
}
err = fm.Setup()
if err != nil {
log.Fatal(err)
}
switch viper.GetString("StaticGen") {
case "hugo":
hugo := &staticgen.Hugo{
Root: viper.GetString("Scope"),
Public: filepath.Join(viper.GetString("Scope"), "public"),
Args: []string{},
CleanPublic: true,
}
if err = fm.Attach(hugo); err != nil {
log.Fatal(err)
}
case "jekyll":
jekyll := &staticgen.Jekyll{
Root: viper.GetString("Scope"),
Public: filepath.Join(viper.GetString("Scope"), "_site"),
Args: []string{"build"},
CleanPublic: true,
}
if err = fm.Attach(jekyll); err != nil {
log.Fatal(err)
}
}
return h.Handler(fm)
}

View File

@ -2,7 +2,7 @@ package bolt
import ( import (
"github.com/asdine/storm" "github.com/asdine/storm"
fb "github.com/filebrowser/filebrowser" fb "github.com/filebrowser/filebrowser/lib"
) )
// ConfigStore is a configuration store. // ConfigStore is a configuration store.

View File

@ -3,7 +3,7 @@ package bolt
import ( import (
"github.com/asdine/storm" "github.com/asdine/storm"
"github.com/asdine/storm/q" "github.com/asdine/storm/q"
fb "github.com/filebrowser/filebrowser" fb "github.com/filebrowser/filebrowser/lib"
) )
// ShareStore is a shareable links store. // ShareStore is a shareable links store.

View File

@ -4,7 +4,7 @@ import (
"reflect" "reflect"
"github.com/asdine/storm" "github.com/asdine/storm"
fm "github.com/filebrowser/filebrowser" fb "github.com/filebrowser/filebrowser/lib"
) )
// UsersStore is a users store. // UsersStore is a users store.
@ -13,11 +13,11 @@ type UsersStore struct {
} }
// Get gets a user with a certain id from the database. // Get gets a user with a certain id from the database.
func (u UsersStore) Get(id int, builder fm.FSBuilder) (*fm.User, error) { func (u UsersStore) Get(id int, builder fb.FSBuilder) (*fb.User, error) {
var us fm.User var us fb.User
err := u.DB.One("ID", id, &us) err := u.DB.One("ID", id, &us)
if err == storm.ErrNotFound { if err == storm.ErrNotFound {
return nil, fm.ErrNotExist return nil, fb.ErrNotExist
} }
if err != nil { if err != nil {
@ -29,11 +29,11 @@ func (u UsersStore) Get(id int, builder fm.FSBuilder) (*fm.User, error) {
} }
// GetByUsername gets a user with a certain username from the database. // GetByUsername gets a user with a certain username from the database.
func (u UsersStore) GetByUsername(username string, builder fm.FSBuilder) (*fm.User, error) { func (u UsersStore) GetByUsername(username string, builder fb.FSBuilder) (*fb.User, error) {
var us fm.User var us fb.User
err := u.DB.One("Username", username, &us) err := u.DB.One("Username", username, &us)
if err == storm.ErrNotFound { if err == storm.ErrNotFound {
return nil, fm.ErrNotExist return nil, fb.ErrNotExist
} }
if err != nil { if err != nil {
@ -45,11 +45,11 @@ func (u UsersStore) GetByUsername(username string, builder fm.FSBuilder) (*fm.Us
} }
// Gets gets all the users from the database. // Gets gets all the users from the database.
func (u UsersStore) Gets(builder fm.FSBuilder) ([]*fm.User, error) { func (u UsersStore) Gets(builder fb.FSBuilder) ([]*fb.User, error) {
var us []*fm.User var us []*fb.User
err := u.DB.All(&us) err := u.DB.All(&us)
if err == storm.ErrNotFound { if err == storm.ErrNotFound {
return nil, fm.ErrNotExist return nil, fb.ErrNotExist
} }
if err != nil { if err != nil {
@ -64,7 +64,7 @@ func (u UsersStore) Gets(builder fm.FSBuilder) ([]*fm.User, error) {
} }
// Update updates the whole user object or only certain fields. // Update updates the whole user object or only certain fields.
func (u UsersStore) Update(us *fm.User, fields ...string) error { func (u UsersStore) Update(us *fb.User, fields ...string) error {
if len(fields) == 0 { if len(fields) == 0 {
return u.Save(us) return u.Save(us)
} }
@ -80,11 +80,11 @@ func (u UsersStore) Update(us *fm.User, fields ...string) error {
} }
// Save saves a user to the database. // Save saves a user to the database.
func (u UsersStore) Save(us *fm.User) error { func (u UsersStore) Save(us *fb.User) error {
return u.DB.Save(us) return u.DB.Save(us)
} }
// Delete deletes a user from the database. // Delete deletes a user from the database.
func (u UsersStore) Delete(id int) error { func (u UsersStore) Delete(id int) error {
return u.DB.DeleteStruct(&fm.User{ID: id}) return u.DB.DeleteStruct(&fb.User{ID: id})
} }

View File

@ -74,4 +74,4 @@ One simple implementation for this, at port 80, in the root of the domain, would
http.ListenAndServe(":80", h.Handler(m)) http.ListenAndServe(":80", h.Handler(m))
*/ */
package filebrowser package lib

View File

@ -1,4 +1,4 @@
package filebrowser package lib
import ( import (
"bytes" "bytes"

View File

@ -1,4 +1,4 @@
package filebrowser package lib
import ( import (
"crypto/rand" "crypto/rand"
@ -15,7 +15,7 @@ import (
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
"github.com/GeertJohan/go.rice" rice "github.com/GeertJohan/go.rice"
"github.com/hacdias/fileutils" "github.com/hacdias/fileutils"
"github.com/mholt/caddy" "github.com/mholt/caddy"
"github.com/robfig/cron" "github.com/robfig/cron"
@ -41,6 +41,25 @@ var (
ErrInvalidOption = errors.New("invalid option") ErrInvalidOption = errors.New("invalid option")
) )
// ReCaptcha settings.
type ReCaptcha struct {
Host string
Key string
Secret string
}
// Auth settings.
type Auth struct {
// Define if which of the following authentication mechansims should be used:
// - 'default', which requires a user and a password.
// - 'proxy', which requires a valid user and the user name has to be provided through an
// http header.
// - 'none', which allows anyone to access the filebrowser instance.
Method string
// If 'Method' is set to 'proxy' the header configured below is used to identify the user.
Header string
}
// FileBrowser is a file manager instance. It should be creating using the // FileBrowser is a file manager instance. It should be creating using the
// 'New' function and not directly. // 'New' function and not directly.
type FileBrowser struct { type FileBrowser struct {
@ -67,24 +86,11 @@ type FileBrowser struct {
// edited directly. Use SetBaseURL. // edited directly. Use SetBaseURL.
BaseURL string BaseURL string
// NoAuth disables the authentication. When the authentication is disabled, // Authentication configuration.
// there will only exist one user, called "admin". Auth *Auth
NoAuth bool
// Define if which of the following authentication mechansims should be used:
// - 'default', which requires a user and a password.
// - 'proxy', which requires a valid user and the user name has to be provided through an
// http header.
// - 'none', which allows anyone to access the filebrowser instance.
AuthMethod string
// When 'AuthMethod' is set to 'proxy' the header configured below is used to identify the user.
LoginHeader string
// ReCaptcha host, key and secret. // ReCaptcha host, key and secret.
ReCaptchaHost string ReCaptcha *ReCaptcha
ReCaptchaKey string
ReCaptchaSecret string
// StaticGen is the static websit generator handler. // StaticGen is the static websit generator handler.
StaticGen StaticGen StaticGen StaticGen
@ -129,7 +135,7 @@ type FSBuilder func(scope string) FileSystem
func (m *FileBrowser) Setup() error { func (m *FileBrowser) Setup() error {
// Creates a new File Browser instance with the Users // Creates a new File Browser instance with the Users
// map and Assets box. // map and Assets box.
m.Assets = rice.MustFindBox("./frontend/dist") m.Assets = rice.MustFindBox("../frontend/dist")
m.Cron = cron.New() m.Cron = cron.New()
// Tries to get the encryption key from the database. // Tries to get the encryption key from the database.

View File

@ -9,7 +9,7 @@ import (
"github.com/dgrijalva/jwt-go" "github.com/dgrijalva/jwt-go"
"github.com/dgrijalva/jwt-go/request" "github.com/dgrijalva/jwt-go/request"
fb "github.com/filebrowser/filebrowser" fb "github.com/filebrowser/filebrowser/lib"
) )
const reCaptchaAPI = "/recaptcha/api/siteverify" const reCaptchaAPI = "/recaptcha/api/siteverify"
@ -51,14 +51,14 @@ func reCaptcha(host, secret, response string) (bool, error) {
// authHandler processes the authentication for the user. // authHandler processes the authentication for the user.
func authHandler(c *fb.Context, w http.ResponseWriter, r *http.Request) (int, error) { func authHandler(c *fb.Context, w http.ResponseWriter, r *http.Request) (int, error) {
if c.NoAuth { if c.Auth.Method == "none" {
// NoAuth instances shouldn't call this method. // NoAuth instances shouldn't call this method.
return 0, nil return 0, nil
} }
if c.AuthMethod == "proxy" { if c.Auth.Method == "proxy" {
// Receive the Username from the Header and check if it exists. // Receive the Username from the Header and check if it exists.
u, err := c.Store.Users.GetByUsername(r.Header.Get(c.LoginHeader), c.NewFS) u, err := c.Store.Users.GetByUsername(r.Header.Get(c.Auth.Header), c.NewFS)
if err != nil { if err != nil {
return http.StatusForbidden, nil return http.StatusForbidden, nil
} }
@ -80,8 +80,8 @@ func authHandler(c *fb.Context, w http.ResponseWriter, r *http.Request) (int, er
} }
// If ReCaptcha is enabled, check the code. // If ReCaptcha is enabled, check the code.
if len(c.ReCaptchaSecret) > 0 { if len(c.ReCaptcha.Secret) > 0 {
ok, err := reCaptcha(c.ReCaptchaHost, c.ReCaptchaSecret, cred.ReCaptcha) ok, err := reCaptcha(c.ReCaptcha.Host, c.ReCaptcha.Secret, cred.ReCaptcha)
if err != nil { if err != nil {
return http.StatusForbidden, err return http.StatusForbidden, err
} }
@ -178,14 +178,14 @@ func (e extractor) ExtractToken(r *http.Request) (string, error) {
// validateAuth is used to validate the authentication and returns the // validateAuth is used to validate the authentication and returns the
// User if it is valid. // User if it is valid.
func validateAuth(c *fb.Context, r *http.Request) (bool, *fb.User) { func validateAuth(c *fb.Context, r *http.Request) (bool, *fb.User) {
if c.NoAuth { if c.Auth.Method == "none" {
c.User = c.DefaultUser c.User = c.DefaultUser
return true, c.User return true, c.User
} }
// If proxy auth is used do not verify the JWT token if the header is provided. // If proxy auth is used do not verify the JWT token if the header is provided.
if c.AuthMethod == "proxy" { if c.Auth.Method == "proxy" {
u, err := c.Store.Users.GetByUsername(r.Header.Get(c.LoginHeader), c.NewFS) u, err := c.Store.Users.GetByUsername(r.Header.Get(c.Auth.Header), c.NewFS)
if err != nil { if err != nil {
return false, nil return false, nil
} }

View File

@ -7,7 +7,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
fb "github.com/filebrowser/filebrowser" fb "github.com/filebrowser/filebrowser/lib"
"github.com/hacdias/fileutils" "github.com/hacdias/fileutils"
"github.com/mholt/archiver" "github.com/mholt/archiver"
) )

View File

@ -10,7 +10,7 @@ import (
"strings" "strings"
"time" "time"
fb "github.com/filebrowser/filebrowser" fb "github.com/filebrowser/filebrowser/lib"
) )
// Handler returns a function compatible with http.HandleFunc. // Handler returns a function compatible with http.HandleFunc.
@ -227,17 +227,17 @@ func renderFile(c *fb.Context, w http.ResponseWriter, file string) (int, error)
w.Header().Set("Content-Type", contentType+"; charset=utf-8") w.Header().Set("Content-Type", contentType+"; charset=utf-8")
data := map[string]interface{}{ data := map[string]interface{}{
"BaseURL": c.RootURL(), "baseurl": c.RootURL(),
"NoAuth": c.NoAuth, "NoAuth": c.Auth.Method == "none",
"Version": fb.Version, "Version": fb.Version,
"CSS": template.CSS(c.CSS), "CSS": template.CSS(c.CSS),
"ReCaptcha": c.ReCaptchaKey != "" && c.ReCaptchaSecret != "", "ReCaptcha": c.ReCaptcha.Key != "" && c.ReCaptcha.Secret != "",
"ReCaptchaHost": c.ReCaptchaHost, "ReCaptchaHost": c.ReCaptcha.Host,
"ReCaptchaKey": c.ReCaptchaKey, "ReCaptchaKey": c.ReCaptcha.Key,
} }
if c.StaticGen != nil { if c.StaticGen != nil {
data["StaticGen"] = c.StaticGen.Name() data["staticgen"] = c.StaticGen.Name()
} }
err := tpl.Execute(w, data) err := tpl.Execute(w, data)
@ -291,7 +291,7 @@ func sharePage(c *fb.Context, w http.ResponseWriter, r *http.Request) (int, erro
w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Header().Set("Content-Type", "text/html; charset=utf-8")
err := tpl.Execute(w, map[string]interface{}{ err := tpl.Execute(w, map[string]interface{}{
"BaseURL": c.RootURL(), "baseurl": c.RootURL(),
"File": c.File, "File": c.File,
}) })

View File

@ -13,7 +13,7 @@ import (
"strings" "strings"
"time" "time"
fb "github.com/filebrowser/filebrowser" fb "github.com/filebrowser/filebrowser/lib"
"github.com/hacdias/fileutils" "github.com/hacdias/fileutils"
) )

View File

@ -6,7 +6,7 @@ import (
"net/http" "net/http"
"reflect" "reflect"
fb "github.com/filebrowser/filebrowser" fb "github.com/filebrowser/filebrowser/lib"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
) )

View File

@ -8,7 +8,7 @@ import (
"strings" "strings"
"time" "time"
fb "github.com/filebrowser/filebrowser" fb "github.com/filebrowser/filebrowser/lib"
) )
func shareHandler(c *fb.Context, w http.ResponseWriter, r *http.Request) (int, error) { func shareHandler(c *fb.Context, w http.ResponseWriter, r *http.Request) (int, error) {

View File

@ -8,19 +8,19 @@ import (
"path/filepath" "path/filepath"
"regexp" "regexp"
fb "github.com/filebrowser/filebrowser" fb "github.com/filebrowser/filebrowser/lib"
) )
func subtitlesHandler(c *fb.Context, w http.ResponseWriter, r *http.Request) (int, error) { func subtitlesHandler(c *fb.Context, w http.ResponseWriter, r *http.Request) (int, error) {
files, err := ReadDir(filepath.Dir(c.File.Path)) files, err := readDir(filepath.Dir(c.File.Path))
if err != nil { if err != nil {
return http.StatusInternalServerError, err return http.StatusInternalServerError, err
} }
var subtitles = make([]map[string]string, 0) subtitles := make([]map[string]string, 0)
for _, file := range files { for _, file := range files {
ext := filepath.Ext(file.Name()) ext := filepath.Ext(file.Name())
if ext == ".vtt" || ext == ".srt" { if ext == ".vtt" || ext == ".srt" {
var sub map[string]string = make(map[string]string) sub := make(map[string]string)
sub["src"] = filepath.Dir(c.File.Path) + "/" + file.Name() sub["src"] = filepath.Dir(c.File.Path) + "/" + file.Name()
sub["kind"] = "subtitles" sub["kind"] = "subtitles"
sub["label"] = file.Name() sub["label"] = file.Name()
@ -31,7 +31,7 @@ func subtitlesHandler(c *fb.Context, w http.ResponseWriter, r *http.Request) (in
} }
func subtitleHandler(c *fb.Context, w http.ResponseWriter, r *http.Request) (int, error) { func subtitleHandler(c *fb.Context, w http.ResponseWriter, r *http.Request) (int, error) {
str, err := CleanSubtitle(c.File.Path) str, err := cleanSubtitle(c.File.Path)
if err != nil { if err != nil {
return http.StatusInternalServerError, err return http.StatusInternalServerError, err
} }
@ -55,7 +55,7 @@ func subtitleHandler(c *fb.Context, w http.ResponseWriter, r *http.Request) (int
} }
func CleanSubtitle(filename string) (string, error) { func cleanSubtitle(filename string) (string, error) {
b, err := ioutil.ReadFile(filename) b, err := ioutil.ReadFile(filename)
if err != nil { if err != nil {
return "", err return "", err
@ -69,7 +69,7 @@ func CleanSubtitle(filename string) (string, error) {
return str, err return str, err
} }
func ReadDir(dirname string) ([]os.FileInfo, error) { func readDir(dirname string) ([]os.FileInfo, error) {
f, err := os.Open(dirname) f, err := os.Open(dirname)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -9,7 +9,7 @@ import (
"strconv" "strconv"
"strings" "strings"
fb "github.com/filebrowser/filebrowser" fb "github.com/filebrowser/filebrowser/lib"
) )
type modifyRequest struct { type modifyRequest struct {
@ -276,7 +276,7 @@ func usersPutHandler(c *fb.Context, w http.ResponseWriter, r *http.Request) (int
// If we're updating the default user. Only for NoAuth // If we're updating the default user. Only for NoAuth
// implementations. Used to change the viewMode. // implementations. Used to change the viewMode.
if id == 0 && c.NoAuth { if id == 0 && c.Auth.Method == "none" {
c.DefaultUser.ViewMode = u.ViewMode c.DefaultUser.ViewMode = u.ViewMode
return http.StatusOK, nil return http.StatusOK, nil
} }

View File

@ -12,7 +12,7 @@ import (
"strings" "strings"
"time" "time"
fb "github.com/filebrowser/filebrowser" fb "github.com/filebrowser/filebrowser/lib"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
) )

View File

@ -10,7 +10,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
fb "github.com/filebrowser/filebrowser" fb "github.com/filebrowser/filebrowser/lib"
"github.com/hacdias/varutils" "github.com/hacdias/varutils"
) )

View File

@ -9,7 +9,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
fb "github.com/filebrowser/filebrowser" fb "github.com/filebrowser/filebrowser/lib"
) )
// Jekyll is the Jekyll static website generator. // Jekyll is the Jekyll static website generator.