repos / pico

pico services - prose.sh, pastes.sh, imgs.sh, feeds.sh, pgs.sh
git clone https://github.com/picosh/pico.git

commit
29f8c9f
parent
2a095b0
author
Antonio Mika
date
2022-08-26 16:08:24 +0000 UTC
Added minio support to imgs
15 files changed,  +245, -39
M go.mod
M go.sum
M cmd/imgs/ssh/main.go
+14, -1
 1@@ -66,10 +66,23 @@ func main() {
 2 	logger := cfg.Logger
 3 	dbh := postgres.NewDB(&cfg.ConfigCms)
 4 	defer dbh.Close()
 5+
 6+	var st storage.ObjectStorage
 7+	var err error
 8+	if cfg.MinioURL == "" {
 9+		st, err = storage.NewStorageFS(cfg.StorageDir)
10+	} else {
11+		st, err = storage.NewStorageMinio(cfg.MinioURL, cfg.MinioUser, cfg.MinioPass)
12+	}
13+
14+	if err != nil {
15+		logger.Fatal(err)
16+	}
17+
18 	handler := uploadimgs.NewUploadImgHandler(
19 		dbh,
20 		cfg,
21-		storage.NewStorageFS(cfg.StorageDir),
22+		st,
23 	)
24 
25 	sshServer := &SSHServer{}
M filehandlers/imgs/handler.go
+2, -2
 1@@ -33,10 +33,10 @@ type UploadImgHandler struct {
 2 	User    *db.User
 3 	DBPool  db.DB
 4 	Cfg     *shared.ConfigSite
 5-	Storage *storage.StorageFS
 6+	Storage storage.ObjectStorage
 7 }
 8 
 9-func NewUploadImgHandler(dbpool db.DB, cfg *shared.ConfigSite, storage *storage.StorageFS) *UploadImgHandler {
10+func NewUploadImgHandler(dbpool db.DB, cfg *shared.ConfigSite, storage storage.ObjectStorage) *UploadImgHandler {
11 	return &UploadImgHandler{
12 		DBPool:  dbpool,
13 		Cfg:     cfg,
M go.mod
+13, -0
 1@@ -19,6 +19,7 @@ require (
 2 	github.com/lib/pq v1.10.6
 3 	github.com/matryer/is v1.4.0
 4 	github.com/microcosm-cc/bluemonday v1.0.19
 5+	github.com/minio/minio-go/v7 v7.0.34
 6 	github.com/muesli/reflow v0.3.0
 7 	github.com/pkg/sftp v1.13.5
 8 	github.com/yuin/goldmark v1.4.13
 9@@ -40,17 +41,26 @@ require (
10 	github.com/charmbracelet/keygen v0.3.0 // indirect
11 	github.com/containerd/console v1.0.3 // indirect
12 	github.com/dlclark/regexp2 v1.7.0 // indirect
13+	github.com/dustin/go-humanize v1.0.0 // indirect
14 	github.com/golang/protobuf v1.5.2 // indirect
15 	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
16+	github.com/google/uuid v1.3.0 // indirect
17 	github.com/gorilla/css v1.0.0 // indirect
18+	github.com/json-iterator/go v1.1.12 // indirect
19+	github.com/klauspost/compress v1.15.9 // indirect
20+	github.com/klauspost/cpuid/v2 v2.1.0 // indirect
21 	github.com/kr/fs v0.1.0 // indirect
22 	github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
23 	github.com/mattn/go-isatty v0.0.16 // indirect
24 	github.com/mattn/go-localereader v0.0.1 // indirect
25 	github.com/mattn/go-runewidth v0.0.13 // indirect
26 	github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
27+	github.com/minio/md5-simd v1.1.2 // indirect
28+	github.com/minio/sha256-simd v1.0.0 // indirect
29 	github.com/mitchellh/go-homedir v1.1.0 // indirect
30 	github.com/mmcloughlin/md4 v0.1.2 // indirect
31+	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
32+	github.com/modern-go/reflect2 v1.0.2 // indirect
33 	github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 // indirect
34 	github.com/muesli/cancelreader v0.2.2 // indirect
35 	github.com/muesli/termenv v0.12.0 // indirect
36@@ -59,6 +69,8 @@ require (
37 	github.com/prometheus/common v0.37.0 // indirect
38 	github.com/prometheus/procfs v0.8.0 // indirect
39 	github.com/rivo/uniseg v0.3.4 // indirect
40+	github.com/rs/xid v1.4.0 // indirect
41+	github.com/sirupsen/logrus v1.9.0 // indirect
42 	go.uber.org/atomic v1.10.0 // indirect
43 	go.uber.org/multierr v1.8.0 // indirect
44 	golang.org/x/net v0.0.0-20220822230855-b0a4917ee28c // indirect
45@@ -66,5 +78,6 @@ require (
46 	golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 // indirect
47 	golang.org/x/text v0.3.7 // indirect
48 	google.golang.org/protobuf v1.28.1 // indirect
49+	gopkg.in/ini.v1 v1.66.6 // indirect
50 	gopkg.in/yaml.v2 v2.4.0 // indirect
51 )
M go.sum
+28, -16
  1@@ -44,8 +44,6 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF
  2 github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
  3 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
  4 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
  5-github.com/antoniomika/go-rsync-receiver v0.0.0-20220817204644-b0fcbb706c46 h1:xf3+67PUvBatjQk3mO2umUrPT6FqAArKru1DBDaZNaY=
  6-github.com/antoniomika/go-rsync-receiver v0.0.0-20220817204644-b0fcbb706c46/go.mod h1:zmqePVIo1hp+WEKxERLLGHJBDOr8/z/T4eFqXgWIw1w=
  7 github.com/antoniomika/go-rsync-receiver v0.0.0-20220825041817-0387edc9afb7 h1:CECWxPqxYAwjlUQxEeuaqnjlTsOsaAMfu6f27xHswm0=
  8 github.com/antoniomika/go-rsync-receiver v0.0.0-20220825041817-0387edc9afb7/go.mod h1:zmqePVIo1hp+WEKxERLLGHJBDOr8/z/T4eFqXgWIw1w=
  9 github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA=
 10@@ -68,8 +66,6 @@ github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
 11 github.com/charmbracelet/bubbles v0.13.0 h1:zP/ROH3wJEBqZWKIsD50ZKKlx3ydLInq3LdD/Nrlb8w=
 12 github.com/charmbracelet/bubbles v0.13.0/go.mod h1:bbeTiXwPww4M031aGi8UK2HT9RDWoiNibae+1yCMtcc=
 13 github.com/charmbracelet/bubbletea v0.21.0/go.mod h1:GgmJMec61d08zXsOhqRC/AiOx4K4pmz+VIcRIm1FKr4=
 14-github.com/charmbracelet/bubbletea v0.22.0 h1:E1BTNSE3iIrq0G0X6TjGAmrQ32cGCbFDPcIuImikrUc=
 15-github.com/charmbracelet/bubbletea v0.22.0/go.mod h1:aoVIwlNlr5wbCB26KhxfrqAn0bMp4YpJcoOelbxApjs=
 16 github.com/charmbracelet/bubbletea v0.22.1 h1:z66q0LWdJNOWEH9zadiAIXp2GN1AWrwNXU8obVY9X24=
 17 github.com/charmbracelet/bubbletea v0.22.1/go.mod h1:8/7hVvbPN6ZZPkczLiB8YpLkLJ0n7DMho5Wvfd2X1C0=
 18 github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
 19@@ -94,6 +90,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
 20 github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
 21 github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
 22 github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
 23+github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
 24+github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 25 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 26 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 27 github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
 28@@ -166,6 +164,8 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf
 29 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 30 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
 31 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
 32+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 33+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 34 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 35 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 36 github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
 37@@ -179,12 +179,19 @@ github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX
 38 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 39 github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 40 github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 41+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
 42 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
 43 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 44 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
 45 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 46 github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
 47 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 48+github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY=
 49+github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
 50+github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
 51+github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
 52+github.com/klauspost/cpuid/v2 v2.1.0 h1:eyi1Ad2aNJMW95zcSbmGg7Cg6cq3ADwLpMAP96d8rF0=
 53+github.com/klauspost/cpuid/v2 v2.1.0/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
 54 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 55 github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 56 github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
 57@@ -215,20 +222,27 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0j
 58 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 59 github.com/microcosm-cc/bluemonday v1.0.19 h1:OI7hoF5FY4pFz2VA//RN8TfM0YJ2dJcl4P4APrCWy6c=
 60 github.com/microcosm-cc/bluemonday v1.0.19/go.mod h1:QNzV2UbLK2/53oIIwTOyLUSABMkjZ4tqiyC1g/DyqxE=
 61+github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
 62+github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
 63+github.com/minio/minio-go/v7 v7.0.34 h1:JMfS5fudx1mN6V2MMNyCJ7UMrjEzZzIvMgfkWc1Vnjk=
 64+github.com/minio/minio-go/v7 v7.0.34/go.mod h1:nCrRzjoSUQh8hgKKtu3Y708OLvRLtuASMg2/nvmbarw=
 65+github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
 66+github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
 67 github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
 68 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 69 github.com/mmcloughlin/md4 v0.1.2 h1:kGYl+iNbxhyz4u76ka9a+0TXP9KWt/LmnM0QhZwhcBo=
 70 github.com/mmcloughlin/md4 v0.1.2/go.mod h1:AAxFX59fddW0IguqNzWlf1lazh1+rXeIt/Bj49cqDTQ=
 71 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 72+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
 73 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 74 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 75 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 76+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
 77 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
 78 github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
 79 github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 h1:kMlmsLSbjkikxQJ1IPwaM+7LJ9ltFu/fi8CRzvSnQmA=
 80 github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
 81 github.com/muesli/cancelreader v0.2.0/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
 82-github.com/muesli/cancelreader v0.2.1/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
 83 github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
 84 github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
 85 github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
 86@@ -278,18 +292,22 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
 87 github.com/rivo/uniseg v0.3.4 h1:3Z3Eu6FGHZWSfNKJTOUiPatWwfc7DzJRU04jFUqJODw=
 88 github.com/rivo/uniseg v0.3.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
 89 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 90+github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY=
 91+github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
 92 github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
 93 github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
 94 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 95 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 96 github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
 97+github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
 98+github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 99 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
100 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
101 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
102 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
103 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
104-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
105 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
106+github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
107 github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
108 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
109 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
110@@ -311,8 +329,6 @@ go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0
111 go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
112 go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
113 go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
114-go.uber.org/zap v1.22.0 h1:Zcye5DUgBloQ9BaT4qc9BnjOFog5TvBSAGkJ3Nf70c0=
115-go.uber.org/zap v1.22.0/go.mod h1:H4siCOZOrAolnUPJEkfaSjDqyP+BDS0DdDWzwcgt3+U=
116 go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY=
117 go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY=
118 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
119@@ -323,8 +339,6 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
120 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
121 golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
122 golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
123-golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 h1:GIAS/yBem/gq2MUqgNIzUHW7cJMmx3TGZOrnyYaNQ6c=
124-golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
125 golang.org/x/crypto v0.0.0-20220824171710-5757bc0c5503 h1:vJ2V3lFLg+bBhgroYuRfyN583UzVveQmIXjc8T/y3to=
126 golang.org/x/crypto v0.0.0-20220824171710-5757bc0c5503/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
127 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
128@@ -337,8 +351,6 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
129 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
130 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
131 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
132-golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
133-golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
134 golang.org/x/exp v0.0.0-20220823124025-807a23277127 h1:S4NrSKDfihhl3+4jSTgwoIevKxX9p7Iv9x++OEIptDo=
135 golang.org/x/exp v0.0.0-20220823124025-807a23277127/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
136 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
137@@ -394,8 +406,6 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx
138 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
139 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
140 golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
141-golang.org/x/net v0.0.0-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E=
142-golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
143 golang.org/x/net v0.0.0-20220822230855-b0a4917ee28c h1:JVAXQ10yGGVbSyoer5VILysz6YKjdNT2bsvlayjqhes=
144 golang.org/x/net v0.0.0-20220822230855-b0a4917ee28c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
145 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
146@@ -456,9 +466,9 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
147 golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
148 golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
149 golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
150+golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
151+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
152 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
153-golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2 h1:fqTvyMIIj+HRzMmnzr9NtpHP6uVpvB5fkHcgPDC4nu8=
154-golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
155 golang.org/x/sys v0.0.0-20220823224334-20c2bfdbfe24 h1:TyKJRhyo17yWxOMCTHKWrc5rddHORMlnZ/j57umaUd8=
156 golang.org/x/sys v0.0.0-20220823224334-20c2bfdbfe24/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
157 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
158@@ -603,6 +613,8 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
159 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
160 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
161 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
162+gopkg.in/ini.v1 v1.66.6 h1:LATuAqN/shcYAOkv3wl2L4rkaKqkcgTBQjOyYDvcPKI=
163+gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
164 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
165 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
166 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
M imgs/api.go
+16, -3
 1@@ -192,6 +192,7 @@ func imgHandler(w http.ResponseWriter, r *http.Request) {
 2 	}
 3 
 4 	dbpool := shared.GetDB(r)
 5+	st := shared.GetStorage(r)
 6 	logger := shared.GetLogger(r)
 7 
 8 	user, err := dbpool.FindUserForName(username)
 9@@ -216,7 +217,6 @@ func imgHandler(w http.ResponseWriter, r *http.Request) {
10 		}
11 	}
12 
13-	st := storage.NewStorageFS(cfg.StorageDir)
14 	bucket, err := st.GetBucket(user.ID)
15 	if err != nil {
16 		logger.Infof("bucket not found %s/%s", username, filename)
17@@ -610,15 +610,28 @@ func createSubdomainRoutes(staticRoutes []shared.Route) []shared.Route {
18 
19 func StartApiServer() {
20 	cfg := NewConfigSite()
21+	logger := cfg.Logger
22+
23 	db := postgres.NewDB(&cfg.ConfigCms)
24 	defer db.Close()
25-	logger := cfg.Logger
26+
27+	var st storage.ObjectStorage
28+	var err error
29+	if cfg.MinioURL == "" {
30+		st, err = storage.NewStorageFS(cfg.StorageDir)
31+	} else {
32+		st, err = storage.NewStorageMinio(cfg.MinioURL, cfg.MinioUser, cfg.MinioPass)
33+	}
34+
35+	if err != nil {
36+		logger.Fatal(err)
37+	}
38 
39 	staticRoutes := createStaticRoutes()
40 	mainRoutes := createMainRoutes(staticRoutes)
41 	subdomainRoutes := createSubdomainRoutes(staticRoutes)
42 
43-	handler := shared.CreateServe(mainRoutes, subdomainRoutes, cfg, db, logger)
44+	handler := shared.CreateServe(mainRoutes, subdomainRoutes, cfg, db, st, logger)
45 	router := http.HandlerFunc(handler)
46 
47 	portStr := fmt.Sprintf(":%s", cfg.Port)
M imgs/client.go
+2, -1
 1@@ -17,6 +17,7 @@ type IImgsAPI interface {
 2 type ImgsAPI struct {
 3 	Cfg *shared.ConfigSite
 4 	Db  db.DB
 5+	St  storage.ObjectStorage
 6 }
 7 
 8 func NewImgsAPI(dbpool db.DB) *ImgsAPI {
 9@@ -32,7 +33,7 @@ func (img *ImgsAPI) HasAccess(userID string) bool {
10 }
11 
12 func (img *ImgsAPI) Upload(s ssh.Session, file *utils.FileEntry) (string, error) {
13-	handler := uploadimgs.NewUploadImgHandler(img.Db, img.Cfg, storage.NewStorageFS(img.Cfg.StorageDir))
14+	handler := uploadimgs.NewUploadImgHandler(img.Db, img.Cfg, img.St)
15 	err := handler.Validate(s)
16 	if err != nil {
17 		return "", err
M imgs/config.go
+6, -0
 1@@ -25,6 +25,9 @@ func NewConfigSite() *shared.ConfigSite {
 2 	protocol := shared.GetEnv("IMGS_PROTOCOL", "https")
 3 	allowRegister := shared.GetEnv("IMGS_ALLOW_REGISTER", "1")
 4 	storageDir := shared.GetEnv("IMGS_STORAGE_DIR", ".storage")
 5+	minioURL := shared.GetEnv("MINIO_URL", "")
 6+	minioUser := shared.GetEnv("MINIO_ROOT_USER", "")
 7+	minioPass := shared.GetEnv("MINIO_ROOT_PASSWORD", "")
 8 	dbURL := shared.GetEnv("DATABASE_URL", "")
 9 	subdomainsEnabled := false
10 	if subdomains == "1" {
11@@ -45,6 +48,9 @@ func NewConfigSite() *shared.ConfigSite {
12 		SubdomainsEnabled:    subdomainsEnabled,
13 		CustomdomainsEnabled: customdomainsEnabled,
14 		StorageDir:           storageDir,
15+		MinioURL:             minioURL,
16+		MinioUser:            minioUser,
17+		MinioPass:            minioPass,
18 		ConfigCms: config.ConfigCms{
19 			Domain:        domain,
20 			Email:         email,
A imgs/storage/minio.go
+100, -0
  1@@ -0,0 +1,100 @@
  2+package storage
  3+
  4+import (
  5+	"bytes"
  6+	"context"
  7+	"errors"
  8+	"fmt"
  9+	"io"
 10+	"net/url"
 11+
 12+	"github.com/minio/minio-go/v7"
 13+	"github.com/minio/minio-go/v7/pkg/credentials"
 14+)
 15+
 16+type StorageMinio struct {
 17+	Client *minio.Client
 18+}
 19+
 20+func NewStorageMinio(address, user, pass string) (*StorageMinio, error) {
 21+	endpoint, err := url.Parse(address)
 22+	if err != nil {
 23+		return nil, err
 24+	}
 25+
 26+	mClient, err := minio.New(endpoint.Host, &minio.Options{
 27+		Creds:  credentials.NewStaticV4(user, pass, ""),
 28+		Secure: endpoint.Scheme == "https",
 29+	})
 30+
 31+	if err != nil {
 32+		return nil, err
 33+	}
 34+
 35+	return &StorageMinio{
 36+		Client: mClient,
 37+	}, err
 38+}
 39+
 40+// GetBucket - A bucket for the filesystem is just a directory.
 41+func (s *StorageMinio) GetBucket(name string) (Bucket, error) {
 42+	bucket := Bucket{
 43+		Name: name,
 44+	}
 45+
 46+	exists, err := s.Client.BucketExists(context.TODO(), bucket.Name)
 47+	if err != nil || !exists {
 48+		if err == nil {
 49+			err = errors.New("bucket does not exist")
 50+		}
 51+		return bucket, err
 52+	}
 53+
 54+	return bucket, nil
 55+}
 56+
 57+func (s *StorageMinio) UpsertBucket(name string) (Bucket, error) {
 58+	bucket, err := s.GetBucket(name)
 59+	if err == nil {
 60+		return bucket, nil
 61+	}
 62+
 63+	err = s.Client.MakeBucket(context.TODO(), name, minio.MakeBucketOptions{})
 64+	if err != nil {
 65+		return bucket, err
 66+	}
 67+
 68+	return bucket, nil
 69+}
 70+
 71+func (s *StorageMinio) GetFile(bucket Bucket, fname string) ([]byte, error) {
 72+	obj, err := s.Client.GetObject(context.TODO(), bucket.Name, fname, minio.GetObjectOptions{})
 73+	if err != nil {
 74+		return []byte{}, err
 75+	}
 76+
 77+	dat, err := io.ReadAll(obj)
 78+	if err != nil {
 79+		return []byte{}, err
 80+	}
 81+
 82+	return dat, nil
 83+}
 84+
 85+func (s *StorageMinio) PutFile(bucket Bucket, fname string, contents []byte) (string, error) {
 86+	info, err := s.Client.PutObject(context.TODO(), bucket.Name, fname, bytes.NewReader(contents), int64(len(contents)), minio.PutObjectOptions{})
 87+	if err != nil {
 88+		return "", err
 89+	}
 90+
 91+	return fmt.Sprintf("%s/%s", info.Bucket, info.Key), nil
 92+}
 93+
 94+func (s *StorageMinio) DeleteFile(bucket Bucket, fname string) error {
 95+	err := s.Client.RemoveObject(context.TODO(), bucket.Name, fname, minio.RemoveObjectOptions{})
 96+	if err != nil {
 97+		return err
 98+	}
 99+
100+	return nil
101+}
M imgs/storage/storage.go
+5, -5
 1@@ -24,8 +24,8 @@ type StorageFS struct {
 2 	Dir string
 3 }
 4 
 5-func NewStorageFS(dir string) *StorageFS {
 6-	return &StorageFS{Dir: dir}
 7+func NewStorageFS(dir string) (*StorageFS, error) {
 8+	return &StorageFS{Dir: dir}, nil
 9 }
10 
11 // GetBucket - A bucket for the filesystem is just a directory.
12@@ -38,16 +38,16 @@ func (s *StorageFS) GetBucket(name string) (Bucket, error) {
13 
14 	info, err := os.Stat(dirPath)
15 	if os.IsNotExist(err) {
16-		return bucket, fmt.Errorf("directory does not exist: %v %w\n", dirPath, err)
17+		return bucket, fmt.Errorf("directory does not exist: %v %w", dirPath, err)
18 	}
19 
20 	if err != nil {
21-		return bucket, fmt.Errorf("directory error: %v %w\n", dirPath, err)
22+		return bucket, fmt.Errorf("directory error: %v %w", dirPath, err)
23 
24 	}
25 
26 	if !info.IsDir() {
27-		return bucket, fmt.Errorf("directory is a file, not a directory: %#v\n", dirPath)
28+		return bucket, fmt.Errorf("directory is a file, not a directory: %#v", dirPath)
29 	}
30 
31 	return bucket, nil
M lists/api.go
+13, -1
 1@@ -13,6 +13,7 @@ import (
 2 
 3 	"git.sr.ht/~erock/pico/db"
 4 	"git.sr.ht/~erock/pico/db/postgres"
 5+	"git.sr.ht/~erock/pico/imgs/storage"
 6 	"git.sr.ht/~erock/pico/shared"
 7 	"github.com/gorilla/feeds"
 8 	"golang.org/x/exp/slices"
 9@@ -737,11 +738,22 @@ func StartApiServer() {
10 	defer db.Close()
11 	logger := cfg.Logger
12 
13+	var st storage.ObjectStorage
14+	var err error
15+	if cfg.MinioURL == "" {
16+		st, err = storage.NewStorageFS(cfg.StorageDir)
17+	} else {
18+		st, err = storage.NewStorageMinio(cfg.MinioURL, cfg.MinioUser, cfg.MinioPass)
19+	}
20+	if err != nil {
21+		logger.Fatal(err)
22+	}
23+
24 	staticRoutes := createStaticRoutes()
25 	mainRoutes := createMainRoutes(staticRoutes)
26 	subdomainRoutes := createSubdomainRoutes(staticRoutes)
27 
28-	handler := shared.CreateServe(mainRoutes, subdomainRoutes, cfg, db, logger)
29+	handler := shared.CreateServe(mainRoutes, subdomainRoutes, cfg, db, st, logger)
30 	router := http.HandlerFunc(handler)
31 
32 	portStr := fmt.Sprintf(":%s", cfg.Port)
M pastes/api.go
+16, -3
 1@@ -3,14 +3,15 @@ package pastes
 2 import (
 3 	"fmt"
 4 	"html/template"
 5-	"io/ioutil"
 6 	"net/http"
 7 	"net/url"
 8+	"os"
 9 	"strings"
10 	"time"
11 
12 	"git.sr.ht/~erock/pico/db"
13 	"git.sr.ht/~erock/pico/db/postgres"
14+	"git.sr.ht/~erock/pico/imgs/storage"
15 	"git.sr.ht/~erock/pico/shared"
16 )
17 
18@@ -308,7 +309,7 @@ func serveFile(file string, contentType string) http.HandlerFunc {
19 		logger := shared.GetLogger(r)
20 		cfg := shared.GetCfg(r)
21 
22-		contents, err := ioutil.ReadFile(cfg.StaticPath(fmt.Sprintf("public/%s", file)))
23+		contents, err := os.ReadFile(cfg.StaticPath(fmt.Sprintf("public/%s", file)))
24 		if err != nil {
25 			logger.Error(err)
26 			http.Error(w, "file not found", 404)
27@@ -389,13 +390,25 @@ func StartApiServer() {
28 	defer db.Close()
29 	logger := cfg.Logger
30 
31+	var st storage.ObjectStorage
32+	var err error
33+	if cfg.MinioURL == "" {
34+		st, err = storage.NewStorageFS(cfg.StorageDir)
35+	} else {
36+		st, err = storage.NewStorageMinio(cfg.MinioURL, cfg.MinioUser, cfg.MinioPass)
37+	}
38+
39+	if err != nil {
40+		logger.Fatal(err)
41+	}
42+
43 	go CronDeleteExpiredPosts(cfg, db)
44 
45 	staticRoutes := createStaticRoutes()
46 	mainRoutes := createMainRoutes(staticRoutes)
47 	subdomainRoutes := createSubdomainRoutes(staticRoutes)
48 
49-	handler := shared.CreateServe(mainRoutes, subdomainRoutes, cfg, db, logger)
50+	handler := shared.CreateServe(mainRoutes, subdomainRoutes, cfg, db, st, logger)
51 	router := http.HandlerFunc(handler)
52 
53 	portStr := fmt.Sprintf(":%s", cfg.Port)
M prose/api.go
+16, -3
 1@@ -4,9 +4,9 @@ import (
 2 	"bytes"
 3 	"fmt"
 4 	"html/template"
 5-	"io/ioutil"
 6 	"net/http"
 7 	"net/url"
 8+	"os"
 9 	"strconv"
10 	"strings"
11 	"time"
12@@ -14,6 +14,7 @@ import (
13 	"git.sr.ht/~erock/pico/db"
14 	"git.sr.ht/~erock/pico/db/postgres"
15 	"git.sr.ht/~erock/pico/imgs"
16+	"git.sr.ht/~erock/pico/imgs/storage"
17 	"git.sr.ht/~erock/pico/shared"
18 	"github.com/gorilla/feeds"
19 	"golang.org/x/exp/slices"
20@@ -739,7 +740,7 @@ func serveFile(file string, contentType string) http.HandlerFunc {
21 		logger := shared.GetLogger(r)
22 		cfg := shared.GetCfg(r)
23 
24-		contents, err := ioutil.ReadFile(cfg.StaticPath(fmt.Sprintf("public/%s", file)))
25+		contents, err := os.ReadFile(cfg.StaticPath(fmt.Sprintf("public/%s", file)))
26 		if err != nil {
27 			logger.Error(err)
28 			http.Error(w, "file not found", 404)
29@@ -832,11 +833,23 @@ func StartApiServer() {
30 	defer db.Close()
31 	logger := cfg.Logger
32 
33+	var st storage.ObjectStorage
34+	var err error
35+	if cfg.MinioURL == "" {
36+		st, err = storage.NewStorageFS(cfg.StorageDir)
37+	} else {
38+		st, err = storage.NewStorageMinio(cfg.MinioURL, cfg.MinioUser, cfg.MinioPass)
39+	}
40+
41+	if err != nil {
42+		logger.Fatal(err)
43+	}
44+
45 	staticRoutes := createStaticRoutes()
46 	mainRoutes := createMainRoutes(staticRoutes)
47 	subdomainRoutes := createSubdomainRoutes(staticRoutes)
48 
49-	handler := shared.CreateServe(mainRoutes, subdomainRoutes, cfg, db, logger)
50+	handler := shared.CreateServe(mainRoutes, subdomainRoutes, cfg, db, st, logger)
51 	router := http.HandlerFunc(handler)
52 
53 	portStr := fmt.Sprintf(":%s", cfg.Port)
M shared/api.go
+2, -2
 1@@ -3,8 +3,8 @@ package shared
 2 import (
 3 	"fmt"
 4 	"html/template"
 5-	"io/ioutil"
 6 	"net/http"
 7+	"os"
 8 	"strings"
 9 )
10 
11@@ -46,7 +46,7 @@ func ServeFile(file string, contentType string) http.HandlerFunc {
12 		logger := GetLogger(r)
13 		cfg := GetCfg(r)
14 
15-		contents, err := ioutil.ReadFile(cfg.StaticPath(fmt.Sprintf("public/%s", file)))
16+		contents, err := os.ReadFile(cfg.StaticPath(fmt.Sprintf("public/%s", file)))
17 		if err != nil {
18 			logger.Error(err)
19 			http.Error(w, "file not found", 404)
M shared/config.go
+3, -0
 1@@ -28,6 +28,9 @@ type ConfigSite struct {
 2 	SubdomainsEnabled    bool
 3 	CustomdomainsEnabled bool
 4 	StorageDir           string
 5+	MinioURL             string
 6+	MinioUser            string
 7+	MinioPass            string
 8 }
 9 
10 func (c *ConfigSite) GetSiteData() *SitePageData {
M shared/router.go
+9, -2
 1@@ -9,6 +9,7 @@ import (
 2 	"strings"
 3 
 4 	"git.sr.ht/~erock/pico/db"
 5+	"git.sr.ht/~erock/pico/imgs/storage"
 6 	"go.uber.org/zap"
 7 )
 8 
 9@@ -28,7 +29,7 @@ func NewRoute(method, pattern string, handler http.HandlerFunc) Route {
10 
11 type ServeFn func(http.ResponseWriter, *http.Request)
12 
13-func CreateServe(routes []Route, subdomainRoutes []Route, cfg *ConfigSite, dbpool db.DB, logger *zap.SugaredLogger) ServeFn {
14+func CreateServe(routes []Route, subdomainRoutes []Route, cfg *ConfigSite, dbpool db.DB, st storage.ObjectStorage, logger *zap.SugaredLogger) ServeFn {
15 	return func(w http.ResponseWriter, r *http.Request) {
16 		var allow []string
17 		var subdomain string
18@@ -63,7 +64,8 @@ func CreateServe(routes []Route, subdomainRoutes []Route, cfg *ConfigSite, dbpoo
19 				loggerCtx := context.WithValue(r.Context(), ctxLoggerKey{}, logger)
20 				subdomainCtx := context.WithValue(loggerCtx, ctxSubdomainKey{}, subdomain)
21 				dbCtx := context.WithValue(subdomainCtx, ctxDBKey{}, dbpool)
22-				cfgCtx := context.WithValue(dbCtx, ctxCfg{}, cfg)
23+				storageCtx := context.WithValue(dbCtx, ctxStorageKey{}, st)
24+				cfgCtx := context.WithValue(storageCtx, ctxCfg{}, cfg)
25 				ctx := context.WithValue(cfgCtx, ctxKey{}, matches[1:])
26 				route.handler(w, r.WithContext(ctx))
27 				return
28@@ -79,6 +81,7 @@ func CreateServe(routes []Route, subdomainRoutes []Route, cfg *ConfigSite, dbpoo
29 }
30 
31 type ctxDBKey struct{}
32+type ctxStorageKey struct{}
33 type ctxKey struct{}
34 type ctxLoggerKey struct{}
35 type ctxSubdomainKey struct{}
36@@ -96,6 +99,10 @@ func GetDB(r *http.Request) db.DB {
37 	return r.Context().Value(ctxDBKey{}).(db.DB)
38 }
39 
40+func GetStorage(r *http.Request) storage.ObjectStorage {
41+	return r.Context().Value(ctxStorageKey{}).(storage.ObjectStorage)
42+}
43+
44 func GetField(r *http.Request, index int) string {
45 	fields := r.Context().Value(ctxKey{}).([]string)
46 	return fields[index]