mirror of
https://github.com/sbrow/envr.git
synced 2025-12-29 23:47:39 -05:00
Compare commits
9 Commits
94a572306a
...
d0924ccaad
| Author | SHA1 | Date | |
|---|---|---|---|
| d0924ccaad | |||
| 0b24637df4 | |||
| 80489ffbb1 | |||
| 1a3634ba84 | |||
| 7604454b74 | |||
| 697968e410 | |||
| 6597d987f9 | |||
| a139b79bcb | |||
| 711f3c4a15 |
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Spencer Reid Brower
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
127
README.md
127
README.md
@@ -1,4 +1,127 @@
|
|||||||
|
# envr - Backup your env files
|
||||||
|
|
||||||
|
Have you ever wanted to back up all your .env files in case your hard drive gets
|
||||||
|
nuked? `envr` makes it easier.
|
||||||
|
|
||||||
|
`envr` is a [Nushell](https://www.nushell.sh) script that tracks your `.env` files
|
||||||
|
in an encyrpted sqlite database. Changes can be effortlessly synced with
|
||||||
|
`envr sync`, and restored with `envr restore`.
|
||||||
|
|
||||||
|
`envr` puts all your .env files in one safe place, so you can back them up with
|
||||||
|
the tool [of your choosing](#backup-options).
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- 🔐 **Encrypted Storage**: All `.env` files are encrypted using your ssh key and
|
||||||
|
[age](https://github.com/FiloSottile/age) encryption.
|
||||||
|
- 🔄 **Automatic Sync**: Update the database with one command, which can easily
|
||||||
|
be run on a cron.
|
||||||
|
- 🔍 **Smart Scanning**: Automatically discover and import `.env` files in your
|
||||||
|
home directory.
|
||||||
|
- 📝 **Multiple Config Formats**: Support for many configuration formats,
|
||||||
|
including: JSON, TOML, YAML, INI, XML, and NUON.
|
||||||
|
- [ ] TODO: 🗂️ **Rename Detection**: Automatically handle renamed repositories.
|
||||||
|
- ✨ **Interactive CLI**: User-friendly prompts for file selection and management
|
||||||
|
thanks to [nushell](https://www.nushell.sh/)
|
||||||
|
|
||||||
## TODOS
|
## TODOS
|
||||||
|
|
||||||
- [ ] `envr sync` - Restore missing .env files, and update backed up ones.
|
- [ ] Allow configuration of ssh key.
|
||||||
- [ ] `envr scan` - Search for missing / moved .env files.
|
- [ ] Allow multiple ssh keys.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- An SSH key pair (for encryption/decryption)
|
||||||
|
- The following binaries:
|
||||||
|
- [nushell](https://www.nushell.sh/)
|
||||||
|
- [age](https://github.com/FiloSottile/age)
|
||||||
|
- [fd](https://github.com/sharkdp/fd)
|
||||||
|
- [sqlite3](https://github.com/sqlite/sqlite)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Clone the repository:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/username/envr.git
|
||||||
|
cd envr
|
||||||
|
```
|
||||||
|
2. Install [dependencies](#prerequisites).
|
||||||
|
3. Configure nushell.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
1. **Initialize envr**:
|
||||||
|
```bash
|
||||||
|
nu mod.nu envr init
|
||||||
|
```
|
||||||
|
This will create your configuration file and set up encrypted storage.
|
||||||
|
|
||||||
|
2. **Scan for existing .env files**:
|
||||||
|
```bash
|
||||||
|
nu mod.nu envr scan
|
||||||
|
```
|
||||||
|
Select files you want to back up from the interactive list.
|
||||||
|
|
||||||
|
3. **List tracked files**:
|
||||||
|
```bash
|
||||||
|
nu mod.nu envr list
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Sync your environment files**:
|
||||||
|
```bash
|
||||||
|
nu mod.nu envr sync
|
||||||
|
```
|
||||||
|
|
||||||
|
## Disclaimers
|
||||||
|
|
||||||
|
> [!CAUTION]
|
||||||
|
> Do not lose your SSH key pair! Your backup will be **lost forever**.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `envr init [format]` | Initialize envr with configuration file |
|
||||||
|
| `envr backup <file>` | Back up a specific .env file |
|
||||||
|
| `envr restore [path]` | Restore a backed-up .env file |
|
||||||
|
| `envr list` | View all tracked environment files |
|
||||||
|
| `envr scan` | Search for and selectively back up .env files |
|
||||||
|
| `envr sync` | Synchronize all tracked files (backup changes, restore missing) |
|
||||||
|
| `envr remove [...paths]` | Remove files from backup storage |
|
||||||
|
| `envr edit config` | Edit your configuration file |
|
||||||
|
| `envr config show` | Display current configuration |
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The configuration file is created during initialization and supports multiple formats:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# Example ~/.envr/config.toml
|
||||||
|
source = "~/.envr/config.toml"
|
||||||
|
priv_key = "~/.ssh/id_ed25519"
|
||||||
|
pub_key = "~/.ssh/id_ed25519.pub"
|
||||||
|
|
||||||
|
[scan]
|
||||||
|
matcher = "\.env"
|
||||||
|
exclude = "*.envrc"
|
||||||
|
include = "~"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Backup Options
|
||||||
|
|
||||||
|
`envr` merely gathers your `.env` files in one local place. It is up to you to
|
||||||
|
back up the database (found at ~/.envr/data.age) to a *secure* and *remote*
|
||||||
|
location.
|
||||||
|
|
||||||
|
### Git
|
||||||
|
|
||||||
|
### restic
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the [MIT License](./LICENSE).
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
For issues, feature requests, or questions, please
|
||||||
|
[open an issue](https://github.com/sbrow/envr/issues).
|
||||||
|
|||||||
@@ -70,6 +70,7 @@
|
|||||||
{
|
{
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
age
|
age
|
||||||
|
fd
|
||||||
nushell
|
nushell
|
||||||
sqlite
|
sqlite
|
||||||
|
|
||||||
|
|||||||
156
mod.nu
156
mod.nu
@@ -1,8 +1,11 @@
|
|||||||
#!/usr/bin/env nu
|
#!/usr/bin/env nu
|
||||||
|
#
|
||||||
|
# TODO: Wrap blocks that use tmp files in try so we can clean them up.
|
||||||
|
|
||||||
use std assert;
|
use std assert;
|
||||||
|
|
||||||
# Manage your .env files with ease
|
# Manage your .env files with ease
|
||||||
|
@example "Set up envr" { envr init }
|
||||||
export def envr [] {
|
export def envr [] {
|
||||||
help envr
|
help envr
|
||||||
}
|
}
|
||||||
@@ -84,7 +87,7 @@ def "close db" [] {
|
|||||||
rm $dec
|
rm $dec
|
||||||
}
|
}
|
||||||
|
|
||||||
# Restore a .env file from backup.
|
# Restore a .env file from backup
|
||||||
export def "envr restore" [
|
export def "envr restore" [
|
||||||
path?: path # The path of the file to restore. Will be prompted if left blank.
|
path?: path # The path of the file to restore. Will be prompted if left blank.
|
||||||
]: nothing -> string {
|
]: nothing -> string {
|
||||||
@@ -143,33 +146,96 @@ const available_formats = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
# Create your initial config
|
# Create your initial config
|
||||||
export def "envr config init" [
|
export def "envr init" [
|
||||||
format?: string
|
format?: string
|
||||||
#identity?: path
|
#identity?: path
|
||||||
] {
|
]: nothing -> record {
|
||||||
mkdir ~/.envr
|
mkdir ~/.envr
|
||||||
|
|
||||||
let format = if ($format | is-empty) {
|
if (glob ~/.envr/config.* | length | $in > 0) {
|
||||||
$available_formats | input list 'Please select the desired format for your config file'
|
error make {
|
||||||
}
|
msg: "A config file already exists"
|
||||||
|
label: {
|
||||||
|
text: ""
|
||||||
|
span: (metadata $format).span
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let format = if ($format | is-empty) {
|
||||||
|
$available_formats | input list 'Please select the desired format for your config file'
|
||||||
|
}
|
||||||
|
|
||||||
let identity = '~/.ssh/id_ed25519';
|
# TODO: Let user select identity.
|
||||||
|
let identity = '~/.ssh/id_ed25519';
|
||||||
|
|
||||||
# The path to the config file.
|
# The path to the config file.
|
||||||
let source = $'~/.envr/config.($format)'
|
let source = $'~/.envr/config.($format)'
|
||||||
|
|
||||||
{
|
# TODO: Let user select file types to scan for.
|
||||||
source: $source
|
|
||||||
priv_key: $identity
|
{
|
||||||
pub_key: $'($identity).pub'
|
source: $source
|
||||||
} | tee {
|
priv_key: $identity
|
||||||
save $source
|
pub_key: $'($identity).pub'
|
||||||
|
scan: {
|
||||||
|
matcher: '\.env'
|
||||||
|
exclude: '*.envrc'
|
||||||
|
include: '~'
|
||||||
|
}
|
||||||
|
} | tee {
|
||||||
|
save $source;
|
||||||
|
open db
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Remove a .env file from your backups.
|
||||||
|
export def "envr remove" [
|
||||||
|
...paths: path
|
||||||
|
] {
|
||||||
|
let $paths = if ($paths | is-empty) {
|
||||||
|
(envr list | get path) | input list -m "Select path(s) to remove"
|
||||||
|
} else {
|
||||||
|
$paths
|
||||||
|
};
|
||||||
|
|
||||||
|
print -e $paths;
|
||||||
|
|
||||||
|
let confirmed = (
|
||||||
|
['No' 'Yes']
|
||||||
|
| input list 'Are you sure you want to delete these backups?'
|
||||||
|
| $in == 'Yes'
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($confirmed) {
|
||||||
|
let $q = (
|
||||||
|
$paths
|
||||||
|
| each { path expand }
|
||||||
|
| to json -r
|
||||||
|
| str replace '[' '('
|
||||||
|
| str replace ']' ')'
|
||||||
|
| str replace -a '"' "'");
|
||||||
|
(
|
||||||
|
open db
|
||||||
|
| stor delete -t envr_env_files -w $'path in ($q)'
|
||||||
|
);
|
||||||
|
|
||||||
|
close db
|
||||||
|
|
||||||
|
$'(ansi green)Paths removed!'
|
||||||
|
} else {
|
||||||
|
$'(ansi yellow)Operation aborted.(ansi reset)'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# View your tracked files
|
# View your tracked files
|
||||||
export def "envr list" [] {
|
export def "envr list" [] {
|
||||||
(files | reject contents)
|
(
|
||||||
|
files
|
||||||
|
| reject contents
|
||||||
|
| update path { $in | path relative-to ~/ | $'~/($in)' }
|
||||||
|
| update dir { $in | path relative-to ~/ | $'~/($in)' }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
# List all the files in the database
|
# List all the files in the database
|
||||||
@@ -181,13 +247,63 @@ def files [] {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
# Update your env backups
|
# Update or restore your env backups
|
||||||
export def "envr sync" [] {
|
export def "envr sync" [] {
|
||||||
'TODO:'
|
let $files = (files);
|
||||||
|
|
||||||
|
$files | each { |it|
|
||||||
|
if ($it.path | path type | $in == 'file') {
|
||||||
|
if (open $it.path | hash sha256 | $in != $it.sha256) {
|
||||||
|
envr backup $it.path
|
||||||
|
} else {
|
||||||
|
'Nothing to do!'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
envr restore $it.path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Search for .env files and select ones to back up.
|
||||||
|
export def "envr scan" [] {
|
||||||
|
let config = (envr config show).scan;
|
||||||
|
|
||||||
|
let ignored_env_files = (
|
||||||
|
find env-files $config.matcher $config.exclude $config.include
|
||||||
|
);
|
||||||
|
|
||||||
|
let tracked_file_paths = (files).path;
|
||||||
|
|
||||||
|
let scanned = $ignored_env_files | where { |it|
|
||||||
|
not ($it in $tracked_file_paths)
|
||||||
|
}
|
||||||
|
|
||||||
|
(
|
||||||
|
$scanned
|
||||||
|
| input list -m "Select files to back up"
|
||||||
|
| each { envr backup $in }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Search for hidden env files
|
||||||
|
def "find env-files" [
|
||||||
|
match: string = '\.env'
|
||||||
|
exclude: string = '*.envrc'
|
||||||
|
search: string = '~/'
|
||||||
|
]: nothing -> list<path> {
|
||||||
|
let search = ($search | path expand);
|
||||||
|
|
||||||
|
let unignored = (fd -a $match -E $exclude -H $search | lines)
|
||||||
|
let all = (fd -a $match -E $exclude -HI $search | lines)
|
||||||
|
let ignored = $all | where { |it|
|
||||||
|
not ($it in $unignored)
|
||||||
|
}
|
||||||
|
|
||||||
|
$ignored | sort
|
||||||
}
|
}
|
||||||
|
|
||||||
# Edit your config
|
# Edit your config
|
||||||
export def "envr config edit" [] {
|
export def "envr edit config" [] {
|
||||||
^$env.EDITOR (config-file)
|
^$env.EDITOR (config-file)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,6 +312,6 @@ def "config-file" []: [nothing -> path nothing -> nothing] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# show your current config
|
# show your current config
|
||||||
export def "envr config show" []: nothing -> record<source: path, priv_key: path, pub_key: path> {
|
def "envr config show" []: nothing -> record<source: path, priv_key: path, pub_key: path> {
|
||||||
open (config-file)
|
open (config-file)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user