Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ecf52616e2 | |||
| 69bc599b5b | |||
| e58b019ffa | |||
| 1fc4e7bddb | |||
| 2cabfd574c | |||
| 3b946d84ac | |||
| 28abe55179 | |||
| e31365c6a5 | |||
| bedb4fc7f4 | |||
| 8f05ba77b4 | |||
| d03e22e26e | |||
| ec667a19a0 | |||
| 8afe1ac37b | |||
| 9dc3188cc0 | |||
| 9cf014adab |
@@ -0,0 +1,43 @@
|
|||||||
|
package chisel
|
||||||
|
|
||||||
|
import (
|
||||||
|
chserver "github.com/jpillora/chisel/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
address string
|
||||||
|
port string
|
||||||
|
fingerprint string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServer(address string, port string) *Server {
|
||||||
|
return &Server{
|
||||||
|
address: address,
|
||||||
|
port: port,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start starts the reverse tunnel server
|
||||||
|
func (server *Server) Start() error {
|
||||||
|
|
||||||
|
// TODO: keyseed management (persistence)
|
||||||
|
// + auth management
|
||||||
|
// Consider multiple users for auth?
|
||||||
|
config := &chserver.Config{
|
||||||
|
Reverse: true,
|
||||||
|
KeySeed: "keyseedexample",
|
||||||
|
Auth: "agent@randomstring",
|
||||||
|
}
|
||||||
|
|
||||||
|
chiselServer, err := chserver.NewServer(config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
server.fingerprint = chiselServer.GetFingerprint()
|
||||||
|
return chiselServer.Start(server.address, server.port)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (server *Server) GetFingerprint() string {
|
||||||
|
return server.fingerprint
|
||||||
|
}
|
||||||
@@ -33,6 +33,8 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
|
|||||||
|
|
||||||
flags := &portainer.CLIFlags{
|
flags := &portainer.CLIFlags{
|
||||||
Addr: kingpin.Flag("bind", "Address and port to serve Portainer").Default(defaultBindAddress).Short('p').String(),
|
Addr: kingpin.Flag("bind", "Address and port to serve Portainer").Default(defaultBindAddress).Short('p').String(),
|
||||||
|
TunnelAddr: kingpin.Flag("tunnel-addr", "Address to serve the tunnel server").Default(defaultTunnelServerAddress).String(),
|
||||||
|
TunnelPort: kingpin.Flag("tunnel-port", "Port to serve the tunnel server").Default(defaultTunnelServerPort).String(),
|
||||||
Assets: kingpin.Flag("assets", "Path to the assets").Default(defaultAssetsDirectory).Short('a').String(),
|
Assets: kingpin.Flag("assets", "Path to the assets").Default(defaultAssetsDirectory).Short('a').String(),
|
||||||
Data: kingpin.Flag("data", "Path to the folder where the data is stored").Default(defaultDataDirectory).Short('d').String(),
|
Data: kingpin.Flag("data", "Path to the folder where the data is stored").Default(defaultDataDirectory).Short('d').String(),
|
||||||
EndpointURL: kingpin.Flag("host", "Endpoint URL").Short('H').String(),
|
EndpointURL: kingpin.Flag("host", "Endpoint URL").Short('H').String(),
|
||||||
|
|||||||
+19
-17
@@ -3,21 +3,23 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultBindAddress = ":9000"
|
defaultBindAddress = ":9000"
|
||||||
defaultDataDirectory = "/data"
|
defaultTunnelServerAddress = "0.0.0.0"
|
||||||
defaultAssetsDirectory = "./"
|
defaultTunnelServerPort = "8000"
|
||||||
defaultNoAuth = "false"
|
defaultDataDirectory = "/data"
|
||||||
defaultNoAnalytics = "false"
|
defaultAssetsDirectory = "./"
|
||||||
defaultTLS = "false"
|
defaultNoAuth = "false"
|
||||||
defaultTLSSkipVerify = "false"
|
defaultNoAnalytics = "false"
|
||||||
defaultTLSCACertPath = "/certs/ca.pem"
|
defaultTLS = "false"
|
||||||
defaultTLSCertPath = "/certs/cert.pem"
|
defaultTLSSkipVerify = "false"
|
||||||
defaultTLSKeyPath = "/certs/key.pem"
|
defaultTLSCACertPath = "/certs/ca.pem"
|
||||||
defaultSSL = "false"
|
defaultTLSCertPath = "/certs/cert.pem"
|
||||||
defaultSSLCertPath = "/certs/portainer.crt"
|
defaultTLSKeyPath = "/certs/key.pem"
|
||||||
defaultSSLKeyPath = "/certs/portainer.key"
|
defaultSSL = "false"
|
||||||
defaultSyncInterval = "60s"
|
defaultSSLCertPath = "/certs/portainer.crt"
|
||||||
defaultSnapshot = "true"
|
defaultSSLKeyPath = "/certs/portainer.key"
|
||||||
defaultSnapshotInterval = "5m"
|
defaultSyncInterval = "60s"
|
||||||
defaultTemplateFile = "/templates.json"
|
defaultSnapshot = "true"
|
||||||
|
defaultSnapshotInterval = "5m"
|
||||||
|
defaultTemplateFile = "/templates.json"
|
||||||
)
|
)
|
||||||
|
|||||||
+19
-17
@@ -1,21 +1,23 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultBindAddress = ":9000"
|
defaultBindAddress = ":9000"
|
||||||
defaultDataDirectory = "C:\\data"
|
defaultTunnelServerAddress = "0.0.0.0"
|
||||||
defaultAssetsDirectory = "./"
|
defaultTunnelServerPort = "8000"
|
||||||
defaultNoAuth = "false"
|
defaultDataDirectory = "C:\\data"
|
||||||
defaultNoAnalytics = "false"
|
defaultAssetsDirectory = "./"
|
||||||
defaultTLS = "false"
|
defaultNoAuth = "false"
|
||||||
defaultTLSSkipVerify = "false"
|
defaultNoAnalytics = "false"
|
||||||
defaultTLSCACertPath = "C:\\certs\\ca.pem"
|
defaultTLS = "false"
|
||||||
defaultTLSCertPath = "C:\\certs\\cert.pem"
|
defaultTLSSkipVerify = "false"
|
||||||
defaultTLSKeyPath = "C:\\certs\\key.pem"
|
defaultTLSCACertPath = "C:\\certs\\ca.pem"
|
||||||
defaultSSL = "false"
|
defaultTLSCertPath = "C:\\certs\\cert.pem"
|
||||||
defaultSSLCertPath = "C:\\certs\\portainer.crt"
|
defaultTLSKeyPath = "C:\\certs\\key.pem"
|
||||||
defaultSSLKeyPath = "C:\\certs\\portainer.key"
|
defaultSSL = "false"
|
||||||
defaultSyncInterval = "60s"
|
defaultSSLCertPath = "C:\\certs\\portainer.crt"
|
||||||
defaultSnapshot = "true"
|
defaultSSLKeyPath = "C:\\certs\\portainer.key"
|
||||||
defaultSnapshotInterval = "5m"
|
defaultSyncInterval = "60s"
|
||||||
defaultTemplateFile = "/templates.json"
|
defaultSnapshot = "true"
|
||||||
|
defaultSnapshotInterval = "5m"
|
||||||
|
defaultTemplateFile = "/templates.json"
|
||||||
)
|
)
|
||||||
|
|||||||
+46
-39
@@ -2,12 +2,14 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/portainer/portainer/api"
|
"github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/bolt"
|
"github.com/portainer/portainer/api/bolt"
|
||||||
|
"github.com/portainer/portainer/api/chisel"
|
||||||
"github.com/portainer/portainer/api/cli"
|
"github.com/portainer/portainer/api/cli"
|
||||||
"github.com/portainer/portainer/api/cron"
|
"github.com/portainer/portainer/api/cron"
|
||||||
"github.com/portainer/portainer/api/crypto"
|
"github.com/portainer/portainer/api/crypto"
|
||||||
@@ -20,8 +22,6 @@ import (
|
|||||||
"github.com/portainer/portainer/api/jwt"
|
"github.com/portainer/portainer/api/jwt"
|
||||||
"github.com/portainer/portainer/api/ldap"
|
"github.com/portainer/portainer/api/ldap"
|
||||||
"github.com/portainer/portainer/api/libcompose"
|
"github.com/portainer/portainer/api/libcompose"
|
||||||
|
|
||||||
"log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func initCLI() *portainer.CLIFlags {
|
func initCLI() *portainer.CLIFlags {
|
||||||
@@ -658,44 +658,51 @@ func main() {
|
|||||||
go terminateIfNoAdminCreated(store.UserService)
|
go terminateIfNoAdminCreated(store.UserService)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var tunnelServer portainer.TunnelServer = chisel.NewServer(*flags.TunnelAddr, *flags.TunnelPort)
|
||||||
|
err = tunnelServer.Start()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
var server portainer.Server = &http.Server{
|
var server portainer.Server = &http.Server{
|
||||||
Status: applicationStatus,
|
TunnelServerFingerprint: tunnelServer.GetFingerprint(),
|
||||||
BindAddress: *flags.Addr,
|
Status: applicationStatus,
|
||||||
AssetsPath: *flags.Assets,
|
BindAddress: *flags.Addr,
|
||||||
AuthDisabled: *flags.NoAuth,
|
AssetsPath: *flags.Assets,
|
||||||
EndpointManagement: endpointManagement,
|
AuthDisabled: *flags.NoAuth,
|
||||||
RoleService: store.RoleService,
|
EndpointManagement: endpointManagement,
|
||||||
UserService: store.UserService,
|
RoleService: store.RoleService,
|
||||||
TeamService: store.TeamService,
|
UserService: store.UserService,
|
||||||
TeamMembershipService: store.TeamMembershipService,
|
TeamService: store.TeamService,
|
||||||
EndpointService: store.EndpointService,
|
TeamMembershipService: store.TeamMembershipService,
|
||||||
EndpointGroupService: store.EndpointGroupService,
|
EndpointService: store.EndpointService,
|
||||||
ExtensionService: store.ExtensionService,
|
EndpointGroupService: store.EndpointGroupService,
|
||||||
ResourceControlService: store.ResourceControlService,
|
ExtensionService: store.ExtensionService,
|
||||||
SettingsService: store.SettingsService,
|
ResourceControlService: store.ResourceControlService,
|
||||||
RegistryService: store.RegistryService,
|
SettingsService: store.SettingsService,
|
||||||
DockerHubService: store.DockerHubService,
|
RegistryService: store.RegistryService,
|
||||||
StackService: store.StackService,
|
DockerHubService: store.DockerHubService,
|
||||||
ScheduleService: store.ScheduleService,
|
StackService: store.StackService,
|
||||||
TagService: store.TagService,
|
ScheduleService: store.ScheduleService,
|
||||||
TemplateService: store.TemplateService,
|
TagService: store.TagService,
|
||||||
WebhookService: store.WebhookService,
|
TemplateService: store.TemplateService,
|
||||||
SwarmStackManager: swarmStackManager,
|
WebhookService: store.WebhookService,
|
||||||
ComposeStackManager: composeStackManager,
|
SwarmStackManager: swarmStackManager,
|
||||||
ExtensionManager: extensionManager,
|
ComposeStackManager: composeStackManager,
|
||||||
CryptoService: cryptoService,
|
ExtensionManager: extensionManager,
|
||||||
JWTService: jwtService,
|
CryptoService: cryptoService,
|
||||||
FileService: fileService,
|
JWTService: jwtService,
|
||||||
LDAPService: ldapService,
|
FileService: fileService,
|
||||||
GitService: gitService,
|
LDAPService: ldapService,
|
||||||
SignatureService: digitalSignatureService,
|
GitService: gitService,
|
||||||
JobScheduler: jobScheduler,
|
SignatureService: digitalSignatureService,
|
||||||
Snapshotter: snapshotter,
|
JobScheduler: jobScheduler,
|
||||||
SSL: *flags.SSL,
|
Snapshotter: snapshotter,
|
||||||
SSLCert: *flags.SSLCert,
|
SSL: *flags.SSL,
|
||||||
SSLKey: *flags.SSLKey,
|
SSLCert: *flags.SSLCert,
|
||||||
DockerClientFactory: clientFactory,
|
SSLKey: *flags.SSLKey,
|
||||||
JobService: jobService,
|
DockerClientFactory: clientFactory,
|
||||||
|
JobService: jobService,
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Starting Portainer %s on %s", portainer.APIVersion, *flags.Addr)
|
log.Printf("Starting Portainer %s on %s", portainer.APIVersion, *flags.Addr)
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ func (handler *Handler) proxyRequestsToDockerAPI(w http.ResponseWriter, r *http.
|
|||||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
|
||||||
}
|
}
|
||||||
|
|
||||||
if endpoint.Status == portainer.EndpointStatusDown {
|
if endpoint.Type != 4 && endpoint.Status == portainer.EndpointStatusDown {
|
||||||
return &httperror.HandlerError{http.StatusServiceUnavailable, "Unable to query endpoint", errors.New("Endpoint is down")}
|
return &httperror.HandlerError{http.StatusServiceUnavailable, "Unable to query endpoint", errors.New("Endpoint is down")}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
package endpoints
|
package endpoints
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"log"
|
"log"
|
||||||
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
httperror "github.com/portainer/libhttp/error"
|
httperror "github.com/portainer/libhttp/error"
|
||||||
"github.com/portainer/libhttp/request"
|
"github.com/portainer/libhttp/request"
|
||||||
@@ -41,7 +44,7 @@ func (payload *endpointCreatePayload) Validate(r *http.Request) error {
|
|||||||
|
|
||||||
endpointType, err := request.RetrieveNumericMultiPartFormValue(r, "EndpointType", false)
|
endpointType, err := request.RetrieveNumericMultiPartFormValue(r, "EndpointType", false)
|
||||||
if err != nil || endpointType == 0 {
|
if err != nil || endpointType == 0 {
|
||||||
return portainer.Error("Invalid endpoint type value. Value must be one of: 1 (Docker environment), 2 (Agent environment) or 3 (Azure environment)")
|
return portainer.Error("Invalid endpoint type value. Value must be one of: 1 (Docker environment), 2 (Agent environment), 3 (Azure environment) or 4 (Edge Agent environment)")
|
||||||
}
|
}
|
||||||
payload.EndpointType = endpointType
|
payload.EndpointType = endpointType
|
||||||
|
|
||||||
@@ -149,6 +152,8 @@ func (handler *Handler) endpointCreate(w http.ResponseWriter, r *http.Request) *
|
|||||||
func (handler *Handler) createEndpoint(payload *endpointCreatePayload) (*portainer.Endpoint, *httperror.HandlerError) {
|
func (handler *Handler) createEndpoint(payload *endpointCreatePayload) (*portainer.Endpoint, *httperror.HandlerError) {
|
||||||
if portainer.EndpointType(payload.EndpointType) == portainer.AzureEnvironment {
|
if portainer.EndpointType(payload.EndpointType) == portainer.AzureEnvironment {
|
||||||
return handler.createAzureEndpoint(payload)
|
return handler.createAzureEndpoint(payload)
|
||||||
|
} else if portainer.EndpointType(payload.EndpointType) == portainer.EdgeAgentEnvironment {
|
||||||
|
return handler.createEdgeAgentEndpoint(payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
if payload.TLS {
|
if payload.TLS {
|
||||||
@@ -195,6 +200,63 @@ func (handler *Handler) createAzureEndpoint(payload *endpointCreatePayload) (*po
|
|||||||
return endpoint, nil
|
return endpoint, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: relocate in a service
|
||||||
|
// must be unique (e.g. not used / referenced)
|
||||||
|
func randomInt(min, max int) int {
|
||||||
|
// should be randomize at service creation time?
|
||||||
|
// if not seeded, will always get same port order
|
||||||
|
// might not be a problem and maybe not required
|
||||||
|
//rand.Seed(time.Now().UnixNano())
|
||||||
|
|
||||||
|
return min + rand.Intn(max-min)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (handler *Handler) createEdgeAgentEndpoint(payload *endpointCreatePayload) (*portainer.Endpoint, *httperror.HandlerError) {
|
||||||
|
endpointType := portainer.EdgeAgentEnvironment
|
||||||
|
endpointID := handler.EndpointService.GetNextIdentifier()
|
||||||
|
|
||||||
|
// get random port
|
||||||
|
// Dynamic ports (also called private ports) are 49152 to 65535.
|
||||||
|
// TODO: register this port somewhere
|
||||||
|
portnumber := randomInt(49152, 65535)
|
||||||
|
|
||||||
|
keyInformation := []string{
|
||||||
|
strings.TrimPrefix(payload.URL, "tcp://"),
|
||||||
|
"8000",
|
||||||
|
handler.TunnelServerFingerprint,
|
||||||
|
strconv.Itoa(portnumber),
|
||||||
|
"agent:randomstring",
|
||||||
|
}
|
||||||
|
|
||||||
|
key := strings.Join(keyInformation, "|")
|
||||||
|
encodedKey := base64.RawStdEncoding.EncodeToString([]byte(key))
|
||||||
|
|
||||||
|
endpoint := &portainer.Endpoint{
|
||||||
|
ID: portainer.EndpointID(endpointID),
|
||||||
|
Name: payload.Name,
|
||||||
|
URL: "tcp://localhost:" + strconv.Itoa(portnumber),
|
||||||
|
Type: endpointType,
|
||||||
|
GroupID: portainer.EndpointGroupID(payload.GroupID),
|
||||||
|
TLSConfig: portainer.TLSConfiguration{
|
||||||
|
TLS: false,
|
||||||
|
},
|
||||||
|
AuthorizedUsers: []portainer.UserID{},
|
||||||
|
AuthorizedTeams: []portainer.TeamID{},
|
||||||
|
Extensions: []portainer.EndpointExtension{},
|
||||||
|
Tags: payload.Tags,
|
||||||
|
Status: portainer.EndpointStatusUp,
|
||||||
|
Snapshots: []portainer.Snapshot{},
|
||||||
|
EdgeKey: string(encodedKey),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := handler.EndpointService.CreateEndpoint(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist endpoint inside the database", err}
|
||||||
|
}
|
||||||
|
|
||||||
|
return endpoint, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (handler *Handler) createUnsecuredEndpoint(payload *endpointCreatePayload) (*portainer.Endpoint, *httperror.HandlerError) {
|
func (handler *Handler) createUnsecuredEndpoint(payload *endpointCreatePayload) (*portainer.Endpoint, *httperror.HandlerError) {
|
||||||
endpointType := portainer.DockerEnvironment
|
endpointType := portainer.DockerEnvironment
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ type Handler struct {
|
|||||||
ProxyManager *proxy.Manager
|
ProxyManager *proxy.Manager
|
||||||
Snapshotter portainer.Snapshotter
|
Snapshotter portainer.Snapshotter
|
||||||
JobService portainer.JobService
|
JobService portainer.JobService
|
||||||
|
// TODO: figure out a way to manage this (service?)
|
||||||
|
TunnelServerFingerprint string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHandler creates a handler to manage endpoint operations.
|
// NewHandler creates a handler to manage endpoint operations.
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ func (handler *Handler) websocketExec(w http.ResponseWriter, r *http.Request) *h
|
|||||||
func (handler *Handler) handleExecRequest(w http.ResponseWriter, r *http.Request, params *webSocketRequestParams) error {
|
func (handler *Handler) handleExecRequest(w http.ResponseWriter, r *http.Request, params *webSocketRequestParams) error {
|
||||||
r.Header.Del("Origin")
|
r.Header.Del("Origin")
|
||||||
|
|
||||||
if params.nodeName != "" || params.endpoint.Type == portainer.AgentOnDockerEnvironment {
|
if params.nodeName != "" || (params.endpoint.Type == portainer.AgentOnDockerEnvironment || params.endpoint.Type == portainer.EdgeAgentEnvironment) {
|
||||||
return handler.proxyWebsocketRequest(w, r, params)
|
return handler.proxyWebsocketRequest(w, r, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
package proxy
|
package proxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/Microsoft/go-winio"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"github.com/Microsoft/go-winio"
|
|
||||||
|
|
||||||
portainer "github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+40
-38
@@ -39,44 +39,45 @@ import (
|
|||||||
|
|
||||||
// Server implements the portainer.Server interface
|
// Server implements the portainer.Server interface
|
||||||
type Server struct {
|
type Server struct {
|
||||||
BindAddress string
|
BindAddress string
|
||||||
AssetsPath string
|
AssetsPath string
|
||||||
AuthDisabled bool
|
TunnelServerFingerprint string
|
||||||
EndpointManagement bool
|
AuthDisabled bool
|
||||||
Status *portainer.Status
|
EndpointManagement bool
|
||||||
ExtensionManager portainer.ExtensionManager
|
Status *portainer.Status
|
||||||
ComposeStackManager portainer.ComposeStackManager
|
ExtensionManager portainer.ExtensionManager
|
||||||
CryptoService portainer.CryptoService
|
ComposeStackManager portainer.ComposeStackManager
|
||||||
SignatureService portainer.DigitalSignatureService
|
CryptoService portainer.CryptoService
|
||||||
JobScheduler portainer.JobScheduler
|
SignatureService portainer.DigitalSignatureService
|
||||||
Snapshotter portainer.Snapshotter
|
JobScheduler portainer.JobScheduler
|
||||||
RoleService portainer.RoleService
|
Snapshotter portainer.Snapshotter
|
||||||
DockerHubService portainer.DockerHubService
|
RoleService portainer.RoleService
|
||||||
EndpointService portainer.EndpointService
|
DockerHubService portainer.DockerHubService
|
||||||
EndpointGroupService portainer.EndpointGroupService
|
EndpointService portainer.EndpointService
|
||||||
FileService portainer.FileService
|
EndpointGroupService portainer.EndpointGroupService
|
||||||
GitService portainer.GitService
|
FileService portainer.FileService
|
||||||
JWTService portainer.JWTService
|
GitService portainer.GitService
|
||||||
LDAPService portainer.LDAPService
|
JWTService portainer.JWTService
|
||||||
ExtensionService portainer.ExtensionService
|
LDAPService portainer.LDAPService
|
||||||
RegistryService portainer.RegistryService
|
ExtensionService portainer.ExtensionService
|
||||||
ResourceControlService portainer.ResourceControlService
|
RegistryService portainer.RegistryService
|
||||||
ScheduleService portainer.ScheduleService
|
ResourceControlService portainer.ResourceControlService
|
||||||
SettingsService portainer.SettingsService
|
ScheduleService portainer.ScheduleService
|
||||||
StackService portainer.StackService
|
SettingsService portainer.SettingsService
|
||||||
SwarmStackManager portainer.SwarmStackManager
|
StackService portainer.StackService
|
||||||
TagService portainer.TagService
|
SwarmStackManager portainer.SwarmStackManager
|
||||||
TeamService portainer.TeamService
|
TagService portainer.TagService
|
||||||
TeamMembershipService portainer.TeamMembershipService
|
TeamService portainer.TeamService
|
||||||
TemplateService portainer.TemplateService
|
TeamMembershipService portainer.TeamMembershipService
|
||||||
UserService portainer.UserService
|
TemplateService portainer.TemplateService
|
||||||
WebhookService portainer.WebhookService
|
UserService portainer.UserService
|
||||||
Handler *handler.Handler
|
WebhookService portainer.WebhookService
|
||||||
SSL bool
|
Handler *handler.Handler
|
||||||
SSLCert string
|
SSL bool
|
||||||
SSLKey string
|
SSLCert string
|
||||||
DockerClientFactory *docker.ClientFactory
|
SSLKey string
|
||||||
JobService portainer.JobService
|
DockerClientFactory *docker.ClientFactory
|
||||||
|
JobService portainer.JobService
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start starts the HTTP server
|
// Start starts the HTTP server
|
||||||
@@ -132,6 +133,7 @@ func (server *Server) Start() error {
|
|||||||
endpointHandler.ProxyManager = proxyManager
|
endpointHandler.ProxyManager = proxyManager
|
||||||
endpointHandler.Snapshotter = server.Snapshotter
|
endpointHandler.Snapshotter = server.Snapshotter
|
||||||
endpointHandler.JobService = server.JobService
|
endpointHandler.JobService = server.JobService
|
||||||
|
endpointHandler.TunnelServerFingerprint = server.TunnelServerFingerprint
|
||||||
|
|
||||||
var endpointGroupHandler = endpointgroups.NewHandler(requestBouncer)
|
var endpointGroupHandler = endpointgroups.NewHandler(requestBouncer)
|
||||||
endpointGroupHandler.EndpointGroupService = server.EndpointGroupService
|
endpointGroupHandler.EndpointGroupService = server.EndpointGroupService
|
||||||
|
|||||||
+12
-1
@@ -10,6 +10,8 @@ type (
|
|||||||
// CLIFlags represents the available flags on the CLI
|
// CLIFlags represents the available flags on the CLI
|
||||||
CLIFlags struct {
|
CLIFlags struct {
|
||||||
Addr *string
|
Addr *string
|
||||||
|
TunnelAddr *string
|
||||||
|
TunnelPort *string
|
||||||
AdminPassword *string
|
AdminPassword *string
|
||||||
AdminPasswordFile *string
|
AdminPasswordFile *string
|
||||||
Assets *string
|
Assets *string
|
||||||
@@ -250,7 +252,7 @@ type (
|
|||||||
Snapshots []Snapshot `json:"Snapshots"`
|
Snapshots []Snapshot `json:"Snapshots"`
|
||||||
UserAccessPolicies UserAccessPolicies `json:"UserAccessPolicies"`
|
UserAccessPolicies UserAccessPolicies `json:"UserAccessPolicies"`
|
||||||
TeamAccessPolicies TeamAccessPolicies `json:"TeamAccessPolicies"`
|
TeamAccessPolicies TeamAccessPolicies `json:"TeamAccessPolicies"`
|
||||||
|
EdgeKey string
|
||||||
// Deprecated fields
|
// Deprecated fields
|
||||||
// Deprecated in DBVersion == 4
|
// Deprecated in DBVersion == 4
|
||||||
TLS bool `json:"TLS,omitempty"`
|
TLS bool `json:"TLS,omitempty"`
|
||||||
@@ -594,6 +596,13 @@ type (
|
|||||||
Start() error
|
Start() error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tunnel server defines the interface for the reverse tunneling server used
|
||||||
|
// with Edge agents.
|
||||||
|
TunnelServer interface {
|
||||||
|
Start() error
|
||||||
|
GetFingerprint() string
|
||||||
|
}
|
||||||
|
|
||||||
// UserService represents a service for managing user data
|
// UserService represents a service for managing user data
|
||||||
UserService interface {
|
UserService interface {
|
||||||
User(ID UserID) (*User, error)
|
User(ID UserID) (*User, error)
|
||||||
@@ -951,6 +960,8 @@ const (
|
|||||||
AgentOnDockerEnvironment
|
AgentOnDockerEnvironment
|
||||||
// AzureEnvironment represents an endpoint connected to an Azure environment
|
// AzureEnvironment represents an endpoint connected to an Azure environment
|
||||||
AzureEnvironment
|
AzureEnvironment
|
||||||
|
// EdgeAgentEnvironment represents an endpoint connected to an Edge agent
|
||||||
|
EdgeAgentEnvironment
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ angular.module('portainer.docker')
|
|||||||
agentProxy: false
|
agentProxy: false
|
||||||
};
|
};
|
||||||
|
|
||||||
if (type === 2) {
|
if (type === 2 || type === 4) {
|
||||||
mode.agentProxy = true;
|
mode.agentProxy = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -74,7 +74,8 @@
|
|||||||
{{ item.Type | endpointtypename }}
|
{{ item.Type | endpointtypename }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ item.URL | stripprotocol }}</td>
|
<td ng-if="item.Type !== 4">{{ item.URL | stripprotocol }}</td>
|
||||||
|
<td ng-if="item.Type === 4">-</td>
|
||||||
<td>{{ item.GroupName }}</td>
|
<td>{{ item.GroupName }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a ui-sref="portainer.endpoints.endpoint.access({id: item.Id})" ng-if="$ctrl.accessManagement">
|
<a ui-sref="portainer.endpoints.endpoint.access({id: item.Id})" ng-if="$ctrl.accessManagement">
|
||||||
|
|||||||
@@ -124,6 +124,8 @@ angular.module('portainer.app')
|
|||||||
return 'Agent';
|
return 'Agent';
|
||||||
} else if (type === 3) {
|
} else if (type === 3) {
|
||||||
return 'Azure ACI';
|
return 'Azure ACI';
|
||||||
|
} else if (type === 4) {
|
||||||
|
return 'Edge Agent';
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
};
|
};
|
||||||
@@ -133,6 +135,8 @@ angular.module('portainer.app')
|
|||||||
return function (type) {
|
return function (type) {
|
||||||
if (type === 3) {
|
if (type === 3) {
|
||||||
return 'fab fa-microsoft';
|
return 'fab fa-microsoft';
|
||||||
|
} else if (type === 4) {
|
||||||
|
return 'fa fa-cloud';
|
||||||
}
|
}
|
||||||
return 'fab fa-docker';
|
return 'fab fa-docker';
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -56,6 +56,15 @@ function ($q, $scope, $state, $filter, clipboard, EndpointService, GroupService,
|
|||||||
addEndpoint(name, 2, URL, publicURL, groupId, tags, true, true, true, null, null, null);
|
addEndpoint(name, 2, URL, publicURL, groupId, tags, true, true, true, null, null, null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.addEdgeAgentEndpoint = function() {
|
||||||
|
var name = $scope.formValues.Name;
|
||||||
|
var groupId = $scope.formValues.GroupId;
|
||||||
|
var tags = $scope.formValues.Tags;
|
||||||
|
var URL = window.location.hostname;
|
||||||
|
|
||||||
|
addEndpoint(name, 4, URL, "", groupId, tags, false, false, false, null, null, null);
|
||||||
|
};
|
||||||
|
|
||||||
$scope.addAzureEndpoint = function() {
|
$scope.addAzureEndpoint = function() {
|
||||||
var name = $scope.formValues.Name;
|
var name = $scope.formValues.Name;
|
||||||
var applicationId = $scope.formValues.AzureApplicationId;
|
var applicationId = $scope.formValues.AzureApplicationId;
|
||||||
@@ -85,9 +94,13 @@ function ($q, $scope, $state, $filter, clipboard, EndpointService, GroupService,
|
|||||||
function addEndpoint(name, type, URL, PublicURL, groupId, tags, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) {
|
function addEndpoint(name, type, URL, PublicURL, groupId, tags, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) {
|
||||||
$scope.state.actionInProgress = true;
|
$scope.state.actionInProgress = true;
|
||||||
EndpointService.createRemoteEndpoint(name, type, URL, PublicURL, groupId, tags, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile)
|
EndpointService.createRemoteEndpoint(name, type, URL, PublicURL, groupId, tags, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile)
|
||||||
.then(function success() {
|
.then(function success(data) {
|
||||||
Notifications.success('Endpoint created', name);
|
Notifications.success('Endpoint created', name);
|
||||||
$state.go('portainer.endpoints', {}, {reload: true});
|
if (type === 4) {
|
||||||
|
$state.go('portainer.endpoints.endpoint', { id: data.Id });
|
||||||
|
} else {
|
||||||
|
$state.go('portainer.endpoints', {}, {reload: true});
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch(function error(err) {
|
.catch(function error(err) {
|
||||||
Notifications.error('Failure', err, 'Unable to create endpoint');
|
Notifications.error('Failure', err, 'Unable to create endpoint');
|
||||||
|
|||||||
@@ -36,6 +36,16 @@
|
|||||||
<p>Portainer agent</p>
|
<p>Portainer agent</p>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="radio" id="edge_agent_endpoint" ng-model="state.EnvironmentType" value="edge_agent">
|
||||||
|
<label for="edge_agent_endpoint">
|
||||||
|
<div class="boxselector_header">
|
||||||
|
<i class="fa fa-cloud" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||||
|
Edge Agent
|
||||||
|
</div>
|
||||||
|
<p>Portainer Edge agent</p>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<input type="radio" id="azure_endpoint" ng-model="state.EnvironmentType" value="azure">
|
<input type="radio" id="azure_endpoint" ng-model="state.EnvironmentType" value="azure">
|
||||||
<label for="azure_endpoint">
|
<label for="azure_endpoint">
|
||||||
@@ -77,6 +87,16 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div ng-if="state.EnvironmentType === 'edge_agent'">
|
||||||
|
<div class="col-sm-12 form-section-title" >
|
||||||
|
Information
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<span class="col-sm-12 text-muted small">
|
||||||
|
Allows you to create an endpoint that can be registered with an Edge agent. All the required information on how to connect an Edge agent to this endpoint will be available after endpoint creation.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div ng-if="state.EnvironmentType === 'azure'">
|
<div ng-if="state.EnvironmentType === 'azure'">
|
||||||
<div class="col-sm-12 form-section-title" >
|
<div class="col-sm-12 form-section-title" >
|
||||||
Information
|
Information
|
||||||
@@ -238,6 +258,10 @@
|
|||||||
<span ng-hide="state.actionInProgress"><i class="fa fa-plus" aria-hidden="true"></i> Add endpoint</span>
|
<span ng-hide="state.actionInProgress"><i class="fa fa-plus" aria-hidden="true"></i> Add endpoint</span>
|
||||||
<span ng-show="state.actionInProgress">Creating endpoint...</span>
|
<span ng-show="state.actionInProgress">Creating endpoint...</span>
|
||||||
</button>
|
</button>
|
||||||
|
<button ng-if="state.EnvironmentType === 'edge_agent'" type="submit" class="btn btn-primary btn-sm" ng-disabled="state.actionInProgress || !endpointCreationForm.$valid" ng-click="addEdgeAgentEndpoint()" button-spinner="state.actionInProgress">
|
||||||
|
<span ng-hide="state.actionInProgress"><i class="fa fa-plus" aria-hidden="true"></i> Add endpoint</span>
|
||||||
|
<span ng-show="state.actionInProgress">Creating endpoint...</span>
|
||||||
|
</button>
|
||||||
<button ng-if="state.EnvironmentType === 'azure'" type="submit" class="btn btn-primary btn-sm" ng-disabled="state.actionInProgress || !endpointCreationForm.$valid" ng-click="addAzureEndpoint()" button-spinner="state.actionInProgress">
|
<button ng-if="state.EnvironmentType === 'azure'" type="submit" class="btn btn-primary btn-sm" ng-disabled="state.actionInProgress || !endpointCreationForm.$valid" ng-click="addAzureEndpoint()" button-spinner="state.actionInProgress">
|
||||||
<span ng-hide="state.actionInProgress"><i class="fa fa-plus" aria-hidden="true"></i> Add endpoint</span>
|
<span ng-hide="state.actionInProgress"><i class="fa fa-plus" aria-hidden="true"></i> Add endpoint</span>
|
||||||
<span ng-show="state.actionInProgress">Creating endpoint...</span>
|
<span ng-show="state.actionInProgress">Creating endpoint...</span>
|
||||||
|
|||||||
@@ -5,6 +5,80 @@
|
|||||||
</rd-header-content>
|
</rd-header-content>
|
||||||
</rd-header>
|
</rd-header>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<information-panel ng-if="endpoint.Type === 4" title-text="Deploy an agent">
|
||||||
|
<span class="small text-muted">
|
||||||
|
<p>
|
||||||
|
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||||
|
Deploy the Edge agent on your remote Docker environment using the following command
|
||||||
|
</p>
|
||||||
|
<div style="margin-top: 10px;">
|
||||||
|
<uib-tabset active="state.deploymentTab">
|
||||||
|
<uib-tab index="0" heading="Standalone">
|
||||||
|
<code style=display:block;white-space:pre-wrap>
|
||||||
|
docker run -d -v /var/run/docker.sock:/var/run/docker.sock \
|
||||||
|
-v /var/lib/docker/volumes:/var/lib/docker/volumes \
|
||||||
|
-v /:/host \
|
||||||
|
--restart always \
|
||||||
|
-e EDGE=1 \
|
||||||
|
-e CAP_HOST_MANAGEMENT=1 \
|
||||||
|
-p 80:80 \
|
||||||
|
--name portainer_edge_agent \
|
||||||
|
portainer/pagent:edge
|
||||||
|
</code>
|
||||||
|
</uib-tab>
|
||||||
|
<uib-tab index="1" heading="Swarm">
|
||||||
|
<code style=display:block;white-space:pre-wrap>
|
||||||
|
docker network create \
|
||||||
|
--driver overlay \
|
||||||
|
--attachable \
|
||||||
|
portainer_agent_network;
|
||||||
|
|
||||||
|
docker service create \
|
||||||
|
--name portainer_edge_agent \
|
||||||
|
--network portainer_agent_network \
|
||||||
|
-e AGENT_CLUSTER_ADDR=tasks.portainer_edge_agent \
|
||||||
|
-e EDGE=1 \
|
||||||
|
-e CAP_HOST_MANAGEMENT=1 \
|
||||||
|
--mode global \
|
||||||
|
--publish mode=host,target=80,published=80 \
|
||||||
|
--constraint 'node.platform.os == linux' \
|
||||||
|
--mount type=bind,src=//var/run/docker.sock,dst=/var/run/docker.sock \
|
||||||
|
--mount type=bind,src=//var/lib/docker/volumes,dst=/var/lib/docker/volumes \
|
||||||
|
--mount type=bind,src=//,dst=/host \
|
||||||
|
portainer/pagent:edge
|
||||||
|
</code>
|
||||||
|
</uib-tab>
|
||||||
|
</uib-tabset>
|
||||||
|
<div style="margin-top: 10px;">
|
||||||
|
<span class="btn btn-primary btn-sm" ng-click="copyEdgeAgentDeploymentCommand()"><i class="fa fa-copy space-right" aria-hidden="true"></i>Copy command</span>
|
||||||
|
<span id="copyNotificationDeploymentCommand" style="margin-left: 7px; display: none; color: #23ae89;">
|
||||||
|
<i class="fa fa-check" aria-hidden="true" ></i> copied
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-12 form-section-title" style="margin-top: 25px;">
|
||||||
|
Join token
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||||
|
Use the following join token to associate the Edge agent with this endpoint
|
||||||
|
</p>
|
||||||
|
<div style="margin-top: 10px;">
|
||||||
|
<code style=display:block;white-space:pre-wrap>
|
||||||
|
{{ endpoint.EdgeKey }}
|
||||||
|
</code>
|
||||||
|
<div style="margin-top: 10px;">
|
||||||
|
<span class="btn btn-primary btn-sm" ng-click="copyEdgeAgentKey()"><i class="fa fa-copy space-right" aria-hidden="true"></i>Copy token</span>
|
||||||
|
<span id="copyNotificationEdgeKey" style="margin-left: 7px; display: none; color: #23ae89;">
|
||||||
|
<i class="fa fa-check" aria-hidden="true" ></i> copied
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</information-panel>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||||
<rd-widget>
|
<rd-widget>
|
||||||
@@ -22,7 +96,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- !name-input -->
|
<!-- !name-input -->
|
||||||
<!-- endpoint-url-input -->
|
<!-- endpoint-url-input -->
|
||||||
<div class="form-group">
|
<div class="form-group" ng-if="endpoint.Type !== 4">
|
||||||
<label for="endpoint_url" class="col-sm-3 col-lg-2 control-label text-left">
|
<label for="endpoint_url" class="col-sm-3 col-lg-2 control-label text-left">
|
||||||
Endpoint URL
|
Endpoint URL
|
||||||
<portainer-tooltip position="bottom" message="URL or IP address of a Docker host. The Docker API must be exposed over a TCP port. Please refer to the Docker documentation to configure it."></portainer-tooltip>
|
<portainer-tooltip position="bottom" message="URL or IP address of a Docker host. The Docker API must be exposed over a TCP port. Please refer to the Docker documentation to configure it."></portainer-tooltip>
|
||||||
@@ -70,7 +144,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- !tags -->
|
<!-- !tags -->
|
||||||
<!-- endpoint-security -->
|
<!-- endpoint-security -->
|
||||||
<div ng-if="endpointType === 'remote' && endpoint.Type !== 3">
|
<div ng-if="endpointType === 'remote' && endpoint.Type !== 3 && endpoint.Type !== 4">
|
||||||
<div class="col-sm-12 form-section-title">
|
<div class="col-sm-12 form-section-title">
|
||||||
Security
|
Security
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { EndpointSecurityFormData } from '../../../components/endpointSecurity/porEndpointSecurityModel';
|
import { EndpointSecurityFormData } from '../../../components/endpointSecurity/porEndpointSecurityModel';
|
||||||
|
|
||||||
angular.module('portainer.app')
|
angular.module('portainer.app')
|
||||||
.controller('EndpointController', ['$q', '$scope', '$state', '$transition$', '$filter', 'EndpointService', 'GroupService', 'TagService', 'EndpointProvider', 'Notifications',
|
.controller('EndpointController', ['$q', '$scope', '$state', '$transition$', '$filter', 'clipboard', 'EndpointService', 'GroupService', 'TagService', 'EndpointProvider', 'Notifications',
|
||||||
function ($q, $scope, $state, $transition$, $filter, EndpointService, GroupService, TagService, EndpointProvider, Notifications) {
|
function ($q, $scope, $state, $transition$, $filter, clipboard, EndpointService, GroupService, TagService, EndpointProvider, Notifications) {
|
||||||
|
|
||||||
if (!$scope.applicationState.application.endpointManagement) {
|
if (!$scope.applicationState.application.endpointManagement) {
|
||||||
$state.go('portainer.endpoints');
|
$state.go('portainer.endpoints');
|
||||||
@@ -10,13 +10,28 @@ function ($q, $scope, $state, $transition$, $filter, EndpointService, GroupServi
|
|||||||
|
|
||||||
$scope.state = {
|
$scope.state = {
|
||||||
uploadInProgress: false,
|
uploadInProgress: false,
|
||||||
actionInProgress: false
|
actionInProgress: false,
|
||||||
|
deploymentTab: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.formValues = {
|
$scope.formValues = {
|
||||||
SecurityFormData: new EndpointSecurityFormData()
|
SecurityFormData: new EndpointSecurityFormData()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.copyEdgeAgentDeploymentCommand = function() {
|
||||||
|
if ($scope.state.deploymentTab === 0) {
|
||||||
|
clipboard.copyText('docker run -d -v /var/run/docker.sock:/var/run/docker.sock -v /var/lib/docker/volumes:/var/lib/docker/volumes -v /:/host --restart always -e EDGE=1 -e CAP_HOST_MANAGEMENT=1 --name portainer_agent_iot portainer/pagent:edge');
|
||||||
|
} else {
|
||||||
|
clipboard.copyText('docker network create --driver overlay --attachable portainer_agent_network; docker service create --name portainer_edge_agent --network portainer_agent_network -e AGENT_CLUSTER_ADDR=tasks.portainer_edge_agent -e EDGE=1 -e CAP_HOST_MANAGEMENT=1 --mode global --publish mode=host,target=80,published=80 --constraint \'node.platform.os == linux\' --mount type=bind,src=//var/run/docker.sock,dst=/var/run/docker.sock --mount type=bind,src=//var/lib/docker/volumes,dst=/var/lib/docker/volume --mount type=bind,src=//,dst=/host portainer/pagent:edge');
|
||||||
|
}
|
||||||
|
$('#copyNotificationDeploymentCommand').show().fadeOut(2500);
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.copyEdgeAgentKey = function() {
|
||||||
|
clipboard.copyText($scope.endpoint.EdgeKey);
|
||||||
|
$('#copyNotificationEdgeKey').show().fadeOut(2500);
|
||||||
|
};
|
||||||
|
|
||||||
$scope.updateEndpoint = function() {
|
$scope.updateEndpoint = function() {
|
||||||
var endpoint = $scope.endpoint;
|
var endpoint = $scope.endpoint;
|
||||||
var securityData = $scope.formValues.SecurityFormData;
|
var securityData = $scope.formValues.SecurityFormData;
|
||||||
|
|||||||
+3
-3
@@ -48,7 +48,7 @@ module.exports = function(grunt) {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
grunt.task.registerTask('devopsbuild', 'devopsbuild:<platform>:<arch>',
|
grunt.task.registerTask('devopsbuild', 'devopsbuild:<platform>:<arch>',
|
||||||
function(p, a) {
|
function(p, a) {
|
||||||
grunt.task.run([
|
grunt.task.run([
|
||||||
'config:prod',
|
'config:prod',
|
||||||
@@ -146,7 +146,7 @@ gruntfile_cfg.copy = {
|
|||||||
files: [
|
files: [
|
||||||
{
|
{
|
||||||
dest: '<%= root %>/',
|
dest: '<%= root %>/',
|
||||||
src: 'templates.json',
|
src: 'templates.json',
|
||||||
cwd: ''
|
cwd: ''
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -185,7 +185,7 @@ function shell_buildBinaryOnDevOps(p, a) {
|
|||||||
function shell_run() {
|
function shell_run() {
|
||||||
return [
|
return [
|
||||||
'docker rm -f portainer',
|
'docker rm -f portainer',
|
||||||
'docker run -d -p 9000:9000 -v $(pwd)/dist:/app -v /tmp/portainer:/data -v /var/run/docker.sock:/var/run/docker.sock:z --name portainer portainer/base /app/portainer --no-analytics --template-file /app/templates.json'
|
'docker run -d -p 9999:9999 -p 9000:9000 -v $(pwd)/dist:/app -v /tmp/portainer:/data -v /var/run/docker.sock:/var/run/docker.sock:z --name portainer portainer/base /app/portainer --no-analytics --template-file /app/templates.json'
|
||||||
].join(';');
|
].join(';');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user