mirror of
				https://github.com/juanfont/headscale.git
				synced 2025-10-28 10:51:44 +01:00 
			
		
		
		
	Merge branch 'main' into apiwork
This commit is contained in:
		
						commit
						b2b2954545
					
				
							
								
								
									
										2
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							@ -31,7 +31,7 @@ jobs:
 | 
				
			|||||||
        if: steps.changed-files.outputs.any_changed == 'true'
 | 
					        if: steps.changed-files.outputs.any_changed == 'true'
 | 
				
			||||||
        uses: actions/setup-go@v2
 | 
					        uses: actions/setup-go@v2
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          go-version: "1.17"
 | 
					          go-version: "1.17.7"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Install dependencies
 | 
					      - name: Install dependencies
 | 
				
			||||||
        if: steps.changed-files.outputs.any_changed == 'true'
 | 
					        if: steps.changed-files.outputs.any_changed == 'true'
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										2
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							@ -18,7 +18,7 @@ jobs:
 | 
				
			|||||||
      - name: Set up Go
 | 
					      - name: Set up Go
 | 
				
			||||||
        uses: actions/setup-go@v2
 | 
					        uses: actions/setup-go@v2
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          go-version: 1.17
 | 
					          go-version: 1.17.7
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Install dependencies
 | 
					      - name: Install dependencies
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										2
									
								
								.github/workflows/test-integration.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/test-integration.yml
									
									
									
									
										vendored
									
									
								
							@ -25,7 +25,7 @@ jobs:
 | 
				
			|||||||
        if: steps.changed-files.outputs.any_changed == 'true'
 | 
					        if: steps.changed-files.outputs.any_changed == 'true'
 | 
				
			||||||
        uses: actions/setup-go@v2
 | 
					        uses: actions/setup-go@v2
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          go-version: "1.17"
 | 
					          go-version: "1.17.7"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Run Integration tests
 | 
					      - name: Run Integration tests
 | 
				
			||||||
        if: steps.changed-files.outputs.any_changed == 'true'
 | 
					        if: steps.changed-files.outputs.any_changed == 'true'
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										2
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							@ -25,7 +25,7 @@ jobs:
 | 
				
			|||||||
        if: steps.changed-files.outputs.any_changed == 'true'
 | 
					        if: steps.changed-files.outputs.any_changed == 'true'
 | 
				
			||||||
        uses: actions/setup-go@v2
 | 
					        uses: actions/setup-go@v2
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          go-version: "1.17"
 | 
					          go-version: "1.17.7"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Install dependencies
 | 
					      - name: Install dependencies
 | 
				
			||||||
        if: steps.changed-files.outputs.any_changed == 'true'
 | 
					        if: steps.changed-files.outputs.any_changed == 'true'
 | 
				
			||||||
 | 
				
			|||||||
@ -16,6 +16,7 @@
 | 
				
			|||||||
- `ip_prefix` is now superseded by `ip_prefixes` in the configuration [#208](https://github.com/juanfont/headscale/pull/208)
 | 
					- `ip_prefix` is now superseded by `ip_prefixes` in the configuration [#208](https://github.com/juanfont/headscale/pull/208)
 | 
				
			||||||
- Upgrade `tailscale` (1.20.4) and other dependencies to latest [#314](https://github.com/juanfont/headscale/pull/314)
 | 
					- Upgrade `tailscale` (1.20.4) and other dependencies to latest [#314](https://github.com/juanfont/headscale/pull/314)
 | 
				
			||||||
- fix swapped machine<->namespace labels in `/metrics` [#312](https://github.com/juanfont/headscale/pull/312)
 | 
					- fix swapped machine<->namespace labels in `/metrics` [#312](https://github.com/juanfont/headscale/pull/312)
 | 
				
			||||||
 | 
					- remove key-value based update mechanism for namespace changes [#316](https://github.com/juanfont/headscale/pull/316)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**0.12.4 (2022-01-29):**
 | 
					**0.12.4 (2022-01-29):**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
# Builder image
 | 
					# Builder image
 | 
				
			||||||
FROM docker.io/golang:1.17.1-bullseye AS build
 | 
					FROM docker.io/golang:1.17.7-bullseye AS build
 | 
				
			||||||
ENV GOPATH /go
 | 
					ENV GOPATH /go
 | 
				
			||||||
WORKDIR /go/src/headscale
 | 
					WORKDIR /go/src/headscale
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
# Builder image
 | 
					# Builder image
 | 
				
			||||||
FROM docker.io/golang:1.17.1-alpine AS build
 | 
					FROM docker.io/golang:1.17.7-alpine AS build
 | 
				
			||||||
ENV GOPATH /go
 | 
					ENV GOPATH /go
 | 
				
			||||||
WORKDIR /go/src/headscale
 | 
					WORKDIR /go/src/headscale
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
# Builder image
 | 
					# Builder image
 | 
				
			||||||
FROM docker.io/golang:1.17.1-bullseye AS build
 | 
					FROM docker.io/golang:1.17.7-bullseye AS build
 | 
				
			||||||
ENV GOPATH /go
 | 
					ENV GOPATH /go
 | 
				
			||||||
WORKDIR /go/src/headscale
 | 
					WORKDIR /go/src/headscale
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										16
									
								
								app.go
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								app.go
									
									
									
									
									
								
							@ -271,20 +271,6 @@ func (h *Headscale) expireEphemeralNodesWorker() {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// WatchForKVUpdates checks the KV DB table for requests to perform tailnet upgrades
 | 
					 | 
				
			||||||
// This is a way to communitate the CLI with the headscale server.
 | 
					 | 
				
			||||||
func (h *Headscale) watchForKVUpdates(milliSeconds int64) {
 | 
					 | 
				
			||||||
	ticker := time.NewTicker(time.Duration(milliSeconds) * time.Millisecond)
 | 
					 | 
				
			||||||
	for range ticker.C {
 | 
					 | 
				
			||||||
		h.watchForKVUpdatesWorker()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (h *Headscale) watchForKVUpdatesWorker() {
 | 
					 | 
				
			||||||
	h.checkForNamespacesPendingUpdates()
 | 
					 | 
				
			||||||
	// more functions will come here in the future
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (h *Headscale) grpcAuthenticationInterceptor(ctx context.Context,
 | 
					func (h *Headscale) grpcAuthenticationInterceptor(ctx context.Context,
 | 
				
			||||||
	req interface{},
 | 
						req interface{},
 | 
				
			||||||
	info *grpc.UnaryServerInfo,
 | 
						info *grpc.UnaryServerInfo,
 | 
				
			||||||
@ -465,8 +451,6 @@ func (h *Headscale) Serve() error {
 | 
				
			|||||||
		go h.scheduledDERPMapUpdateWorker(derpMapCancelChannel)
 | 
							go h.scheduledDERPMapUpdateWorker(derpMapCancelChannel)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// I HATE THIS
 | 
					 | 
				
			||||||
	go h.watchForKVUpdates(updateInterval)
 | 
					 | 
				
			||||||
	go h.expireEphemeralNodes(updateInterval)
 | 
						go h.expireEphemeralNodes(updateInterval)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if zl.GlobalLevel() == zl.TraceLevel {
 | 
						if zl.GlobalLevel() == zl.TraceLevel {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										11
									
								
								machine.go
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								machine.go
									
									
									
									
									
								
							@ -353,13 +353,12 @@ func (h *Headscale) DeleteMachine(machine *Machine) error {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	machine.Registered = false
 | 
						machine.Registered = false
 | 
				
			||||||
	namespaceID := machine.NamespaceID
 | 
					 | 
				
			||||||
	h.db.Save(&machine) // we mark it as unregistered, just in case
 | 
						h.db.Save(&machine) // we mark it as unregistered, just in case
 | 
				
			||||||
	if err := h.db.Delete(&machine).Error; err != nil {
 | 
						if err := h.db.Delete(&machine).Error; err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return h.RequestMapUpdates(namespaceID)
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (h *Headscale) TouchMachine(machine *Machine) error {
 | 
					func (h *Headscale) TouchMachine(machine *Machine) error {
 | 
				
			||||||
@ -377,12 +376,11 @@ func (h *Headscale) HardDeleteMachine(machine *Machine) error {
 | 
				
			|||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	namespaceID := machine.NamespaceID
 | 
					 | 
				
			||||||
	if err := h.db.Unscoped().Delete(&machine).Error; err != nil {
 | 
						if err := h.db.Unscoped().Delete(&machine).Error; err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return h.RequestMapUpdates(namespaceID)
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetHostInfo returns a Hostinfo struct for the machine.
 | 
					// GetHostInfo returns a Hostinfo struct for the machine.
 | 
				
			||||||
@ -864,11 +862,6 @@ func (h *Headscale) EnableRoutes(machine *Machine, routeStrs ...string) error {
 | 
				
			|||||||
	machine.EnabledRoutes = datatypes.JSON(routes)
 | 
						machine.EnabledRoutes = datatypes.JSON(routes)
 | 
				
			||||||
	h.db.Save(&machine)
 | 
						h.db.Save(&machine)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err = h.RequestMapUpdates(machine.NamespaceID)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,6 @@
 | 
				
			|||||||
package headscale
 | 
					package headscale
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"encoding/json"
 | 
					 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -88,18 +87,6 @@ func (s *Suite) TestDeleteMachine(c *check.C) {
 | 
				
			|||||||
	err = app.DeleteMachine(&machine)
 | 
						err = app.DeleteMachine(&machine)
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	namespacesPendingUpdates, err := app.getValue("namespaces_pending_updates")
 | 
					 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	names := []string{}
 | 
					 | 
				
			||||||
	err = json.Unmarshal([]byte(namespacesPendingUpdates), &names)
 | 
					 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
					 | 
				
			||||||
	c.Assert(names, check.DeepEquals, []string{namespace.Name})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	app.checkForNamespacesPendingUpdates()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	namespacesPendingUpdates, _ = app.getValue("namespaces_pending_updates")
 | 
					 | 
				
			||||||
	c.Assert(namespacesPendingUpdates, check.Equals, "")
 | 
					 | 
				
			||||||
	_, err = app.GetMachine(namespace.Name, "testmachine")
 | 
						_, err = app.GetMachine(namespace.Name, "testmachine")
 | 
				
			||||||
	c.Assert(err, check.NotNil)
 | 
						c.Assert(err, check.NotNil)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +1,7 @@
 | 
				
			|||||||
package headscale
 | 
					package headscale
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"encoding/json"
 | 
					 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
	"fmt"
 | 
					 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -104,11 +102,6 @@ func (h *Headscale) RenameNamespace(oldName, newName string) error {
 | 
				
			|||||||
		return result.Error
 | 
							return result.Error
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err = h.RequestMapUpdates(oldNamespace.ID)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -187,92 +180,6 @@ func (h *Headscale) SetMachineNamespace(machine *Machine, namespaceName string)
 | 
				
			|||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO(kradalby): Remove the need for this.
 | 
					 | 
				
			||||||
// RequestMapUpdates signals the KV worker to update the maps for this namespace.
 | 
					 | 
				
			||||||
func (h *Headscale) RequestMapUpdates(namespaceID uint) error {
 | 
					 | 
				
			||||||
	namespace := Namespace{}
 | 
					 | 
				
			||||||
	if err := h.db.First(&namespace, namespaceID).Error; err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	namespacesPendingUpdates, err := h.getValue("namespaces_pending_updates")
 | 
					 | 
				
			||||||
	if err != nil || namespacesPendingUpdates == "" {
 | 
					 | 
				
			||||||
		err = h.setValue(
 | 
					 | 
				
			||||||
			"namespaces_pending_updates",
 | 
					 | 
				
			||||||
			fmt.Sprintf(`["%s"]`, namespace.Name),
 | 
					 | 
				
			||||||
		)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	names := []string{}
 | 
					 | 
				
			||||||
	err = json.Unmarshal([]byte(namespacesPendingUpdates), &names)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		err = h.setValue(
 | 
					 | 
				
			||||||
			"namespaces_pending_updates",
 | 
					 | 
				
			||||||
			fmt.Sprintf(`["%s"]`, namespace.Name),
 | 
					 | 
				
			||||||
		)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	names = append(names, namespace.Name)
 | 
					 | 
				
			||||||
	data, err := json.Marshal(names)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Error().
 | 
					 | 
				
			||||||
			Str("func", "RequestMapUpdates").
 | 
					 | 
				
			||||||
			Err(err).
 | 
					 | 
				
			||||||
			Msg("Could not marshal namespaces_pending_updates")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return h.setValue("namespaces_pending_updates", string(data))
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (h *Headscale) checkForNamespacesPendingUpdates() {
 | 
					 | 
				
			||||||
	namespacesPendingUpdates, err := h.getValue("namespaces_pending_updates")
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if namespacesPendingUpdates == "" {
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	namespaces := []string{}
 | 
					 | 
				
			||||||
	err = json.Unmarshal([]byte(namespacesPendingUpdates), &namespaces)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	for _, namespace := range namespaces {
 | 
					 | 
				
			||||||
		log.Trace().
 | 
					 | 
				
			||||||
			Str("func", "RequestMapUpdates").
 | 
					 | 
				
			||||||
			Str("machine", namespace).
 | 
					 | 
				
			||||||
			Msg("Sending updates to nodes in namespacespace")
 | 
					 | 
				
			||||||
		h.setLastStateChangeToNow(namespace)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	newPendingUpdateValue, err := h.getValue("namespaces_pending_updates")
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if namespacesPendingUpdates == newPendingUpdateValue { // only clear when no changes, so we notified everybody
 | 
					 | 
				
			||||||
		err = h.setValue("namespaces_pending_updates", "")
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			log.Error().
 | 
					 | 
				
			||||||
				Str("func", "checkForNamespacesPendingUpdates").
 | 
					 | 
				
			||||||
				Err(err).
 | 
					 | 
				
			||||||
				Msg("Could not save to KV")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (n *Namespace) toUser() *tailcfg.User {
 | 
					func (n *Namespace) toUser() *tailcfg.User {
 | 
				
			||||||
	user := tailcfg.User{
 | 
						user := tailcfg.User{
 | 
				
			||||||
		ID:            tailcfg.UserID(n.ID),
 | 
							ID:            tailcfg.UserID(n.ID),
 | 
				
			||||||
 | 
				
			|||||||
@ -143,10 +143,5 @@ func (h *Headscale) EnableNodeRoute(
 | 
				
			|||||||
	machine.EnabledRoutes = datatypes.JSON(routes)
 | 
						machine.EnabledRoutes = datatypes.JSON(routes)
 | 
				
			||||||
	h.db.Save(&machine)
 | 
						h.db.Save(&machine)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err = h.RequestMapUpdates(machine.NamespaceID)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -67,11 +67,6 @@ func (h *Headscale) RemoveSharedMachineFromNamespace(
 | 
				
			|||||||
		return errMachineNotShared
 | 
							return errMachineNotShared
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err := h.RequestMapUpdates(namespace.ID)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user