From 69a3f853bd2821d2c52a435277aaac68a468d39b Mon Sep 17 00:00:00 2001 From: 1138-4EB <1138-4EB@users.noreply.github.com> Date: Tue, 21 Aug 2018 22:42:03 +0100 Subject: [PATCH] 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`. --- .travis.yml | 3 +- Docker.json | 12 +- Gopkg.lock | 395 ---------------------- Gopkg.toml | 74 ---- build/build.sh | 13 +- build/build_all.sh | 9 +- build/build_assets.sh | 1 + build/push_ricebox.sh | 4 +- build/run_gometalinter.sh | 2 +- cli/cmd/db.go | 24 ++ cli/cmd/root.go | 75 ++++ cli/cmd/rootalias.go | 46 +++ cli/cmd/serve.go | 111 ++++++ cli/cmd/server.go | 150 ++++++++ cli/cmd/version.go | 26 ++ cli/main.go | 7 + cmd/filebrowser/main.go | 279 --------------- {bolt => lib/bolt}/config.go | 2 +- {bolt => lib/bolt}/share.go | 2 +- {bolt => lib/bolt}/users.go | 26 +- doc.go => lib/doc.go | 2 +- file.go => lib/file.go | 2 +- filebrowser.go => lib/filebrowser.go | 44 +-- {http => lib/http}/auth.go | 18 +- {http => lib/http}/download.go | 2 +- {http => lib/http}/http.go | 16 +- {http => lib/http}/resource.go | 2 +- {http => lib/http}/settings.go | 2 +- {http => lib/http}/share.go | 2 +- {http => lib/http}/subtitle.go | 14 +- {http => lib/http}/users.go | 4 +- {http => lib/http}/websockets.go | 2 +- {staticgen => lib/staticgen}/hugo.go | 2 +- {staticgen => lib/staticgen}/jekyll.go | 2 +- {staticgen => lib/staticgen}/staticgen.go | 0 35 files changed, 535 insertions(+), 840 deletions(-) delete mode 100644 Gopkg.lock delete mode 100644 Gopkg.toml create mode 100644 cli/cmd/db.go create mode 100644 cli/cmd/root.go create mode 100644 cli/cmd/rootalias.go create mode 100644 cli/cmd/serve.go create mode 100644 cli/cmd/server.go create mode 100644 cli/cmd/version.go create mode 100644 cli/main.go delete mode 100644 cmd/filebrowser/main.go rename {bolt => lib/bolt}/config.go (92%) rename {bolt => lib/bolt}/share.go (97%) rename {bolt => lib/bolt}/users.go (70%) rename doc.go => lib/doc.go (99%) rename file.go => lib/file.go (99%) rename filebrowser.go => lib/filebrowser.go (96%) rename {http => lib/http}/auth.go (91%) rename {http => lib/http}/download.go (98%) rename {http => lib/http}/http.go (96%) rename {http => lib/http}/resource.go (99%) rename {http => lib/http}/settings.go (98%) rename {http => lib/http}/share.go (98%) rename {http => lib/http}/subtitle.go (82%) rename {http => lib/http}/users.go (99%) rename {http => lib/http}/websockets.go (99%) rename {staticgen => lib/staticgen}/hugo.go (99%) rename {staticgen => lib/staticgen}/jekyll.go (98%) rename {staticgen => lib/staticgen}/staticgen.go (100%) diff --git a/.travis.yml b/.travis.yml index a65ef550..43984199 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,8 +17,7 @@ stages: - release cache: directories: - - vendor - - rice-box.go + - lib/rice-box.go jobs: include: - stage: lint diff --git a/Docker.json b/Docker.json index d3b14f36..8d9d290c 100644 --- a/Docker.json +++ b/Docker.json @@ -2,9 +2,11 @@ "port": 80, "address": "", "database": "/database.db", - "scope": "/srv", - "allowCommands": true, - "allowEdit": true, - "allowNew": true, - "commands": [] + "defaults": { + "scope": "/srv", + "allowCommands": true, + "allowEdit": true, + "allowNew": true, + "commands": [] + } } diff --git a/Gopkg.lock b/Gopkg.lock deleted file mode 100644 index e6712507..00000000 --- a/Gopkg.lock +++ /dev/null @@ -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 diff --git a/Gopkg.toml b/Gopkg.toml deleted file mode 100644 index 479c539b..00000000 --- a/Gopkg.toml +++ /dev/null @@ -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 diff --git a/build/build.sh b/build/build.sh index 1619c161..9e3c099c 100755 --- a/build/build.sh +++ b/build/build.sh @@ -2,22 +2,19 @@ set -e -cd $(dirname $0)/.. +cd $(dirname $0)/../cli -dep ensure -vendor-only +go get -v ./... if [ "$COMMIT_SHA" != "" ]; then 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 echo "Build cmd/filebrowser" -cd cmd/filebrowser -CGO_ENABLED=0 go build -a -cd ../.. -cp cmd/filebrowser/filebrowser ./ +CGO_ENABLED=0 go build -a -o filebrowser if [ "$COMMIT_SHA" != "" ]; then 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 diff --git a/build/build_all.sh b/build/build_all.sh index 4c41be5e..cea7f31c 100755 --- a/build/build_all.sh +++ b/build/build_all.sh @@ -2,8 +2,8 @@ cd $(dirname $0)/.. -if [ -d "rice-box.go" ]; then - rm -rf rice-box.go +if [ -d lib/"rice-box.go" ]; then + rm -rf lib/rice-box.go fi if [ "$USE_DOCKER" != "" ]; then @@ -41,9 +41,8 @@ if [ "$USE_DOCKER" != "" ]; then for d in "dist/" "node_modules/"; do docker cp filebrowser-tmp:/$WDIR/frontend/$d frontend done - for d in "vendor/" "rice-box.go" "filebrowser"; do - docker cp filebrowser-tmp:/$WDIR/$d ./ - done + docker cp filebrowser-tmp:/$WDIR/cli/filebrowser ./filebrowser + docker cp filebrowser-tmp:/$WDIR/lib/rice-box.go ./lib/rice-box.go fi docker rm -f filebrowser-tmp else diff --git a/build/build_assets.sh b/build/build_assets.sh index bd86ac6f..73a825f8 100755 --- a/build/build_assets.sh +++ b/build/build_assets.sh @@ -19,4 +19,5 @@ if ! [ -x "$(command -v rice)" ]; then fi # Embed the assets using rice +cd lib rice embed-go diff --git a/build/push_ricebox.sh b/build/push_ricebox.sh index c1c038d0..5ba8fbac 100755 --- a/build/push_ricebox.sh +++ b/build/push_ricebox.sh @@ -11,8 +11,8 @@ openssl aes-256-cbc -K $encrypted_9ca81b5594f5_key -iv $encrypted_9ca81b5594f5_i git clone git@github.com:filebrowser/caddy caddy cd caddy -cp ../../rice-box.go assets/ -sed -i 's/package filebrowser/package assets/g' assets/rice-box.go +cp ../../lib/rice-box.go assets/ +sed -i 's/package lib/package assets/g' assets/rice-box.go git checkout -b update-rice-box origin/master git config --local user.name "Filebrowser Bot" git config --local user.email "FilebrowserBot@users.noreply.github.com" diff --git a/build/run_gometalinter.sh b/build/run_gometalinter.sh index def539cf..a90ba71c 100755 --- a/build/run_gometalinter.sh +++ b/build/run_gometalinter.sh @@ -8,7 +8,7 @@ dolint='gometalinter --exclude="rice-box.go" --exclude="vendor" --deadline=300s if [ "$USE_DOCKER" != "" ]; then 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" else $dolint diff --git a/cli/cmd/db.go b/cli/cmd/db.go new file mode 100644 index 00000000..8dc45e2e --- /dev/null +++ b/cli/cmd/db.go @@ -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) +} diff --git a/cli/cmd/root.go b/cli/cmd/root.go new file mode 100644 index 00000000..b1037c43 --- /dev/null +++ b/cli/cmd/root.go @@ -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()) + } +} diff --git a/cli/cmd/rootalias.go b/cli/cmd/rootalias.go new file mode 100644 index 00000000..9f1222d8 --- /dev/null +++ b/cli/cmd/rootalias.go @@ -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 +} diff --git a/cli/cmd/serve.go b/cli/cmd/serve.go new file mode 100644 index 00000000..6b626883 --- /dev/null +++ b/cli/cmd/serve.go @@ -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") +} diff --git a/cli/cmd/server.go b/cli/cmd/server.go new file mode 100644 index 00000000..042e9877 --- /dev/null +++ b/cli/cmd/server.go @@ -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) +} diff --git a/cli/cmd/version.go b/cli/cmd/version.go new file mode 100644 index 00000000..7687e77c --- /dev/null +++ b/cli/cmd/version.go @@ -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) +} diff --git a/cli/main.go b/cli/main.go new file mode 100644 index 00000000..38b21602 --- /dev/null +++ b/cli/main.go @@ -0,0 +1,7 @@ +package main + +import "github.com/filebrowser/filebrowser/cli/cmd" + +func main() { + cmd.Execute() +} diff --git a/cmd/filebrowser/main.go b/cmd/filebrowser/main.go deleted file mode 100644 index e2bb787e..00000000 --- a/cmd/filebrowser/main.go +++ /dev/null @@ -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) -} diff --git a/bolt/config.go b/lib/bolt/config.go similarity index 92% rename from bolt/config.go rename to lib/bolt/config.go index aeb646b9..59c44a56 100644 --- a/bolt/config.go +++ b/lib/bolt/config.go @@ -2,7 +2,7 @@ package bolt import ( "github.com/asdine/storm" - fb "github.com/filebrowser/filebrowser" + fb "github.com/filebrowser/filebrowser/lib" ) // ConfigStore is a configuration store. diff --git a/bolt/share.go b/lib/bolt/share.go similarity index 97% rename from bolt/share.go rename to lib/bolt/share.go index c908bd36..629ecca8 100644 --- a/bolt/share.go +++ b/lib/bolt/share.go @@ -3,7 +3,7 @@ package bolt import ( "github.com/asdine/storm" "github.com/asdine/storm/q" - fb "github.com/filebrowser/filebrowser" + fb "github.com/filebrowser/filebrowser/lib" ) // ShareStore is a shareable links store. diff --git a/bolt/users.go b/lib/bolt/users.go similarity index 70% rename from bolt/users.go rename to lib/bolt/users.go index 58976306..43c33fcc 100644 --- a/bolt/users.go +++ b/lib/bolt/users.go @@ -4,7 +4,7 @@ import ( "reflect" "github.com/asdine/storm" - fm "github.com/filebrowser/filebrowser" + fb "github.com/filebrowser/filebrowser/lib" ) // UsersStore is a users store. @@ -13,11 +13,11 @@ type UsersStore struct { } // Get gets a user with a certain id from the database. -func (u UsersStore) Get(id int, builder fm.FSBuilder) (*fm.User, error) { - var us fm.User +func (u UsersStore) Get(id int, builder fb.FSBuilder) (*fb.User, error) { + var us fb.User err := u.DB.One("ID", id, &us) if err == storm.ErrNotFound { - return nil, fm.ErrNotExist + return nil, fb.ErrNotExist } 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. -func (u UsersStore) GetByUsername(username string, builder fm.FSBuilder) (*fm.User, error) { - var us fm.User +func (u UsersStore) GetByUsername(username string, builder fb.FSBuilder) (*fb.User, error) { + var us fb.User err := u.DB.One("Username", username, &us) if err == storm.ErrNotFound { - return nil, fm.ErrNotExist + return nil, fb.ErrNotExist } 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. -func (u UsersStore) Gets(builder fm.FSBuilder) ([]*fm.User, error) { - var us []*fm.User +func (u UsersStore) Gets(builder fb.FSBuilder) ([]*fb.User, error) { + var us []*fb.User err := u.DB.All(&us) if err == storm.ErrNotFound { - return nil, fm.ErrNotExist + return nil, fb.ErrNotExist } 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. -func (u UsersStore) Update(us *fm.User, fields ...string) error { +func (u UsersStore) Update(us *fb.User, fields ...string) error { if len(fields) == 0 { 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. -func (u UsersStore) Save(us *fm.User) error { +func (u UsersStore) Save(us *fb.User) error { return u.DB.Save(us) } // Delete deletes a user from the database. func (u UsersStore) Delete(id int) error { - return u.DB.DeleteStruct(&fm.User{ID: id}) + return u.DB.DeleteStruct(&fb.User{ID: id}) } diff --git a/doc.go b/lib/doc.go similarity index 99% rename from doc.go rename to lib/doc.go index 23fabeec..91907439 100644 --- a/doc.go +++ b/lib/doc.go @@ -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)) */ -package filebrowser +package lib diff --git a/file.go b/lib/file.go similarity index 99% rename from file.go rename to lib/file.go index 1aa3ebd4..2fb19425 100644 --- a/file.go +++ b/lib/file.go @@ -1,4 +1,4 @@ -package filebrowser +package lib import ( "bytes" diff --git a/filebrowser.go b/lib/filebrowser.go similarity index 96% rename from filebrowser.go rename to lib/filebrowser.go index ed1ac681..caad4357 100644 --- a/filebrowser.go +++ b/lib/filebrowser.go @@ -1,4 +1,4 @@ -package filebrowser +package lib import ( "crypto/rand" @@ -15,7 +15,7 @@ import ( "golang.org/x/crypto/bcrypt" - "github.com/GeertJohan/go.rice" + rice "github.com/GeertJohan/go.rice" "github.com/hacdias/fileutils" "github.com/mholt/caddy" "github.com/robfig/cron" @@ -41,6 +41,25 @@ var ( 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 // 'New' function and not directly. type FileBrowser struct { @@ -67,24 +86,11 @@ type FileBrowser struct { // edited directly. Use SetBaseURL. BaseURL string - // NoAuth disables the authentication. When the authentication is disabled, - // there will only exist one user, called "admin". - 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 + // Authentication configuration. + Auth *Auth // ReCaptcha host, key and secret. - ReCaptchaHost string - ReCaptchaKey string - ReCaptchaSecret string + ReCaptcha *ReCaptcha // StaticGen is the static websit generator handler. StaticGen StaticGen @@ -129,7 +135,7 @@ type FSBuilder func(scope string) FileSystem func (m *FileBrowser) Setup() error { // Creates a new File Browser instance with the Users // map and Assets box. - m.Assets = rice.MustFindBox("./frontend/dist") + m.Assets = rice.MustFindBox("../frontend/dist") m.Cron = cron.New() // Tries to get the encryption key from the database. diff --git a/http/auth.go b/lib/http/auth.go similarity index 91% rename from http/auth.go rename to lib/http/auth.go index 58d12212..c43b20d2 100644 --- a/http/auth.go +++ b/lib/http/auth.go @@ -9,7 +9,7 @@ import ( "github.com/dgrijalva/jwt-go" "github.com/dgrijalva/jwt-go/request" - fb "github.com/filebrowser/filebrowser" + fb "github.com/filebrowser/filebrowser/lib" ) const reCaptchaAPI = "/recaptcha/api/siteverify" @@ -51,14 +51,14 @@ func reCaptcha(host, secret, response string) (bool, error) { // authHandler processes the authentication for the user. 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. return 0, nil } - if c.AuthMethod == "proxy" { + if c.Auth.Method == "proxy" { // 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 { 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 len(c.ReCaptchaSecret) > 0 { - ok, err := reCaptcha(c.ReCaptchaHost, c.ReCaptchaSecret, cred.ReCaptcha) + if len(c.ReCaptcha.Secret) > 0 { + ok, err := reCaptcha(c.ReCaptcha.Host, c.ReCaptcha.Secret, cred.ReCaptcha) if err != nil { 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 // User if it is valid. func validateAuth(c *fb.Context, r *http.Request) (bool, *fb.User) { - if c.NoAuth { + if c.Auth.Method == "none" { c.User = c.DefaultUser return true, c.User } // If proxy auth is used do not verify the JWT token if the header is provided. - if c.AuthMethod == "proxy" { - u, err := c.Store.Users.GetByUsername(r.Header.Get(c.LoginHeader), c.NewFS) + if c.Auth.Method == "proxy" { + u, err := c.Store.Users.GetByUsername(r.Header.Get(c.Auth.Header), c.NewFS) if err != nil { return false, nil } diff --git a/http/download.go b/lib/http/download.go similarity index 98% rename from http/download.go rename to lib/http/download.go index a1a1d08e..d2143646 100644 --- a/http/download.go +++ b/lib/http/download.go @@ -7,7 +7,7 @@ import ( "path/filepath" "strings" - fb "github.com/filebrowser/filebrowser" + fb "github.com/filebrowser/filebrowser/lib" "github.com/hacdias/fileutils" "github.com/mholt/archiver" ) diff --git a/http/http.go b/lib/http/http.go similarity index 96% rename from http/http.go rename to lib/http/http.go index a5e93311..cadc893d 100644 --- a/http/http.go +++ b/lib/http/http.go @@ -10,7 +10,7 @@ import ( "strings" "time" - fb "github.com/filebrowser/filebrowser" + fb "github.com/filebrowser/filebrowser/lib" ) // 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") data := map[string]interface{}{ - "BaseURL": c.RootURL(), - "NoAuth": c.NoAuth, + "baseurl": c.RootURL(), + "NoAuth": c.Auth.Method == "none", "Version": fb.Version, "CSS": template.CSS(c.CSS), - "ReCaptcha": c.ReCaptchaKey != "" && c.ReCaptchaSecret != "", - "ReCaptchaHost": c.ReCaptchaHost, - "ReCaptchaKey": c.ReCaptchaKey, + "ReCaptcha": c.ReCaptcha.Key != "" && c.ReCaptcha.Secret != "", + "ReCaptchaHost": c.ReCaptcha.Host, + "ReCaptchaKey": c.ReCaptcha.Key, } if c.StaticGen != nil { - data["StaticGen"] = c.StaticGen.Name() + data["staticgen"] = c.StaticGen.Name() } 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") err := tpl.Execute(w, map[string]interface{}{ - "BaseURL": c.RootURL(), + "baseurl": c.RootURL(), "File": c.File, }) diff --git a/http/resource.go b/lib/http/resource.go similarity index 99% rename from http/resource.go rename to lib/http/resource.go index df776dcc..af39962d 100644 --- a/http/resource.go +++ b/lib/http/resource.go @@ -13,7 +13,7 @@ import ( "strings" "time" - fb "github.com/filebrowser/filebrowser" + fb "github.com/filebrowser/filebrowser/lib" "github.com/hacdias/fileutils" ) diff --git a/http/settings.go b/lib/http/settings.go similarity index 98% rename from http/settings.go rename to lib/http/settings.go index 1663f5e4..5008bea7 100644 --- a/http/settings.go +++ b/lib/http/settings.go @@ -6,7 +6,7 @@ import ( "net/http" "reflect" - fb "github.com/filebrowser/filebrowser" + fb "github.com/filebrowser/filebrowser/lib" "github.com/mitchellh/mapstructure" ) diff --git a/http/share.go b/lib/http/share.go similarity index 98% rename from http/share.go rename to lib/http/share.go index df78d24d..79f5236b 100644 --- a/http/share.go +++ b/lib/http/share.go @@ -8,7 +8,7 @@ import ( "strings" "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) { diff --git a/http/subtitle.go b/lib/http/subtitle.go similarity index 82% rename from http/subtitle.go rename to lib/http/subtitle.go index e83f295b..3ccf0046 100644 --- a/http/subtitle.go +++ b/lib/http/subtitle.go @@ -8,19 +8,19 @@ import ( "path/filepath" "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) { - files, err := ReadDir(filepath.Dir(c.File.Path)) + files, err := readDir(filepath.Dir(c.File.Path)) if err != nil { return http.StatusInternalServerError, err } - var subtitles = make([]map[string]string, 0) + subtitles := make([]map[string]string, 0) for _, file := range files { ext := filepath.Ext(file.Name()) 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["kind"] = "subtitles" 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) { - str, err := CleanSubtitle(c.File.Path) + str, err := cleanSubtitle(c.File.Path) if err != nil { 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) if err != nil { return "", err @@ -69,7 +69,7 @@ func CleanSubtitle(filename string) (string, error) { return str, err } -func ReadDir(dirname string) ([]os.FileInfo, error) { +func readDir(dirname string) ([]os.FileInfo, error) { f, err := os.Open(dirname) if err != nil { return nil, err diff --git a/http/users.go b/lib/http/users.go similarity index 99% rename from http/users.go rename to lib/http/users.go index f924e304..d78c19c0 100644 --- a/http/users.go +++ b/lib/http/users.go @@ -9,7 +9,7 @@ import ( "strconv" "strings" - fb "github.com/filebrowser/filebrowser" + fb "github.com/filebrowser/filebrowser/lib" ) 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 // implementations. Used to change the viewMode. - if id == 0 && c.NoAuth { + if id == 0 && c.Auth.Method == "none" { c.DefaultUser.ViewMode = u.ViewMode return http.StatusOK, nil } diff --git a/http/websockets.go b/lib/http/websockets.go similarity index 99% rename from http/websockets.go rename to lib/http/websockets.go index e135d110..54178e5b 100644 --- a/http/websockets.go +++ b/lib/http/websockets.go @@ -12,7 +12,7 @@ import ( "strings" "time" - fb "github.com/filebrowser/filebrowser" + fb "github.com/filebrowser/filebrowser/lib" "github.com/gorilla/websocket" ) diff --git a/staticgen/hugo.go b/lib/staticgen/hugo.go similarity index 99% rename from staticgen/hugo.go rename to lib/staticgen/hugo.go index d9e989a6..f8c6d7a5 100644 --- a/staticgen/hugo.go +++ b/lib/staticgen/hugo.go @@ -10,7 +10,7 @@ import ( "path/filepath" "strings" - fb "github.com/filebrowser/filebrowser" + fb "github.com/filebrowser/filebrowser/lib" "github.com/hacdias/varutils" ) diff --git a/staticgen/jekyll.go b/lib/staticgen/jekyll.go similarity index 98% rename from staticgen/jekyll.go rename to lib/staticgen/jekyll.go index c8902798..ba119fcc 100644 --- a/staticgen/jekyll.go +++ b/lib/staticgen/jekyll.go @@ -9,7 +9,7 @@ import ( "path/filepath" "strings" - fb "github.com/filebrowser/filebrowser" + fb "github.com/filebrowser/filebrowser/lib" ) // Jekyll is the Jekyll static website generator. diff --git a/staticgen/staticgen.go b/lib/staticgen/staticgen.go similarity index 100% rename from staticgen/staticgen.go rename to lib/staticgen/staticgen.go