Podman kube pod
- Added YAML for a Podman kube pod - Support configuring with environment variables
This commit is contained in:
parent
8a6a9e9380
commit
375d7fc0b7
41
README.md
41
README.md
|
@ -24,13 +24,11 @@ Binary:
|
||||||
```
|
```
|
||||||
Container:
|
Container:
|
||||||
```shell
|
```shell
|
||||||
podman run -it --rm -v hydroxide-config:/data ghcr.io/0ranki/hydroxide-push auth your.proton@email.address
|
podman run -it --rm -v hydroxide-push:/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.
|
You will be prompted for the Proton account credentials and the details for the push server. Proton credentials are stored encrypted form.
|
||||||
|
|
||||||
The auth flow generates a separate password for the bridge, which is stored in plaintext
|
The auth flow generates a separate password for the bridge to fake a login to the bridge, which is stored in plaintext to `$HOME/.config/notify.json`. Unlike upstream `hydroxide`, there is no service listening on any port, the password isn't useful for anything else.
|
||||||
to `$HOME/.config/notify.json`. Unlike upstream `hydroxide`, there is no service listening on any port,
|
|
||||||
all communications is internal to the program.
|
|
||||||
|
|
||||||
### Reconfigure push server
|
### Reconfigure push server
|
||||||
Binary:
|
Binary:
|
||||||
|
@ -39,12 +37,13 @@ hydroxide-push setup-ntfy
|
||||||
```
|
```
|
||||||
Container:
|
Container:
|
||||||
```shell
|
```shell
|
||||||
podman run -it --rm -v hydroxide-config:/data ghcr.io/0ranki/hydroxide-push setup-ntfy
|
podman run -it --rm -v hydroxide-push:/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
|
You'll be asked for the base URL of the push server, and the topic. The push endpoint configuration can be changed while the daemon is running.
|
||||||
be combined to a single string in future versions.
|
|
||||||
|
|
||||||
**NOTE:** Authentication for the push endpoint is not yet supported.
|
The currently configured values are shown inside braces. Leave input blank to use the current values.
|
||||||
|
|
||||||
|
>**NOTE:** Authentication for the push endpoint is not yet supported.
|
||||||
|
|
||||||
### Start the service
|
### Start the service
|
||||||
|
|
||||||
|
@ -54,8 +53,32 @@ hydroxide-push notify
|
||||||
```
|
```
|
||||||
Container:
|
Container:
|
||||||
```shell
|
```shell
|
||||||
podman run -it --rm -v hydroxide-config:/data ghcr.io/0ranki/hydroxide-push
|
podman run -it --rm -v hydroxide-push:/data ghcr.io/0ranki/hydroxide-push
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Podman pod
|
||||||
|
|
||||||
|
A Podman kube YAML file is provided in the repo.
|
||||||
|
|
||||||
|
> **Note:** If you're using 2FA or just don't want to put your password to a file, use the manual method above. Make sure the volume name (claimName) in the YAML mathces what you use in the commands.
|
||||||
|
|
||||||
|
- Download/copy `hydroxide-push-podman.yaml` to the machine you intend to run the daemon on
|
||||||
|
- Edit the config values at the top of the file
|
||||||
|
- Start the pod:
|
||||||
|
```shell
|
||||||
|
podman kube play ./hydroxide-push-podman.yaml
|
||||||
|
```
|
||||||
|
- Latest container image is pulled
|
||||||
|
- A named volume (`hydroxide-push`) will be created for the configuration
|
||||||
|
- Login to Proton and push URL configuration is handled automatically, after which the daemon starts
|
||||||
|
- After the initial setup, the ConfigMap (before `---`) can be removed from the YAML.). Optionally to clear the environment variables, run
|
||||||
|
|
||||||
|
```shell
|
||||||
|
podman kube play ./hydroxide-push-podman.yaml --replace
|
||||||
|
```
|
||||||
|
The command can also be used to pull the latest version and restart the pod.
|
||||||
|
- To reauthenticate or clear data, simply remove the named volume or run the `auth` command
|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
MIT
|
MIT
|
||||||
|
|
|
@ -74,11 +74,117 @@ func listenEventsAndNotify(addr string, debug bool, authManager *auth.Manager, e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func authenticate(authCmd *flag.FlagSet) {
|
||||||
|
var username string
|
||||||
|
if os.Getenv("PROTON_ACCT") != "" {
|
||||||
|
username = os.Getenv("PROTON_ACCT")
|
||||||
|
} else {
|
||||||
|
username = authCmd.Arg(0)
|
||||||
|
}
|
||||||
|
if username == "" {
|
||||||
|
log.Fatal("usage: hydroxide auth <username>")
|
||||||
|
}
|
||||||
|
|
||||||
|
c := newClient()
|
||||||
|
|
||||||
|
var a *protonmail.Auth
|
||||||
|
/*if cachedAuth, ok := auths[username]; ok {
|
||||||
|
var err error
|
||||||
|
a, err = c.AuthRefresh(a)
|
||||||
|
if err != nil {
|
||||||
|
// TODO: handle expired token error
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
var loginPassword string
|
||||||
|
if a == nil {
|
||||||
|
if os.Getenv("PROTON_ACCT_PASSWORD") != "" {
|
||||||
|
loginPassword = os.Getenv("PROTON_ACCT_PASSWORD")
|
||||||
|
} else if pass, err := askPass("Password"); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
} else {
|
||||||
|
loginPassword = string(pass)
|
||||||
|
}
|
||||||
|
|
||||||
|
authInfo, err := c.AuthInfo(username)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
a, err = c.Auth(username, loginPassword, authInfo)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.TwoFactor.Enabled != 0 {
|
||||||
|
if a.TwoFactor.TOTP != 1 {
|
||||||
|
log.Fatal("Only TOTP is supported as a 2FA method")
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(os.Stdin)
|
||||||
|
fmt.Printf("2FA TOTP code: ")
|
||||||
|
scanner.Scan()
|
||||||
|
code := scanner.Text()
|
||||||
|
|
||||||
|
scope, err := c.AuthTOTP(code)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
a.Scope = scope
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var mailboxPassword string
|
||||||
|
if a.PasswordMode == protonmail.PasswordSingle {
|
||||||
|
mailboxPassword = loginPassword
|
||||||
|
}
|
||||||
|
if mailboxPassword == "" {
|
||||||
|
prompt := "Password"
|
||||||
|
if a.PasswordMode == protonmail.PasswordTwo {
|
||||||
|
prompt = "Mailbox password"
|
||||||
|
}
|
||||||
|
if pass, err := askPass(prompt); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
} else {
|
||||||
|
mailboxPassword = string(pass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keySalts, err := c.ListKeySalts()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = c.Unlock(a, keySalts, mailboxPassword)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
secretKey, bridgePassword, err := auth.GeneratePassword()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = auth.EncryptAndSave(&auth.CachedAuth{
|
||||||
|
Auth: *a,
|
||||||
|
LoginPassword: loginPassword,
|
||||||
|
MailboxPassword: mailboxPassword,
|
||||||
|
KeySalts: keySalts,
|
||||||
|
}, username, secretKey)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
cfg.BridgePw = bridgePassword
|
||||||
|
cfg.Setup()
|
||||||
|
}
|
||||||
|
|
||||||
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
|
||||||
status View hydroxide status
|
status View hydroxide status
|
||||||
notify Start the notification daemon
|
notify Start the notification daemon
|
||||||
|
setup-ntfy (Re)configure the push endpoint
|
||||||
|
|
||||||
Global options:
|
Global options:
|
||||||
-debug
|
-debug
|
||||||
|
@ -123,101 +229,8 @@ func main() {
|
||||||
switch cmd {
|
switch cmd {
|
||||||
case "auth":
|
case "auth":
|
||||||
authCmd.Parse(flag.Args()[1:])
|
authCmd.Parse(flag.Args()[1:])
|
||||||
username := authCmd.Arg(0)
|
authenticate(authCmd)
|
||||||
if username == "" {
|
|
||||||
log.Fatal("usage: hydroxide auth <username>")
|
|
||||||
}
|
|
||||||
|
|
||||||
c := newClient()
|
|
||||||
|
|
||||||
var a *protonmail.Auth
|
|
||||||
/*if cachedAuth, ok := auths[username]; ok {
|
|
||||||
var err error
|
|
||||||
a, err = c.AuthRefresh(a)
|
|
||||||
if err != nil {
|
|
||||||
// TODO: handle expired token error
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
var loginPassword string
|
|
||||||
if a == nil {
|
|
||||||
if pass, err := askPass("Password"); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
} else {
|
|
||||||
loginPassword = string(pass)
|
|
||||||
}
|
|
||||||
|
|
||||||
authInfo, err := c.AuthInfo(username)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
a, err = c.Auth(username, loginPassword, authInfo)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.TwoFactor.Enabled != 0 {
|
|
||||||
if a.TwoFactor.TOTP != 1 {
|
|
||||||
log.Fatal("Only TOTP is supported as a 2FA method")
|
|
||||||
}
|
|
||||||
|
|
||||||
scanner := bufio.NewScanner(os.Stdin)
|
|
||||||
fmt.Printf("2FA TOTP code: ")
|
|
||||||
scanner.Scan()
|
|
||||||
code := scanner.Text()
|
|
||||||
|
|
||||||
scope, err := c.AuthTOTP(code)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
a.Scope = scope
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var mailboxPassword string
|
|
||||||
if a.PasswordMode == protonmail.PasswordSingle {
|
|
||||||
mailboxPassword = loginPassword
|
|
||||||
}
|
|
||||||
if mailboxPassword == "" {
|
|
||||||
prompt := "Password"
|
|
||||||
if a.PasswordMode == protonmail.PasswordTwo {
|
|
||||||
prompt = "Mailbox password"
|
|
||||||
}
|
|
||||||
if pass, err := askPass(prompt); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
} else {
|
|
||||||
mailboxPassword = string(pass)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
keySalts, err := c.ListKeySalts()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = c.Unlock(a, keySalts, mailboxPassword)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
secretKey, bridgePassword, err := auth.GeneratePassword()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = auth.EncryptAndSave(&auth.CachedAuth{
|
|
||||||
Auth: *a,
|
|
||||||
LoginPassword: loginPassword,
|
|
||||||
MailboxPassword: mailboxPassword,
|
|
||||||
KeySalts: keySalts,
|
|
||||||
}, username, secretKey)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
cfg.BridgePw = bridgePassword
|
|
||||||
cfg.Setup()
|
|
||||||
case "status":
|
case "status":
|
||||||
usernames, err := auth.ListUsernames()
|
usernames, err := auth.ListUsernames()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -235,7 +248,15 @@ func main() {
|
||||||
|
|
||||||
case "setup-ntfy":
|
case "setup-ntfy":
|
||||||
cfg.Setup()
|
cfg.Setup()
|
||||||
|
|
||||||
case "notify":
|
case "notify":
|
||||||
|
if os.Getenv("PROTON_ACCT_PASSWORD") != "" && os.Getenv("PROTON_ACCT") != "" && os.Getenv("PUSH_URL") != "" && os.Getenv("PUSH_TOPIC") != "" && cfg.BridgePw == "" {
|
||||||
|
log.Println("Logging in to Proton account using values from environment")
|
||||||
|
cfg.URL = os.Getenv("PUSH_URL")
|
||||||
|
cfg.Topic = os.Getenv("PUSH_TOPIC")
|
||||||
|
cfg.Save()
|
||||||
|
authenticate(new(flag.FlagSet))
|
||||||
|
}
|
||||||
authManager := auth.NewManager(newClient)
|
authManager := auth.NewManager(newClient)
|
||||||
eventsManager := events.NewManager()
|
eventsManager := events.NewManager()
|
||||||
listenEventsAndNotify("0", debug, authManager, eventsManager, tlsConfig)
|
listenEventsAndNotify("0", debug, authManager, eventsManager, tlsConfig)
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
## Remove this ConfigMap section after the initial run
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: hydroxide-push-config
|
||||||
|
data:
|
||||||
|
PROTON_ACCT: "my.account@protonmail.com"
|
||||||
|
PROTON_ACCT_PASSWORD: "myprotonaccountpassword"
|
||||||
|
PUSH_URL: "http://ntfy.sh"
|
||||||
|
PUSH_TOPIC: ""
|
||||||
|
## Remove the above after first run
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
creationTimestamp: "2024-03-27T07:02:59Z"
|
||||||
|
labels:
|
||||||
|
app: hydroxide-push
|
||||||
|
name: hydroxide-push
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- args:
|
||||||
|
- notify
|
||||||
|
image: ghcr.io/0ranki/hydroxide-push:latest
|
||||||
|
name: main
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /data
|
||||||
|
name: hydroxide-push-pvc
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: hydroxide-push-config
|
||||||
|
optional: true
|
||||||
|
volumes:
|
||||||
|
- name: hydroxide-push-pvc
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: hydroxide-push
|
||||||
|
|
16
ntfy/ntfy.go
16
ntfy/ntfy.go
|
@ -63,7 +63,7 @@ func ntfyConfigFile() (string, error) {
|
||||||
func Notify() {
|
func Notify() {
|
||||||
cfg := NtfyConfig{}
|
cfg := NtfyConfig{}
|
||||||
if err := cfg.Read(); err != nil {
|
if err := cfg.Read(); err != nil {
|
||||||
log.Printf("error reading notification: %v", err)
|
log.Printf("error reading configuration: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
req, _ := http.NewRequest("POST", cfg.URI(), strings.NewReader("New message received"))
|
req, _ := http.NewRequest("POST", cfg.URI(), strings.NewReader("New message received"))
|
||||||
|
@ -148,11 +148,25 @@ func Login(cfg *NtfyConfig, be backend.Backend) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *NtfyConfig) Setup() {
|
func (cfg *NtfyConfig) Setup() {
|
||||||
|
|
||||||
|
// Configure using environment
|
||||||
|
if os.Getenv("PUSH_URL") != "" && os.Getenv("PUSH_TOPIC") != "" {
|
||||||
|
cfg.URL = os.Getenv("PUSH_URL")
|
||||||
|
cfg.Topic = os.Getenv("PUSH_TOPIC")
|
||||||
|
log.Printf("Current push endpoint: %s\n", cfg.URI())
|
||||||
|
err := cfg.Save()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var n string
|
var n string
|
||||||
if cfg.URL != "" && cfg.Topic != "" {
|
if cfg.URL != "" && cfg.Topic != "" {
|
||||||
fmt.Printf("Current push endpoint: %s\n", cfg.URI())
|
fmt.Printf("Current push endpoint: %s\n", cfg.URI())
|
||||||
n = "new "
|
n = "new "
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read push base URL
|
// Read push base URL
|
||||||
notValid := true
|
notValid := true
|
||||||
scanner := bufio.NewScanner(os.Stdin)
|
scanner := bufio.NewScanner(os.Stdin)
|
||||||
|
|
Loading…
Reference in New Issue