├── README.md ├── bay ├── downloader.go ├── proxy │ ├── Dockerfile │ ├── README.md │ └── proxy.go └── tracker │ ├── Dockerfile │ ├── README.md │ └── tracker.go ├── build.sh ├── dist ├── cert.pem ├── key.pem ├── proxy-go.sh └── tracker-go.sh └── slides └── dataman-dockerhackday.key /README.md: -------------------------------------------------------------------------------- 1 | # p2pull 2 | Peer-to-peer push/pull between docker hosts on beijing #dockerhackday 3 | 4 | 5 | # Member 6 | * Xiao Deshi 7 | * Yao Yun 8 | * Zhang Mingfeng 9 | 10 | ## How to make it work 11 | 12 | generating the server certificate and private key with OpenSSL takes just one 13 | command: 14 | ``` 15 | openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout key.pem -out 16 | cert.pem 17 | ``` 18 | 19 | registry to make it work with p2p easily. 20 | 21 | Let's run tracker on `192.168.0.1` (`host1`) and proxies on `192.168.0.{2,3,4}` (`host{2,3,4}`). 22 | 23 | ``` 24 | host1> docker run -d --net=host bobrik/bay-tracker \ 25 | -listen 192.168.0.1:8888 -tracker 192.168.0.4:6881 -root /tmp 26 | ``` 27 | 28 | Now let's run local proxies on each box: 29 | 30 | ``` 31 | host2> docker run -d -p 127.0.0.1:80:80 bobrik/bay-proxy \ 32 | -tracker http://192.168.0.1:8888/ -listen :80 -root /tmp 33 | 34 | host3> docker run -d -p 127.0.0.1:80:80 bobrik/bay-proxy \ 35 | -tracker http://192.168.0.1:8888/ -listen :80 -root /tmp 36 | 37 | host4> docker run -d -p 127.0.0.1:80:80 bobrik/bay-proxy \ 38 | -tracker http://192.168.0.1:8888/ -listen :80 -root /tmp 39 | ``` 40 | 41 | In `/etc/hosts` on each machine add the next record: 42 | 43 | ``` 44 | 127.0.0.1 p2p- 45 | ``` 46 | 47 | where `my-registry.com` should be your usual registry. 48 | 49 | After that on `host{2,3,4}` you can run: 50 | 51 | 52 | ``` 53 | docker pull p2p-/myimage 54 | ``` 55 | 56 | and it will work just like 57 | 58 | ``` 59 | docker pull /myimage 60 | ``` 61 | 62 | but with p2p magic and unicorns. 63 | 64 | # Thanks 65 | https://github.com/bobrik/bay 66 | -------------------------------------------------------------------------------- /bay/downloader.go: -------------------------------------------------------------------------------- 1 | package bay 2 | 3 | import "sync" 4 | import "os" 5 | import "path" 6 | import "net/http" 7 | import "io" 8 | import "crypto/sha1" 9 | import "fmt" 10 | import "log" 11 | 12 | type Downloader struct { 13 | root string 14 | mutex sync.Mutex 15 | current map[string]*sync.Mutex 16 | } 17 | 18 | func NewDownloader(root string) *Downloader { 19 | return &Downloader{ 20 | root: root, 21 | mutex: sync.Mutex{}, 22 | current: map[string]*sync.Mutex{}, 23 | } 24 | } 25 | 26 | func (d *Downloader) Download(url string) (file string, err error) { 27 | name := fmt.Sprintf("%x", sha1.Sum([]byte(url))) 28 | file = path.Join(d.root, name) 29 | if _, err = os.Stat(file); err == nil { 30 | return 31 | } 32 | 33 | d.mutex.Lock() 34 | 35 | // existing mutex: just wait for it to unlock 36 | if e, ok := d.current[name]; ok { 37 | log.Println("waiting for current download to finish") 38 | d.mutex.Unlock() 39 | e.Lock() 40 | 41 | // here file is downloaded already or failed 42 | // and "e" is no longer referenced from the map 43 | return d.Download(url) 44 | } 45 | 46 | log.Println("starting new download") 47 | 48 | // new mutex: create it and put into map 49 | m := &sync.Mutex{} 50 | d.current[name] = m 51 | 52 | m.Lock() 53 | defer m.Unlock() 54 | 55 | d.mutex.Unlock() 56 | 57 | err = d.save(url, file) 58 | if err != nil { 59 | return 60 | } 61 | 62 | // delete mutex from the map 63 | d.mutex.Lock() 64 | delete(d.current, name) 65 | d.mutex.Unlock() 66 | 67 | return 68 | } 69 | 70 | func (d *Downloader) save(url, file string) error { 71 | out, err := os.Create(file + ".downloading") 72 | if err != nil { 73 | return err 74 | } 75 | 76 | defer out.Close() 77 | 78 | resp, err := http.Get(url) 79 | if err != nil { 80 | return err 81 | } 82 | 83 | defer resp.Body.Close() 84 | 85 | _, err = io.Copy(out, resp.Body) 86 | if err != nil { 87 | return err 88 | } 89 | 90 | return os.Rename(file+".downloading", file) 91 | } 92 | -------------------------------------------------------------------------------- /bay/proxy/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.3-onbuild 2 | EXPOSE 80 3 | ENTRYPOINT ["/go/bin/app"] 4 | -------------------------------------------------------------------------------- /bay/proxy/README.md: -------------------------------------------------------------------------------- 1 | # Bay proxy 2 | 3 | See readme [here](https://github.com/bobrik/bay). 4 | -------------------------------------------------------------------------------- /bay/proxy/proxy.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "github.com/jackpal/Taipei-Torrent/torrent" 6 | "io" 7 | "io/ioutil" 8 | "log" 9 | "math" 10 | "net/http" 11 | "net/http/httputil" 12 | "net/url" 13 | "os" 14 | "path" 15 | "strings" 16 | ) 17 | 18 | func main() { 19 | tracker := flag.String("tracker", "", "tracker url: http://host:port/") 20 | listen := flag.String("listen", "0.0.0.0:8888", "bind location") 21 | root := flag.String("root", "", "root dir to keep working files") 22 | flag.Parse() 23 | 24 | if *tracker == "" || *root == "" { 25 | flag.PrintDefaults() 26 | return 27 | } 28 | 29 | tu, err := url.Parse(*tracker) 30 | if err != nil { 31 | log.Fatal(err) 32 | } 33 | 34 | mux := http.NewServeMux() 35 | mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { 36 | u := *req.URL 37 | u.Host = strings.TrimPrefix(req.Host, "p2p-") 38 | 39 | if req.TLS != nil { 40 | u.Scheme = "https" 41 | } else { 42 | u.Scheme = "http" 43 | } 44 | 45 | log.Println("got request:", req.Method, u.String()) 46 | 47 | // should be support V2 registry api 48 | if req.Method == "PUT" || 49 | strings.HasPrefix(u.Path, "/v2/") || 50 | strings.Contains(u.Path, "ping") { 51 | pu := u 52 | pu.Path = "/" 53 | proxy := httputil.NewSingleHostReverseProxy(&pu) 54 | proxy.ServeHTTP(w, req) 55 | return 56 | } 57 | 58 | tu := *tu 59 | q := tu.Query() 60 | q.Set("url", u.String()) 61 | 62 | log.Println(tu.String() + "?" + q.Encode()) 63 | 64 | resp, err := http.Get(tu.String() + "?" + q.Encode()) 65 | if err != nil { 66 | http.Error(w, "getting torrent failed", http.StatusInternalServerError) 67 | return 68 | } 69 | 70 | defer resp.Body.Close() 71 | 72 | f, err := ioutil.TempFile(*root, "image-torrent-") 73 | if err != nil { 74 | http.Error(w, "torrent file creation failed", http.StatusInternalServerError) 75 | return 76 | } 77 | 78 | defer func() { 79 | f.Close() 80 | os.Remove(f.Name()) 81 | }() 82 | 83 | _, err = io.Copy(f, resp.Body) 84 | if err != nil { 85 | http.Error(w, "reading torrent contents failed", http.StatusInternalServerError) 86 | return 87 | } 88 | 89 | m, err := torrent.GetMetaInfo(nil, f.Name()) 90 | if err != nil { 91 | http.Error(w, "reading torrent failed", http.StatusInternalServerError) 92 | return 93 | } 94 | log.Printf("============> get metainfo %p", m) 95 | 96 | err = torrent.RunTorrents(&torrent.TorrentFlags{ 97 | FileDir: *root, 98 | SeedRatio: math.Inf(0), 99 | FileSystemProvider: torrent.OsFsProvider{}, 100 | InitialCheck: true, 101 | MaxActive: 1, 102 | ExecOnSeeding: "", 103 | Cacher: torrent.NewRamCacheProvider(1), 104 | }, []string{f.Name()}) 105 | 106 | lf := path.Join(*root, m.Info.Name) 107 | log.Println("==============>ls path:", lf) 108 | 109 | defer os.Remove(lf) 110 | 111 | // TODO: start another RunTorrents for configured interval 112 | // TODO: and remove data after that 113 | // TODO: or to hell with it 114 | 115 | if err != nil { 116 | http.Error(w, "downloading torrent failed", http.StatusInternalServerError) 117 | return 118 | } 119 | 120 | l, err := os.Open(lf) 121 | if err != nil { 122 | http.Error(w, "layer file open failed", http.StatusInternalServerError) 123 | return 124 | } 125 | 126 | defer l.Close() 127 | 128 | io.Copy(w, l) 129 | }) 130 | 131 | cert := "cert.pem" 132 | key := "key.pem" 133 | err = http.ListenAndServeTLS(*listen, cert, key, mux) 134 | if err != nil { 135 | log.Fatal("ListenAndServe: ", err) 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /bay/tracker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.3-onbuild 2 | EXPOSE 80 6881 7788 3 | ENTRYPOINT ["/go/bin/app"] 4 | -------------------------------------------------------------------------------- /bay/tracker/README.md: -------------------------------------------------------------------------------- 1 | # Bay tracker 2 | 3 | See readme [here](https://github.com/bobrik/bay). 4 | -------------------------------------------------------------------------------- /bay/tracker/tracker.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "log" 4 | import "flag" 5 | import "github.com/jackpal/Taipei-Torrent/tracker" 6 | import "github.com/jackpal/Taipei-Torrent/torrent" 7 | import "os" 8 | import "net/http" 9 | import "github.com/Dataman-Cloud/p2pull/bay" 10 | import "math" 11 | import "sync" 12 | 13 | type Tracker struct { 14 | mutex sync.Mutex 15 | requests map[string]*sync.Mutex 16 | 17 | downloader *bay.Downloader 18 | 19 | flags *torrent.TorrentFlags 20 | conns chan *torrent.BtConn 21 | sessions map[string]*torrent.TorrentSession 22 | sessionCh chan *torrent.TorrentSession 23 | sessionMutex sync.RWMutex 24 | 25 | tr *tracker.Tracker 26 | trListen string 27 | 28 | mux *http.ServeMux 29 | listen string 30 | 31 | port int 32 | } 33 | 34 | func NewTracker(listen, trListen, root string, port int) (*Tracker, error) { 35 | flags := &torrent.TorrentFlags{ 36 | Port: port, 37 | FileDir: root, 38 | SeedRatio: math.Inf(0), 39 | UseDeadlockDetector: true, 40 | FileSystemProvider: torrent.OsFsProvider{}, 41 | UseDHT: false, 42 | } 43 | 44 | conns, listenPort, err := torrent.ListenForPeerConnections(flags) 45 | 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | tr := tracker.NewTracker() 51 | tr.Addr = trListen 52 | 53 | mux := http.NewServeMux() 54 | 55 | t := &Tracker{ 56 | mutex: sync.Mutex{}, 57 | requests: map[string]*sync.Mutex{}, 58 | 59 | downloader: bay.NewDownloader(root), 60 | 61 | flags: flags, 62 | conns: conns, 63 | sessions: map[string]*torrent.TorrentSession{}, 64 | sessionCh: make(chan *torrent.TorrentSession), 65 | sessionMutex: sync.RWMutex{}, 66 | 67 | tr: tr, 68 | trListen: trListen, 69 | 70 | mux: mux, 71 | listen: listen, 72 | 73 | port: listenPort, 74 | } 75 | 76 | mux.HandleFunc("/", t.handle) 77 | 78 | return t, nil 79 | } 80 | 81 | func (t *Tracker) ensureTorrentExists(file string) (string, error) { 82 | tf := file + ".torrent" 83 | 84 | if _, err := os.Stat(tf); os.IsNotExist(err) { 85 | m, err := torrent.CreateMetaInfoFromFileSystem(nil, file, "0.0.0.0:8888", 0, true) 86 | if err != nil { 87 | return tf, err 88 | } 89 | 90 | m.Announce = "http://" + t.trListen + "/announce" 91 | 92 | meta, err := os.Create(tf) 93 | if err != nil { 94 | return tf, err 95 | } 96 | 97 | defer meta.Close() 98 | 99 | err = m.Bencode(meta) 100 | if err != nil { 101 | return tf, err 102 | } 103 | } 104 | 105 | return tf, nil 106 | } 107 | 108 | func (t *Tracker) handleSafely(w http.ResponseWriter, u string) error { 109 | f, err := t.downloader.Download(u) 110 | if err != nil { 111 | return err 112 | } 113 | 114 | tf, err := t.ensureTorrentExists(f) 115 | if err != nil { 116 | return err 117 | } 118 | 119 | m, err := torrent.GetMetaInfo(nil, tf) 120 | if err != nil { 121 | return err 122 | } 123 | 124 | m.Bencode(w) 125 | 126 | ts, err := torrent.NewTorrentSession(t.flags, tf, uint16(t.port)) 127 | if err != nil { 128 | log.Println("=========ts.err:", err) 129 | return err 130 | } 131 | 132 | t.sessionCh <- ts 133 | 134 | return nil 135 | } 136 | 137 | func (t *Tracker) handle(w http.ResponseWriter, req *http.Request) { 138 | u := req.URL.Query().Get("url") 139 | if u == "" { 140 | http.Error(w, "no url provided", http.StatusBadRequest) 141 | return 142 | } 143 | 144 | log.Println("dealing with " + u) 145 | 146 | t.mutex.Lock() 147 | 148 | // existing mutex: just wait for it to unlock 149 | if e, ok := t.requests[u]; ok { 150 | log.Println("waiting for current download to finish") 151 | t.mutex.Unlock() 152 | e.Lock() 153 | 154 | // here file is downloaded already or failed 155 | // and "e" is no longer referenced from the map 156 | t.handle(w, req) 157 | return 158 | } 159 | 160 | // new mutex: create it and put into map 161 | m := &sync.Mutex{} 162 | t.requests[u] = m 163 | 164 | m.Lock() 165 | defer m.Unlock() 166 | 167 | t.mutex.Unlock() 168 | 169 | // actually do some job 170 | err := t.handleSafely(w, u) 171 | if err != nil { 172 | http.Error(w, err.Error(), http.StatusInternalServerError) 173 | return 174 | } 175 | 176 | // delete mutex from the map 177 | t.mutex.Lock() 178 | delete(t.requests, u) 179 | t.mutex.Unlock() 180 | } 181 | 182 | func (t *Tracker) Start() error { 183 | errch := make(chan error) 184 | 185 | go func() { 186 | for ts := range t.sessionCh { 187 | t.sessionMutex.Lock() 188 | 189 | if _, ok := t.sessions[ts.M.InfoHash]; ok { 190 | t.sessionMutex.Unlock() 191 | continue 192 | } 193 | 194 | t.sessions[ts.M.InfoHash] = ts 195 | 196 | t.sessionMutex.Unlock() 197 | 198 | err := t.tr.Register(ts.M.InfoHash, ts.M.Info.Name) 199 | if err != nil { 200 | errch <- err 201 | } 202 | 203 | go ts.DoTorrent() 204 | 205 | log.Println("seeding " + ts.M.Info.Name) 206 | } 207 | }() 208 | 209 | go func() { 210 | for c := range t.conns { 211 | t.sessionMutex.RLock() 212 | 213 | if ts, ok := t.sessions[c.Infohash]; ok { 214 | log.Printf("New bt connection for ih %x", c.Infohash) 215 | ts.AcceptNewPeer(c) 216 | } else { 217 | log.Printf("Unknown hash: %x", c.Infohash) 218 | } 219 | 220 | t.sessionMutex.RUnlock() 221 | } 222 | }() 223 | 224 | go func() { 225 | err := http.ListenAndServe(t.listen, t.mux) 226 | if err != nil { 227 | errch <- err 228 | } 229 | }() 230 | 231 | go func() { 232 | err := t.tr.ListenAndServe() 233 | if err != nil { 234 | errch <- err 235 | } 236 | }() 237 | 238 | return <-errch 239 | } 240 | 241 | func main() { 242 | listen := flag.String("listen", "", "bind location") 243 | addr := flag.String("tracker", "0.0.0.0:6881", "tracker location") 244 | root := flag.String("root", "", "root dir to keep working files") 245 | port := flag.Int("port", 7788, "peering port") 246 | flag.Parse() 247 | 248 | if *listen == "" || *root == "" { 249 | flag.PrintDefaults() 250 | return 251 | } 252 | 253 | tr, err := NewTracker(*listen, *addr, *root, *port) 254 | if err != nil { 255 | log.Fatal(err) 256 | } 257 | 258 | err = tr.Start() 259 | if err != nil { 260 | log.Fatal(err) 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | echo "=> fetch source code" 5 | git pull origin master 6 | 7 | echo "=> build...." 8 | go build bay/proxy/proxy.go 9 | go build bay/tracker/tracker.go 10 | 11 | echo "=> cp to dist folder" 12 | mv proxy dist/ 13 | mv tracker dist/ 14 | 15 | echo "==> done" 16 | 17 | -------------------------------------------------------------------------------- /dist/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIID3TCCAsWgAwIBAgIJANp2nLm1ZUEnMA0GCSqGSIb3DQEBBQUAMFIxCzAJBgNV 3 | BAYTAkNOMRMwEQYDVQQIEwpTb21lLVN0YXRlMRAwDgYDVQQKEwdEYXRhTWFuMRww 4 | GgYDVQQDExNwMnAtaW5kZXguYWxhdWRhLmNuMB4XDTE1MDkxOTE1NTkzMVoXDTE2 5 | MDkxODE1NTkzMVowUjELMAkGA1UEBhMCQ04xEzARBgNVBAgTClNvbWUtU3RhdGUx 6 | EDAOBgNVBAoTB0RhdGFNYW4xHDAaBgNVBAMTE3AycC1pbmRleC5hbGF1ZGEuY24w 7 | ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCdyb2Cd1BtO/PE3jc5fkZs 8 | lPGM8Nrh/j0I8NTyyxRLW3KnHKCsAxwnVkvWQykAfBuZk4VAXUOuUIn+jibImh0n 9 | OK+IBrPmsv1HBS/OUIYrVW9E/TGd8Y5SoA44HNlEJBxTPspCI9LhQA3P+2UwLiRM 10 | KQ5bUxZmcCGfq8cPkz9ul3uex74hOybQnr5g85VUshnlQHrlhOQef7gVTnEJL015 11 | YNbepz6hreZl486br7kmzzhdzvxRbPmL0UGmbndvCPC7N0ODlXQEQVSIkieqFK56 12 | MSgtl24pFdloBW8Kv0JCvfnu9C1y/vCdghIREAYXR76C5Z3BXc3rmrKALHx8dcHB 13 | AgMBAAGjgbUwgbIwHQYDVR0OBBYEFFuOhZi+IeEVwmHnjC6SVaeGeSIGMIGCBgNV 14 | HSMEezB5gBRbjoWYviHhFcJh54wuklWnhnkiBqFWpFQwUjELMAkGA1UEBhMCQ04x 15 | EzARBgNVBAgTClNvbWUtU3RhdGUxEDAOBgNVBAoTB0RhdGFNYW4xHDAaBgNVBAMT 16 | E3AycC1pbmRleC5hbGF1ZGEuY26CCQDadpy5tWVBJzAMBgNVHRMEBTADAQH/MA0G 17 | CSqGSIb3DQEBBQUAA4IBAQAfn9wJLFXCJrDShIlcUNvaZxE1RZb77IdpSrTbpMDE 18 | g2E4Vo1jhb4Wkbs/VwGVBIA9BYYYOB3JmHJHBzKsg2tdnr3NsBpMnFkL2CmGLSK0 19 | 8xgQ4wBBJhAnPuHtnUvWlB4jXQ206VZvdeTvcusKDHN+ldd+L2RXAOlthDDgc51R 20 | Krb3u1qehx+nUrAWntkTTpLLngaPra4FfSGtKuO79D+1KI0Mut9KtQD0uz0ljkjy 21 | aTHwgANmkuvyN/5oi1VHrdhJxoI7+WPkks0QLCeZbhbDlSzSgcVeZHLXzXBt2aAc 22 | mZgjjtT369BDNt7lkKtwaqTZHGwhhZO1pbjRKC+UK4cP 23 | -----END CERTIFICATE----- 24 | -------------------------------------------------------------------------------- /dist/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAncm9gndQbTvzxN43OX5GbJTxjPDa4f49CPDU8ssUS1typxyg 3 | rAMcJ1ZL1kMpAHwbmZOFQF1DrlCJ/o4myJodJziviAaz5rL9RwUvzlCGK1VvRP0x 4 | nfGOUqAOOBzZRCQcUz7KQiPS4UANz/tlMC4kTCkOW1MWZnAhn6vHD5M/bpd7nse+ 5 | ITsm0J6+YPOVVLIZ5UB65YTkHn+4FU5xCS9NeWDW3qc+oa3mZePOm6+5Js84Xc78 6 | UWz5i9FBpm53bwjwuzdDg5V0BEFUiJInqhSuejEoLZduKRXZaAVvCr9CQr357vQt 7 | cv7wnYISERAGF0e+guWdwV3N65qygCx8fHXBwQIDAQABAoIBAGFnD91h2fwUbj6x 8 | DlYnZUHSD+p5JCUt1G+Ye1wFkNmT8grG2UhWAyMlWltByiXKdx8uuasp54XLoePJ 9 | zJc4y5ZSn8jQmm1nQIJ+6Svq+Wf2rWFhCyf83WaT9zMcW+J8iB+7Ow/pHVx/NgeV 10 | oLlFQpLhJUOq3sENpzPxzkgHFbkmQaVNuqn9+LspRQVbk/inhYvhaQJgwx7xY04T 11 | mLfvvHASb/xCYMQfcoRk3DUnyq6BaXRiQOU1kJ6hv+qXArcxAEEor5Ve/itxd9m7 12 | NNGgDrxVbnZsf3XAdXDbQ8fMoHItA/qNrZhtjfeMUTOrzzctSfSZRdBEyDiFgxtG 13 | ETU5yqECgYEAzsByV/xzJ/s61xcnBq8qD4Mk3tIVBsGMAjqFBWxiGmSV9QPOkUXi 14 | ZcFCHn7/PcGvgcbqEuPpoKdnAdD9WJdTOTvWjEX5bNSW+BwSBZPdRfzmFD2f89Mn 15 | rX0FORzfn50QTUXhaxRnpm63LFNw0l4IKHX/aV76k2NT/rtTMS9+7xsCgYEAw1+G 16 | 9YJt+H+fVWbaUXelHAdy5zoaBirBlFh9i1SpOsbxDTJB1vrmvwls/FZfyka5Z5S+ 17 | m4XFbIHjTEJwlKdKc0t7vmQKkOpqZ0maHKO6KjIKkQ2DAgqan6IzXG8Pheakxsja 18 | nhW5hWEY5w6WOTj9+MDK51OW3GQLZlyO5pdCdFMCgYBtNEkxIC7TF2fk5F3AshTN 19 | TL/U0bI5d1G0e6R3UOAggLCUUgZxxS/n/0QlR38Z54wESESq5acpLELEOtcWTBex 20 | S2HvhaZRTZjQRENgntFpfkMovcQWzZlPkZGIsS+M5zENH7QJEyYMSr6Fsy0McI5u 21 | kKTvyzMfVn+vNNKkUCoDsQKBgQCBRFllY7EjdD2WIgFdWc5y5zLNbxH89pkycAJR 22 | B+kYrLydQJ1zgnrd93yW0Qd8LKNLt40OFcp0atRbhrI6iR2nf1AWvtaG37MbOAWx 23 | 4VqxxH5dTxVZam64JHfbNtiMffiUVhzf7/vaFk6Fwo7+uYjA577Yo+PzWnZ4edFL 24 | HNWpZQKBgQCkz+pkrA3fsYyOE38IFv/ae7oH9V0Y+btJBdEHCfGqBbc32EXjCoRF 25 | C9Cb7w96tP58V3y/w3xSUJb4bdclqTl95D0qfrAPi0jWLl7oMrs9HNLSe1SgxAym 26 | kbCz2AuPE5SnTQ1TV7bcGl5ydlO/iqFJg+N6z0+idzK18ppMBXzUGw== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /dist/proxy-go.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | TIP=${TIP:-$1} 5 | 6 | if [ -z "$TIP" ] 7 | then 8 | echo 'ERROR: you should append tracker ip just like:' 9 | echo './proxy 10.3.11.2' 10 | exit 0 11 | fi 12 | 13 | echo "P2 Pull Proxy..." 14 | 15 | ./proxy -tracker http://${TIP}:8888/ -listen :443 -root /tmp 16 | -------------------------------------------------------------------------------- /dist/tracker-go.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | TIP=${TIP:-$1} 5 | 6 | if [ -z "$TIP" ] 7 | then 8 | echo 'ERROR: you should append tracker ip just like:' 9 | echo './tracker 10.3.11.2' 10 | exit 0 11 | fi 12 | 13 | echo "===>P2 Docker Pull Tracker" 14 | ./tracker -listen ${TIP}:8888 -tracker ${TIP}:6881 -root /tmp 15 | 16 | echo "done" 17 | -------------------------------------------------------------------------------- /slides/dataman-dockerhackday.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dataman-Cloud/p2pull/eff73e7d1937050d04935921f056e3ca763fbdaf/slides/dataman-dockerhackday.key --------------------------------------------------------------------------------