Combine auth/setup flow, update readme
This commit is contained in:
parent
f66ce030cf
commit
14056ff639
109
README.md
109
README.md
|
@ -1,103 +1,50 @@
|
||||||
# hydroxide
|
# hydroxide-push
|
||||||
|
### *Forked from [Hydroxide](https://github.com/emersion/hydroxide)*
|
||||||
|
|
||||||
A third-party, open-source ProtonMail bridge. For power users only, designed to
|
## Push notifications for Proton Mail mobile via a UP provider
|
||||||
run on a server.
|
|
||||||
|
|
||||||
hydroxide supports CardDAV, IMAP and SMTP.
|
Protonmail depends on Google services to deliver push notifications,
|
||||||
|
This is a stripped down version of [Hydroxide](https://github.com/emersion/hydroxide)
|
||||||
Rationale:
|
to get notified of new mail. See original repo for details on operation.
|
||||||
|
|
||||||
* No GUI, only a CLI (so it runs in headless environments)
|
|
||||||
* Standard-compliant (we don't care about Microsoft Outlook)
|
|
||||||
* Fully open-source
|
|
||||||
|
|
||||||
Feel free to join the IRC channel: #emersion on Libera Chat.
|
|
||||||
|
|
||||||
## How does it work?
|
|
||||||
|
|
||||||
hydroxide is a server that translates standard protocols (SMTP, IMAP, CardDAV)
|
|
||||||
into ProtonMail API requests. It allows you to use your preferred e-mail clients
|
|
||||||
and `git-send-email` with ProtonMail.
|
|
||||||
|
|
||||||
+-----------------+ +-------------+ ProtonMail +--------------+
|
|
||||||
| | IMAP, SMTP | | API | |
|
|
||||||
| E-mail client <-------------> hydroxide <--------------> ProtonMail |
|
|
||||||
| | | | | |
|
|
||||||
+-----------------+ +-------------+ +--------------+
|
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
### Go
|
Download (soon), build the binary or the container image or pull the container image yourself.
|
||||||
|
Simplest way is to run the container image.
|
||||||
|
|
||||||
hydroxide is implemented in Go. Head to [Go website](https://golang.org) for
|
Login and push gateway details are saved under `$HOME/.config/hydroxide`. The container
|
||||||
setup information.
|
image saves configuration under `/data`, so mount a named volume or host directory there.
|
||||||
|
The examples below use a named volume.
|
||||||
|
|
||||||
### Installing
|
If using Docker, substitute `podman` with `docker` in the examples.
|
||||||
|
|
||||||
Start by installing hydroxide:
|
|
||||||
|
|
||||||
|
Binary:
|
||||||
```shell
|
```shell
|
||||||
git clone https://github.com/emersion/hydroxide.git
|
./hydroxide-push auth your.proton@email.address
|
||||||
go build ./cmd/hydroxide
|
|
||||||
```
|
```
|
||||||
|
Container:
|
||||||
Then you'll need to login to ProtonMail via hydroxide, so that hydroxide can
|
|
||||||
retrieve e-mails from ProtonMail. You can do so with this command:
|
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
hydroxide auth <username>
|
podman run -it --rm -v hydroxide-config:/data ghcr.io/0ranki/hydroxide-push auth your.proton@email.address
|
||||||
```
|
```
|
||||||
|
You will be prompted for the Proton account credentials and the details for the push server. Proton credentials are stored encrypted form.
|
||||||
|
|
||||||
Once you're logged in, a "bridge password" will be printed. Don't close your
|
The auth flow generates a separate password for the bridge, which is stored in plaintext
|
||||||
terminal yet, as this password is not stored anywhere by hydroxide and will be
|
to `$HOME/.config/notify.json`. Unlike upstream `hydroxide`, there is no service listening on any port,
|
||||||
needed when configuring your e-mail client.
|
all communications is internal to the program.
|
||||||
|
|
||||||
Your ProtonMail credentials are stored on disk encrypted with this bridge
|
|
||||||
password (a 32-byte random password generated when logging in).
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
hydroxide can be used in multiple modes.
|
|
||||||
|
|
||||||
> Don't start hydroxide multiple times, instead you can use `hydroxide serve`.
|
|
||||||
> This requires ports 1025 (smtp), 1143 (imap), and 8080 (carddav).
|
|
||||||
|
|
||||||
### SMTP
|
|
||||||
|
|
||||||
To run hydroxide as an SMTP server:
|
|
||||||
|
|
||||||
|
### Reconfigure push server
|
||||||
|
Binary:
|
||||||
```shell
|
```shell
|
||||||
hydroxide smtp
|
hydroxide-push setup-ntfy
|
||||||
```
|
```
|
||||||
|
Container:
|
||||||
Once the bridge is started, you can configure your e-mail client with the
|
|
||||||
following settings:
|
|
||||||
|
|
||||||
* Hostname: `localhost`
|
|
||||||
* Port: 1025
|
|
||||||
* Security: none
|
|
||||||
* Username: your ProtonMail username
|
|
||||||
* Password: the bridge password (not your ProtonMail password)
|
|
||||||
|
|
||||||
### CardDAV
|
|
||||||
|
|
||||||
You must setup an HTTPS reverse proxy to forward requests to `hydroxide`.
|
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
hydroxide carddav
|
podman run -it --rm -v hydroxide-config:/data ghcr.io/0ranki/hydroxide-push setup-ntfy
|
||||||
```
|
```
|
||||||
|
You'll be asked for the base URL of the push server, and the topic. These will probably
|
||||||
|
be combined to a single string in future versions.
|
||||||
|
|
||||||
Tested on GNOME (Evolution) and Android (DAVDroid).
|
**NOTE:** Authentication for the push endpoint is not yet supported.
|
||||||
|
|
||||||
### IMAP
|
|
||||||
|
|
||||||
⚠️ **Warning**: IMAP support is work-in-progress. Here be dragons.
|
|
||||||
|
|
||||||
For now, it only supports unencrypted local connections.
|
|
||||||
|
|
||||||
```shell
|
|
||||||
hydroxide imap
|
|
||||||
```
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|
|
@ -74,6 +74,38 @@ func listenEventsAndNotify(addr string, debug bool, authManager *auth.Manager, e
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setupNtfy() {
|
||||||
|
err := cfg.Read()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
var n string
|
||||||
|
if cfg.URL != "" && cfg.Topic != "" {
|
||||||
|
fmt.Printf("Current push endpoint: %s\n", cfg.String())
|
||||||
|
n = "new "
|
||||||
|
}
|
||||||
|
scanner := bufio.NewScanner(os.Stdin)
|
||||||
|
fmt.Printf("Input %spush server URL (e.g. 'http://ntfy.sh') : ", n)
|
||||||
|
scanner.Scan()
|
||||||
|
cfg.URL = scanner.Text()
|
||||||
|
scanner = bufio.NewScanner(os.Stdin)
|
||||||
|
fmt.Printf("Input push topic (e.g. my-proton-notifications): ")
|
||||||
|
scanner.Scan()
|
||||||
|
cfg.Topic = scanner.Text()
|
||||||
|
fmt.Printf("Using URL %s\n", cfg.String())
|
||||||
|
err = cfg.Save()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
err = ntfy.LoginBridge(&cfg)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Println("Notification configuration saved")
|
||||||
|
}
|
||||||
|
|
||||||
const usage = `usage: hydroxide-push [options...] <command>
|
const usage = `usage: hydroxide-push [options...] <command>
|
||||||
Commands:
|
Commands:
|
||||||
auth <username> Login to ProtonMail via hydroxide
|
auth <username> Login to ProtonMail via hydroxide
|
||||||
|
@ -211,8 +243,15 @@ func main() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
cfg.BridgePw = bridgePassword
|
||||||
fmt.Println("Bridge password:", bridgePassword)
|
reply, err := ntfy.AskToSaveBridgePw(&cfg)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if reply != "yes" {
|
||||||
|
fmt.Println("Bridge password:", bridgePassword)
|
||||||
|
}
|
||||||
|
setupNtfy()
|
||||||
case "status":
|
case "status":
|
||||||
usernames, err := auth.ListUsernames()
|
usernames, err := auth.ListUsernames()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -229,30 +268,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
case "setup-ntfy":
|
case "setup-ntfy":
|
||||||
err = cfg.Read()
|
setupNtfy()
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
var new string
|
|
||||||
if cfg.URL != "" && cfg.Topic != "" {
|
|
||||||
fmt.Printf("Current push endpoint: %s\n", cfg.String())
|
|
||||||
new = "new "
|
|
||||||
}
|
|
||||||
scanner := bufio.NewScanner(os.Stdin)
|
|
||||||
fmt.Printf("Input %spush server URL (e.g. 'http://ntfy.sh') : ", new)
|
|
||||||
scanner.Scan()
|
|
||||||
cfg.URL = scanner.Text()
|
|
||||||
scanner = bufio.NewScanner(os.Stdin)
|
|
||||||
fmt.Printf("Input push topic (e.g. my-proton-notifications): ")
|
|
||||||
scanner.Scan()
|
|
||||||
cfg.Topic = scanner.Text()
|
|
||||||
fmt.Printf("Using URL %s\n", cfg.String())
|
|
||||||
err = cfg.Save()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
fmt.Println("Notification configuration saved")
|
|
||||||
case "notify":
|
case "notify":
|
||||||
authManager := auth.NewManager(newClient)
|
authManager := auth.NewManager(newClient)
|
||||||
eventsManager := events.NewManager()
|
eventsManager := events.NewManager()
|
||||||
|
|
46
ntfy/ntfy.go
46
ntfy/ntfy.go
|
@ -3,6 +3,7 @@ package ntfy
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/0ranki/hydroxide-push/auth"
|
"github.com/0ranki/hydroxide-push/auth"
|
||||||
"github.com/0ranki/hydroxide-push/config"
|
"github.com/0ranki/hydroxide-push/config"
|
||||||
|
@ -63,6 +64,34 @@ func (cfg *NtfyConfig) Read() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func AskToSaveBridgePw(cfg *NtfyConfig) (string, error) {
|
||||||
|
scanner := bufio.NewScanner(os.Stdin)
|
||||||
|
//fmt.Printf("Save bridge password to config?\nThe password is stored in plain text, but (yes/n): ")
|
||||||
|
//scanner.Scan()
|
||||||
|
//if scanner.Text() == "yes" {
|
||||||
|
if err := cfg.Save(); err != nil {
|
||||||
|
return "", errors.New("failed to save notification config")
|
||||||
|
}
|
||||||
|
//}
|
||||||
|
return scanner.Text(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoginBridge(cfg *NtfyConfig) error {
|
||||||
|
if cfg.BridgePw == "" {
|
||||||
|
cfg.BridgePw = os.Getenv("HYDROXIDE_BRIDGE_PASSWORD")
|
||||||
|
}
|
||||||
|
if cfg.BridgePw == "" {
|
||||||
|
scanner := bufio.NewScanner(os.Stdin)
|
||||||
|
fmt.Printf("Bridge password: ")
|
||||||
|
scanner.Scan()
|
||||||
|
cfg.BridgePw = scanner.Text()
|
||||||
|
_, err := AskToSaveBridgePw(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
func Login(cfg *NtfyConfig, be backend.Backend) {
|
func Login(cfg *NtfyConfig, be backend.Backend) {
|
||||||
//time.Sleep(1 * time.Second)
|
//time.Sleep(1 * time.Second)
|
||||||
c, _ := net.ResolveIPAddr("ip", "127.0.0.1")
|
c, _ := net.ResolveIPAddr("ip", "127.0.0.1")
|
||||||
|
@ -88,20 +117,9 @@ func Login(cfg *NtfyConfig, be backend.Backend) {
|
||||||
log.Fatalln("then setup ntfy using " + executable + "setup-ntfy")
|
log.Fatalln("then setup ntfy using " + executable + "setup-ntfy")
|
||||||
}
|
}
|
||||||
if cfg.BridgePw == "" {
|
if cfg.BridgePw == "" {
|
||||||
cfg.BridgePw = os.Getenv("HYDROXIDE_BRIDGE_PASSWORD")
|
err = LoginBridge(cfg)
|
||||||
}
|
if err != nil {
|
||||||
if cfg.BridgePw == "" {
|
log.Fatal(err)
|
||||||
scanner := bufio.NewScanner(os.Stdin)
|
|
||||||
fmt.Printf("Bridge password: ")
|
|
||||||
scanner.Scan()
|
|
||||||
cfg.BridgePw = scanner.Text()
|
|
||||||
scanner = bufio.NewScanner(os.Stdin)
|
|
||||||
fmt.Printf("Save password to config? The password is stored in plain text! (yes/n): ")
|
|
||||||
scanner.Scan()
|
|
||||||
if scanner.Text() == "yes" {
|
|
||||||
if err = cfg.Save(); err != nil {
|
|
||||||
log.Fatal("failed to save notification config")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_, err = be.Login(&conn, usernames[0], cfg.BridgePw)
|
_, err = be.Login(&conn, usernames[0], cfg.BridgePw)
|
||||||
|
|
Loading…
Reference in New Issue