# `asfa` - avoid sending file attachments
[![Crates.io](https://img.shields.io/crates/v/asfa)](https://crates.io/crates/asfa)
[![AUR version](https://img.shields.io/aur/version/asfa)](https://aur.archlinux.org/packages/asfa/)
[![AUR version](https://img.shields.io/aur/version/asfa-bin)](https://aur.archlinux.org/packages/asfa-bin/)
[![AUR version](https://img.shields.io/aur/version/asfa-git)](https://aur.archlinux.org/packages/asfa-git/)
[![Changelog](https://img.shields.io/badge/changelog-asfa-yellow)](https://github.com/obreitwi/asfa/blob/master/CHANGELOG.md)
[![GitHub commits since tagged version](https://img.shields.io/github/commits-since/obreitwi/asfa/v0.10.0)](https://www.github.com/obreitwi/asfa)
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/obreitwi/asfa/rust.yml?branch=master)](https://github.com/obreitwi/asfa/actions?query=workflow%3A%22cargo+test%22)
[![dependency status](https://deps.rs/repo/github/obreitwi/asfa/status.svg)](https://deps.rs/repo/github/obreitwi/asfa)
[![Rustdoc](https://img.shields.io/badge/docs-rustdoc-blue)](https://obreitwi.github.io/asfa/)
[![Crates.io](https://img.shields.io/crates/l/asfa)](#license)
![][gif-send]
Instead of email attachments or direct file transfers, upload files from the command line to your web server via `ssh` and send the link instead.
The link prefix is generated from the uploaded file's checksum.
Hence, only people with the correct link can access it.
Comes with a few convenience features:
* Has support to expire links after a set amount of time.
* The link "just works" for non-tech-savvy people, but is still only accessible for people who possess the link.
* Does not require any custom binary to be executed to the web server.
* Optional server-side dependencies are readily available ([`at`][at], [`sha2`][sha2]).
* Easily [keep track](#list) of which files are currently shared.
* [Clean](#clean) files by index, checksum or [age](#filtering-by-upload-date).
* After upload files are [verified](#verify) (optionally).
* Supports [aliases](#push-with-alias) at upload because sometimes `plot_with_specific_parameters.svg` is more descriptive than `plot.svg`, especially a few weeks later.
* And _most importantly_, of course: Have a name that can be typed with the left hand on home row only.
`asfa` uses a single `ssh`-connection for each invocation which is convenient if you have [confirmations enabled][gpg-agent-confirm] for each ssh-agent usage (see [details](#background)).
Alternatively, private key files in OpenSSH or [PEM-format][pem] can be used directly.
Even though they should not, plain passwords are accepted as well.
## Requirements
A remote server that
* is accessible via ssh
* has a web server running
* has a folder by your user that is served by your web server
* _(optional)_ has `sha2`-related hashing tools installed (`sha256sum`/`sha512sum`)
* _(optional)_ has [`at`][at] installed to support expiring links.
## Usage
Note: All commands can be abbreviated:
* `p` → `push`
* `l` → `list`
* `ch` → `check`
* `cl` → `clean`
* `v` → `verify`
#### Push
Push (upload) a local file to the remote site and print the URL under which it is reachable.
```text
$ asfa push my-file.txt
https://my-domain.eu/asfa/V66lLtli0Ei4hw3tNkCTXOcweBrneNjt/my-very-specific-file.txt
```
See example at the top. Because the file is identified by its hash, uploading the same file twice will generate the same link.
#### Push with alias
Push a file to the server under a different name. This is useful if you want to share a logfile or plot with a generic name.
![][gif-alias-01]
Note that if you specify several files to upload with their own aliases, you need to explicity assign the arguments.
![][gif-alias-02]
Or specify the aliases afterwards.
```text
$ asfa push my-file.txt my-file-2.txt --alias my-very-specific-file.txt my-very-specific-file-2.txt
https://my-domain.eu/asfa/V66lLtli0Ei4hw3tNkCTXOcweBrneNjt/my-very-specific-file.txt
https://my-domain.eu/asfa/HiGdwtoXcXotyhDxQxydu4zqKwFQ-9pY/my-very-specific-file-2.txt
```
#### Automatic Expire
Uploads can be automatically expired after a certain time via `--expire `.
`` can be anything from minutes to hours, days or even months.
It requires [`at`][at] to be installed and running at the remote site.
#### List
List all files currently available online:
![][gif-list]
#### Detailed list
List all files with meta data via `--details`:
![][gif-list-details]
#### Check
Check if files have already been uploaded (via hash) and print them.
#### Clean
Remove the file from remote site via index (negative indices _no longer_ need to be sepearated by `--`):
![][gif-clean]
![][gif-clean-signed]
You can also ensure that a specific file is deleted by specifying `--file`:
![][gif-clean-checksum]
Note that the file is deleted even though it was uploaded with an alias.
#### Verify
In case an upload gets canceled early, all files can be checked for validity via `verify`:
```text
$ asfa verify
✓ my-very-specific-file.txt ..... Verified.
✓ my-very-specific-file-2.txt ... Verified.
```
Since the prefix is the checksum, the check can be performed whether the file exists locally or not.
#### Filtering by upload date
All commands accept a `--newer`/`--older` `{min,hour,day,week,month}`
argument that can be used to narrow down the number of files.
Cleaning all files older than a month can, for example, be achieved via
```text
$ asfa clean --older 1month
$ asfa clean --older 1M
```
All files uploaded within the last five minutes can be listed via
```text
$ asfa list --newer 5min
$ asfa list --newer 5m
```
#### Rename Uploaded files
Uploaded files can be renamed after the fact via the `rename` command (shorthand `mv`).
The remtoe file is specified either by index (returned from `list`) or by specifying the local file to be renamed.
```text
$ asfa rename -1 foobar
┌┤Renaming:├────────────────────────────────────────────────────────────────────────┐
│ my-very-specific-file-2.txt → https://breitwieser.eu/asfa/6x-SVlgRJn39wpsV/foobar │
└───────────────────────────────────────────────────────────────────────────────────┘
```
## Install
### `cargo`
```text
$ cargo install asfa
```
### AUR
The following AUR packages are provided:
* [`asfa`][aur-asfa]: Latest stable release built from source.
* [`asfa-bin`][aur-asfa-bin] Pre-built binaries for target `x86_64-unknown-linux-gnu`.
* [`asfa-git`][aur-asfa-git] Current development snapshot built from source.
Either use your favorite AUR helper or install manually:
```text
$ cd
$ curl -o PKGBUILD https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD?h=asfa-git
$ makepkg
[...]
==> Finished making: asfa-git 0.7.2.r16.g763f726-1 (Sun 07 Feb 2021 04:18:12 PM CET)
$ sudo pacman -U asfa-git-0.7.2.r16.g763f726-1-x86_64.pkg.tar.zst
```
### From source
```text
$ git clone https://github.com/obreitwi/asfa.git
$ cargo install --path asfa
```
## Configuration
Configuration resides in `~/.config/asfa/config.yaml`.
Host-specific configuration can also be split into single files residing under `~/.config/asfa/hosts/.yaml`.
System-wide configuration can be placed in `/etc/asfa` with the same folder structure.
An example config can be found in `./example-config`.
Here, we assume that your server can be reached at `https://my-domain.eu` and that the folder `/var/wwww/default/asfa` will be served at `https://my-domain.eu/asfa`.
### `asfa`-side
A fully commented example config can be found [here](example-config/asfa).
#### Minimal: `~/.config/asfa/hosts/my-remote-site.yaml`
```yaml
hostname: my-hostname.eu # if not specified, will defaulted from ssh or filename
folder: /var/www/default/asfa
url: https://my-domain.eu/asfa
group: www-data
```
#### Full (single-file): `~/.config/asfa/config.yaml`
```yaml
default_host: my-remote-site
details: true # optional, acts as if --details is given
prefix_length: 32
verify_via_hash: true
auth:
interactive: true
use_agent: true
hosts:
my-remote-site:
# note: port is optional, will be inferred form ssh and defaults to 22
hostname: my-hostname.eu:22
folder: /var/www/default/asfa
url: https://my-domain.eu/asfa
group: www-data
auth:
interactive: false
use_agent: true
private_key_file: /path/to/private/key/in/pem/format #optional
```
### Web Server
Whatever web server you are using, you have to make sure the following requirements are met:
* The user as which you upload needs to have write access to your configured `folder`.
* Your web server needs to serve `folder` at `url`.
* In case you do not want your uploaded data to be world-readable, set `group` to the group of your web server.
* Make sure your web server does not serve indexes of `folder`, otherwise any visitor can see all uploaded files rather easily.
#### Apache
Your apache config can be as simple as:
```apache
Options None
allow from all
```
Make sure that Options does not contain `Indexes`, otherwise any visitor could _very_ easily access all uploaded files!
#### nginx
```nginx
location /asfa {
autoindex off
}
```
## Background
Since I handle my emails mostly via ssh on a remote server (shoutout to [neomutt][], [OfflineIMAP][offlineimap] and [msmtp][]), I needed a quick and easy possibility to attach files to emails.
As email attachments are rightfully frowned upon, I did not want to simply copy files over to the remote site to attach them.
Furthermore, I often need to share generated files (such as plots or logfiles) on our group-internal [mattermost](https://www.mattermost.org) or any other form of text-based communication.
Ideally, I want to do this from the folder I am already in on the terminal - and not by to navigating back to it from the browser's "file open" menu…
As a small exercise for writing rust (other than [Advent of Code](https://adventofcode.com/)), I ported a small [python script][py-rpush] I had been using for a couple of years.
For [security reasons][ssh-agent-hijacking] I have my `gpg-agent` (acting as `ssh-agent`) set up to [confirm][gpg-agent-confirm] each usage upon connecting to remote servers and the previous hack required three connections (and confirmations) to perform its task.
`asfa` is set up to only use one ssh-connection per invocation.
## License
Licensed under either of
* Apache License, Version 2.0
([LICENSE-APACHE](LICENSE-APACHE) or )
* MIT license
([LICENSE-MIT](LICENSE-MIT) or )
at your option.
[at]: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/at.html
[aur-asfa-bin]: https://aur.archlinux.org/packages/asfa-bin/
[aur-asfa-git]: https://aur.archlinux.org/packages/asfa-git/
[aur-asfa]: https://aur.archlinux.org/packages/asfa/
[gif-alias-01]: https://raw.githubusercontent.com/obreitwi/asfa/17b954a6f4aafa03e8f6ef8fcd49f8619c4af7dc/img/push_alias_01.gif
[gif-alias-02]: https://raw.githubusercontent.com/obreitwi/asfa/17b954a6f4aafa03e8f6ef8fcd49f8619c4af7dc/img/push_alias_02.gif
[gif-aliases]: https://raw.githubusercontent.com/obreitwi/asfa/17b954a6f4aafa03e8f6ef8fcd49f8619c4af7dc/img/push_alias_02.gif
[gif-clean-checksum]: https://raw.githubusercontent.com/obreitwi/asfa/17b954a6f4aafa03e8f6ef8fcd49f8619c4af7dc/img/clean_02.gif
[gif-clean-signed]: https://raw.githubusercontent.com/obreitwi/asfa/17b954a6f4aafa03e8f6ef8fcd49f8619c4af7dc/img/clean_03.gif
[gif-clean]: https://raw.githubusercontent.com/obreitwi/asfa/17b954a6f4aafa03e8f6ef8fcd49f8619c4af7dc/img/clean_01.gif
[gif-list-details]: https://raw.githubusercontent.com/obreitwi/asfa/17b954a6f4aafa03e8f6ef8fcd49f8619c4af7dc/img/list_details_01.gif
[gif-list]: https://raw.githubusercontent.com/obreitwi/asfa/17b954a6f4aafa03e8f6ef8fcd49f8619c4af7dc/img/list_01.gif
[gif-send]: https://raw.githubusercontent.com/obreitwi/asfa/17b954a6f4aafa03e8f6ef8fcd49f8619c4af7dc/img/push_single_01.gif
[gpg-agent-confirm]: https://www.gnupg.org/documentation/manuals/gnupg/Agent-Configuration.html#index-sshcontrol
[msmtp]: https://marlam.de/msmtp/
[neomutt]: https://neomutt.org/
[offlineimap]: http://www.offlineimap.org/
[pem]: https://serverfault.com/a/706342
[py-rpush]: https://github.com/obreitwi/py-rpush
[sha2]: https://linux.die.net/man/1/sha256sum
[ssh-agent-hijacking]: https://www.clockwork.com/news/2012/09/28/602/ssh_agent_hijacking/