Podman kube pod

- Added YAML for a Podman kube pod
- Support configuring with environment variables
This commit is contained in:
Jarno Rankinen 2024-03-27 08:22:26 +00:00
parent 8a6a9e9380
commit 375d7fc0b7
4 changed files with 199 additions and 104 deletions

View File

@ -24,13 +24,11 @@ Binary:
```
Container:
```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.
The auth flow generates a separate password for the bridge, which is stored in plaintext
to `$HOME/.config/notify.json`. Unlike upstream `hydroxide`, there is no service listening on any port,
all communications is internal to the program.
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.
### Reconfigure push server
Binary:
@ -39,12 +37,13 @@ hydroxide-push setup-ntfy
```
Container:
```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
be combined to a single string in future versions.
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.
**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
@ -54,8 +53,32 @@ hydroxide-push notify
```
Container:
```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
MIT

View File

@ -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>
Commands:
auth <username> Login to ProtonMail via hydroxide
status View hydroxide status
notify Start the notification daemon
setup-ntfy (Re)configure the push endpoint
Global options:
-debug
@ -123,101 +229,8 @@ func main() {
switch cmd {
case "auth":
authCmd.Parse(flag.Args()[1:])
username := authCmd.Arg(0)
if username == "" {
log.Fatal("usage: hydroxide auth <username>")
}
authenticate(authCmd)
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":
usernames, err := auth.ListUsernames()
if err != nil {
@ -235,7 +248,15 @@ func main() {
case "setup-ntfy":
cfg.Setup()
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)
eventsManager := events.NewManager()
listenEventsAndNotify("0", debug, authManager, eventsManager, tlsConfig)

View File

@ -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

View File

@ -63,7 +63,7 @@ func ntfyConfigFile() (string, error) {
func Notify() {
cfg := NtfyConfig{}
if err := cfg.Read(); err != nil {
log.Printf("error reading notification: %v", err)
log.Printf("error reading configuration: %v", err)
return
}
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() {
// 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
if cfg.URL != "" && cfg.Topic != "" {
fmt.Printf("Current push endpoint: %s\n", cfg.URI())
n = "new "
}
// Read push base URL
notValid := true
scanner := bufio.NewScanner(os.Stdin)