From 2c431a9c9d915c890f4dd1ccd57350bd9305430d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?=
Date: Thu, 10 Oct 2024 08:07:24 +0200
Subject: [PATCH] update nix flakes, gh workflows and docs
---
.github/workflows/pre-release.yml | 51 +++
.github/workflows/tests.yml | 24 --
README.md | 476 ++++++++++++++++++++++-
config.sample.toml | 623 +++++++++++++++++++++++++++---
flake.lock | 18 +-
flake.nix | 284 +++++++++-----
rust-toolchain.nix | 27 +-
rust-toolchain.toml | 2 +-
8 files changed, 1288 insertions(+), 217 deletions(-)
create mode 100644 .github/workflows/pre-release.yml
delete mode 100644 .github/workflows/tests.yml
diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml
new file mode 100644
index 0000000..8137961
--- /dev/null
+++ b/.github/workflows/pre-release.yml
@@ -0,0 +1,51 @@
+name: pre-release
+
+on:
+ push:
+
+jobs:
+ pre-release:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - target: x86_64-linux
+ os: ubuntu-latest
+ - target: aarch64-linux
+ os: ubuntu-latest
+ - target: x86_64-windows
+ os: ubuntu-latest
+ - target: x86_64-darwin
+ os: macos-13
+ - target: aarch64-darwin
+ os: macos-14
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ - name: Install Nix
+ uses: cachix/install-nix-action@v27
+ with:
+ nix_path: nixpkgs=channel:nixos-24.05
+ enable_kvm: true
+ extra_nix_config: "experimental-features = nix-command flakes"
+ - name: Cache Nix store
+ uses: cachix/cachix-action@v15
+ with:
+ name: soywod
+ authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
+ extraPullNames: nix-community
+ - name: Build release
+ run: |
+ nix build -L .#${{ matrix.target }}
+ nix run -L .#${{ matrix.target }} -- --version
+ - name: Upload release artifact (tarball)
+ uses: actions/upload-artifact@v4
+ with:
+ name: "himalaya.${{ matrix.target }}.tgz"
+ path: result/himalaya.tgz
+ - name: Upload release artifact (zip)
+ uses: actions/upload-artifact@v4
+ with:
+ name: "himalaya.${{ matrix.target }}.zip"
+ path: result/himalaya.zip
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
deleted file mode 100644
index 57dc697..0000000
--- a/.github/workflows/tests.yml
+++ /dev/null
@@ -1,24 +0,0 @@
-name: tests
-
-on:
- pull_request:
- push:
-
-jobs:
- tests:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout code
- uses: actions/checkout@v2
- - name: Install Nix
- uses: cachix/install-nix-action@v26
- with:
- nix_path: nixpkgs=channel:nixos-23.11
- extra_nix_config: |
- experimental-features = nix-command flakes
- - uses: cachix/cachix-action@v12
- with:
- name: soywod
- authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- - name: Build then test
- run: nix build
diff --git a/README.md b/README.md
index b9edb5a..184fb11 100644
--- a/README.md
+++ b/README.md
@@ -7,19 +7,481 @@
-
- 🚧 Work In Progress, stay tuned! 🚧
-
+*🚧 Himalaya REPL is still experimental. For a more stable alternative, have a look at [Himalaya CLI](https://github.com/pimalaya/himalaya).*
+
+## Features
+
+- Multi-accounting
+- Interactive configuration via **wizard** (requires `wizard` feature)
+- Mailbox, envelope, message and flag management
+- Message composition based on `$EDITOR`
+- **IMAP** backend (requires `imap` feature)
+- **Maildir** backend (requires `maildir` feature)
+- **Notmuch** backend (requires `notmuch` feature)
+- **SMTP** backend (requires `smtp` feature)
+- **Sendmail** backend (requires `sendmail` feature)
+- Global system **keyring** for managing secrets (requires `keyring` feature)
+- **OAuth 2.0** authorization (requires `oauth2` feature)
+- **PGP** encryption:
+ - via shell commands (requires `pgp-commands` feature)
+ - via [GPG](https://www.gnupg.org/) bindings (requires `pgp-gpg` feature)
+ - via native implementation (requires `pgp-native` feature)
+
+*Himalaya REPL is written in [Rust](https://www.rust-lang.org/), and relies on [cargo features](https://doc.rust-lang.org/cargo/reference/features.html) to enable or disable functionalities. Default features can be found in the `features` section of the [`Cargo.toml`](https://github.com/pimalaya/himalaya-repl/blob/master/Cargo.toml#L18).*
+
+## Installation
+
+### Pre-built binary
+
+Himalaya REPL can be installed with a pre-built binary. Find the latest [`pre-release`](https://github.com/pimalaya/himalaya-repl/actions/workflows/pre-release.yml) GitHub workflow and look for the *Artifacts* section. You should find a pre-built binary matching your OS.
+
+### Cargo (git)
+
+Himalaya REPL can also be installed with [cargo](https://doc.rust-lang.org/cargo/):
+
+```bash
+$ cargo install --locked --git https://github.com/pimalaya/himalaya-repl.git
+```
+
+## Configuration
+
+Just run `himalaya-repl`, the wizard will help you to configure your default account.
+
+You can also manually edit your own configuration, from scratch:
+
+- Copy the content of the documented [`./config.sample.toml`](./config.sample.toml)
+- Paste it in a new file `~/.config/himalaya/config.toml`
+- Edit, then comment or uncomment the options you want
+
+
+ Proton Mail (Bridge)
+
+ When using Proton Bridge, emails are synchronized locally and exposed via a local IMAP/SMTP server. This implies 2 things:
+
+ - Id order may be reversed or shuffled, but envelopes will still be sorted by date.
+ - SSL/TLS needs to be deactivated manually.
+ - The password to use is the one generated by Proton Bridge, not the one from your Proton Mail account.
+
+ ```toml
+ [accounts.proton]
+ email = "example@proton.me"
+
+ backend = "imap"
+ imap.host = "127.0.0.1"
+ imap.port = 1143
+ imap.encryption = false
+ imap.login = "example@proton.me"
+ imap.auth.type = "password"
+ imap.auth.raw = ""
+
+ message.send.backend = "smtp"
+ smtp.host = "127.0.0.1"
+ smtp.port = 1025
+ smtp.encryption = false
+ smtp.login = "example@proton.me"
+ smtp.auth.type = "password"
+ smtp.auth.raw = ""
+ ```
+
+ Keeping your password inside the configuration file is good for testing purpose, but it is not safe. You have 2 better alternatives:
+
+ - Save your password in any password manager that can be queried via the CLI:
+
+ ```toml
+ imap.auth.type = "password"
+ imap.auth.cmd = "pass show proton"
+ ```
+
+ - Use the global keyring of your system (requires the `keyring` cargo feature):
+
+ ```toml
+ imap.auth.type = "password"
+ imap.auth.keyring = "proton-example"
+ ```
+
+ Running `himalaya configure -a proton` will ask for your IMAP password, just paste the one generated previously.
+
+
+
+ Gmail
+
+ Google passwords cannot be used directly. There is two ways to authenticate yourself:
+
+ ### Using [App Passwords](https://support.google.com/mail/answer/185833)
+
+ This option is the simplest and the fastest. First, be sure that:
+
+ - IMAP is enabled
+ - Two-step authentication is enabled
+ - Less secure app access is enabled
+
+ First create a [dedicated password](https://myaccount.google.com/apppasswords) for Himalaya.
+
+ ```toml
+ [accounts.gmail]
+ email = "example@gmail.com"
+
+ folder.alias.inbox = "INBOX"
+ folder.alias.sent = "[Gmail]/Sent Mail"
+ folder.alias.drafts = "[Gmail]/Drafts"
+ folder.alias.trash = "[Gmail]/Trash"
+
+ backend = "imap"
+ imap.host = "imap.gmail.com"
+ imap.port = 993
+ imap.login = "example@gmail.com"
+ imap.auth.type = "password"
+ imap.auth.cmd = "pass show gmail"
+
+ message.send.backend = "smtp"
+ smtp.host = "smtp.gmail.com"
+ smtp.port = 465
+ smtp.login = "example@gmail.com"
+ smtp.auth.type = "password"
+ smtp.auth.cmd = "pass show gmail"
+ ```
+
+ Keeping your password inside the configuration file is good for testing purpose, but it is not safe. You have 2 better alternatives:
+
+ - Save your password in any password manager that can be queried via the CLI:
+
+ ```toml
+ imap.auth.type = "password"
+ imap.auth.cmd = "pass show gmail"
+ ```
+
+ - Use the global keyring of your system (requires the `keyring` cargo feature):
+
+ ```toml
+ imap.auth.type = "password"
+ imap.auth.keyring = "gmail-example"
+ ```
+
+ Running `himalaya configure -a gmail` will ask for your IMAP password, just paste the one generated previously.
+
+ ### Using OAuth 2.0
+
+ This option is the most secure but the hardest to configure. It requires the `oauth2` and `keyring` cargo features.
+
+ First, you need to get your OAuth 2.0 credentials by following [this guide](https://developers.google.com/identity/protocols/oauth2#1.-obtain-oauth-2.0-credentials-from-the-dynamic_data.setvar.console_name-.). Once you get your client id and your client secret, you can configure your Himalaya account this way:
+
+ ```toml
+ [accounts.gmail]
+ email = "example@gmail.com"
+
+ folder.alias.inbox = "INBOX"
+ folder.alias.sent = "[Gmail]/Sent Mail"
+ folder.alias.drafts = "[Gmail]/Drafts"
+ folder.alias.trash = "[Gmail]/Trash"
+
+ backend = "imap"
+ imap.host = "imap.gmail.com"
+ imap.port = 993
+ imap.login = "example@gmail.com"
+ imap.auth.type = "oauth2"
+ imap.auth.client-id = ""
+ imap.auth.auth-url = "https://accounts.google.com/o/oauth2/v2/auth"
+ imap.auth.token-url = "https://www.googleapis.com/oauth2/v3/token"
+ imap.auth.pkce = true
+ imap.auth.scope = "https://mail.google.com/"
+
+ message.send.backend = "smtp"
+ smtp.host = "smtp.gmail.com"
+ smtp.port = 465
+ smtp.login = "example@gmail.com"
+ smtp.auth.type = "oauth2"
+ smtp.auth.client-id = ""
+ smtp.auth.auth-url = "https://accounts.google.com/o/oauth2/v2/auth"
+ smtp.auth.token-url = "https://www.googleapis.com/oauth2/v3/token"
+ smtp.auth.pkce = true
+ smtp.auth.scope = "https://mail.google.com/"
+
+ # If you want your SMTP to share the same client id (and so the same access token)
+ # as your IMAP config, you can add the following:
+ #
+ # imap.auth.type = "oauth2"
+ # imap.auth.client-id = ""
+ # imap.auth.client-secret.keyring = "gmail-oauth2-client-secret"
+ # imap.auth.access-token.keyring = "gmail-oauth2-access-token"
+ # imap.auth.refresh-token.keyring = "gmail-oauth2-refresh-token"
+ #
+ # smtp.auth.type = "oauth2"
+ # smtp.auth.client-id = ""
+ # smtp.auth.client-secret.keyring = "gmail-oauth2-client-secret"
+ # smtp.auth.access-token.keyring = "gmail-oauth2-access-token"
+ # smtp.auth.refresh-token.keyring = "gmail-oauth2-refresh-token"
+ ```
+
+ Running `himalaya configure -a gmail` will complete your OAuth 2.0 setup and ask for your client secret.
+
+
+
+ Outlook
+
+ ```toml
+ [accounts.outlook]
+ email = "example@outlook.com"
+
+ backend = "imap"
+ imap.host = "outlook.office365.com"
+ imap.port = 993
+ imap.login = "example@outlook.com"
+ imap.auth.type = "password"
+ imap.auth.cmd = "pass show outlook"
+
+ message.send.backend = "smtp"
+ smtp.host = "smtp.mail.outlook.com"
+ smtp.port = 587
+ smtp.encryption = "start-tls"
+ smtp.login = "example@outlook.com"
+ smtp.auth.type = "password"
+ smtp.auth.cmd = "pass show outlook"
+ ```
+
+ ### Using OAuth 2.0
+
+ This option is the most secure but the hardest to configure. First, you need to get your OAuth 2.0 credentials by following [this guide](https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth). Once you get your client id and your client secret, you can configure your Himalaya account this way:
+
+ ```toml
+ [accounts.outlook]
+ email = "example@outlook.com"
+
+ backend = "imap"
+ imap.host = "outlook.office365.com"
+ imap.port = 993
+ imap.login = "example@outlook.com"
+ imap.auth.type = "oauth2"
+ imap.auth.client-id = ""
+ imap.auth.auth-url = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"
+ imap.auth.token-url = "https://login.microsoftonline.com/common/oauth2/v2.0/token"
+ imap.auth.pkce = true
+ imap.auth.scope = "https://outlook.office.com/IMAP.AccessAsUser.All"
+
+ message.send.backend = "smtp"
+ smtp.host = "smtp.mail.outlook.com"
+ smtp.port = 587
+ smtp.starttls = true
+ smtp.login = "example@outlook.com"
+ smtp.auth.type = "oauth2"
+ smtp.auth.client-id = ""
+ smtp.auth.auth-url = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"
+ smtp.auth.token-url = "https://login.microsoftonline.com/common/oauth2/v2.0/token"
+ smtp.auth.pkce = true
+ smtp.auth.scope = "https://outlook.office.com/SMTP.Send"
+
+ # If you want your SMTP to share the same client id (and so the same access token)
+ # as your IMAP config, you can add the following:
+ #
+ # imap.auth.type = "oauth2"
+ # imap.auth.client-id = ""
+ # imap.auth.client-secret.keyring = "outlook-oauth2-client-secret"
+ # imap.auth.access-token.keyring = "outlook-oauth2-access-token"
+ # imap.auth.refresh-token.keyring = "outlook-oauth2-refresh-token"
+ #
+ # smtp.auth.type = "oauth2"
+ # smtp.auth.client-id = ""
+ # smtp.auth.client-secret.keyring = "outlook-oauth2-client-secret"
+ # smtp.auth.access-token.keyring = "outlook-oauth2-access-token"
+ # smtp.auth.refresh-token.keyring = "outlook-oauth2-refresh-token"
+ ```
+
+ Running `himalaya configure -a outlook` will complete your OAuth 2.0 setup and ask for your client secret.
+
+
+
+ iCloud Mail
+
+ From the [iCloud Mail](https://support.apple.com/en-us/HT202304) support page:
+
+ - IMAP port = `993`.
+ - IMAP login = name of your iCloud Mail email address (for example, `johnappleseed`, not `johnappleseed@icloud.com`)
+ - SMTP port = `587` with `STARTTLS`
+ - SMTP login = full iCloud Mail email address (for example, `johnappleseed@icloud.com`, not `johnappleseed`)
+
+ ```toml
+ [accounts.icloud]
+ email = "johnappleseed@icloud.com"
+
+ backend = "imap"
+ imap.host = "imap.mail.me.com"
+ imap.port = 993
+ imap.login = "johnappleseed"
+ imap.auth.type = "password"
+ imap.auth.cmd = "pass show icloud"
+
+ message.send.backend = "smtp"
+ smtp.host = "smtp.mail.me.com"
+ smtp.port = 587
+ smtp.encryption = "start-tls"
+ smtp.login = "johnappleseed@icloud.com"
+ smtp.auth.type = "password"
+ smtp.auth.cmd = "pass show icloud"
+ ```
+
+
+## FAQ
+
+
+ How to compose a message?
+
+ An email message is a list of **headers** (`key: val`) followed by a **body**. They form together a template:
+
+ ```eml
+ Header: value
+ Header: value
+ Header: value
+
+ Body
+ ```
+
+ ***Headers and body must be separated by an empty line.***
+
+ ### Headers
+
+ Here a non-exhaustive list of valid email message template headers:
+
+ - `Message-ID`: represents the message identifier (you usually do not need to set up it manually)
+ - `In-Reply-To`: represents the identifier of the replied message
+ - `Date`: represents the date of the message
+ - `Subject`: represents the subject of the message
+ - `From`: represents the address of the sender
+ - `To`: represents the addresses of the receivers
+ - `Reply-To`: represents the address the receiver should reply to instead of the `From` header
+ - `Cc`: represents the addresses of the other receivers (carbon copy)
+ - `Bcc`: represents the addresses of the other hidden receivers (blind carbon copy)
+
+ An address can be:
+
+ - a single email address `user@domain`
+ - a named address `Name `
+ - a quoted named address `"Name" `
+
+ Multiple address are separated by a coma `,`: `user@domain, Name , "Name" `.
+
+ ### Plain text body
+
+ Email message template body can be written in plain text. The result will be compiled into a single `text/plain` MIME part:
+
+ ```eml
+ From: alice@localhost
+ To: Bob
+ Subject: Hello from Himalaya
+
+ Hello, world!
+ ```
+
+ ### MML boby
+
+ Email message template body can also be written in MML. The MIME Meta Language was introduced by the Emacs [`mml`](https://www.gnu.org/software/emacs/manual/html_node/emacs-mime/Composing.html) ELisp module. Pimalaya [ported it](https://github.com/pimalaya/core/tree/master/mml) in Rust.
+
+ A raw email message is structured according to the [MIME](https://www.rfc-editor.org/rfc/rfc2045) standard. This standard produces verbose, non-friendly messages. Here comes MML: it simplifies the way email message body are structured. Thanks to its simple XML-based syntax, it allows you to easily add multiple parts, attach a binary file, or attach inline image to your body without dealing with the MIME standard.
+
+ For instance, this MML template:
+
+ ```eml
+ From: alice@localhost
+ To: bob@localhost
+ Subject: MML simple
+
+ <#multipart type=alternative>
+ This is a plain text part.
+ <#part type=text/enriched>
+ This is a centered enriched part
+ <#/multipart>
+ ```
+
+ compiles into the following MIME Message:
+
+ ```eml
+ Subject: MML simple
+ To: bob@localhost
+ From: alice@localhost
+ MIME-Version: 1.0
+ Date: Tue, 29 Nov 2022 13:07:01 +0000
+ Content-Type: multipart/alternative;
+ boundary="4CV1Cnp7mXkDyvb55i77DcNSkKzB8HJzaIT84qZe"
+
+ --4CV1Cnp7mXkDyvb55i77DcNSkKzB8HJzaIT84qZe
+ Content-Type: text/plain; charset=utf-8
+ Content-Transfer-Encoding: 7bit
+
+ This is a plain text part.
+ --4CV1Cnp7mXkDyvb55i77DcNSkKzB8HJzaIT84qZe
+ Content-Type: text/enriched
+ Content-Transfer-Encoding: 7bit
+
+ This is a centered enriched part
+ --4CV1Cnp7mXkDyvb55i77DcNSkKzB8HJzaIT84qZe--
+ ```
+
+ *See more examples at [pimalaya/core/mml](https://github.com/pimalaya/core/tree/master/mml/examples).*
+
+
+
+ How to add attachments to a message?
+
+ *Read first about the FAQ: How to compose a message?*.
+
+ ```eml
+ From: alice@localhost
+ To: bob@localhost
+ Subject: How to attach stuff
+
+ Regular binary attachment:
+ <#part filename=/path/to/file.pdf><#/part>
+
+ Custom file name:
+ <#part filename=/path/to/file.pdf name=custom.pdf><#/part>
+
+ Inline image:
+ <#part disposition=inline filename=/path/to/image.png><#/part>
+ ```
+
+ *See more examples at [pimalaya/core/mml](https://github.com/pimalaya/core/tree/master/mml/examples).*
+
+
+
+ How to debug Himalaya REPL?
+
+ The simplest way is to use `--debug` and `--trace` arguments.
+
+ The advanced way is based on environment variables:
+
+ - `RUST_LOG=`: determines the log level filter, can be one of `off`, `error`, `warn`, `info`, `debug` and `trace`.
+ - `RUST_SPANTRACE=1`: enables the spantrace (a span represent periods of time in which a program was executing in a particular context).
+ - `RUST_BACKTRACE=1`: enables the error backtrace.
+ - `RUST_BACKTRACE=full`: enables the full error backtrace, which include source lines where the error originated from.
+
+ Logs are written to the `stderr`, which means that you can redirect them easily to a file:
+
+ ```
+ RUST_LOG=debug himalaya 2>/tmp/himalaya.log
+ ```
+
+
+
+ How the wizard discovers IMAP/SMTP configs?
+
+ All the lookup mechanisms use the email address domain as base for the lookup. It is heavily inspired from the Thunderbird [Autoconfiguration](https://udn.realityripple.com/docs/Mozilla/Thunderbird/Autoconfiguration) protocol. For example, for the email address `test@example.com`, the lookup is performed as (in this order):
+
+ 1. check for `autoconfig.example.com`
+ 2. look up of `example.com` in the ISPDB (the Thunderbird central database)
+ 3. look up `MX example.com` in DNS, and for `mx1.mail.hoster.com`, look up `hoster.com` in the ISPDB
+ 4. look up `SRV example.com` in DNS
+ 5. try to guess (`imap.example.com`, `smtp.example.com`…)
+
+
## Sponsoring
-[![nlnet](https://nlnet.nl/logo/banner-160x60.png)](https://nlnet.nl/project/Himalaya/index.html)
+[![nlnet](https://nlnet.nl/logo/banner-160x60.png)](https://nlnet.nl/)
-Special thanks to the [NLnet foundation](https://nlnet.nl/project/Himalaya/index.html) and the [European Commission](https://www.ngi.eu/) that helped the project to receive financial support from:
+Special thanks to the [NLnet foundation](https://nlnet.nl/) that helped the project to receive financial support from various programs of the [European Commission](https://www.ngi.eu/):
-- [NGI Assure](https://nlnet.nl/assure/) in 2022
-- [NGI Zero Entrust](https://nlnet.nl/entrust/) in 2023
+- [NGI Assure](https://nlnet.nl/project/Himalaya/) in 2022-2023
+- [NGI Zero Entrust](https://nlnet.nl/project/Pimalaya/) in 2023-2024
+- [NGI Zero Core](https://nlnet.nl/project/Pimalaya-PIM/) in 2024-2025
If you appreciate the project, feel free to donate using one of the following providers:
diff --git a/config.sample.toml b/config.sample.toml
index ca1b709..b87c562 100644
--- a/config.sample.toml
+++ b/config.sample.toml
@@ -1,98 +1,603 @@
-# The account name.
+################################################################################
+#### Global configuration ######################################################
+################################################################################
+
+# Default display name for all accounts. It is used to build the full
+# email address of an account: "Example"
+#
+display-name = "Example"
+
+# Default signature for all accounts. The signature is put at the
+# bottom of all messages. It can be a path or a string. Supports TOML
+# multilines.
+#
+# signature = "/path/to/signature/file"
+# signature = """
+# Thanks you,
+# Regards
+# """
+signature = "Regards,\n"
+
+# Default signature delimiter for all accounts. It delimits the end of
+# the message body from the signature.
+#
+signature-delim = "-- \n"
+
+# Default downloads directory path for all accounts. It is mostly used
+# for downloading attachments. Defaults to the system temporary
+# directory.
+#
+downloads-dir = "~/downloads"
+
+# Customizes the charset used to build the table. Defaults to markdown
+# table style.
+#
+# See .
+#
+account.list.table.preset = "|| |-||| "
+
+# Customizes the color of the NAME column of the account listing
+# table.
+#
+account.list.table.name-color = "green"
+
+# Customizes the color of the BACKENDS column of the account listing
+# table.
+#
+account.list.table.backends-color = "blue"
+
+# Customizes the color of the DEFAULT column of the account listing
+# table.
+#
+account.list.table.default-color = "reset"
+
+# Customizes the keybindings of command prompts.
+#
+# It does not yet apply to interactive prompts like folder selection,
+# but it should in the future.
+#
+#repl.keybinds = "vi"
+repl.keybinds = "emacs"
+
+################################################################################
+#### Account configuration #####################################################
+################################################################################
+
[accounts.example]
-# The current account will be used by default for all other commands.
+# Defaultness of the account. The current account will be used by
+# default in all commands.
+#
default = true
-# The display-name and the email are used to build the full email
-# address: "My example account"
-display-name = "My example account"
+# The email address associated to the current account.
+#
email = "example@localhost"
-# The signature can be a string or a path to a file.
-signature = "Regards,"
-signature-delim = "-- \n"
+# The display name of the account. This and the email are used to
+# build the full email address: "Example"
+#
+display-name = "Example"
-# Enable the synchronization for this account. Running the command
-# `account sync example` will synchronize all folders and all emails
-# to a local Maildir at `$XDG_DATA_HOME/himalaya/example`.
-sync.enable = false
+# The signature put at the bottom of composed messages. It can be a
+# path or a string. Supports TOML multilines.
+#
+#signature = "/path/to/signature/file"
+#signature = """
+# Thanks you,
+# Regards
+#"""
+signature = "Regards,\n"
-# Override the default Maildir path for synchronization.
-sync.dir = "/tmp/himalaya-sync-example"
+# Signature delimiter. It delimits the end of the message body from
+# the signature.
+#
+signature-delim = "-- \n"
+
+# Downloads directory path. It is mostly used for downloading
+# attachments. Defaults to the system temporary directory.
+downloads-dir = "~/downloads"
-# Filter folders to sync
-folder.sync.filter.include = ["INBOX"]
-# folder.sync.filter.exclude = ["All mails"]
-# folder.sync.filter = "all"
+########################################
+#### Folder configuration ##############
+########################################
-# Define main folder aliases
+# Defines aliases for your mailboxes. There are 4 special aliases used
+# by the tool: inbox, sent, drafts and trash. Other aliases can be
+# defined as well.
+#
folder.alias.inbox = "INBOX"
folder.alias.sent = "Sent"
folder.alias.drafts = "Drafts"
folder.alias.trash = "Trash"
+folder.alias.a23 = "Archives/2023"
-# Also define custom folder aliases
-folder.alias.prev-year = "Archives/2023"
+# Customizes the number of folders to show by page.
+#
+folder.list.page-size = 10
-# Default backend used for all the features like adding folders,
-# listing envelopes or copying messages.
-backend = "imap"
+# Customizes the charset used to build the table. Defaults to markdown
+# table style.
+#
+# See .
+#
+folder.list.table.preset = "|| |-||| "
+
+# Customizes the color of the NAME column of the folder listing table.
+#
+folder.list.table.name-color = "blue"
+# Customizes the color of the DESC column of the folder listing table.
+#
+folder.list.table.desc-color = "green"
+
+########################################
+#### Envelope configuration ############
+########################################
+
+# Customizes the number of envelopes to show by page.
+#
envelope.list.page-size = 10
+
+# Customizes the format of the envelope date.
+#
+# See supported formats at .
+#
envelope.list.datetime-fmt = "%F %R%:z"
-# Date are converted to the user's local timezone.
+# Transforms envelopes date timezone into the user's local one. For
+# example, if the user's local timezone is UTC, the envelope date
+# `2023-06-15T09:00:00+02:00` becomes `2023-06-15T07:00:00-00:00`.
+#
envelope.list.datetime-local-tz = true
-# Override the backend used for listing envelopes.
-# envelope.list.backend = "imap"
+# Customizes the charset used to build the table. Defaults to markdown
+# table style.
+#
+# See .
+#
+envelope.list.table.preset = "|| |-||| "
-# Send notification on receiving new envelopes
-envelope.watch.received.notify.summary = "📬 New message from {sender}"
+# Customizes the character of the unseen flag of the envelope listing
+# table.
+#
+envelope.list.table.unseen-char = "*"
-# Available placeholders: id, subject, sender, sender.name,
-# sender.address, recipient, recipient.name, recipient.address.
-envelope.watch.received.notify.body = "{subject}"
+# Customizes the character of the replied flag of the envelope listing
+# table.
+#
+envelope.list.table.replied-char = "R"
-# Shell commands can also be executed when envelopes change
-# envelope.watch.any.cmd = "mbsync -a"
+# Customizes the character of the flagged flag of the envelope listing
+# table.
+#
+envelope.list.table.flagged-char = "!"
-# Override the backend used for sending messages.
-message.send.backend = "smtp"
+# Customizes the character of the attachment property of the envelope
+# listing table.
+#
+envelope.list.table.attachment-char = "@"
+
+# Customizes the color of the ID column of the envelope listing table.
+#
+envelope.list.table.id-color = "red"
+
+# Customizes the color of the FLAGS column of the envelope listing
+# table.
+#
+envelope.list.table.flags-color = "reset"
+
+# Customizes the color of the SUBJECT column of the envelope listing
+# table.
+#
+envelope.list.table.subject-color = "green"
+
+# Customizes the color of the SENDER column of the envelope listing
+# table.
+#
+envelope.list.table.sender-color = "blue"
+
+# Customizes the color of the DATE column of the envelope listing
+# table.
+#
+envelope.list.table.date-color = "yellow"
+
+########################################
+#### Message configuration #############
+########################################
+
+# Defines headers to show at the top of messages when reading them.
+#
+message.read.headers = ["From", "To", "Cc", "Subject"]
+
+# Represents the message text/plain format as defined in the
+# RFC2646.
+#
+# See .
+#
+#message.read.format.fixed = 80
+#message.read.format = "flowed"
+message.read.format = "auto"
+
+# Defines headers to show at the top of messages when writing them.
+#
+message.write.headers = ["From", "To", "In-Reply-To", "Cc", "Subject"]
+
+# Saves a copy of sent messages to the sent folder. The sent folder is
+# taken from folder.alias, defaults to Sent.
+#
+message.send.save-copy = true
+
+# Hook called just before sending a message. The command should take a
+# raw message as standard input (stdin) and returns the modified raw
+# message to the standard output (stdout).
+#
+message.send.pre-hook = "process-markdown.sh"
+
+# Customizes the message deletion style. Message deletion can be
+# performed either by moving messages to the Trash folder or by adding
+# the Deleted flag to their respective envelopes.
+#
+#message.delete.style = "flag"
+message.delete.style = "folder"
-# Save a copy of sent messages to the sent folder.
-message.send.save-copy = false
+########################################
+#### Template configuration ############
+########################################
-# IMAP config
+# Defines how and where the signature should be displayed when writing
+# a new message.
+#
+#template.new.signature-style = "hidden"
+#template.new.signature-style = "attached"
+template.new.signature-style = "inlined"
+
+# Defines the posting style when replying to a message.
+#
+# See .
+#
+#template.reply.posting-style = "interleaved"
+#template.reply.posting-style = "bottom"
+template.reply.posting-style = "top"
+
+# Defines how and where the signature should be displayed when
+# repyling to a message.
+#
+#template.reply.signature-style = "hidden"
+#template.reply.signature-style = "attached"
+#template.reply.signature-style = "above-quote"
+template.reply.signature-style = "below-quote"
+
+# Defines the headline format put at the top of a quote when replying
+# to a message.
+#
+# Available placeholders: {senders}
+# See supported date formats at .
+#
+template.reply.quote-headline-fmt = "On %d/%m/%Y %H:%M, {senders} wrote:\n"
+
+# Defines the posting style when forwarding a message.
+#
+# See .
+#
+#template.forward.posting-style = "attached"
+template.forward.posting-style = "top"
+
+# Defines how and where the signature should be displayed when
+# forwarding a message.
+#
+#template.forward.signature-style = "hidden"
+#template.forward.signature-style = "attached"
+template.forward.signature-style = "inlined"
+
+# Defines the headline format put at the top of the quote when
+# forwarding a message.
+#
+template.forward.quote-headline = "-------- Forwarded Message --------\n"
+
+########################################
+#### PGP configuration #################
+########################################
+
+# TODO
+#pgp.backend = "commands"
+#pgp.backend = "gpg"
+#pgp.backend = "native"
+
+########################################
+#### IMAP configuration ################
+########################################
+
+# Defines the IMAP backend as the default one for all features.
+#
+backend = "imap"
+
+# IMAP server host name.
+#
imap.host = "localhost"
-imap.port = 3143
+
+# IMAP server port.
+#
+#imap.port = 143
+imap.port = 993
+
+# IMAP server encryption.
+#
+#imap.encryption = "none" # or false
+#imap.encryption = "start-tls"
+imap.encryption = "tls" # or true
+
+# IMAP server login.
+#
imap.login = "example@localhost"
-# Encryption can be either "tls" (or true), "start-tls" or "none" (or false).
-imap.encryption = "none"
+# IMAP server password authentication configuration.
+#
+imap.auth.type = "password"
+#
+# Password can be inlined (not recommended).
+#
+#imap.auth.raw = "p@assw0rd"
+#
+# Password can be stored inside your system global keyring (requires
+# the keyring cargo feature). You must run at least once `himalaya
+# account configure` to set up the password.
+#
+#imap.auth.keyring = "example-imap"
+#
+# Password can be retrieved from a shell command.
+#
+imap.auth.cmd = "pass show example-imap"
+
+# IMAP server OAuth 2.0 authorization configuration.
+#
+#imap.auth.type = "oauth2"
+#
+# Client identifier issued to the client during the registration
+# process described in RFC6749.
+# See .
+#
+#imap.auth.client-id = "client-id"
+#
+# Client password issued to the client during the registration process
+# described in RFC6749.
+#
+# Defaults to keyring "-imap-client-secret".
+# See .
+#
+#imap.auth.client-secret.raw = ""
+#imap.auth.client-secret.keyring = "example-imap-client-secret"
+#imap.auth.client-secret.cmd = "pass show example-imap-client-secret"
+#
+# Method for presenting an OAuth 2.0 bearer token to a service for
+# authentication.
+#
+#imap.auth.method = "oauthbearer"
+#imap.auth.method = "xoauth2"
+#
+# URL of the authorization server's authorization endpoint.
+#
+#imap.auth.auth-url = "https://accounts.google.com/o/oauth2/v2/auth"
+#
+# URL of the authorization server's token endpoint.
+#
+#imap.auth.token-url = "https://www.googleapis.com/oauth2/v3/token"
+#
+# Access token returned by the token endpoint and used to access
+# protected resources. It is recommended to use the keyring variant,
+# as it will refresh automatically.
+#
+# Defaults to keyring "-imap-access-token".
+#
+#imap.auth.access-token.raw = ""
+#imap.auth.access-token.keyring = "example-imap-access-token"
+#imap.auth.access-token.cmd = "pass show example-imap-access-token"
+#
+# Refresh token used to obtain a new access token (if supported by the
+# authorization server). It is recommended to use the keyring variant,
+# as it will refresh automatically.
+#
+# Defaults to keyring "-imap-refresh-token".
+#
+#imap.auth.refresh-token.raw = ""
+#imap.auth.refresh-token.keyring = "example-imap-refresh-token"
+#imap.auth.refresh-token.cmd = "pass show example-imap-refresh-token"
+#
+# Enable the protection, as defined in RFC7636.
+#
+# See .
+#
+#imap.auth.pkce = true
+#
+# Access token scope(s), as defined by the authorization server.
+#
+#imap.auth.scope = "unique scope"
+#imap.auth.scopes = ["multiple", "scopes"]
+#
+# Host name of the redirect server.
+# Defaults to localhost.
+#
+#imap.auth.redirect-host = "localhost"
+#
+# Port of the redirect server.
+# Defaults to the first available one.
+#
+#imap.auth.redirect-port = 9999
-# Get password from a raw string (not safe)
-imap.passwd.raw = "password"
+########################################
+#### Maildir configuration #############
+########################################
-# Get password from a shell command
-# imap.passwd.cmd = "echo password"
+# Defines the Maildir backend as the default one for all features.
+#
+#backend = "maildir"
-# Get password from your global system keyring using secret service
-# Keyring secrets can be (re)set with the command `account configure example`
-# imap.passwd.keyring = "example-imap-password"
+# The Maildir root directory. The path should point to the root level
+# of the Maildir directory.
+#
+#maildir.root-dir = "~/.Mail/example"
-# Customize at which period, in seconds, the IMAP IDLE mode should refresh.
-# Defaults to 1740 (29 min), as defined in the RFC.
-# imap.watch.timeout = 25
+# Does the Maildir folder follows the Maildir++ standard?
+#
+# See .
+#
+#maildir.maildirpp = false
-# SMTP config
+########################################
+#### Notmuch configuration #############
+########################################
+
+# Defines the Notmuch backend as the default one for all features.
+#
+#backend = "notmuch"
+
+# The path to the Notmuch database. The path should point to the root
+# directory containing the Notmuch database (usually the root Maildir
+# directory).
+#
+#notmuch.db-path = "~/.Mail/example"
+
+# Overrides the default path to the Maildir folder.
+#
+#notmuch.maildir-path = "~/.Mail/example"
+
+# Overrides the default Notmuch configuration file path.
+#
+#notmuch.config-path = "~/.notmuchrc"
+
+# Override the default Notmuch profile name.
+#
+#notmuch.profile = "example"
+
+########################################
+#### SMTP configuration ################
+########################################
+
+# Defines the SMTP backend for the message sending feature.
+#
+message.send.backend = "smtp"
+
+# SMTP server host name.
+#
smtp.host = "localhost"
-smtp.port = 3025
+
+# SMTP server port.
+#
+#smtp.port = 25
+#smtp.port = 465
+smtp.port = 587
+
+# SMTP server encryption.
+#
+#smtp.encryption = "none" # or false
+#smtp.encryption = "start-tls"
+smtp.encryption = "tls" # or true
+
+# SMTP server login.
+#
smtp.login = "example@localhost"
-smtp.encryption = false
-smtp.passwd.raw = "password"
-# PGP needs to be enabled with one of those cargo feature:
-# pgp-commands, pgp-gpg or pgp-native
-# pgp.backend = "gpg"
+# SMTP server password authentication configuration.
+#
+smtp.auth.type = "password"
+#
+# Password can be inlined (not recommended).
+#
+#smtp.auth.raw = "p@assw0rd"
+#
+# Password can be stored inside your system global keyring (requires
+# the keyring cargo feature). You must run at least once `himalaya
+# account configure` to set up the password.
+#
+#smtp.auth.keyring = "example-smtp"
+#
+# Password can be retrieved from a shell command.
+#
+smtp.auth.cmd = "pass show example-smtp"
+
+# SMTP server OAuth 2.0 authorization configuration.
+#
+#smtp.auth.type = "oauth2"
+#
+# Client identifier issued to the client during the registration
+# process described in RFC6749.
+# See .
+#
+#smtp.auth.client-id = "client-id"
+#
+# Client password issued to the client during the registration process
+# described in RFC6749.
+#
+# Defaults to keyring "-smtp-client-secret".
+# See .
+#
+#smtp.auth.client-secret.raw = ""
+#smtp.auth.client-secret.keyring = "example-smtp-client-secret"
+#smtp.auth.client-secret.cmd = "pass show example-smtp-client-secret"
+#
+# Method for presenting an OAuth 2.0 bearer token to a service for
+# authentication.
+#
+#smtp.auth.method = "oauthbearer"
+#smtp.auth.method = "xoauth2"
+#
+# URL of the authorization server's authorization endpoint.
+#
+#smtp.auth.auth-url = "https://accounts.google.com/o/oauth2/v2/auth"
+#
+# URL of the authorization server's token endpoint.
+#
+#smtp.auth.token-url = "https://www.googleapis.com/oauth2/v3/token"
+#
+# Access token returned by the token endpoint and used to access
+# protected resources. It is recommended to use the keyring variant,
+# as it will refresh automatically.
+#
+# Defaults to keyring "-smtp-access-token".
+#
+#smtp.auth.access-token.raw = ""
+#smtp.auth.access-token.keyring = "example-smtp-access-token"
+#smtp.auth.access-token.cmd = "pass show example-smtp-access-token"
+#
+# Refresh token used to obtain a new access token (if supported by the
+# authorization server). It is recommended to use the keyring variant,
+# as it will refresh automatically.
+#
+# Defaults to keyring "-smtp-refresh-token".
+#
+#smtp.auth.refresh-token.raw = ""
+#smtp.auth.refresh-token.keyring = "example-smtp-refresh-token"
+#smtp.auth.refresh-token.cmd = "pass show example-smtp-refresh-token"
+#
+# Enable the protection, as defined in RFC7636.
+#
+# See .
+#
+#smtp.auth.pkce = true
+#
+# Access token scope(s), as defined by the authorization server.
+#
+#smtp.auth.scope = "unique scope"
+#smtp.auth.scopes = ["multiple", "scopes"]
+#
+# Host name of the redirect server.
+# Defaults to localhost.
+#
+#smtp.auth.redirect-host = "localhost"
+#
+# Port of the redirect server.
+# Defaults to the first available one.
+#
+#smtp.auth.redirect-port = 9999
+
+########################################
+#### Sendmail configuration ############
+########################################
+
+# Defines the Sendmail backend for the message sending feature.
+#
+#message.send.backend = "sendmail"
+
+# Customizes the sendmail shell command.
+#
+#sendmail.cmd = "/usr/bin/sendmail"
diff --git a/flake.lock b/flake.lock
index 06ce42e..5a4deaf 100644
--- a/flake.lock
+++ b/flake.lock
@@ -8,15 +8,15 @@
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
- "lastModified": 1708842026,
- "narHash": "sha256-1r+7l66CFPCjRDZEKq6r7aCpfjqhIkrF3ZTQyEFnkTs=",
- "owner": "nix-community",
+ "lastModified": 1713081044,
+ "narHash": "sha256-ZwbJDrizU+nzU7wTgokYuu5yK71wLPmOLukiunm5B6Y=",
+ "owner": "soywod",
"repo": "fenix",
- "rev": "b7fff64b5bc36b1662f8317632b11b15b3839b2a",
+ "rev": "af99e7e9c87389c0a5aaf953478664d7126c2b14",
"type": "github"
},
"original": {
- "owner": "nix-community",
+ "owner": "soywod",
"repo": "fenix",
"type": "github"
}
@@ -79,16 +79,16 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1708702655,
- "narHash": "sha256-qxT5jSLhelfLhQ07+AUxSTm1VnVH+hQxDkQSZ/m/Smo=",
+ "lastModified": 1728328465,
+ "narHash": "sha256-a0a0M1TmXMK34y3M0cugsmpJ4FJPT/xsblhpiiX1CXo=",
"owner": "nixos",
"repo": "nixpkgs",
- "rev": "c5101e457206dd437330d283d6626944e28794b3",
+ "rev": "1bfbbbe5bbf888d675397c66bfdb275d0b99361c",
"type": "github"
},
"original": {
"owner": "nixos",
- "ref": "nixos-23.11",
+ "ref": "nixos-24.05",
"repo": "nixpkgs",
"type": "github"
}
diff --git a/flake.nix b/flake.nix
index 64e442f..cac2f28 100644
--- a/flake.nix
+++ b/flake.nix
@@ -2,13 +2,15 @@
description = "REPL to manage emails";
inputs = {
- nixpkgs.url = "github:nixos/nixpkgs/nixos-23.11";
+ nixpkgs.url = "github:nixos/nixpkgs/nixos-24.05";
gitignore = {
url = "github:hercules-ci/gitignore.nix";
inputs.nixpkgs.follows = "nixpkgs";
};
fenix = {
- url = "github:nix-community/fenix";
+ # https://github.com/nix-community/fenix/pull/145
+ # url = "github:nix-community/fenix";
+ url = "github:soywod/fenix";
inputs.nixpkgs.follows = "nixpkgs";
};
naersk = {
@@ -23,132 +25,210 @@
outputs = { self, nixpkgs, gitignore, fenix, naersk, ... }:
let
+ inherit (nixpkgs) lib;
inherit (gitignore.lib) gitignoreSource;
- mkToolchain = import ./rust-toolchain.nix fenix;
+ crossSystems = {
+ x86_64-linux = {
+ x86_64-linux = {
+ rustTarget = "x86_64-unknown-linux-musl";
+ };
- mkDevShells = buildPlatform:
- let
- pkgs = import nixpkgs { system = buildPlatform; };
- rust-toolchain = mkToolchain.fromFile { system = buildPlatform; };
- in
- {
- default = pkgs.mkShell {
- nativeBuildInputs = with pkgs; [
- pkg-config
- ];
- buildInputs = with pkgs; [
- # Nix
- rnix-lsp
- nixpkgs-fmt
+ aarch64-linux = rec {
+ rustTarget = "aarch64-unknown-linux-musl";
+ runner = { pkgs, himalaya }: "${pkgs.qemu}/bin/qemu-aarch64 ${himalaya}";
+ mkPackage = { system, pkgs }: package:
+ let
+ inherit (mkPkgsCross system rustTarget) stdenv;
+ cc = "${stdenv.cc}/bin/${stdenv.cc.targetPrefix}cc";
+ in
+ package // {
+ TARGET_CC = cc;
+ CARGO_BUILD_RUSTFLAGS = package.CARGO_BUILD_RUSTFLAGS ++ [ "-Clinker=${cc}" ];
+ };
+ };
- # Rust
- rust-toolchain
- cargo-watch
+ x86_64-windows = {
+ rustTarget = "x86_64-pc-windows-gnu";
+ runner = { pkgs, himalaya }:
+ let wine = pkgs.wine.override { wineBuild = "wine64"; };
+ in "${wine}/bin/wine64 ${himalaya}.exe";
+ mkPackage = { system, pkgs }: package:
+ let
+ inherit (pkgs.pkgsCross.mingwW64) stdenv windows;
+ cc = "${stdenv.cc}/bin/${stdenv.cc.targetPrefix}cc";
+ in
+ package // {
+ depsBuildBuild = [ stdenv.cc windows.pthreads ];
+ TARGET_CC = cc;
+ CARGO_BUILD_RUSTFLAGS = package.CARGO_BUILD_RUSTFLAGS ++ [ "-Clinker=${cc}" ];
+ };
+ };
+ };
+
+ aarch64-linux = {
+ aarch64-linux = {
+ rustTarget = "aarch64-unknown-linux-musl";
+ };
+ };
- # OpenSSL
- openssl.dev
+ x86_64-darwin = {
+ x86_64-darwin = {
+ rustTarget = "x86_64-apple-darwin";
+ mkPackage = { pkgs, ... }: package:
+ let inherit (pkgs.darwin.apple_sdk.frameworks) AppKit Cocoa;
+ in
+ package // {
+ buildInputs = [ Cocoa ];
+ NIX_LDFLAGS = "-F${AppKit}/Library/Frameworks -framework AppKit";
+ };
+ };
- # Notmuch
- notmuch
+ # FIXME: https://github.com/NixOS/nixpkgs/issues/273442
+ aarch64-darwin = {
+ rustTarget = "aarch64-apple-darwin";
+ runner = { pkgs, himalaya }: "${pkgs.qemu}/bin/qemu-aarch64 ${himalaya}";
+ mkPackage = { system, pkgs }: package:
+ let
+ inherit ((mkPkgsCross system "aarch64-darwin").pkgsStatic) stdenv darwin;
+ inherit (darwin.apple_sdk.frameworks) AppKit Cocoa;
+ cc = "${stdenv.cc}/bin/${stdenv.cc.targetPrefix}cc";
+ in
+ package // {
+ buildInputs = [ Cocoa ];
+ NIX_LDFLAGS = "-F${AppKit}/Library/Frameworks -framework AppKit";
+ TARGET_CC = cc;
+ CARGO_BUILD_RUSTFLAGS = package.CARGO_BUILD_RUSTFLAGS ++ [ "-Clinker=${cc}" ];
+ };
+ };
+ };
- # GPG
- gnupg
- gpgme
- ];
+ aarch64-darwin = {
+ aarch64-darwin = {
+ rustTarget = "aarch64-apple-darwin";
+ mkPackage = { pkgs, ... }: package:
+ let inherit (pkgs.darwin.apple_sdk.frameworks) AppKit Cocoa;
+ in
+ package // {
+ buildInputs = [ Cocoa ];
+ NIX_LDFLAGS = "-F${AppKit}/Library/Frameworks -framework AppKit";
+ };
};
};
+ };
+
+ eachBuildSystem = lib.genAttrs (builtins.attrNames crossSystems);
- mkPackage = pkgs: buildPlatform: targetPlatform: package:
+ mkPkgsCross = buildSystem: crossSystem: import nixpkgs {
+ system = buildSystem;
+ crossSystem.config = crossSystem;
+ };
+
+ mkToolchain = import ./rust-toolchain.nix fenix;
+
+ mkApp = { pkgs, buildSystem, targetSystem ? buildSystem }:
let
+ himalaya = lib.getExe self.packages.${buildSystem}.${targetSystem};
+ wrapper = crossSystems.${buildSystem}.${targetSystem}.runner or (_: himalaya) { inherit pkgs himalaya; };
+ program = lib.getExe (pkgs.writeShellScriptBin "himalaya" "${wrapper} $@");
+ app = { inherit program; type = "app"; };
+ in
+ app;
+
+ mkApps = buildSystem:
+ let
+ pkgs = import nixpkgs { system = buildSystem; };
+ mkApp' = targetSystem: _: mkApp { inherit pkgs buildSystem targetSystem; };
+ defaultApp = mkApp { inherit pkgs buildSystem; };
+ apps = builtins.mapAttrs mkApp' crossSystems.${buildSystem};
+ in
+ apps // { default = defaultApp; };
+
+ mkPackage = { pkgs, buildSystem, targetSystem ? buildSystem }:
+ let
+ targetConfig = crossSystems.${buildSystem}.${targetSystem};
toolchain = mkToolchain.fromTarget {
- inherit pkgs buildPlatform targetPlatform;
+ inherit pkgs buildSystem;
+ targetSystem = targetConfig.rustTarget;
};
- naersk' = naersk.lib.${buildPlatform}.override {
+ rust = naersk.lib.${buildSystem}.override {
cargo = toolchain;
rustc = toolchain;
};
- package' = {
- name = "himalaya-repl";
+ mkPackage' = targetConfig.mkPackage or (_: p: p);
+ himalaya = "./himalaya";
+ runner = targetConfig.runner or (_: himalaya) { inherit pkgs himalaya; };
+ package = mkPackage' { inherit pkgs; system = buildSystem; } {
+ name = "himalaya";
src = gitignoreSource ./.;
- overrideMain = _: {
- postInstall = ''
- mkdir -p $out/share/applications/
- cp assets/himalaya-repl.desktop $out/share/applications/
- '';
- };
- doCheck = true;
- cargoTestOptions = opts: opts ++ [ "--lib" ];
- } // pkgs.lib.optionalAttrs (!isNull targetPlatform) {
- CARGO_BUILD_TARGET = targetPlatform;
- } // package;
+ strictDeps = true;
+ doCheck = false;
+ auditable = false;
+ nativeBuildInputs = with pkgs; [ pkg-config ];
+ CARGO_BUILD_TARGET = targetConfig.rustTarget;
+ CARGO_BUILD_RUSTFLAGS = [ "-Ctarget-feature=+crt-static" ];
+ postInstall = ''
+ export WINEPREFIX="$(mktemp -d)"
+
+ mkdir -p $out/bin/share/{applications,completions,man,services}
+ cp assets/himalaya.desktop $out/bin/share/applications/
+ cp assets/himalaya-watch@.service $out/bin/share/services/
+
+ cd $out/bin
+ ${runner} man ./share/man
+ ${runner} completion bash > ./share/completions/himalaya.bash
+ ${runner} completion elvish > ./share/completions/himalaya.elvish
+ ${runner} completion fish > ./share/completions/himalaya.fish
+ ${runner} completion powershell > ./share/completions/himalaya.powershell
+ ${runner} completion zsh > ./share/completions/himalaya.zsh
+ tar -czf himalaya.tgz himalaya* share
+ ${pkgs.zip}/bin/zip -r himalaya.zip himalaya* share
+
+ mv share ../
+ mv himalaya.tgz himalaya.zip ../
+ '';
+ };
in
- naersk'.buildPackage package';
+ rust.buildPackage package;
- mkPackages = buildPlatform:
+ mkPackages = buildSystem:
let
- pkgs = import nixpkgs { system = buildPlatform; };
- mkPackage' = mkPackage pkgs buildPlatform;
+ pkgs = import nixpkgs { system = buildSystem; };
+ mkPackage' = targetSystem: _: mkPackage { inherit pkgs buildSystem targetSystem; };
+ defaultPackage = mkPackage { inherit pkgs buildSystem; };
+ packages = builtins.mapAttrs mkPackage' crossSystems.${buildSystem};
in
- rec {
- default = if pkgs.stdenv.isDarwin then macos else linux;
- linux = mkPackage' null { };
- linux-musl = mkPackage' "x86_64-unknown-linux-musl" (with pkgs.pkgsStatic; {
- CARGO_BUILD_RUSTFLAGS = "-C target-feature=+crt-static";
- hardeningDisable = [ "all" ];
- });
- macos = mkPackage' null (with pkgs.darwin.apple_sdk.frameworks; {
- # NOTE: needed to prevent error Undefined symbols
- # "_OBJC_CLASS_$_NSImage" and
- # "_LSCopyApplicationURLsForBundleIdentifier"
- NIX_LDFLAGS = "-F${AppKit}/Library/Frameworks -framework AppKit";
- buildInputs = [ Cocoa ];
- });
- # FIXME: bzlip: fatal error: windows.h: No such file or directory
- # May be related to SQLite.
- windows = mkPackage' "x86_64-pc-windows-gnu" {
- strictDeps = true;
- depsBuildBuild = with pkgs.pkgsCross.mingwW64; [
- stdenv.cc
- windows.pthreads
+ packages // { default = defaultPackage; };
+
+ mkDevShells = buildSystem:
+ let
+ pkgs = import nixpkgs { system = buildSystem; };
+ rust-toolchain = mkToolchain.fromFile { inherit buildSystem; };
+ defaultShell = pkgs.mkShell {
+ nativeBuildInputs = with pkgs; [ pkg-config ];
+ buildInputs = with pkgs; [
+ # Nix
+ nixd
+ nixpkgs-fmt
+
+ # Rust
+ rust-toolchain
+ cargo-watch
+
+ # Email env
+ gnupg
+ gpgme
+ msmtp
+ notmuch
];
};
- };
-
- mkApp = drv:
- let exePath = drv.passthru.exePath or "/bin/himalaya-repl";
in
- {
- type = "app";
- program = "${drv}${exePath}";
- };
+ { default = defaultShell; };
- mkApps = buildPlatform:
- let
- pkgs = import nixpkgs { system = buildPlatform; };
- in
- rec {
- default = if pkgs.stdenv.isDarwin then macos else linux;
- linux = mkApp self.packages.${buildPlatform}.linux;
- linux-musl = mkApp self.packages.${buildPlatform}.linux-musl;
- macos = mkApp self.packages.${buildPlatform}.macos;
- windows =
- let
- wine = pkgs.wine.override { wineBuild = "wine64"; };
- himalaya = self.packages.${buildPlatform}.windows;
- app = pkgs.writeShellScriptBin "himalaya" ''
- export WINEPREFIX="$(mktemp -d)"
- ${wine}/bin/wine64 ${himalaya}/bin/himalaya.exe $@
- '';
- in
- mkApp app;
- };
- supportedSystems = [ "aarch64-linux" "aarch64-darwin" "x86_64-darwin" "x86_64-linux" ];
- forEachSupportedSystem = f: nixpkgs.lib.genAttrs supportedSystems f;
in
{
- apps = forEachSupportedSystem mkApps;
- packages = forEachSupportedSystem mkPackages;
- devShells = forEachSupportedSystem mkDevShells;
+ apps = eachBuildSystem mkApps;
+ packages = eachBuildSystem mkPackages;
+ devShells = eachBuildSystem mkDevShells;
};
}
diff --git a/rust-toolchain.nix b/rust-toolchain.nix
index ba00e1f..57140c2 100644
--- a/rust-toolchain.nix
+++ b/rust-toolchain.nix
@@ -2,26 +2,23 @@ fenix:
let
file = ./rust-toolchain.toml;
- sha256 = "e4mlaJehWBymYxJGgnbuCObVlqMlQSilZ8FljG9zPHY=";
+ sha256 = "+syqAd2kX8KVa8/U2gz3blIQTTsYYt3U63xBWaGOSc8=";
in
{
- fromFile = { system }: fenix.packages.${system}.fromToolchainFile {
+ fromFile = { buildSystem }: fenix.packages.${buildSystem}.fromToolchainFile {
inherit file sha256;
};
- fromTarget = { pkgs, buildPlatform, targetPlatform ? null }:
+ fromTarget = { pkgs, buildSystem, targetSystem }:
let
- inherit ((pkgs.lib.importTOML file).toolchain) channel;
- toolchain = fenix.packages.${buildPlatform};
+ name = (pkgs.lib.importTOML file).toolchain.channel;
+ fenixPackage = fenix.packages.${buildSystem};
+ toolchain = fenixPackage.fromToolchainName { inherit name sha256; };
+ targetToolchain = fenixPackage.targets.${targetSystem}.fromToolchainName { inherit name sha256; };
in
- if
- isNull targetPlatform
- then
- fenix.packages.${buildPlatform}.${channel}.toolchain
- else
- toolchain.combine [
- toolchain.${channel}.rustc
- toolchain.${channel}.cargo
- toolchain.targets.${targetPlatform}.${channel}.rust-std
- ];
+ fenixPackage.combine [
+ toolchain.rustc
+ toolchain.cargo
+ targetToolchain.rust-std
+ ];
}
diff --git a/rust-toolchain.toml b/rust-toolchain.toml
index 3ccec48..a727497 100644
--- a/rust-toolchain.toml
+++ b/rust-toolchain.toml
@@ -1,4 +1,4 @@
[toolchain]
-channel = "stable"
+channel = "1.77.0"
profile = "default"
components = [ "rust-src", "rust-analyzer" ]