Skip to content

Commit

Permalink
Pull request 56: AGDNS-2244 Darwin service
Browse files Browse the repository at this point in the history
Updates #2.

Squashed commit of the following:

commit f403747
Author: Eugene Burkov <[email protected]>
Date:   Mon Jun 24 19:46:31 2024 +0300

    README: imp doc

commit 1c15e76
Author: Eugene Burkov <[email protected]>
Date:   Mon Jun 24 17:11:40 2024 +0300

    all: imp code

commit 8fcfd5e
Author: Eugene Burkov <[email protected]>
Date:   Mon Jun 24 16:45:59 2024 +0300

    all: imp docs

commit 40da1bc
Author: Eugene Burkov <[email protected]>
Date:   Mon Jun 24 16:39:21 2024 +0300

    all: imp docs

commit ff804fd
Author: Eugene Burkov <[email protected]>
Date:   Mon Jun 24 15:21:49 2024 +0300

    all: require darwin service exec path
  • Loading branch information
EugeneOne1 committed Jun 25, 2024
1 parent 97642d4 commit da3b068
Show file tree
Hide file tree
Showing 8 changed files with 83 additions and 8 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ See also the [v0.0.2 GitHub milestone][ms-v0.0.2].
NOTE: Add new changes BELOW THIS COMMENT.
-->

### Changed

- Path to the executable is now validated when the application installs itself as a `launchd` service on macOS ([#2]).

[#2]: https://github.com/AdguardTeam/AdGuardDNSClient/issues/2

<!--
NOTE: Add new changes ABOVE THIS COMMENT.
-->
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ Supported CPU architectures:

1. Download and unpack the `.tar.gz` or `.zip` archive from the [releases page][releases].

> [!WARNING]
> On macOS, it's crucial that globally installed daemons are owned by `root` (see the [`launchd` documentation][launchd-requirements]), so the `AdGuardDNSClient` executable must be placed in the `/Applications/` directory or its subdirectory.
2. Install it as a service by running:

```sh
Expand All @@ -58,6 +61,7 @@ To check that it works, use any DNS checking utility. For example, using `nslook
nslookup -debug 'www.example.com' '127.0.0.1'
```

[launchd-requirements]: https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html
[releases]: https://github.com/AdguardTeam/AdGuardDNSClient/releases

### <a href="#start-basic-win" id="start-basic-win" name="start-basic-win">Windows</a>
Expand Down
9 changes: 9 additions & 0 deletions internal/agdcos/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Package agdcos contains utilities for functions requiring system calls and
// other OS-specific APIs.
package agdcos

// ValidateExecPath returns an error if the path to the executable is not valid
// for the current platform.
func ValidateExecPath(execPath string) (err error) {
return validateExecPath(execPath)
}
38 changes: 38 additions & 0 deletions internal/agdcos/service_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//go:build darwin

package agdcos

import (
"fmt"
"path/filepath"
"strings"

"github.com/AdguardTeam/golibs/errors"
)

// errBadExecPath is returned when the executable is installed into an invalid
// location.
const errBadExecPath errors.Error = "bad executable path for service"

// validateExecPath returns an error if execPath is not a valid executable's
// location, i.e. is not within the /Applications directory.
//
// TODO(e.burkov): Consider allowing the executable to be installed in other
// directories owned by root.
func validateExecPath(execPath string) (err error) {
execPath, err = filepath.EvalSymlinks(execPath)
if err != nil {
return fmt.Errorf("evaluating executable path symlinks: %v", err)
}

execPath, err = filepath.Abs(execPath)
if err != nil {
return fmt.Errorf("getting absolute path of %q: %v", execPath, err)
}

if !strings.HasPrefix(execPath, "/Applications/") {
return errBadExecPath
}

return nil
}
8 changes: 8 additions & 0 deletions internal/agdcos/service_others.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//go:build !darwin

package agdcos

// validateExecPath is a no-op on non-Darwin platforms.
func validateExecPath(_ string) (err error) {
return nil
}
2 changes: 1 addition & 1 deletion internal/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
osservice "github.com/kardianos/service"
)

// Main is the entrypoint of AdGuardDNSClient. Main may accept arguments, such
// Main is the entrypoint of AdGuardDNS Client. Main may accept arguments, such
// as embedded assets and command-line arguments.
func Main() {
// TODO(a.garipov): Use for start cancelation.
Expand Down
23 changes: 16 additions & 7 deletions internal/cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"os"
"path/filepath"

"github.com/AdguardTeam/AdGuardDNSClient/internal/agdcos"
"github.com/AdguardTeam/golibs/errors"
"gopkg.in/yaml.v3"
)
Expand Down Expand Up @@ -32,27 +33,27 @@ type configuration struct {
// TODO(e.burkov): Make configurable via flags or environment.
const defaultConfigPath = "config.yaml"

// configPath return the default path to the configuration file. It assumes
// absolutePaths return the default path to the configuration file. It assumes
// that the configuration file is located in the same directory as the
// executable.
func configPath() (confPath string, err error) {
execPath, err := os.Executable()
func absolutePaths() (execPath, confPath string, err error) {
execPath, err = os.Executable()
if err != nil {
return "", fmt.Errorf("getting executable path: %w", err)
return "", "", fmt.Errorf("getting executable path: %w", err)
}

absExecPath, err := filepath.Abs(execPath)
if err != nil {
return "", fmt.Errorf("getting absolute path of %q: %w", execPath, err)
return "", "", fmt.Errorf("getting absolute path of %q: %w", execPath, err)
}

return filepath.Join(filepath.Dir(absExecPath), defaultConfigPath), nil
return absExecPath, filepath.Join(filepath.Dir(absExecPath), defaultConfigPath), nil
}

// handleServiceConfig returns the service configuration based on the specified
// [serviceAction].
func handleServiceConfig(action serviceAction) (conf *configuration, err error) {
confPath, err := configPath()
execPath, confPath, err := absolutePaths()
if err != nil {
// Don't wrap the error since it's informative enough as is.
return nil, err
Expand All @@ -72,6 +73,14 @@ func handleServiceConfig(action serviceAction) (conf *configuration, err error)
return nil, err
}
case serviceActionInstall:
err = agdcos.ValidateExecPath(execPath)
if err != nil {
_, _ = fmt.Fprintln(os.Stderr, "service executable must be located in the /Applications/ directory or its subdirectories")

// Don't wrap the error since it's informative enough as is.
return nil, err
}

err = writeDefaultConfig(confPath)
if err != nil {
// Don't wrap the error since it's informative enough as is.
Expand Down
1 change: 1 addition & 0 deletions scripts/make/go-lint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ underscores() {
-e '_test.go'\
-e '_unix.go'\
-e '_windows.go'\
-e '_others.go'\
-v\
| sed -e 's/./\t\0/'
)"
Expand Down

0 comments on commit da3b068

Please sign in to comment.