Compare commits

...
56 Commits
Author SHA1 Message Date
RandyTheSilly b96db3ad92 Updated date in manpage for release v1.1.0
Former-commit-id: f9b74560f437e3dcc3d731909599957c460e8e8b [formerly 393ef08a21]
Former-commit-id: ddd1f4834f044d5e3da5016218138690627bcbfe
2022-07-14 03:21:43 -04:00
RandyTheSilly 224fc1fc6d Minor visual consistency tweak for Haiku gpg key generation
Former-commit-id: 1afd8df260d65f210e90cc93270971bf2c2fcb46 [formerly c5c110d0b4]
Former-commit-id: a24c571e55d987595250ebcfc558924cec675c62
2022-07-14 02:53:39 -04:00
RandyTheSilly 0bd2e45dba Fixed Termux packaging
Former-commit-id: 6d38a02d6211412bc99615704f311f820bdcef27 [formerly 4b02fd02b8]
Former-commit-id: 92f48521175fb89015cdb6dc9d99fb9485650c1f
2022-07-13 07:44:29 -04:00
RandyTheSilly 6410843911 Updated changelog for v1.1.0
Former-commit-id: 7ebe77ae88f470586d291eb4054cbdfa6f365950 [formerly de28d19969]
Former-commit-id: 7d27d41aef11546e74dc7073c8b260566addb0d7
2022-07-13 04:48:11 -04:00
RandyTheSilly 3dda6d8428 Update README.md
Former-commit-id: 5b20287fab20945ddc8841249503c321d6cbf999 [formerly beeb426ac8]
Former-commit-id: 90746e5b5225435bf8fd7ab9c2659fa5547a0c14
2022-07-11 22:30:48 -04:00
RandyTheSilly 4f31af5eff Clean up for automatically created gpg generation file
Former-commit-id: eba9c2f414fd59d655e2d3b992b2a8982745e01f [formerly e2b8e31f50]
Former-commit-id: 1166007ce70d0e1d61f2e2d8c3fab78970a6407b
2022-07-11 22:19:31 -04:00
RandyTheSilly bfb830c5e5 Fixed Haiku gpg key generation
Former-commit-id: 1a441cea301049608232742f4fddcc6338adf0a7 [formerly a673404fae]
Former-commit-id: 5ad1eef49307617c0f629f0a660d977f1784d380
2022-07-11 19:06:58 -04:00
RandyTheSilly 790121e7ea Greatly improved 'sshyp tweak' readability
Former-commit-id: 1f0f3ad2ca92e321c4819a14e9db1a961ff0816d [formerly d6bc4a2301]
Former-commit-id: 331e6e9b3d78f4aab1e4c958d526466a469ac515
2022-07-11 04:16:33 -04:00
rwinkhart 4168b800f8 Reduced code used for argument parsing
Former-commit-id: fb6b5f10b1077e2bba9c6bf43c23a59a455491eb [formerly faea36f6cd]
Former-commit-id: 5292e931128c413557c0e9609c009d7430949cf7
2022-07-10 05:41:49 -04:00
rwinkhart 801d515697 Restored banner image
Former-commit-id: 77219101a9c71848dce9d96e7dd17d9440d03b6c [formerly 398999b23f]
Former-commit-id: 77a032369f2f492f8227fa927da3a1b3d88ad7d0
2022-07-10 00:16:50 -04:00
rwinkhart 379970d53b Moved conversion scripts to sshyp-labs repo
Former-commit-id: b2ce1378324b6b19b71a54f02e4ac34a94d17728 [formerly 77873d061b]
Former-commit-id: ab23a45ed7b1d11597823081b2f0132b4b01ec2f
2022-07-10 00:15:56 -04:00
rwinkhart 4abb3ed718 Fixed entries readout not skipping lines between fields under certain conditions
Former-commit-id: 952acd940a754745d1798070113ea6c4d9d84408 [formerly f6875b15f0]
Former-commit-id: 496bdf8eb8abb6aa6c55e355e2b0457002793811
2022-07-09 23:35:08 -04:00
rwinkhart 6690fd15c9 Updated sshyp-refresh to use new entry directory
Former-commit-id: 9dedbb71ef7cf4f45511780d62a4c51d75d782a9 [formerly 8c520d972e]
Former-commit-id: 900cc5e3e7c0508a2629cfcb002faebb7c1d3753
2022-07-09 18:17:24 -04:00
rwinkhart 1582e73a98 Removed unused import
Former-commit-id: a7209c33c11d1eb36b7b27a1184b783f23253ff4 [formerly 01d59ce79a]
Former-commit-id: 8a9c2cb3017b966c138ebe9ae4cf8a1ea252ddca
2022-07-08 06:17:06 -04:00
rwinkhart dfe1703e62 gpg key generation and input is now automated
Former-commit-id: 97072166a69bc920cf39a87aa05f03ffc783d3d0 [formerly 9e49aa0bcb]
Former-commit-id: 9037e0b2a5f41f5db6616fc730ba21a8aec4dbe1
2022-07-08 06:14:13 -04:00
rwinkhart 7a76130b26 Updated banner artwork
Former-commit-id: c51116e0cdb7866b2c0bb969a23b57c4cb2cb7ff [formerly a2a473454f]
Former-commit-id: b5636d731c0398a17ca972d15325cced3c4df36a
2022-07-08 02:24:55 -04:00
rwinkhart 98dcbf57c6 Updated directory specified in the manpage
Former-commit-id: 934137a00c709169d9c316aa6a670eb6ef832f8f [formerly 2639cea365]
Former-commit-id: 98831f08192eb3deeb596594a9a24cbdb1c8dd1f
2022-07-08 00:46:23 -04:00
rwinkhart a5946d558a Added comments to every function
Former-commit-id: 6b8f15be379d51af5ced2449b1db7749d528dcf4 [formerly 87ae61ae30]
Former-commit-id: f5919588095deb509e899cff4e024dd76affbbce
2022-07-08 00:39:48 -04:00
rwinkhart 30fb5ed041 Updated compatibility with the upcoming sshyp-mfa extension
Former-commit-id: 3d900bfcce18345a9595deca8951b3a91bdfa85b [formerly bf22987544]
Former-commit-id: 34cbff97d0e671e71acd2baa95a0d129e1bafe79
2022-06-30 22:11:19 -04:00
rwinkhart fb1f5b92c3 Disabled all arguments on server except for 'tweak'
Former-commit-id: b35e7fca86b9fc7937dfb6119a8ba154ebade8fe [formerly c7fa3e3d09]
Former-commit-id: 7cab5ad48bd0a41d630fd5f943aa2e32c50ad434
2022-06-30 21:56:08 -04:00
rwinkhart 61b2186b0a In compliance with the XDG base directory spec, entries are now stored in ~/.local/share/sshyp/
Former-commit-id: fbb98f890b417395345a7a71ad158e48fa36a7ef [formerly 0c1f34bc30]
Former-commit-id: 91bd7170c0c56c093cf2126ce089be0e8255d6ce
2022-06-30 21:22:20 -04:00
rwinkhart 2113b93481 Allow the user to choose preferred editor, drop nano as dependency
Former-commit-id: d84048c212beca87419aac3577cf32c01d4f518f [formerly 9891fe4a81]
Former-commit-id: 2204730779caa4adad44a03f1fcd0bb76f25a1cd
2022-06-30 20:27:59 -04:00
RandyTheSilly 2030d90114 Fixed license name for Fedora package
Former-commit-id: 4caad0c333bdb66c3945692368ba26c702060426 [formerly 50972fcdf8]
Former-commit-id: 2645a1286c5d23f05f22e9dde0ead1edde6e27d5
2022-06-21 23:39:07 -04:00
RandyTheSilly 75071601e6 Update packaging for sshyp.py rename
Former-commit-id: 1f52fb7fb48c7e1152314d065602139f62b5ea2e [formerly 37fe8e37e7]
Former-commit-id: 8f17c38475e647b511bc7858c3859d3c65e29882
2022-06-21 23:37:45 -04:00
RandyTheSilly 9a0a261d55 Removed unused code in entry_name_fetch()
Former-commit-id: 047a66dc53042dda37f6825862c2fc14fb3a17cc [formerly 1f2543fe3f]
Former-commit-id: 4bb6fca31988c8ef74a4e4a730f1d25e9b21481d
2022-06-21 23:18:54 -04:00
RandyTheSilly 4ca2d89464 Updated read_shortcut() to use argument_list[] notation
Former-commit-id: 01a62c169c089618c8015f94f9d63ddbfb295d37 [formerly c0f3296a75]
Former-commit-id: 4bf2ff467f12d9f1e7c5f7fd0187d441718808a7
2022-06-21 23:12:03 -04:00
RandyTheSilly f90ec5c900 Optimized import statement for random
Former-commit-id: e18122f2bc1df6525c890d42ae02d90062e5bd6e [formerly c2f43e563a]
Former-commit-id: a993e29ce0410dd63a4171d10e3dc842c9297be5
2022-06-21 23:03:00 -04:00
RandyTheSilly fa0a4fe0b8 Removed unnecessary arguments in usage of decrypt()
Former-commit-id: cd966935bf552d5b5222c59e1887544b79e8a6aa [formerly db0142d202]
Former-commit-id: c3347ea5164901f8377414b097af426b1569b26d
2022-06-21 22:58:39 -04:00
RandyTheSilly c85a9d2ccd Compatibility with upcoming sshyp-mfa standalone extension
Former-commit-id: adf34e9d38e3ce5a7d96738a97593fca777aa244 [formerly f184864e90]
Former-commit-id: ff632e3bd21132c838ba4602d99ab4106d387971
2022-06-21 22:44:10 -04:00
RandyTheSilly 43351fd66f Bump version to v1.0.1
Former-commit-id: 73a9e537a08596e3a527f7c7b45405bd43818d54 [formerly 4aa9036d42]
Former-commit-id: 249daa62a333460a5c3142815872b3e26b4ad6a8
2022-06-21 03:46:24 -04:00
RandyTheSilly 096bdb0773 Updated changelog
Former-commit-id: 90c2c0be99f4e250b4a710c7f4013f73f648c0ed [formerly fdaff41f6f]
Former-commit-id: 4d8658fd6473e33ac3d4fecf88cfb500d2d44959
2022-06-21 03:44:48 -04:00
RandyTheSilly 330396c5f1 Improved usage of Popen in copy() function + removed import statement for subprocess.STDOUT
Former-commit-id: bbc39da1d30845aadac3430b0d8758d6958a159c [formerly 88417f5f67]
Former-commit-id: 5be7fe4d68ae5153ef1dbde3018b15ed5fe944b0
2022-06-21 03:28:56 -04:00
RandyTheSilly 8576f63479 Optimized copy_name_check() function
Former-commit-id: 07e02ab7b0bbf030b0ee1baedd8dbb7ec5ef2e4e [formerly 47e8383146]
Former-commit-id: 570a930a9c63aaa5315285ccf1a84316f4e6199a
2022-06-21 03:13:55 -04:00
RandyTheSilly 393f241538 Optimized decrypt() function (fixes issue #1)
Former-commit-id: d94cce05e9695f4d0744b5a3f4232ad48b146ed2 [formerly 3ad5acfc99]
Former-commit-id: b7edcf31903826ebb8010f6e694b728e1616a70f
2022-06-21 03:03:23 -04:00
rwinkhart c557d5bfee Update README.md
Added CodeQL badge

Former-commit-id: fa618db3f76919e31da43758bda1652f19e63912 [formerly 6f18076068]
Former-commit-id: 809209a1ba762b6dacb8a1eb9f7199873787438b
2022-06-20 09:15:39 +00:00
rwinkhart acca7d888c Create codeql-analysis.yml
Former-commit-id: 8323ea0d21b761f9ec50a79e926b15d691c9700a [formerly 492b10a44e]
Former-commit-id: 72b9d6a984840d399124efe919adaadd5d0d7f04
2022-06-20 09:05:07 +00:00
rwinkhart 2f3b3c67ba Delete codeql-analysis.yml
Former-commit-id: bedaabebf13648b28a564c575ff82723ddf70904 [formerly 94f5420f83]
Former-commit-id: 61593eb69cc8abb1120925965aaf81504cb8dd4f
2022-06-20 09:04:37 +00:00
rwinkhart 88d2402e88 Create codeql-analysis.yml
Former-commit-id: 3551b0bdb4902910a23abfcb369965f8bfb3dde8 [formerly 4f90e5ca0b]
Former-commit-id: b50cad12f70384329acc728a2628a83158c604af
2022-06-20 08:58:03 +00:00
RandyTheSilly 928aacc64a No longer package Fedora with the "All" option (to ensure Fedora is only packaged from itself)
Former-commit-id: 84a32263eb6ceb32063ba0c3fd6ecc49c59fc579 [formerly 90454645e0]
Former-commit-id: 8e57ca2e811b3cbf73c9356c103ae189022f9a9f
2022-06-20 03:49:51 -04:00
RandyTheSilly 094bdcb418 Updated documentation
Former-commit-id: 49716c1645645bdc47992c7d480f51a81e457e0e [formerly c0ac590e4b]
Former-commit-id: d0d798a715d10136bfc9c67352a2e8893ddfbff1
2022-06-20 03:45:43 -04:00
RandyTheSilly 419ff51e75 Properly handle exception for non-integer input with pass_gen()
Former-commit-id: b2c4bd2cd8cf7db4516f47a48bde46610d2e9f5c [formerly 964561b33b]
Former-commit-id: c92d23fa9ee173e5e0540ecbb060b1edf8ec05c9
2022-06-20 03:35:19 -04:00
RandyTheSilly 2111a0454e Optimized gen() function
Former-commit-id: 3706f28b1e9a0ccf59dd75076692a30534fe017c [formerly 6398ef8ec0]
Former-commit-id: 35419b0570ca452d27e0f8cae322bec552e1023d
2022-06-20 03:23:36 -04:00
RandyTheSilly 5d902f0324 Fixed error when generating a new password entry with no additional arguments
Former-commit-id: 642e7756a42374eac08df6ce1ec2ac37a803a2ce [formerly 45ee9ff40b]
Former-commit-id: b5e1a2cca9be722ae40e5a8b6b2431251344daac
2022-06-20 03:15:08 -04:00
RandyTheSilly 505ffdb489 Fixed accidental overwriting when creating a new file of a name that is already present + some fixes for renaming and generating
Former-commit-id: 947ab5d1ac6dc5b07836bc075d202b32f563ab6c [formerly a06fc38968]
Former-commit-id: 97c3bda15e6e67e8f5503aa21ac69e54c27ae42c
2022-06-20 03:08:08 -04:00
RandyTheSilly 5cab1ad091 Fix FreeBSD errors on entry creation/editing due to attempting to access a directory with limited permissions
Former-commit-id: d0ec9fcc1d155c11c4c4cee86ba13815ccf06d2b [formerly 1860edaccf]
Former-commit-id: e94a9e94804fccea66d9f57673c26ac54dd0e13d
2022-06-20 02:11:33 -04:00
RandyTheSilly 2e2404636b Corrected installed size for Debian/Termux packages
Former-commit-id: 6b15b2962add508a3e52e4301080143188641a7c [formerly abe9fed749]
Former-commit-id: 76bbde9f50cbf2bb662d93c537dee25ad6b03c64
2022-06-20 02:02:07 -04:00
RandyTheSilly 6cb5bd405f Update README.md
Former-commit-id: 38d003983687f031ef2dc50122db52429d0cd340 [formerly 71662a53f1]
Former-commit-id: cb658bef3fdf54ed6dfb546468368113a4660518
2022-06-20 01:12:47 -04:00
RandyTheSilly c93965c9a5 Clarify FreeBSD support in version screen
Former-commit-id: 92ef1fe11f9c5818e7356f475b93ade6af37e4b2 [formerly 99282ea5b0]
Former-commit-id: d072d2719fc308239efde87f0547548e568ccd1c
2022-06-20 00:31:08 -04:00
RandyTheSilly d685e07d32 Clarify that the Fedora package must be built using Fedora
Former-commit-id: 872539a685e693ad39e98dc7147fb03b14b03799 [formerly 4b77990d97]
Former-commit-id: fb405f8b9845c1b14def2e4be7a865e158937042
2022-06-20 00:26:21 -04:00
RandyTheSilly 192f58fef5 Replace claims of Red Hat support with Fedora, as testing is not done with other Red Hat distributions
Former-commit-id: 8b8a719d2ab61fb3071d7977876edb406a6de322 [formerly afe05cf76f]
Former-commit-id: d5e6f980a9f354d2202ae5c9cfa275eca3eb337d
2022-06-20 00:16:06 -04:00
RandyTheSilly 106f22f757 Cleaned up ascii art in banner
Former-commit-id: 3738bcb955c614a67f95c459888d8f3dfdd20444 [formerly f2d704a447]
Former-commit-id: f10b328ca54dc3114f3c1611a59da6bb52247a24
2022-06-19 23:28:46 -04:00
RandyTheSilly ae414dadda Added banner to README.md
Former-commit-id: 02625dc2573dc43cceab4a5d8aba597f64f39039 [formerly 3e8ab76db7]
Former-commit-id: 1c20fe718096e7b8106b42c4ebe687d77c004582
2022-06-19 23:11:56 -04:00
RandyTheSilly c9e99732a2 Replaced 100h mascot with banner
Former-commit-id: 7334c3e7b066816a68858f051781a4c80b02b99c [formerly be0c226e15]
Former-commit-id: bad996992a2b239424cffd8bd1649d638082e229
2022-06-19 23:06:36 -04:00
RandyTheSilly fc37368447 Replaced 400h mascot image with 100h mascot image
Former-commit-id: b6a210e20f3c04806775cabd9abde4fe9a9506aa [formerly 61525bd81e]
Former-commit-id: 98835f143f3857377a8df2899d0123e5e8053fdd
2022-06-19 22:21:01 -04:00
RandyTheSilly ca2bc98735 Added 400 pixel tall version of sshyp mascot image
Former-commit-id: 157fdf477353d9134c8a80efbcfc1fd769c0c484 [formerly 21205f48dd]
Former-commit-id: 4fc37ca74a4c4ac1cc8f11edd1e1595c9b0e6253
2022-06-19 22:19:20 -04:00
RandyTheSilly f44391612d Added full-resolution mascot image
Former-commit-id: 86c57c8f87
2022-06-19 22:12:44 -04:00
12 changed files with 412 additions and 347 deletions
+72
View File
@@ -0,0 +1,72 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ "main" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "main" ]
schedule:
- cron: '34 22 * * 5'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'python' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
Regular → Executable
View File
Regular → Executable
+21 -24
View File
@@ -1,12 +1,16 @@
# sshyp ![sshyp](https://github.com/rwinkhart/sshyp-labs/blob/main/extra/artwork/sshyp-banner.png)
A very simple self-hosted, synchronized password manager for UNIX(-like) systems (currently Haiku/FreeBSD/Linux). Alternative to (and compatible with) pass/password-store.
Compatible with entries created by pass/password-store. [![CodeQL](https://github.com/rwinkhart/sshyp/actions/workflows/codeql-analysis.yml/badge.svg?branch=main)](https://github.com/rwinkhart/sshyp/actions/workflows/codeql-analysis.yml)
The only password-store compatible CLI password manager available for Haiku. sshyp is a very simple self-hosted, synchronized password manager for UNIX(-like) systems (currently Haiku/FreeBSD/Linux).
"sshyp" stands for "sshync passwords", or "ssh sync passwords". sshyp is compatible with entries created by pass/password-store, as its original goal was to be like pass/password-store, but far more user-friendly to synchronize with a self-hosted server.
"sshync" is the custom syncing backend (based on sftp) used by "sshyp".
sshyp is (as of writing) the only password-store compatible CLI password manager available for Haiku.
sshyp makes use of a custom sftp wrapper, called sshync (ssh+sync), to reliably sync user entries with a local or remote server.
The name "sshyp" is a combination of its syncing library, "sshync", and "passwords".
# WARNING # WARNING
It is your responsibility to assess the security and stability of "sshyp" before using it and ensure it meets your needs. It is your responsibility to assess the security and stability of "sshyp" before using it and ensure it meets your needs.
@@ -21,12 +25,12 @@ What sshyp can do:
- securely manage a collection of encrypted passwords and notes via CLI - securely manage a collection of encrypted passwords and notes via CLI
- generate new, secure passwords to the user's choice in length and complexity - generate new, secure passwords to the user's choice in length and complexity
- securely sync said passwords and notes seamlessly between devices - securely sync said passwords and notes seamlessly between devices
- utilize [extensions](https://github.com/rwinkhart/sshyp-labs) to interact with your entries is additional ways (such as generating TOTP keys)
- everything above with entries created by pass/password-store! - everything above with entries created by pass/password-store!
- everything above on Haiku, FreeBSD, Linux, and Termux (an Android terminal emulator)! - everything above on Haiku, FreeBSD, Linux, and Termux (an Android terminal emulator)!
What sshyp will likely do: What sshyp will likely do:
- (planned v1.1.0) 2FA/MFA management (was previously not on the roadmap, but could be beneficial in reducing phone usage)
- (planned v1.2.0) everything it already does, but also in a GUI for Linux/Haiku - (planned v1.2.0) everything it already does, but also in a GUI for Linux/Haiku
What sshyp definitely won't do: What sshyp definitely won't do:
@@ -34,19 +38,13 @@ What sshyp definitely won't do:
- Non-UNIX(-like) support, e.g. Windows (I'd be happy to link to third-party ports, if someone were to make them) - Non-UNIX(-like) support, e.g. Windows (I'd be happy to link to third-party ports, if someone were to make them)
# Installation # Installation
Arch Linux (all ISAs) Please see the [installation guide](https://github.com/rwinkhart/sshyp/wiki/Installation) in the sshyp wiki for directions specific to your distribution/OS.
sshyp releases are available in the Arch User Repository as 'sshyp'. Pre-built packages exist for Haiku, FreeBSD, Arch Linux, Debian/Ubuntu Linux, Fedora Linux, and Termux. These can be downloaded from the releases page.
Install with your preferred AUR helper or use: Requests for additional distribution/OS support can be filed as issues.
``` Extensions are available in the [sshyp-labs](https://github.com/rwinkhart/sshyp-labs) repository.
git clone https://aur.archlinux.org/sshyp.git
cd sshyp
makepkg -si
```
Pre-built packages exist for Haiku, FreeBSD, Arch Linux, Debian Linux, Red Hat Linux, and Termux. These can be downloaded from the releases page.
# Building # Building
Since sshyp is written entirely in Python, it doesn't need to be compiled. It does, however, need to be packaged for installation. Since sshyp is written entirely in Python, it doesn't need to be compiled. It does, however, need to be packaged for installation.
@@ -59,13 +57,13 @@ cd sshyp
./package.sh ./package.sh
``` ```
The packaging script has been tested on Arch Linux with "dpkg" as a dependency for Debian and Termux packaging and "freebsd-pkg" as a dependency for FreeBSD packaging. The packaging script has been tested on Arch Linux with "dpkg" as a dependency for Debian/Ubuntu and Termux packaging and "freebsd-pkg" as a dependency for FreeBSD packaging.
Haiku packaging must be done from within Haiku. Haiku and Fedora packaging must be done on their own respective distributions.
The AUR version and the packages attatched to the release tags were already packaged using this script. The AUR version and the packages attatched to the release tags were already packaged using this script.
Currently, the script can create packages for Haiku, FreeBSD, Arch Linux (PKGBUILD), Debian Linux, Red Hat Linux, Termux, and generic. Packaging for other systems coming soon. Currently, the script can create packages for Haiku, FreeBSD, Arch Linux (PKGBUILD), Debian/Ubuntu Linux, Fedora Linux, Termux, and generic.
# Usage # Usage
Upon initial installation (on both the server and client devices), be sure to run: Upon initial installation (on both the server and client devices), be sure to run:
@@ -74,8 +72,7 @@ Upon initial installation (on both the server and client devices), be sure to ru
sshyp tweak sshyp tweak
``` ```
This command will allow you to configure the settings necessary for sshyp to function. This command will allow you to configure the settings necessary for sshyp to function. To ensure configuration compatibility, it is a good idea to run 'sshyp tweak' after each major update.
To ensure configuration compatibility, it is a good idea to run 'sshyp tweak' after each major update.
Please note that decrypting and reading entries is disabled on server devices for security reasons. Only devices configured as clients can use the gpg key to decrypt entries. Please note that decrypting and reading entries is disabled on server devices for security reasons. Only devices configured as clients can use the gpg key to decrypt entries.
@@ -94,10 +91,10 @@ man sshyp
# Roadmap # Roadmap
Short-term Goals: Short-term Goals:
- 2FA/MFA management - create minimal GUI app (Linux x86_64, Linux aarch64)
- create minimal GUI apps (Linux x86_64, Linux aarch64)
- various optimizations/bug fixes - various optimizations/bug fixes
Long-term Goals: Long-term Goals:
- a fun surprise
- seize the thrones, shear the humans - seize the thrones, shear the humans
+6 -5
View File
@@ -10,7 +10,7 @@ from sys import exit as s_exit
# utility functions # utility functions
def get_titles_mods(_directory, _destination, _user_data): def get_titles_mods(_directory, _destination, _user_data): # fetches and returns lists of titles and their mod times
_title_list, _mod_list = [], [] _title_list, _mod_list = [], []
Path(path.expanduser('~/.config/sshync')).mkdir(0o700, parents=True, exist_ok=True) # create config directory Path(path.expanduser('~/.config/sshync')).mkdir(0o700, parents=True, exist_ok=True) # create config directory
# local fetching # local fetching
@@ -37,7 +37,8 @@ def get_titles_mods(_directory, _destination, _user_data):
return _title_list, _mod_list return _title_list, _mod_list
def sort_titles_mods(_list_1, _list_2): def sort_titles_mods(_list_1, _list_2): # creates and returns two lists (of titles and mod times) generated by sorting
# information from two different, provided lists
_title_list_2_sorted, _mod_list_2_sorted = [], [] _title_list_2_sorted, _mod_list_2_sorted = [], []
# title sorting # title sorting
for _title in _list_1[0]: for _title in _list_1[0]:
@@ -52,12 +53,12 @@ def sort_titles_mods(_list_1, _list_2):
return _title_list_2_sorted, _mod_list_2_sorted return _title_list_2_sorted, _mod_list_2_sorted
def make_profile(_profile_dir, _local_dir, _remote_dir, _identity, _ip, _port, _user): def make_profile(_profile_dir, _local_dir, _remote_dir, _identity, _ip, _port, _user): # creates a sshync job profile
open(_profile_dir, 'w').write(_user + '\n' + _ip + '\n' + _port + '\n' + _local_dir + '\n' + _remote_dir + '\n' + open(_profile_dir, 'w').write(_user + '\n' + _ip + '\n' + _port + '\n' + _local_dir + '\n' + _remote_dir + '\n' +
_identity + '\n') _identity + '\n')
def get_profile(_profile_dir): def get_profile(_profile_dir): # returns a list of data read from a sshync job profile
try: try:
_profile_data = open(_profile_dir).readlines() _profile_data = open(_profile_dir).readlines()
except (FileNotFoundError, IndexError): except (FileNotFoundError, IndexError):
@@ -74,7 +75,7 @@ def get_profile(_profile_dir):
return _user, _ip, _port, _local_dir, _remote_dir, _identity return _user, _ip, _port, _local_dir, _remote_dir, _identity
def run_profile(_profile_dir): def run_profile(_profile_dir): # runs a sshync job profile
_user_data = get_profile(_profile_dir) _user_data = get_profile(_profile_dir)
_remote_titles_mods_saver = get_titles_mods(_user_data[4], 'r', _user_data) # saved to prevent re-walking directory _remote_titles_mods_saver = get_titles_mods(_user_data[4], 'r', _user_data) # saved to prevent re-walking directory
_index_l = sort_titles_mods(_remote_titles_mods_saver, get_titles_mods(_user_data[3], 'l', _user_data)) _index_l = sort_titles_mods(_remote_titles_mods_saver, get_titles_mods(_user_data[3], 'l', _user_data))
+190 -153
View File
@@ -4,22 +4,22 @@
from os import environ, listdir, path, remove, system, uname, walk from os import environ, listdir, path, remove, system, uname, walk
from pathlib import Path from pathlib import Path
import random from random import randint, SystemRandom
from shutil import get_terminal_size, move, rmtree from shutil import get_terminal_size, move, rmtree
import sshync import sshync
import string import string
from subprocess import Popen, PIPE, STDOUT from subprocess import CalledProcessError, Popen, PIPE, run
from sys import argv, exit as s_exit from sys import argv, exit as s_exit
from textwrap import fill from textwrap import fill
# utility functions # BELOW - utility functions
def entry_list_gen(): # generates and prints a list of all folders and entries def entry_list_gen(_directory=path.expanduser('~/.local/share/sshyp/')): # generates and prints full entry list
print('\n\u001b[38;5;0;48;5;15msshyp entries:\u001b[0m\n') print('\n\u001b[38;5;0;48;5;15msshyp entries:\u001b[0m\n')
_entry_list, _color_alternator = [], 1 _entry_list, _color_alternator = [], 1
for _entry in sorted(listdir(path.expanduser('~/.password-pasture'))): for _entry in sorted(listdir(_directory)):
if Path(path.expanduser(f"~/.password-pasture/{_entry}")).is_file(): if Path(f"{_directory}{_entry}").is_file():
if _color_alternator == 1: if _color_alternator == 1:
_entry_list.append(f"{_entry.replace('.gpg', '')}") _entry_list.append(f"{_entry.replace('.gpg', '')}")
_color_alternator = 2 _color_alternator = 2
@@ -35,12 +35,12 @@ def entry_list_gen(): # generates and prints a list of all folders and entries
print(fill(' '.join(_entry_list), width=_width) + '\n') print(fill(' '.join(_entry_list), width=_width) + '\n')
except ValueError: except ValueError:
pass pass
for _root, _directories, _files in walk(path.expanduser('~/.password-pasture')): for _root, _directories, _files in walk(_directory):
for _dir in sorted(_directories): for _dir in sorted(_directories):
_inner_dir = f"{_root.replace(path.expanduser('~/.password-pasture'), '')}/{_dir}" _inner_dir = f"{_root.replace(_directory, '')}/{_dir}"
print(f"\u001b[38;5;15;48;5;238m{_inner_dir}/\u001b[0m") print(f"\u001b[38;5;15;48;5;238m{_inner_dir}/\u001b[0m")
_entry_list, _color_alternator = [], 1 _entry_list, _color_alternator = [], 1
for _s_root, _s_directories, _s_files in walk(path.expanduser(f"~/.password-pasture{_inner_dir}")): for _s_root, _s_directories, _s_files in walk(f"{_directory[:-1]}{_inner_dir}"):
for _entry in sorted(_s_files): for _entry in sorted(_s_files):
if _color_alternator == 1: if _color_alternator == 1:
_entry_list.append(f"{_entry.replace('.gpg', '')}") _entry_list.append(f"{_entry.replace('.gpg', '')}")
@@ -59,20 +59,20 @@ def entry_list_gen(): # generates and prints a list of all folders and entries
print('\u001b[38;5;9m-empty directory-\u001b[0m\n') print('\u001b[38;5;9m-empty directory-\u001b[0m\n')
def entry_reader(_decrypted_entry): def entry_reader(_decrypted_entry): # displays the contents of an entry in a readable format
_entry_lines, _notes_flag = open(_decrypted_entry, 'r').readlines(), 0 _entry_lines, _notes_flag = open(_decrypted_entry, 'r').readlines(), 0
print() print()
for _num in range(len(_entry_lines)): for _num in range(len(_entry_lines)):
try: try:
if _num == 0 and _entry_lines[1] != '\n': if _num == 0 and _entry_lines[1] != '\n':
print(f"\u001b[38;5;15;48;5;238musername:\u001b[0m\n{_entry_lines[1]}") print(f"\u001b[38;5;15;48;5;238musername:\u001b[0m\n{_entry_lines[1].strip()}\n")
elif _num == 1 and _entry_lines[0] != '\n': elif _num == 1 and _entry_lines[0] != '\n':
print(f"\u001b[38;5;15;48;5;238mpassword:\u001b[0m\n{_entry_lines[0]}") print(f"\u001b[38;5;15;48;5;238mpassword:\u001b[0m\n{_entry_lines[0].strip()}\n")
elif _num == 2 and _entry_lines[2] != '\n': elif _num == 2 and _entry_lines[2] != '\n':
print(f"\u001b[38;5;15;48;5;238murl:\u001b[0m\n{_entry_lines[_num]}") print(f"\u001b[38;5;15;48;5;238murl:\u001b[0m\n{_entry_lines[_num].strip()}\n")
elif _num >= 3 and _entry_lines[_num] != '\n' and _notes_flag != 1: elif _num >= 3 and _entry_lines[_num] != '\n' and _notes_flag != 1:
_notes_flag = 1 _notes_flag = 1
print('\u001b[38;5;15;48;5;238mnotes:\u001b[0m\n' + _entry_lines[_num].replace('\n', '')) print(f"\u001b[38;5;15;48;5;238mnotes:\u001b[0m\n{_entry_lines[_num].strip()}")
elif _num >= 3 and _notes_flag == 1: elif _num >= 3 and _notes_flag == 1:
print(_entry_lines[_num].replace('\n', '')) print(_entry_lines[_num].replace('\n', ''))
if _notes_flag == 1: if _notes_flag == 1:
@@ -91,12 +91,10 @@ def replace_line(file_name, line_num, text): # replaces text in a given line wi
open(file_name, 'w').writelines(_lines) open(file_name, 'w').writelines(_lines)
def entry_name_fetch(_entry_name_location): def entry_name_fetch(_entry_name_location): # fetches and returns entry name from user input or from provided argument
if type(_entry_name_location) == str: if type(_entry_name_location) == str:
entry_list_gen() entry_list_gen()
_entry_name = str(input(_entry_name_location)) _entry_name = str(input(_entry_name_location))
elif _entry_name_location == 0:
_entry_name = argument
else: else:
_entry_name_split = argument.split(' ') _entry_name_split = argument.split(' ')
del _entry_name_split[:_entry_name_location] del _entry_name_split[:_entry_name_location]
@@ -107,22 +105,22 @@ def entry_name_fetch(_entry_name_location):
return _entry_name return _entry_name
def shm_gen(): # generates a random temporary folder for security and returns random names for the folder and temp file def shm_gen(_tmp_dir=path.expanduser('~/.config/sshyp/tmp/')): # creates a temporary directory for entry editing
_shm_folder_gen = ''.join(random.SystemRandom().choice(string.ascii_letters + string.digits) _shm_folder_gen = ''.join(SystemRandom().choice(string.ascii_letters + string.digits)
for _ in range(random.randint(10, 30))) for _ in range(randint(10, 30)))
_shm_entry_gen = ''.join(random.SystemRandom().choice(string.ascii_letters + string.digits) _shm_entry_gen = ''.join(SystemRandom().choice(string.ascii_letters + string.digits)
for _ in range(random.randint(10, 30))) for _ in range(randint(10, 30)))
Path(tmp_dir + _shm_folder_gen).mkdir(0o700) Path(_tmp_dir + _shm_folder_gen).mkdir(0o700)
return _shm_folder_gen, _shm_entry_gen return _shm_folder_gen, _shm_entry_gen
def pass_gen(): def pass_gen(): # generates and returns a random password based on user-specified options
def _pass_gen_function(__complexity, __length): def _pass_gen_function(__complexity, __length):
if __complexity.lower() == 's': if __complexity.lower() == 's':
__character_pool = string.ascii_letters + string.digits __character_pool = string.ascii_letters + string.digits
else: else:
__character_pool = string.ascii_letters + string.digits + string.punctuation __character_pool = string.ascii_letters + string.digits + string.punctuation
__gen = ''.join(random.SystemRandom().choice(__character_pool) for _ in range(__length)) __gen = ''.join(SystemRandom().choice(__character_pool) for _ in range(__length))
__min_special, __special = round(.2 * __length), 0 __min_special, __special = round(.2 * __length), 0
for __character in __gen: for __character in __gen:
if not __character.isalpha(): if not __character.isalpha():
@@ -130,7 +128,12 @@ def pass_gen():
if __special < __min_special: if __special < __min_special:
__gen = _pass_gen_function(__complexity, __length) __gen = _pass_gen_function(__complexity, __length)
return __gen return __gen
_length = int(input('password length: ')) try:
_length = int(input('password length: '))
except ValueError:
print(f"\n\u001b[38;5;9merror: a non-integer value was input for password length\u001b[0m\n")
_gen = pass_gen()
return _gen
if _length > 840: if _length > 840:
_length = 840 _length = 840
print('\n\u001b[38;5;9mpassword length has been limited to the maximum of 840 characters\u001b[0m\n') print('\n\u001b[38;5;9mpassword length has been limited to the maximum of 840 characters\u001b[0m\n')
@@ -139,57 +142,59 @@ def pass_gen():
return _gen return _gen
def encrypt(_shm_folder, _shm_entry, _location): def encrypt(_shm_folder, _shm_entry, _location): # encrypts an entry and cleans up the temporary files
system(f"{gpg} -qr {str(gpg_id)} -e '{tmp_dir}{_shm_folder}/{_shm_entry}'") system(f"{gpg} -qr {str(gpg_id)} -e '{tmp_dir}{_shm_folder}/{_shm_entry}'")
move(f"{tmp_dir}{_shm_folder}/{_shm_entry}.gpg", f"{directory}{_location}.gpg") move(f"{tmp_dir}{_shm_folder}/{_shm_entry}.gpg", f"{directory}{_location}.gpg")
rmtree(f"{tmp_dir}{_shm_folder}") rmtree(f"{tmp_dir}{_shm_folder}")
def decrypt(_entry_dir, _shm_folder, _shm_entry): def decrypt(_entry_dir, _shm_folder, _shm_entry, _gpg_command, _tmp_dir=path.expanduser('~/.config/sshyp/tmp/')):
# decrypts an entry to a temporary directory
if _shm_folder == 0 and _shm_entry == 0: if _shm_folder == 0 and _shm_entry == 0:
_command = f"{gpg} -qd --output /dev/null {path.expanduser('~/.config/sshyp/lock.gpg')}" _command = f"{_gpg_command} -qd --output /dev/null {path.expanduser('~/.config/sshyp/lock.gpg')}"
else: else:
_command = f"{gpg} -qd --output {tmp_dir}{_shm_folder}/{_shm_entry} {_entry_dir}.gpg" _command = f"{_gpg_command} -qd --output {_tmp_dir}{_shm_folder}/{_shm_entry} {_entry_dir}.gpg"
_output = Popen(_command, shell=True, stdout=PIPE, stderr=STDOUT, close_fds=True) try:
_block = _output.communicate()[0].strip() run(_command, shell=True, stderr=PIPE, check=True, close_fds=True)
if _output.returncode == 2: except CalledProcessError:
print(f"\n\u001b[38;5;9merror: could not decrypt - ensure the correct gpg key is present\u001b[0m\n") print(f"\n\u001b[38;5;9merror: could not decrypt - ensure the correct gpg key is present\u001b[0m\n")
s_exit(1) s_exit(1)
def edit_note(_shm_folder, _shm_entry): def edit_note(_shm_folder, _shm_entry): # edits the note attached to an entry
lines = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}").readlines() lines = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}").readlines()
open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'w').writelines(lines[0:3]) open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'w').writelines(lines[0:3])
open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n", 'w').writelines(lines[3:]) open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n", 'w').writelines(lines[3:])
system(f"nano {tmp_dir}{_shm_folder}/{_shm_entry}-n") system(f"{editor} {tmp_dir}{_shm_folder}/{_shm_entry}-n")
edit_notes = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n").read() edit_notes = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n").read()
open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'a').write(edit_notes) open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'a').write(edit_notes)
def copy_name_check(_port, _username_ssh, _ip, _client_device_name): def copy_name_check(_port, _username_ssh, _ip, _client_device_name):
# attempts to connect to the user's server via ssh to register the device for syncing
_command = f"ssh -o ConnectTimeout=3 -i '{path.expanduser('~/.ssh/sshyp')}' -p {_port} {_username_ssh}@{_ip} " \ _command = f"ssh -o ConnectTimeout=3 -i '{path.expanduser('~/.ssh/sshyp')}' -p {_port} {_username_ssh}@{_ip} " \
f"\"touch '/home/{_username_ssh}/.config/sshyp/devices/{_client_device_name}'\"" f"\"touch '/home/{_username_ssh}/.config/sshyp/devices/{_client_device_name}'\""
_output = Popen(_command, shell=True, stdout=PIPE, stderr=STDOUT, close_fds=True) try:
_block = _output.communicate()[0].strip() run(_command, shell=True, stderr=PIPE, check=True, close_fds=True)
if _output.returncode != 0: except CalledProcessError:
print('\n\u001b[38;5;9mwarning: ssh connection could not be made - ensure the public key (~/.ssh/sshyp.pub) is ' print('\n\u001b[38;5;9mwarning: ssh connection could not be made - ensure the public key (~/.ssh/sshyp.pub) is '
'registered on the remote server and that the entered ip, port, and username are correct\n\nsyncing ' 'registered on the remote server and that the entered ip, port, and username are correct\n\nsyncing '
'functionality will be disabled until this is addressed\u001b[0m\n') 'functionality will be disabled until this is addressed\u001b[0m\n')
open(path.expanduser('~/.config/sshyp/ssh-error'), 'w').write('1') open(path.expanduser('~/.config/sshyp/ssh-error'), 'w').write('1')
return 1 return 1
else: open(path.expanduser('~/.config/sshyp/ssh-error'), 'w').write('0')
open(path.expanduser('~/.config/sshyp/ssh-error'), 'w').write('0') return 0
return 0
# argument-specific functions # BELOW - argument-specific functions
def tweak(): # runs configuration wizard def tweak(): # runs configuration wizard
_divider = f"\n{'=' * (get_terminal_size()[0] - int((.5 * get_terminal_size()[0])))}\n\n"
# storage/config directory creation # storage/config directory creation
Path(path.expanduser('~/.password-pasture')).mkdir(0o700, parents=True, exist_ok=True) Path(path.expanduser('~/.local/share/sshyp')).mkdir(0o700, parents=True, exist_ok=True)
Path(path.expanduser('~/.config/sshyp/devices')).mkdir(0o700, parents=True, exist_ok=True) Path(path.expanduser('~/.config/sshyp/devices')).mkdir(0o700, parents=True, exist_ok=True)
if not Path(f"{path.expanduser('~/.config/sshyp/tmp')}").exists(): if not Path(f"{path.expanduser('~/.config/sshyp/tmp')}").exists():
if uname()[0] == 'Haiku': if uname()[0] == 'Haiku' or uname()[0] == 'FreeBSD':
system(f"ln -s /tmp {path.expanduser('~/.config/sshyp/tmp')}") system(f"ln -s /tmp {path.expanduser('~/.config/sshyp/tmp')}")
elif Path("/data/data/com.termux").exists(): elif Path("/data/data/com.termux").exists():
system(f"ln -s '/data/data/com.termux/files/usr/tmp' {path.expanduser('~/.config/sshyp/tmp')}") system(f"ln -s '/data/data/com.termux/files/usr/tmp' {path.expanduser('~/.config/sshyp/tmp')}")
@@ -200,7 +205,8 @@ def tweak(): # runs configuration wizard
if _device_type.lower() == 's': if _device_type.lower() == 's':
open(path.expanduser('~/.config/sshyp/sshyp-device'), 'w').write(_device_type) open(path.expanduser('~/.config/sshyp/sshyp-device'), 'w').write(_device_type)
Path(path.expanduser('~/.config/sshyp/deleted')).mkdir(0o700, parents=True, exist_ok=True) Path(path.expanduser('~/.config/sshyp/deleted')).mkdir(0o700, parents=True, exist_ok=True)
print('\nmake sure the ssh service is running and properly configured\n\nconfiguration complete\n') print(f"\n\u001b[4;1mmake sure the ssh service is running and properly configured\u001b[0m\n"
f"{_divider}configuration complete\n")
s_exit(0) s_exit(0)
else: else:
# device type configuration # device type configuration
@@ -208,57 +214,75 @@ def tweak(): # runs configuration wizard
open(path.expanduser('~/.config/sshyp/sshyp-device'), 'w').write(_device_type) open(path.expanduser('~/.config/sshyp/sshyp-device'), 'w').write(_device_type)
# gpg configuration # gpg configuration
_gpg_id = input('\nsshyp requires the use of a unique gpg key - do you already have one that you are ' _gpg_id = input(f"{_divider}sshyp requires the use of a unique gpg key - use an (e)xisting key or (g)enerate "
'willing to use? (y/N) ') f"a new one? (E/g) ")
if _gpg_id.lower() != 'y': if _gpg_id.lower() != 'g':
print('\na unique gpg key has been generated for you.') system(f"{gpg} -k")
system(f"{gpg} --full-generate-key") _gpg_id = str(input('gpg key id: '))
_gpg_id = str(input('\nplease input the id of your gpg key: ')) else:
open(path.expanduser('~/.config/sshyp/sshyp-gpg'), 'w').write(_gpg_id + '\n') print('\na unique gpg key is being generated for you...')
if not Path(path.expanduser('~/.config/sshyp/gpg-gen')).is_file():
open(path.expanduser('~/.config/sshyp/gpg-gen'), 'w').write('Key-Type: 1\nKey-Length: 4096\nKey-Usage:\
sign encrypt\nName-Real: sshyp\nName-Comment: password manager encryption\nName-Email: \
https://github.com/rwinkhart/sshyp\nExpire-Date: 0')
if uname()[0] == 'Haiku':
run(gpg + ' --batch --generate-key --passphrase ' + "'" +
input('\ngpg passphrase: ') + "'" + " '" +
path.expanduser('~/.config/sshyp/gpg-gen') + "'", shell=True)
else:
run(f"{gpg} --batch --generate-key '{path.expanduser('~/.config/sshyp/gpg-gen')}'", shell=True)
remove(path.expanduser('~/.config/sshyp/gpg-gen'))
_gpg_id = run(f"{gpg} -k", shell=True, stdout=PIPE, text=True).stdout.split('\n')[-4].strip()
# lock file generation # lock file generation
if Path(path.expanduser('~/.config/sshyp/lock.gpg')).is_file():
remove(path.expanduser('~/.config/sshyp/lock.gpg'))
open(path.expanduser('~/.config/sshyp/lock'), 'w') open(path.expanduser('~/.config/sshyp/lock'), 'w')
system(f"{gpg} -qr {str(_gpg_id)} -e {path.expanduser('~/.config/sshyp/lock')}") system(f"{gpg} -qr {str(_gpg_id)} -e {path.expanduser('~/.config/sshyp/lock')}")
remove(f"{path.expanduser('~/.config/sshyp')}/lock") remove(path.expanduser('~/.config/sshyp/lock'))
# ssh key configuration # ssh key configuration
_ssh_gen = (input('\nmake sure the ssh service on the remote server is running and properly configured' _ssh_gen = (input(f"{_divider}make sure the ssh service on the remote server is running and properly "
'\n\nsync support requires a unique ssh key - would you like to have this automatically ' f"configured\n\nsync support requires a unique ssh key - would you like to have this "
'generated? (Y/n) ')) f"automatically generated? (Y/n) "))
if _ssh_gen.lower() != 'n': if _ssh_gen.lower() != 'n':
if uname()[0] == 'Haiku': if uname()[0] == 'Haiku':
Path(f"{path.expanduser('~')}/.ssh").mkdir(0o700, exist_ok=True) Path(f"{path.expanduser('~')}/.ssh").mkdir(0o700, exist_ok=True)
system('ssh-keygen -t ed25519 -f ~/.ssh/sshyp') system('ssh-keygen -t ed25519 -f ~/.ssh/sshyp')
else: else:
print(f"\nensure that the key file you are using is located at {path.expanduser('~/.ssh/sshyp')}") print(f"\n\u001b[4;1mensure that the key file you are using is located at "
f"{path.expanduser('~/.ssh/sshyp')}\u001b[0m")
# ssh ip+port configuration # ssh ip+port configuration
_ip_port = str(input('\nexample input: 10.10.10.10:9999\n\nip and ssh port of the remote server: ')) _ip_port = str(input(f"{_divider}example input: 10.10.10.10:9999\n\nip and ssh port of the remote server: "))
_ip, _sep, _port = _ip_port.partition(':') _ip, _sep, _port = _ip_port.partition(':')
# ssh user configuration # ssh user configuration
_username_ssh = str(input('\nusername of the remote server: ')) _username_ssh = str(input('\nusername of the remote server: '))
print(f"\nentry directory: /home/{_username_ssh}/.password-pasture/")
# sshyp-only data storage
open(path.expanduser('~/.config/sshyp/sshyp-data'), 'w')\
.write(_gpg_id + '\n' + input(f"{_divider}example input: vim\n\npreferred text editor: "))
# sshync profile generation # sshync profile generation
sshync.make_profile(path.expanduser('~/.config/sshyp/sshyp.sshync'), sshync.make_profile(path.expanduser('~/.config/sshyp/sshyp.sshync'),
path.expanduser('~/.password-pasture/'), f"/home/{_username_ssh}/.password-pasture/", path.expanduser('~/.local/share/sshyp/'), f"/home/{_username_ssh}/.local/share/sshyp/",
path.expanduser('~/.ssh/sshyp'), _ip, _port, _username_ssh) path.expanduser('~/.ssh/sshyp'), _ip, _port, _username_ssh)
# device name configuration # device name configuration
for _name in listdir(path.expanduser('~/.config/sshyp/devices')): # remove existing device name for _name in listdir(path.expanduser('~/.config/sshyp/devices')): # remove existing device name
remove(f"{path.expanduser('~/.config/sshyp/devices/')}{_name}") remove(f"{path.expanduser('~/.config/sshyp/devices/')}{_name}")
print('\n\u001b[4;1mimportant:\u001b[0m This name \u001b[4;1mmust\u001b[0m be unique amongst your client ' print(f"{_divider}\u001b[4;1mimportant:\u001b[0m this name \u001b[4;1mmust\u001b[0m be unique amongst your "
'devices\n\nthis is used to keep track of which devices have up-to-date databases.\n') f"client devices\n\nthis is used to keep track of which devices have up-to-date databases\n")
_client_device_name = str(input('device name: ')) _client_device_name = str(input('device name: '))
open(f"{path.expanduser('~/.config/sshyp/devices/')}{_client_device_name}", 'w') open(f"{path.expanduser('~/.config/sshyp/devices/')}{_client_device_name}", 'w')
copy_name_check(_port, _username_ssh, _ip, _client_device_name) copy_name_check(_port, _username_ssh, _ip, _client_device_name)
print('\nconfiguration complete\n') print(f"{_divider}configuration complete\n")
def print_info(): # prints help text based on argument def print_info(): # prints help text based on argument
if argument_list[0] == 'help' or argument_list[0] == '--help' or argument_list[0] == '-h': if argument_list[1] == 'help' or argument_list[1] == '--help' or argument_list[1] == '-h':
print('\n\u001b[1msshyp copyright (c) 2021-2022 randall winkhart\u001b[0m\n') print('\n\u001b[1msshyp copyright (c) 2021-2022 randall winkhart\u001b[0m\n')
print("this is free software, and you are welcome to redistribute it under certain conditions;\nthis program " print("this is free software, and you are welcome to redistribute it under certain conditions;\nthis program "
"comes with absolutely no warranty;\ntype `sshyp license' for details") "comes with absolutely no warranty;\ntype `sshyp license' for details")
@@ -292,9 +316,9 @@ def print_info(): # prints help text based on argument
print('gen:') print('gen:')
print(' update/-u generate a password for an existing entry\n') print(' update/-u generate a password for an existing entry\n')
print("\u001b[1mtip:\u001b[0m you can quickly read an entry with 'sshyp /<entry name>'") print("\u001b[1mtip:\u001b[0m you can quickly read an entry with 'sshyp /<entry name>'")
elif argument_list[0] == 'version' or argument_list[0] == '-v': elif argument_list[1] == 'version' or argument_list[1] == '-v':
print('\nsshyp is a simple, self-hosted, sftp-synchronized password manager\nfor unix(-like) systems (currently' print('\nsshyp is a simple, self-hosted, sftp-synchronized password manager\nfor unix(-like) systems (haiku/'
' haiku/linux/termux)\n\nsshyp is a viable alternative to (and compatible with) pass/password-store\n') 'freebsd/linux/termux)\n\nsshyp is a viable alternative to (and compatible with) pass/password-store\n')
print(" .. \u001b[38;5;9m♥♥ ♥♥\u001b[0m ..\n .''.''/()\\ \u001b[38;5;13m" print(" .. \u001b[38;5;9m♥♥ ♥♥\u001b[0m ..\n .''.''/()\\ \u001b[38;5;13m"
"♥♥♥♥♥♥♥\u001b[0m /()\\''.''.\n * : \u001b[38;5;9m♥♥♥♥♥\u001b[0m : *" "♥♥♥♥♥♥♥\u001b[0m /()\\''.''.\n * : \u001b[38;5;9m♥♥♥♥♥\u001b[0m : *"
"\n `..'..' \u001b[38;5;13m♥♥♥\u001b[0m `..'..'\n // \\\\ " "\n `..'..' \u001b[38;5;13m♥♥♥\u001b[0m `..'..'\n // \\\\ "
@@ -306,29 +330,29 @@ def print_info(): # prints help text based on argument
'randall winkhart\u001b[38;5;15;48;5;15m \u001b[38;5;7;48;5;8m/\u001b[0m') 'randall winkhart\u001b[38;5;15;48;5;15m \u001b[38;5;7;48;5;8m/\u001b[0m')
print('\u001b[38;5;7;48;5;8m/\u001b[38;5;15;48;5;15m ' print('\u001b[38;5;7;48;5;8m/\u001b[38;5;15;48;5;15m '
'\u001b[38;5;7;48;5;8m/\u001b[0m') '\u001b[38;5;7;48;5;8m/\u001b[0m')
print('\u001b[38;5;7;48;5;8m/\u001b[38;5;15;48;5;15m \u001b[38;5;15;48;5;8mversion 1.0.0' print('\u001b[38;5;7;48;5;8m/\u001b[38;5;15;48;5;15m \u001b[38;5;15;48;5;8mversion 1.1.0'
'\u001b[38;5;15;48;5;15m \u001b[38;5;7;48;5;8m/\u001b[0m') '\u001b[38;5;15;48;5;15m \u001b[38;5;7;48;5;8m/\u001b[0m')
print('\u001b[38;5;7;48;5;8m/\u001b[38;5;15;48;5;15m \u001b[38;5;15;48;5;8mthe polished trotters ' print('\u001b[38;5;7;48;5;8m/\u001b[38;5;15;48;5;15m \u001b[38;5;15;48;5;8mthe sheecrets '
'update\u001b[38;5;15;48;5;15m \u001b[38;5;7;48;5;8m/\u001b[0m') 'update\u001b[38;5;15;48;5;15m \u001b[38;5;7;48;5;8m/\u001b[0m')
print('\u001b[38;5;7;48;5;8m/\u001b[38;5;15;48;5;15m ' print('\u001b[38;5;7;48;5;8m/\u001b[38;5;15;48;5;15m '
'\u001b[38;5;7;48;5;8m/\u001b[0m') '\u001b[38;5;7;48;5;8m/\u001b[0m')
print('\u001b[38;5;7;48;5;8m<><><><><><><><><><><><><><><><><><><><><><><><><><><><>\u001b[0m\n') print('\u001b[38;5;7;48;5;8m<><><><><><><><><><><><><><><><><><><><><><><><><><><><>\u001b[0m\n')
print('see https://github.com/rwinkhart/sshyp for more information\n') print('see https://github.com/rwinkhart/sshyp for more information\n')
elif argument_list[0] == 'license': elif argument_list[1] == 'license':
print('\nThis program is free software: you can redistribute it and/or modify it under the terms of the GNU ' print('\nThis program is free software: you can redistribute it and/or modify it under the terms of the GNU '
'General\nPublic License as published by the Free Software Foundation, either version 3 of the License,' 'General\nPublic License as published by the Free Software Foundation, either version 3 of the License,'
'\nor (at your option) any later version.\n\nThis program is distributed in the hope that it will be ' '\nor (at your option) any later version.\n\nThis program is distributed in the hope that it will be '
'useful, but WITHOUT ANY WARRANTY;\nwithout even the implied warranty of MERCHANTABILITY or FITNESS FOR A' 'useful, but WITHOUT ANY WARRANTY;\nwithout even the implied warranty of MERCHANTABILITY or FITNESS FOR A'
' PARTICULAR PURPOSE.\nSee the GNU General Public License for more details.' ' PARTICULAR PURPOSE.\nSee the GNU General Public License for more details.'
'\n\nhttps://opensource.org/licenses/GPL-3.0\n') '\n\nhttps://opensource.org/licenses/GPL-3.0\n')
elif argument_list[0] == 'add': elif argument_list[1] == 'add':
print('\n\u001b[1musage:\u001b[0m sshyp add [flag [<entry name>]]\u001b[0m\n') print('\n\u001b[1musage:\u001b[0m sshyp add [flag [<entry name>]]\u001b[0m\n')
print('\u001b[1mflags:\u001b[0m') print('\u001b[1mflags:\u001b[0m')
print('add:') print('add:')
print(' password/-p add a password entry') print(' password/-p add a password entry')
print(' note/-n add a note entry') print(' note/-n add a note entry')
print(' folder/-f add a new folder for entries\n') print(' folder/-f add a new folder for entries\n')
elif argument_list[0] == 'edit': elif argument_list[1] == 'edit':
print('\n\u001b[1musage:\u001b[0m sshyp edit [flag [<entry name>]]\u001b[0m\n') print('\n\u001b[1musage:\u001b[0m sshyp edit [flag [<entry name>]]\u001b[0m\n')
print('\u001b[1mflags:\u001b[0m') print('\u001b[1mflags:\u001b[0m')
print('edit:') print('edit:')
@@ -337,7 +361,7 @@ def print_info(): # prints help text based on argument
print(' password/-p change the password of an entry') print(' password/-p change the password of an entry')
print(' url/-l change the url attached to an entry') print(' url/-l change the url attached to an entry')
print(' note/-n change the note attached to an entry\n') print(' note/-n change the note attached to an entry\n')
elif argument_list[0] == 'copy': elif argument_list[1] == 'copy':
print('\n\u001b[1musage:\u001b[0m sshyp copy [flag [<entry name>]]\u001b[0m\n') print('\n\u001b[1musage:\u001b[0m sshyp copy [flag [<entry name>]]\u001b[0m\n')
print('\u001b[1mflags:\u001b[0m') print('\u001b[1mflags:\u001b[0m')
print('copy:') print('copy:')
@@ -354,7 +378,7 @@ def no_arg(): # displays a list of entries and gives an option to select one fo
print(f"\n\u001b[38;5;9merror: entry ({_entry_name}) does not exist\u001b[0m\n") print(f"\n\u001b[38;5;9merror: entry ({_entry_name}) does not exist\u001b[0m\n")
s_exit(1) s_exit(1)
_shm_folder, _shm_entry = shm_gen() _shm_folder, _shm_entry = shm_gen()
decrypt(directory + _entry_name, _shm_folder, _shm_entry) decrypt(directory + _entry_name, _shm_folder, _shm_entry, gpg)
entry_reader(f"{tmp_dir}{_shm_folder}/{_shm_entry}") entry_reader(f"{tmp_dir}{_shm_folder}/{_shm_entry}")
rmtree(f"{tmp_dir}{_shm_folder}") rmtree(f"{tmp_dir}{_shm_folder}")
@@ -364,7 +388,7 @@ def read_shortcut(): # shortcut to quickly read an entry
print(f"\n\u001b[38;5;9merror: entry ({argument.replace('/', '', 1)}) does not exist\u001b[0m\n") print(f"\n\u001b[38;5;9merror: entry ({argument.replace('/', '', 1)}) does not exist\u001b[0m\n")
s_exit(1) s_exit(1)
_shm_folder, _shm_entry = shm_gen() _shm_folder, _shm_entry = shm_gen()
decrypt(directory + argument.replace('/', '', 1), _shm_folder, _shm_entry) decrypt(directory + argument.replace('/', '', 1), _shm_folder, _shm_entry, gpg)
entry_reader(f"{tmp_dir}{_shm_folder}/{_shm_entry}") entry_reader(f"{tmp_dir}{_shm_folder}/{_shm_entry}")
rmtree(f"{tmp_dir}{_shm_folder}") rmtree(f"{tmp_dir}{_shm_folder}")
@@ -379,7 +403,7 @@ def sync(): # calls sshync to sync changes to the user's server
try: try:
_deletion_database = open(path.expanduser('~/.config/sshyp/deletion_database')).readlines() _deletion_database = open(path.expanduser('~/.config/sshyp/deletion_database')).readlines()
except (FileNotFoundError, IndexError): except (FileNotFoundError, IndexError):
print('\n\u001b[38;5;9merror: the deletion database does not exist or is corrupted.\u001b[0m\n') print('\n\u001b[38;5;9merror: the deletion database does not exist or is corrupted\u001b[0m\n')
_deletion_database = None _deletion_database = None
s_exit(1) s_exit(1)
for _file in _deletion_database: for _file in _deletion_database:
@@ -387,7 +411,7 @@ def sync(): # calls sshync to sync changes to the user's server
try: try:
if silent_sync != 1: if silent_sync != 1:
print(f"\u001b[38;5;208m{_file[:-1]}\u001b[0m has been sheared, removing...") print(f"\u001b[38;5;208m{_file[:-1]}\u001b[0m has been sheared, removing...")
rmtree(f"{path.expanduser('~/.password-pasture/')}{_file[:-1]}") rmtree(f"{directory}{_file[:-1]}")
except FileNotFoundError: except FileNotFoundError:
if silent_sync != 1: if silent_sync != 1:
print('folder does not exist locally.') print('folder does not exist locally.')
@@ -395,7 +419,7 @@ def sync(): # calls sshync to sync changes to the user's server
try: try:
if silent_sync != 1: if silent_sync != 1:
print(f"\u001b[38;5;208m{_file[:-1]}\u001b[0m has been sheared, removing...") print(f"\u001b[38;5;208m{_file[:-1]}\u001b[0m has been sheared, removing...")
remove(f"{path.expanduser('~/.password-pasture/')}{_file[:-1]}.gpg") remove(f"{directory}{_file[:-1]}.gpg")
except (FileNotFoundError, IsADirectoryError): except (FileNotFoundError, IsADirectoryError):
if silent_sync != 1: if silent_sync != 1:
print('file does not exist locally.') print('file does not exist locally.')
@@ -407,14 +431,14 @@ def sync(): # calls sshync to sync changes to the user's server
try: try:
_folder_database = open(path.expanduser('~/.config/sshyp/folder_database')).readlines() _folder_database = open(path.expanduser('~/.config/sshyp/folder_database')).readlines()
except (FileNotFoundError, IndexError): except (FileNotFoundError, IndexError):
print('\n\u001b[38;5;9merror: the folder database does not exist or is corrupted.\u001b[0m\n') print('\n\u001b[38;5;9merror: the folder database does not exist or is corrupted\u001b[0m\n')
_folder_database = None _folder_database = None
s_exit(1) s_exit(1)
for _folder in _folder_database: for _folder in _folder_database:
if Path(f"{path.expanduser('~')}{_folder[:-1]}").is_dir(): if Path(f"{path.expanduser('~')}{_folder[:-1]}").is_dir():
pass pass
else: else:
print(f"\u001b[38;5;2m{_folder.replace('/.password-pasture/', '')[:-1]}/\u001b[0m does not exist locally, " print(f"\u001b[38;5;2m{_folder.replace('/.local/share/sshyp/', '')[:-1]}/\u001b[0m does not exist locally, "
f"creating...") f"creating...")
Path(f"{path.expanduser('~')}{_folder[:-1]}").mkdir(0o700, parents=True, exist_ok=True) Path(f"{path.expanduser('~')}{_folder[:-1]}").mkdir(0o700, parents=True, exist_ok=True)
# set permissions before uploading # set permissions before uploading
@@ -425,23 +449,26 @@ def sync(): # calls sshync to sync changes to the user's server
def add_entry(): # adds a new entry def add_entry(): # adds a new entry
_shm_folder, _shm_entry = None, None # sets base-line values to avoid errors _shm_folder, _shm_entry = None, None # sets base-line values to avoid errors
if len(argument_list) < 3: if len(argument_list) < 4:
_entry_name = entry_name_fetch('name of new entry: ') _entry_name = entry_name_fetch('name of new entry: ')
else: else:
_entry_name = entry_name_fetch(2) _entry_name = entry_name_fetch(2)
if argument_list[1] == 'note' or argument_list[1] == '-n': if Path(f"{directory}{_entry_name}.gpg").is_file():
print(f"\n\u001b[38;5;9merror: entry ({_entry_name}) already exists\u001b[0m\n")
s_exit(1)
if argument_list[2] == 'note' or argument_list[2] == '-n':
_shm_folder, _shm_entry = shm_gen() _shm_folder, _shm_entry = shm_gen()
system(f"nano {tmp_dir}{_shm_folder}/{_shm_entry}-n") system(f"{editor} {tmp_dir}{_shm_folder}/{_shm_entry}-n")
_notes = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n", 'r').read() _notes = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n", 'r').read()
open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'w').writelines('\n\n\n' + _notes) open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'w').writelines('\n\n\n' + _notes)
elif argument_list[1] == 'password' or argument_list[1] == '-p': elif argument_list[2] == 'password' or argument_list[2] == '-p':
_username = str(input('username: ')) _username = str(input('username: '))
_password = str(input('password: ')) _password = str(input('password: '))
_url = str(input('url: ')) _url = str(input('url: '))
_add_note = input('add a note to this entry? (y/N) ') _add_note = input('add a note to this entry? (y/N) ')
_shm_folder, _shm_entry = shm_gen() _shm_folder, _shm_entry = shm_gen()
if _add_note.lower() == 'y': if _add_note.lower() == 'y':
system(f"nano {tmp_dir}{_shm_folder}/{_shm_entry}-n") system(f"{editor} {tmp_dir}{_shm_folder}/{_shm_entry}-n")
_notes = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n", 'r').read() _notes = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n", 'r').read()
else: else:
_notes = '' _notes = ''
@@ -453,7 +480,7 @@ def add_entry(): # adds a new entry
def add_folder(): # creates a new folder def add_folder(): # creates a new folder
if len(argument_list) < 3: if len(argument_list) < 4:
_entry_name = entry_name_fetch('name of new folder: ') _entry_name = entry_name_fetch('name of new folder: ')
else: else:
_entry_name = entry_name_fetch(2) _entry_name = entry_name_fetch(2)
@@ -467,10 +494,14 @@ def rename(): # renames an entry or folder
_entry_name = entry_name_fetch('entry/folder to rename/relocate: ') _entry_name = entry_name_fetch('entry/folder to rename/relocate: ')
else: else:
_entry_name = entry_name_fetch(2) _entry_name = entry_name_fetch(2)
if not Path(f"{directory}{_entry_name}.gpg").exists(): if not Path(f"{directory}{_entry_name}.gpg").is_file() and not Path(f"{directory}{_entry_name}").is_dir():
print(f"\n\u001b[38;5;9merror: entry ({_entry_name}) does not exist\u001b[0m\n") print(f"\n\u001b[38;5;9merror: entry ({_entry_name}) does not exist\u001b[0m\n")
s_exit(1) s_exit(1)
_new_name = str(input('new name: ')) _new_name = str(input('new name: '))
print(f"{directory}{_new_name}")
if Path(f"{directory}{_new_name}.gpg").is_file() or Path(f"{directory}{_new_name}").is_dir():
print(f"\n\u001b[38;5;9merror: ({_new_name}) already exists\u001b[0m\n")
s_exit(1)
if _entry_name.endswith('/'): if _entry_name.endswith('/'):
move(f"{directory}{_entry_name}", f"{directory}{_new_name}") move(f"{directory}{_entry_name}", f"{directory}{_new_name}")
system(f"ssh -i '{path.expanduser('~/.ssh/sshyp')}' -p {port} {username_ssh}@{ip} \"mkdir -p '{directory_ssh}" system(f"ssh -i '{path.expanduser('~/.ssh/sshyp')}' -p {port} {username_ssh}@{ip} \"mkdir -p '{directory_ssh}"
@@ -483,7 +514,7 @@ def rename(): # renames an entry or folder
def edit(): # edits the contents of an entry def edit(): # edits the contents of an entry
_shm_folder, _shm_entry = None, None # sets base-line values to avoid errors _shm_folder, _shm_entry = None, None # sets base-line values to avoid errors
if len(argument_list) < 3: if len(argument_list) < 4:
_entry_name = entry_name_fetch('entry to edit: ') _entry_name = entry_name_fetch('entry to edit: ')
else: else:
_entry_name = entry_name_fetch(2) _entry_name = entry_name_fetch(2)
@@ -491,7 +522,7 @@ def edit(): # edits the contents of an entry
print(f"\n\u001b[38;5;9merror: entry ({_entry_name}) does not exist\u001b[0m\n") print(f"\n\u001b[38;5;9merror: entry ({_entry_name}) does not exist\u001b[0m\n")
s_exit(1) s_exit(1)
_shm_folder, _shm_entry = shm_gen() _shm_folder, _shm_entry = shm_gen()
decrypt(directory + _entry_name, _shm_folder, _shm_entry) decrypt(directory + _entry_name, _shm_folder, _shm_entry, gpg)
# compatibility check for GNU pass entries (ensuring they have at least four lines) # compatibility check for GNU pass entries (ensuring they have at least four lines)
_lines = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'r').readlines() _lines = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'r').readlines()
@@ -500,16 +531,16 @@ def edit(): # edits the contents of an entry
_lines.append('\n') _lines.append('\n')
open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'w').writelines(_lines) open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'w').writelines(_lines)
if argument_list[1] == 'username' or argument_list[1] == '-u': if argument_list[2] == 'username' or argument_list[2] == '-u':
_detail = str(input('new username: ')) _detail = str(input('new username: '))
replace_line(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 1, _detail + '\n') replace_line(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 1, _detail + '\n')
elif argument_list[1] == 'password' or argument_list[1] == '-p': elif argument_list[2] == 'password' or argument_list[2] == '-p':
_detail = str(input('new password: ')) _detail = str(input('new password: '))
replace_line(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 0, _detail + '\n') replace_line(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 0, _detail + '\n')
elif argument_list[1] == 'url' or argument_list[1] == '-l': elif argument_list[2] == 'url' or argument_list[2] == '-l':
_detail = str(input('new url: ')) _detail = str(input('new url: '))
replace_line(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 2, _detail + '\n') replace_line(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 2, _detail + '\n')
elif argument_list[1] == 'note' or argument_list[1] == '-n': elif argument_list[2] == 'note' or argument_list[2] == '-n':
edit_note(_shm_folder, _shm_entry) edit_note(_shm_folder, _shm_entry)
remove(f"{directory}{_entry_name}.gpg") remove(f"{directory}{_entry_name}.gpg")
print('\n\u001b[1mentry preview:\u001b[0m') print('\n\u001b[1mentry preview:\u001b[0m')
@@ -521,21 +552,24 @@ def gen(): # generates a password for a new or an existing entry
_username, _url, _notes = None, None, None # sets base-line values to avoid errors _username, _url, _notes = None, None, None # sets base-line values to avoid errors
if argument == 'gen update' or argument == 'gen -u' or argument == 'gen': if argument == 'gen update' or argument == 'gen -u' or argument == 'gen':
_entry_name = entry_name_fetch('name of entry: ') _entry_name = entry_name_fetch('name of entry: ')
elif argument_list[1] == 'update' or argument_list[1] == '-u': elif argument_list[2] == 'update' or argument_list[2] == '-u':
_entry_name = entry_name_fetch(2) _entry_name = entry_name_fetch(2)
if not Path(f"{directory}{_entry_name}.gpg").is_file(): if not Path(f"{directory}{_entry_name}.gpg").is_file():
print(f"\n\u001b[38;5;9merror: entry ({_entry_name}) does not exist\u001b[0m\n") print(f"\n\u001b[38;5;9merror: entry ({_entry_name}) does not exist\u001b[0m\n")
s_exit(1) s_exit(1)
else: else:
_entry_name = entry_name_fetch(1) _entry_name = entry_name_fetch(1)
if len(argument_list) == 2 or (not argument_list[2] == 'update' and not argument_list[2] == '-u'):
if Path(f"{directory}{_entry_name}.gpg").is_file():
print(f"\n\u001b[38;5;9merror: entry ({_entry_name}) already exists\u001b[0m\n")
s_exit(1)
_username = str(input('username: ')) _username = str(input('username: '))
_password = pass_gen() _password = pass_gen()
if not argument_list[1] == 'update' and not argument_list[1] == '-u':
_url = str(input('url: ')) _url = str(input('url: '))
_add_note = input('add a note to this entry? (y/N) ') _add_note = input('add a note to this entry? (y/N) ')
_shm_folder, _shm_entry = shm_gen() _shm_folder, _shm_entry = shm_gen()
if _add_note.lower() == 'y': if _add_note.lower() == 'y':
system(f"nano {tmp_dir}{_shm_folder}/{_shm_entry}-n") system(f"{editor} {tmp_dir}{_shm_folder}/{_shm_entry}-n")
_notes = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n", 'r').read() _notes = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}-n", 'r').read()
else: else:
_notes = '' _notes = ''
@@ -545,8 +579,9 @@ def gen(): # generates a password for a new or an existing entry
entry_reader(f"{tmp_dir}{_shm_folder}/{_shm_entry}") entry_reader(f"{tmp_dir}{_shm_folder}/{_shm_entry}")
encrypt(_shm_folder, _shm_entry, _entry_name) encrypt(_shm_folder, _shm_entry, _entry_name)
else: else:
_password = pass_gen()
_shm_folder, _shm_entry = shm_gen() _shm_folder, _shm_entry = shm_gen()
decrypt(directory + _entry_name, _shm_folder, _shm_entry) decrypt(directory + _entry_name, _shm_folder, _shm_entry, gpg)
replace_line(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 0, _password + '\n') replace_line(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 0, _password + '\n')
remove(f"{directory}{_entry_name}.gpg") remove(f"{directory}{_entry_name}.gpg")
print('\n\u001b[1mentry preview:\u001b[0m') print('\n\u001b[1mentry preview:\u001b[0m')
@@ -555,7 +590,7 @@ def gen(): # generates a password for a new or an existing entry
def copy_data(): # copies a specified field of an entry to the clipboard def copy_data(): # copies a specified field of an entry to the clipboard
if len(argument_list) < 3: if len(argument_list) < 4:
_entry_name = entry_name_fetch('entry to copy: ') _entry_name = entry_name_fetch('entry to copy: ')
else: else:
_entry_name = entry_name_fetch(2) _entry_name = entry_name_fetch(2)
@@ -563,48 +598,48 @@ def copy_data(): # copies a specified field of an entry to the clipboard
print(f"\n\u001b[38;5;9merror: entry ({_entry_name}) does not exist\u001b[0m\n") print(f"\n\u001b[38;5;9merror: entry ({_entry_name}) does not exist\u001b[0m\n")
s_exit(1) s_exit(1)
_shm_folder, _shm_entry = shm_gen() _shm_folder, _shm_entry = shm_gen()
decrypt(directory + _entry_name, _shm_folder, _shm_entry) decrypt(directory + _entry_name, _shm_folder, _shm_entry, gpg)
_copy_line = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'r').readlines() _copy_line = open(f"{tmp_dir}{_shm_folder}/{_shm_entry}", 'r').readlines()
if uname()[0] == 'Haiku': # Haiku clipboard detection if uname()[0] == 'Haiku': # Haiku clipboard detection
if argument_list[1] == 'username' or argument_list[1] == '-u': if argument_list[2] == 'username' or argument_list[2] == '-u':
system('clipboard -c ' + "'" + _copy_line[1].replace('\n', '').replace("'", "'\\''") + "'") system('clipboard -c ' + "'" + _copy_line[1].replace('\n', '').replace("'", "'\\''") + "'")
elif argument_list[1] == 'password' or argument_list[1] == '-p': elif argument_list[2] == 'password' or argument_list[2] == '-p':
system('clipboard -c ' + "'" + _copy_line[0].replace('\n', '').replace("'", "'\\''") + "'") system('clipboard -c ' + "'" + _copy_line[0].replace('\n', '').replace("'", "'\\''") + "'")
elif argument_list[1] == 'url' or argument_list[1] == '-l': elif argument_list[2] == 'url' or argument_list[2] == '-l':
system('clipboard -c ' + "'" + _copy_line[2].replace('\n', '').replace("'", "'\\''") + "'") system('clipboard -c ' + "'" + _copy_line[2].replace('\n', '').replace("'", "'\\''") + "'")
elif argument_list[1] == 'note' or argument_list[1] == '-n': elif argument_list[2] == 'note' or argument_list[2] == '-n':
system('clipboard -c ' + "'" + _copy_line[3].replace('\n', '').replace("'", "'\\''") + "'") system('clipboard -c ' + "'" + _copy_line[3].replace('\n', '').replace("'", "'\\''") + "'")
Popen('sleep 30; clipboard -r', shell=True) Popen('sleep 30; clipboard -r', shell=True, close_fds=True)
elif Path("/data/data/com.termux").exists(): # Termux (Android) clipboard detection elif Path("/data/data/com.termux").exists(): # Termux (Android) clipboard detection
if argument_list[1] == 'username' or argument_list[1] == '-u': if argument_list[2] == 'username' or argument_list[2] == '-u':
system('termux-clipboard-set ' + "'" + _copy_line[1].replace('\n', '').replace("'", "'\\''") + "'") system('termux-clipboard-set ' + "'" + _copy_line[1].replace('\n', '').replace("'", "'\\''") + "'")
elif argument_list[1] == 'password' or argument_list[1] == '-p': elif argument_list[2] == 'password' or argument_list[2] == '-p':
system('termux-clipboard-set ' + "'" + _copy_line[0].replace('\n', '').replace("'", "'\\''") + "'") system('termux-clipboard-set ' + "'" + _copy_line[0].replace('\n', '').replace("'", "'\\''") + "'")
elif argument_list[1] == 'url' or argument_list[1] == '-l': elif argument_list[2] == 'url' or argument_list[2] == '-l':
system('termux-clipboard-set ' + "'" + _copy_line[2].replace('\n', '').replace("'", "'\\''") + "'") system('termux-clipboard-set ' + "'" + _copy_line[2].replace('\n', '').replace("'", "'\\''") + "'")
elif argument_list[1] == 'note' or argument_list[1] == '-n': elif argument_list[2] == 'note' or argument_list[2] == '-n':
system('termux-clipboard-set ' + "'" + _copy_line[3].replace('\n', '').replace("'", "'\\''") + "'") system('termux-clipboard-set ' + "'" + _copy_line[3].replace('\n', '').replace("'", "'\\''") + "'")
Popen("sleep 30; termux-clipboard-set ''", shell=True) Popen("sleep 30; termux-clipboard-set ''", shell=True, close_fds=True)
elif environ.get('WAYLAND_DISPLAY') == 'wayland-0': # Wayland clipboard detection elif environ.get('WAYLAND_DISPLAY') == 'wayland-0': # Wayland clipboard detection
if argument_list[1] == 'username' or argument_list[1] == '-u': if argument_list[2] == 'username' or argument_list[2] == '-u':
system('wl-copy ' + "'" + _copy_line[1].replace('\n', '').replace("'", "'\\''") + "'") system('wl-copy ' + "'" + _copy_line[1].replace('\n', '').replace("'", "'\\''") + "'")
elif argument_list[1] == 'password' or argument_list[1] == '-p': elif argument_list[2] == 'password' or argument_list[2] == '-p':
system('wl-copy ' + "'" + _copy_line[0].replace('\n', '').replace("'", "'\\''") + "'") system('wl-copy ' + "'" + _copy_line[0].replace('\n', '').replace("'", "'\\''") + "'")
elif argument_list[1] == 'url' or argument_list[1] == '-l': elif argument_list[2] == 'url' or argument_list[2] == '-l':
system('wl-copy ' + "'" + _copy_line[2].replace('\n', '').replace("'", "'\\''") + "'") system('wl-copy ' + "'" + _copy_line[2].replace('\n', '').replace("'", "'\\''") + "'")
elif argument_list[1] == 'note' or argument_list[1] == '-n': elif argument_list[2] == 'note' or argument_list[2] == '-n':
system('wl-copy ' + "'" + _copy_line[3].replace('\n', '').replace("'", "'\\''") + "'") system('wl-copy ' + "'" + _copy_line[3].replace('\n', '').replace("'", "'\\''") + "'")
Popen('sleep 30; wl-copy -c', shell=True) Popen('sleep 30; wl-copy -c', shell=True, close_fds=True)
else: # X11 clipboard detection else: # X11 clipboard detection
if argument_list[1] == 'username' or argument_list[1] == '-u': if argument_list[2] == 'username' or argument_list[2] == '-u':
system('echo -n ' + "'" + _copy_line[1].replace('\n', '').replace("'", "'\\''") + "'" + ' | xclip -sel c') system('echo -n ' + "'" + _copy_line[1].replace('\n', '').replace("'", "'\\''") + "'" + ' | xclip -sel c')
elif argument_list[1] == 'password' or argument_list[1] == '-p': elif argument_list[2] == 'password' or argument_list[2] == '-p':
system('echo -n ' + "'" + _copy_line[0].replace('\n', '').replace("'", "'\\''") + "'" + ' | xclip -sel c') system('echo -n ' + "'" + _copy_line[0].replace('\n', '').replace("'", "'\\''") + "'" + ' | xclip -sel c')
elif argument_list[1] == 'url' or argument_list[1] == '-l': elif argument_list[2] == 'url' or argument_list[2] == '-l':
system('echo -n ' + "'" + _copy_line[2].replace('\n', '').replace("'", "'\\''") + "'" + ' | xclip -sel c') system('echo -n ' + "'" + _copy_line[2].replace('\n', '').replace("'", "'\\''") + "'" + ' | xclip -sel c')
elif argument_list[1] == 'note' or argument_list[1] == '-n': elif argument_list[2] == 'note' or argument_list[2] == '-n':
system('echo -n ' + "'" + _copy_line[3].replace('\n', '').replace("'", "'\\''") + "'" + ' | xclip -sel c') system('echo -n ' + "'" + _copy_line[3].replace('\n', '').replace("'", "'\\''") + "'" + ' | xclip -sel c')
Popen("sleep 30; echo -n '' | xclip -sel c", shell=True) Popen("sleep 30; echo -n '' | xclip -sel c", shell=True, close_fds=True)
rmtree(f"{tmp_dir}{_shm_folder}") rmtree(f"{tmp_dir}{_shm_folder}")
@@ -613,7 +648,7 @@ def remove_data(): # deletes an entry from the server and flags it for local de
_entry_name = entry_name_fetch('entry/folder to shear: ') _entry_name = entry_name_fetch('entry/folder to shear: ')
else: else:
_entry_name = entry_name_fetch(1) _entry_name = entry_name_fetch(1)
decrypt(path.expanduser('~/.config/sshyp/lock.gpg'), 0, 0) decrypt(path.expanduser('~/.config/sshyp/lock.gpg'), 0, 0, gpg)
system(f"ssh -i '{path.expanduser('~/.ssh/sshyp')}' -p {port} {username_ssh}@{ip} \"cd /bin; python -c " system(f"ssh -i '{path.expanduser('~/.ssh/sshyp')}' -p {port} {username_ssh}@{ip} \"cd /bin; python -c "
f"'import sshypRemote; sshypRemote.delete(\"'\"{_entry_name}\"'\")'\"") f"'import sshypRemote; sshypRemote.delete(\"'\"{_entry_name}\"'\")'\"")
@@ -622,11 +657,8 @@ if __name__ == "__main__":
try: try:
silent_sync, ssh_error = 0, 0 silent_sync, ssh_error = 0, 0
# retrieve typed argument # retrieve typed argument
argument_list, argument = argv, '' argument_list = argv
del argument_list[0] argument = ' '.join(argument_list[1:])
for _argument in argument_list:
argument += _argument + ' '
argument = argument[:-1]
if uname()[0] == 'Haiku': # set proper gpg command for OS if uname()[0] == 'Haiku': # set proper gpg command for OS
gpg = 'gpg --pinentry-mode loopback' gpg = 'gpg --pinentry-mode loopback'
else: else:
@@ -644,12 +676,17 @@ if __name__ == "__main__":
ip = ssh_info[1].replace('\n', '') ip = ssh_info[1].replace('\n', '')
port = ssh_info[2].replace('\n', '') port = ssh_info[2].replace('\n', '')
directory = str(ssh_info[3].replace('\n', '')) directory = str(ssh_info[3].replace('\n', ''))
directory_ssh = '/home/' + username_ssh + '/.password-pasture/' directory_ssh = str(ssh_info[4].replace('\n', ''))
client_device_name = listdir(path.expanduser('~/.config/sshyp/devices'))[0] client_device_name = listdir(path.expanduser('~/.config/sshyp/devices'))[0]
gpg_id = open(path.expanduser('~/.config/sshyp/sshyp-gpg')).read().strip() sshyp_data = open(path.expanduser('~/.config/sshyp/sshyp-data')).readlines()
gpg_id = sshyp_data[0].replace('\n', '')
editor = sshyp_data[1].replace('\n', '')
ssh_error = int(open(path.expanduser('~/.config/sshyp/ssh-error')).read().strip()) ssh_error = int(open(path.expanduser('~/.config/sshyp/ssh-error')).read().strip())
if ssh_error != 0: if ssh_error != 0:
ssh_error = copy_name_check(port, username_ssh, ip, client_device_name) ssh_error = copy_name_check(port, username_ssh, ip, client_device_name)
else:
print('running as server, option disabled')
s_exit(0)
except (FileNotFoundError, IndexError): except (FileNotFoundError, IndexError):
if device_type.lower() != 's': if device_type.lower() != 's':
print('\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!') print('\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!')
@@ -668,53 +705,53 @@ if __name__ == "__main__":
elif argument == 'help' or argument == '--help' or argument == '-h' or argument == 'license' or argument \ elif argument == 'help' or argument == '--help' or argument == '-h' or argument == 'license' or argument \
== 'version' or argument == '-v': == 'version' or argument == '-v':
print_info() print_info()
elif argument_list[0] == 'add': elif argument_list[1] == 'add':
if len(argument_list) == 1: if len(argument_list) == 2:
print_info() print_info()
elif argument_list[1] == 'note' or argument_list[1] == '-n' or argument_list[1] == 'password' or \ elif argument_list[2] == 'note' or argument_list[2] == '-n' or argument_list[2] == 'password' or \
argument_list[1] == '-p': argument_list[2] == '-p':
add_entry() add_entry()
elif argument_list[1] == 'folder' or argument_list[1] == '-f': elif argument_list[2] == 'folder' or argument_list[2] == '-f':
add_folder() add_folder()
else: else:
print_info() print_info()
s_exit(0) s_exit(0)
elif argument_list[0] == 'edit': elif argument_list[1] == 'edit':
if len(argument_list) == 1: if len(argument_list) == 2:
print_info() print_info()
elif argument_list[1] == 'rename' or argument_list[1] == 'relocate' or argument_list[1] == '-r': elif argument_list[2] == 'rename' or argument_list[2] == 'relocate' or argument_list[2] == '-r':
silent_sync = 1 silent_sync = 1
rename() rename()
elif argument_list[1] == 'username' or argument_list[1] == '-u' or argument_list[1] == 'password' or \ elif argument_list[2] == 'username' or argument_list[2] == '-u' or argument_list[2] == 'password' or \
argument_list[1] == '-p' or argument_list[1] == 'url' or argument_list[1] == '-l' or \ argument_list[2] == '-p' or argument_list[2] == 'url' or argument_list[2] == '-l' or \
argument_list[1] == 'note' or argument_list[1] == '-n': argument_list[2] == 'note' or argument_list[2] == '-n':
edit() edit()
else: else:
print_info() print_info()
s_exit(0) s_exit(0)
elif argument_list[0] == 'gen': elif argument_list[1] == 'gen':
gen() gen()
elif argument_list[0] == 'copy': elif argument_list[1] == 'copy':
if len(argument_list) == 1: if len(argument_list) == 2:
print_info() print_info()
elif argument_list[1] == 'username' or argument_list[1] == '-u' or argument_list[1] == 'password' or \ elif argument_list[2] == 'username' or argument_list[2] == '-u' or argument_list[2] == 'password' or \
argument_list[1] == '-p' or argument_list[1] == 'url' or argument_list[1] == '-l' or \ argument_list[2] == '-p' or argument_list[2] == 'url' or argument_list[2] == '-l' or \
argument_list[1] == 'note' or argument_list[1] == '-n': argument_list[2] == 'note' or argument_list[2] == '-n':
copy_data() copy_data()
else: else:
print_info() print_info()
elif argument_list[0] == 'shear' or argument_list[0] == '-rm': elif argument_list[1] == 'shear' or argument_list[1] == '-rm':
remove_data() remove_data()
elif argument_list[0] != 'sync' and argument_list[0] != '-s': elif argument_list[1] != 'sync' and argument_list[1] != '-s':
print(f"\n\u001b[38;5;9merror: invalid argument - run 'sshyp help' to list usable commands\u001b[0m\n") print(f"\n\u001b[38;5;9merror: invalid argument - run 'sshyp help' to list usable commands\u001b[0m\n")
s_exit(1) s_exit(1)
# sync if any changes were made # sync if any changes were made
if len(argument_list) > 0 and ssh_error == 0 and \ if len(argument_list) > 1 and ssh_error == 0 and \
((argument_list[0] == 'sync' or argument_list[0] == '-s' or argument_list[0] == 'gen' or ((argument_list[1] == 'sync' or argument_list[1] == '-s' or argument_list[1] == 'gen' or
argument_list[0] == 'shear' or argument_list[0] == '-rm') or ((argument_list[0] == 'add' argument_list[1] == 'shear' or argument_list[1] == '-rm') or ((argument_list[1] == 'add'
or argument_list[0] == 'edit') or argument_list[1] == 'edit')
and len(argument_list) > 1)): and len(argument_list) > 2)):
sync() sync()
s_exit(0) s_exit(0)
+6 -6
View File
@@ -9,22 +9,22 @@ from shutil import rmtree
# utility functions # utility functions
def delete(_file_path): def delete(_file_path): # deletes an entry or folder, should be run remotely via ssh
if _file_path.endswith('/'): if _file_path.endswith('/'):
try: try:
rmtree(f"{expanduser('~/.password-pasture/')}{_file_path}") rmtree(f"{expanduser('~/.local/share/sshyp/')}{_file_path}")
except FileNotFoundError: except FileNotFoundError:
print('folder does not exist remotely') print('folder does not exist remotely')
else: else:
try: try:
remove(f"{expanduser('~/.password-pasture/')}{_file_path}.gpg") remove(f"{expanduser('~/.local/share/sshyp/')}{_file_path}.gpg")
except FileNotFoundError: except FileNotFoundError:
print('file does not exist remotely') print('file does not exist remotely')
for _device_name in listdir(expanduser('~/.config/sshyp/devices')): for _device_name in listdir(expanduser('~/.config/sshyp/devices')):
open(f"{expanduser('~/.config/sshyp/deleted/')}{_file_path.replace('/', '@')}^&*{_device_name}", 'w') open(f"{expanduser('~/.config/sshyp/deleted/')}{_file_path.replace('/', '@')}^&*{_device_name}", 'w')
def deletion_check(_client_device_name): def deletion_check(_client_device_name): # creates a list of entries and folders to be deleted from a local machine
open(expanduser('~/.config/sshyp/deletion_database'), 'w').write('') open(expanduser('~/.config/sshyp/deletion_database'), 'w').write('')
for _file in listdir(expanduser('~/.config/sshyp/deleted')): for _file in listdir(expanduser('~/.config/sshyp/deleted')):
_file_path = _file.replace('@', '/').split('^&*')[0] _file_path = _file.replace('@', '/').split('^&*')[0]
@@ -37,9 +37,9 @@ def deletion_check(_client_device_name):
open(expanduser('~/.config/sshyp/deletion_database'), 'a').write(_file_path + '\n') open(expanduser('~/.config/sshyp/deletion_database'), 'a').write(_file_path + '\n')
def folder_check(): def folder_check(): # creates a list of folders to be compared with those of a local machine
open(expanduser('~/.config/sshyp/folder_database'), 'w').write('') open(expanduser('~/.config/sshyp/folder_database'), 'w').write('')
for _root, _directories, _files in walk(expanduser('~/.password-pasture')): for _root, _directories, _files in walk(expanduser('~/.local/share/sshyp')):
for _dir in _directories: for _dir in _directories:
open(expanduser('~/.config/sshyp/folder_database'), 'a')\ open(expanduser('~/.config/sshyp/folder_database'), 'a')\
.write(f"{_root.replace(expanduser('~'), '')}/{_dir}\n") .write(f"{_root.replace(expanduser('~'), '')}/{_dir}\n")
+52 -1
View File
@@ -1,3 +1,53 @@
sshyp v1.1.0
the sheecrets update
the main focus of this release was adding extension support (particularly for sshyp-mfa)
and cleaning up the configuration experience
compatibility-breaking changes:
- the entry directory has been moved from ~/.password-pasture to ~/.local/share/sshyp
as a means of complying with the XDG base directory spec
^ this requires the user to move their entry directory to the new location manually
^ if this is not done, sshyp will not be able to find entries made prior to v1.1.0
- due to a new configuration option (preferred text editor), 'sshyp tweak' must be run after updating
user-facing features:
- extension support
^ some of sshyp's critical functions can now be imported into other programs to extend
upon sshyp's functionality
^ the first usecase of this is sshyp-mfa, which can be found at https://github.com/rwinkhart/sshyp-labs
- a new configuration experience
^ the 'sshyp tweak' menu has been made much more readable, and now the automatically generated
gpg key has its id automatically input, making the process easier for the user
- customizable text editor
^ the text editor used for editing notes can now be set to any editor preferred by the user
^ this means that nano was dropped as a dependency
fixes:
- to avoid exceptions on server devices, all arguments (apart from 'tweak') have been disabled on servers
- fixed entry readout not printing blank lines between fields under certain conditions
- various optimizations
<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
sshyp v1.0.1
The Polished Trotters Update - Patch 1
This is a hotfix for the Haiku decryption bug.
Changes:
- subprocess.Popen has been replaced with subprocess.run where applicable
- stdout is no longer captured with any subprocess.Popen or subprocess.run usages
^ this fixes decryption on Haiku
<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
sshyp v1.0.0 sshyp v1.0.0
The Polished Trotters Update The Polished Trotters Update
@@ -8,7 +58,7 @@ As such, it is being crowned "v1.0.0", and is considered a stable base for futur
Features: Features:
- sshyp has been modified to run on Haiku, an open-source re-implementation of BeOS - sshyp has been modified to run on Haiku, an open-source re-implementation of BeOS
^ new package formats have been added for Haiku, FreeBSD, and Red Hat distributions (such as Fedora) ^ new package formats have been added for Haiku, FreeBSD, and Fedora Linux
- the entry format has been changed to support entries created in pass/password-store - the entry format has been changed to support entries created in pass/password-store
^ this change requires existing entry libraries to be converted - see the sshyp v1.0.0 release page on GitHub for a conversion script ^ this change requires existing entry libraries to be converted - see the sshyp v1.0.0 release page on GitHub for a conversion script
- majorly overhauled visual experience for sshyp - majorly overhauled visual experience for sshyp
@@ -27,6 +77,7 @@ Changes:
- rename function now properly allows for renaming folders - rename function now properly allows for renaming folders
- renaming an entry/folder will now properly delete the old entry/folder from the server - renaming an entry/folder will now properly delete the old entry/folder from the server
- entry previews at the time of entry creation/editing no longer require entering the gpg password more times than necessary - entry previews at the time of entry creation/editing no longer require entering the gpg password more times than necessary
- entries will no longer be overwritten if attempting to create a new entry that uses an already existing name
- the clipboard now automatically clears after 30 seconds - the clipboard now automatically clears after 30 seconds
- sshyp server no longer needs to create copies of its libraries in ~/ - sshyp server no longer needs to create copies of its libraries in ~/
- added more and improved existing exceptions - added more and improved existing exceptions
Regular → Executable
+9 -9
View File
@@ -1,4 +1,4 @@
.TH sshyp 1 "17 June 2022" "v1.0.0" "sshyp man page" .TH sshyp 1 "14 July 2022" "v1.1.0" "sshyp man page"
.SH NAME .SH NAME
sshyp \- A very simple self-hosted, synchronized password manager for UNIX(-like) systems. Alternative to (and compatible with) pass/password-store. sshyp \- A very simple self-hosted, synchronized password manager for UNIX(-like) systems. Alternative to (and compatible with) pass/password-store.
.SH SYNOPSIS .SH SYNOPSIS
@@ -12,25 +12,25 @@ Setting up sshyp for the first time or altering its configuration (also recommen
Viewing the entry database: Viewing the entry database:
sshyp sshyp
Reading an existing entry saved as ~/.password-pasture/development/github.gpg: Reading an existing entry saved as ~/.local/share/sshyp/development/github.gpg:
sshyp /development/github sshyp /development/github
Copying the password of an existing entry saved as ~/.password-pasture/financial/bank.gpg: Copying the password of an existing entry saved as ~/.local/share/sshyp/financial/bank.gpg:
sshyp copy -p /financial/bank sshyp copy -p /financial/bank
Editing the username of an existing entry saved as ~/.password-pasture/game.gpg: Editing the username of an existing entry saved as ~/.local/share/sshyp/game.gpg:
sshyp edit -u game sshyp edit -u game
Making a new entry saved as ~/.password-pasture/school/university.gpg using the built-in password generator: Making a new entry saved as ~/.local/share/sshyp/school/university.gpg using the built-in password generator:
sshyp gen /school/university sshyp gen /school/university
Creating a note-only entry saved as ~/.password-pasture/notes/testNote.gpg: Creating a note-only entry saved as ~/.local/share/sshyp/notes/testNote.gpg:
sshyp add -n /notes/testNote sshyp add -n /notes/testNote
Manually syncing entries with the server: Manually syncing entries with the server:
sshyp sync sshyp sync
Removing an existing folder saved as ~/.password-pasture/social: Removing an existing folder saved as ~/.local/share/sshyp/social:
sshyp shear social/ sshyp shear social/
.SH OPTIONS .SH OPTIONS
@@ -73,7 +73,7 @@ sshyp operates on a client-server model, and thus requires you to have access to
The sshyp package includes modes for operating on both client and server devices, so only one package is necessary. The sshyp package includes modes for operating on both client and server devices, so only one package is necessary.
Server setup: Server setup:
Configure an openssh server (if sshyp was installed from the Debian package, the ssh server was not installed as a dependency and needs to be installed manually) Configure an OpenSSH server (if sshyp was installed from the Debian package, the ssh server was not installed as a dependency and needs to be installed manually)
Allow remote access to the SSH server w/public key authentication (disabling password-only authentication recommended) Allow remote access to the SSH server w/public key authentication (disabling password-only authentication recommended)
Install sshyp Install sshyp
Run "sshyp tweak" and set the device type as server Run "sshyp tweak" and set the device type as server
@@ -82,7 +82,7 @@ Server setup:
Client setup: Client setup:
Install sshyp Install sshyp
Run "sshyp tweak" and follow the prompts Run "sshyp tweak" and follow the prompts
Copy the generated public SSH key to the server using preferred method (manually adding to authorized_keys, ssh-copy-id, etc.) Copy the generated public SSH key (located at ~/.ssh/sshyp.pub) to the server using preferred method (manually adding to authorized_keys, ssh-copy-id, etc.)
If you already have entries on the server, sync them using "sshyp sync" If you already have entries on the server, sync them using "sshyp sync"
Done! Done!
-48
View File
@@ -1,48 +0,0 @@
#!/bin/python3
from os import path, remove, system, walk
from pathlib import Path
from shutil import move, rmtree
if __name__ == "__main__":
gpg_id = input('\nWhat is the ID of the GPG key you would like to use for re-encryption?\n\nID: ')
pasture = path.expanduser('~/.password-pasture')
if Path(f"{pasture}.new").exists():
rmtree(f"{pasture}.new")
if Path(f"{pasture}.old").exists():
rmtree(f"{pasture}.old")
if path.isdir(pasture):
for dirPath, dirNames, filenames in walk(pasture):
for filename in sorted(filenames):
Path(dirPath.replace(pasture, pasture + '.new')).mkdir(0o700, parents=True, exist_ok=True)
system(f"gpg -d '{dirPath}/{filename}' "
f"> '{dirPath.replace(pasture, pasture + '.new')}/{filename[:-4]}'")
_old_contents, _new_contents = \
open(f"{dirPath.replace(pasture, pasture + '.new')}/{filename[:-4]}", 'r').readlines(), ''
for _num in range(len(_old_contents)):
if _num == 0:
try:
_new_contents += _old_contents[1]
except IndexError:
_new_contents += '\n'
elif _num == 1:
_new_contents += _old_contents[0]
elif _num == 3:
if _old_contents[_num] == ' ' or _old_contents[_num] == '\n':
pass
else:
_new_contents += _old_contents[_num]
else:
_new_contents += _old_contents[_num]
for _num in reversed(range(len(_new_contents))):
if _new_contents[_num] == '\n' or _new_contents[_num] == '':
_new_contents = _new_contents[:-1]
else:
break
open(f"{dirPath.replace(pasture, pasture + '.new')}/{filename[:-4]}", 'w').writelines(_new_contents)
system(f"gpg -qr {str(gpg_id)} -e '{dirPath.replace(pasture, pasture + '.new')}/{filename[:-4]}'")
remove(f"{dirPath.replace(pasture, pasture + '.new')}/{filename[:-4]}")
else:
print(f"\nError: no entry folder ({pasture}) found\n")
move(pasture, f"{pasture}.old")
move(f"{pasture}.new", pasture)
-41
View File
@@ -1,41 +0,0 @@
#!/bin/python3
from os import path, remove, system, walk
from pathlib import Path
from shutil import move, rmtree
if __name__ == "__main__":
gpg_id = input('\nWhat is the ID of the GPG key you would like to use for re-encryption?\n\nID: ')
pasture = path.expanduser('~/.password-pasture')
if Path(f"{pasture}.new").exists():
rmtree(f"{pasture}.new")
if Path(f"{pasture}.old").exists():
rmtree(f"{pasture}.old")
if path.isdir(pasture):
for dirPath, dirNames, filenames in walk(pasture):
for filename in sorted(filenames):
Path(dirPath.replace(pasture, pasture + '.new')).mkdir(0o700, parents=True, exist_ok=True)
system(f"gpg -d '{dirPath}/{filename}' "
f"> '{dirPath.replace(pasture, pasture + '.new')}/{filename[:-4]}'")
_old_contents, _new_contents = \
open(f"{dirPath.replace(pasture, pasture + '.new')}/{filename[:-4]}", 'r').readlines(), ''
for _num in range(len(_old_contents)):
if _num == 3:
if _old_contents[_num] == ' ' or _old_contents[_num] == '\n':
pass
else:
_new_contents += _old_contents[_num]
else:
_new_contents += _old_contents[_num]
for _num in reversed(range(len(_new_contents))):
if _new_contents[_num] == '\n' or _new_contents[_num] == '':
_new_contents = _new_contents[:-1]
else:
break
open(f"{dirPath.replace(pasture, pasture + '.new')}/{filename[:-4]}", 'w').writelines(_new_contents)
system(f"gpg -qr {str(gpg_id)} -e '{dirPath.replace(pasture, pasture + '.new')}/{filename[:-4]}'")
remove(f"{dirPath.replace(pasture, pasture + '.new')}/{filename[:-4]}")
else:
print(f"\nError: no entry folder ({pasture}) found\n")
move(pasture, f"{pasture}.old")
move(f"{pasture}.new", pasture)
+29 -27
View File
@@ -1,13 +1,14 @@
#!/bin/bash #!/bin/bash
# This script packages sshyp (from source) for various UNIX environments. # This script packages sshyp (from source) for various UNIX(-like) environments.
# Dependencies (Arch Linux): dpkg (packaging for Debian/Termux), rpmdevtools (packaging for Red Hat), freebsd-pkg (packaging for FreeBSD) # Dependencies (Arch Linux): dpkg (packaging for Debian/Termux), freebsd-pkg (packaging for FreeBSD)
# Dependencies (Fedora) (can only package for self): rpmdevtools
# NOTE It is recommended to instead use the latest officially packaged and tagged release. # NOTE It is recommended to instead use the latest officially packaged and tagged release.
echo -e '\nOptions (please enter the number only):' echo -e '\nOptions (please enter the number only):'
echo -e '\nPackage Formats:\n\n1. Haiku\n2. Debian\n3. Red Hat\n4. FreeBSD\n5. Termux\n6. Generic (used for PKGBUILD/APKBUILD)' echo -e '\nPackage Formats:\n\n1. Haiku\n2. Debian&Ubuntu Linux\n3. Fedora Linux\n4. FreeBSD\n5. Termux\n6. Generic (used for PKGBUILD/APKBUILD)'
echo -e '\nBuild Scripts:\n\n7. Arch Linux (PKGBUILD)' echo -e '\nBuild Scripts:\n\n7. Arch Linux (PKGBUILD)'
echo -e '\nOther:\n\n8. All (generates all distribution packages (excluding Haiku, as this must be done on Haiku) and build scripts)\n' echo -e '\nOther:\n\n8. All (generates all distribution packages (excluding Haiku and Fedora, as these must be packaged on their respective distributions) and build scripts)\n'
read -n 1 -r -p "Distribution: " distro read -n 1 -r -p "Distribution: " distro
echo -e '\n\nThe value entered in this field will only affect the version reported to the package manager. The latest source is used regardless.\n' echo -e '\n\nThe value entered in this field will only affect the version reported to the package manager. The latest source is used regardless.\n'
@@ -54,13 +55,13 @@ requires {
gnupg gnupg
openssh openssh
python3 python3
nano
} }
urls { urls {
\"https://github.com/rwinkhart/sshyp\" \"https://github.com/rwinkhart/sshyp\"
} }
" > packages/haikutemp/.PackageInfo " > packages/haikutemp/.PackageInfo
cp -r bin packages/haikutemp/ cp -r bin packages/haikutemp/
ln -s /bin/sshyp.py packages/haikutemp/bin/sshyp
cp -r share/doc/sshyp/ packages/haikutemp/documentation/packages/ cp -r share/doc/sshyp/ packages/haikutemp/documentation/packages/
cp -r share/licenses/sshyp/ packages/haikutemp/documentation/packages/ cp -r share/licenses/sshyp/ packages/haikutemp/documentation/packages/
cp extra/manpage packages/haikutemp/documentation/man/man1/sshyp.1 cp extra/manpage packages/haikutemp/documentation/man/man1/sshyp.1
@@ -83,11 +84,12 @@ Section: utils
Architecture: all Architecture: all
Maintainer: Randall Winkhart <idgr at tutanota dot com> Maintainer: Randall Winkhart <idgr at tutanota dot com>
Description: A light-weight, self-hosted, synchronized password manager Description: A light-weight, self-hosted, synchronized password manager
Depends: python3, gnupg, openssh-client, nano, xclip, wl-clipboard Depends: python3, gnupg, openssh-client, xclip, wl-clipboard
Priority: optional Priority: optional
Installed-Size: 35 Installed-Size: 185
" > packages/debiantemp/sshyp_"$version"-"$revision"_all/DEBIAN/control " > packages/debiantemp/sshyp_"$version"-"$revision"_all/DEBIAN/control
cp -r bin packages/debiantemp/sshyp_"$version"-"$revision"_all/usr/ cp -r bin packages/debiantemp/sshyp_"$version"-"$revision"_all/usr/
ln -s /usr/bin/sshyp.py packages/debiantemp/sshyp_"$version"-"$revision"_all/usr/bin/sshyp
cp -r share packages/debiantemp/sshyp_"$version"-"$revision"_all/usr/ cp -r share packages/debiantemp/sshyp_"$version"-"$revision"_all/usr/
cp extra/manpage packages/debiantemp/sshyp_"$version"-"$revision"_all/usr/share/man/man1/sshyp.1 cp extra/manpage packages/debiantemp/sshyp_"$version"-"$revision"_all/usr/share/man/man1/sshyp.1
gzip packages/debiantemp/sshyp_"$version"-"$revision"_all/usr/share/man/man1/sshyp.1 gzip packages/debiantemp/sshyp_"$version"-"$revision"_all/usr/share/man/man1/sshyp.1
@@ -99,19 +101,19 @@ fi
if [ "$distro" == "5" ] || [ "$distro" == "8" ]; then if [ "$distro" == "5" ] || [ "$distro" == "8" ]; then
echo -e '\nPackaging for Termux...\n' echo -e '\nPackaging for Termux...\n'
mkdir -p packages/termuxtemp/sshyp_"$version"-"$revision"_all_termux/{data,DEBIAN} mkdir -p packages/termuxtemp/sshyp_"$version"-"$revision"_all_termux/{data/data/com.termux/files/usr/share/man/man1,DEBIAN}
mkdir -p packages/termuxtemp/sshyp_"$version"-"$revision"_all_termux/data/data/com.termux/files/usr/share/man/man1
echo "Package: sshyp echo "Package: sshyp
Version: $version Version: $version
Section: utils Section: utils
Architecture: all Architecture: all
Maintainer: Randall Winkhart <idgr at tutanota dot com> Maintainer: Randall Winkhart <idgr at tutanota dot com>
Description: A light-weight, self-hosted, synchronized password manager Description: A light-weight, self-hosted, synchronized password manager
Depends: python, gnupg, openssh, nano, termux-api, termux-am Depends: python, gnupg, openssh, termux-api, termux-am
Priority: optional Priority: optional
Installed-Size: 35 Installed-Size: 185
" > packages/termuxtemp/sshyp_"$version"-"$revision"_all_termux/DEBIAN/control " > packages/termuxtemp/sshyp_"$version"-"$revision"_all_termux/DEBIAN/control
cp -r bin packages/termuxtemp/sshyp_"$version"-"$revision"_all_termux/data/data/com.termux/files/usr/ cp -r bin packages/termuxtemp/sshyp_"$version"-"$revision"_all_termux/data/data/com.termux/files/usr/
ln -s /data/data/com.termux/files/usr/bin/sshyp.py packages/termuxtemp/sshyp_"$version"-"$revision"_all_termux/data/data/com.termux/files/usr/bin/sshyp
cp -r share packages/termuxtemp/sshyp_"$version"-"$revision"_all_termux/data/data/com.termux/files/usr/ cp -r share packages/termuxtemp/sshyp_"$version"-"$revision"_all_termux/data/data/com.termux/files/usr/
cp extra/manpage packages/termuxtemp/sshyp_"$version"-"$revision"_all_termux/data/data/com.termux/files/usr/share/man/man1/sshyp.1 cp extra/manpage packages/termuxtemp/sshyp_"$version"-"$revision"_all_termux/data/data/com.termux/files/usr/share/man/man1/sshyp.1
gzip packages/termuxtemp/sshyp_"$version"-"$revision"_all_termux/data/data/com.termux/files/usr/share/man/man1/sshyp.1 gzip packages/termuxtemp/sshyp_"$version"-"$revision"_all_termux/data/data/com.termux/files/usr/share/man/man1/sshyp.1
@@ -123,20 +125,21 @@ fi
if [ "$distro" == "3" ] || [ "$distro" == "4" ] || [ "$distro" == "6" ] || [ "$distro" == "7" ] || [ "$distro" == "8" ]; then if [ "$distro" == "3" ] || [ "$distro" == "4" ] || [ "$distro" == "6" ] || [ "$distro" == "7" ] || [ "$distro" == "8" ]; then
echo -e '\nPackaging as generic...\n' echo -e '\nPackaging as generic...\n'
mkdir -p packages/archtemp/usr/share/man/man1 mkdir -p packages/generictemp/usr/share/man/man1
cp -r bin packages/archtemp/usr/ cp -r bin packages/generictemp/usr/
cp -r share packages/archtemp/usr/ ln -s /usr/bin/sshyp.py packages/generictemp/usr/bin/sshyp
cp extra/manpage packages/archtemp/usr/share/man/man1/sshyp.1 cp -r share packages/generictemp/usr/
gzip packages/archtemp/usr/share/man/man1/sshyp.1 cp extra/manpage packages/generictemp/usr/share/man/man1/sshyp.1
tar -C packages/archtemp -cvf packages/sshyp-"$version".tar.xz usr/ gzip packages/generictemp/usr/share/man/man1/sshyp.1
rm -rf packages/archtemp tar -C packages/generictemp -cvf packages/sshyp-"$version".tar.xz usr/
rm -rf packages/generictemp
sha512="$(sha512sum packages/sshyp-"$version".tar.xz | awk '{print $1;}')" sha512="$(sha512sum packages/sshyp-"$version".tar.xz | awk '{print $1;}')"
echo -e "\nsha512 sum:\n$sha512" echo -e "\nsha512 sum:\n$sha512"
echo -e "\nGeneric packaging complete.\n" echo -e "\nGeneric packaging complete.\n"
fi fi
if [ "$distro" == "3" ] || [ "$distro" == "8" ]; then if [ "$distro" == "3" ]; then
echo -e '\nPackaging for Red Hat...\n' echo -e '\nPackaging for Fedora...\n'
rm -rf ~/rpmbuild rm -rf ~/rpmbuild
rpmdev-setuptree rpmdev-setuptree
cp packages/sshyp-"$version".tar.xz ~/rpmbuild/SOURCES cp packages/sshyp-"$version".tar.xz ~/rpmbuild/SOURCES
@@ -146,11 +149,11 @@ Release: "$revision"
Summary: A light-weight, self-hosted, synchronized password manager Summary: A light-weight, self-hosted, synchronized password manager
BuildArch: noarch BuildArch: noarch
License: GPL3 License: GPLv3
URL: https://github.com/rwinkhart/sshyp URL: https://github.com/rwinkhart/sshyp
Source0: sshyp-"$version".tar.xz Source0: sshyp-"$version".tar.xz
Requires: python gnupg openssh nano wl-clipboard Requires: python gnupg openssh wl-clipboard
%description %description
sshyp is a password-store compatible CLI password manager available for UNIX(-like) systems - its primary goal is to make syncing passwords and notes across devices as easy as possible via CLI. sshyp is a password-store compatible CLI password manager available for UNIX(-like) systems - its primary goal is to make syncing passwords and notes across devices as easy as possible via CLI.
@@ -161,6 +164,7 @@ cp -r %{_sourcedir}/usr %{buildroot}
%files %files
/usr/bin/sshyp /usr/bin/sshyp
/usr/bin/sshyp.py
/usr/bin/sshync.py /usr/bin/sshync.py
/usr/bin/sshypRemote.py /usr/bin/sshypRemote.py
%license /usr/share/licenses/sshyp/license %license /usr/share/licenses/sshyp/license
@@ -170,7 +174,7 @@ cp -r %{_sourcedir}/usr %{buildroot}
rpmbuild -bb ~/rpmbuild/SPECS/sshyp.spec rpmbuild -bb ~/rpmbuild/SPECS/sshyp.spec
mv ~/rpmbuild/RPMS/noarch/* packages/ mv ~/rpmbuild/RPMS/noarch/* packages/
rm -rf ~/rpmbuild rm -rf ~/rpmbuild
echo -e "\nRed Hat packaging complete.\n" echo -e "\nFedora packaging complete.\n"
fi fi
if [ "$distro" == "4" ] || [ "$distro" == "8" ]; then if [ "$distro" == "4" ] || [ "$distro" == "8" ]; then
@@ -195,9 +199,6 @@ prefix: /
\"gnupg\" : { \"gnupg\" : {
\"origin\" : \"security/gnupg\" \"origin\" : \"security/gnupg\"
}, },
\"nano\" : {
\"origin\" : \"editors/nano\"
},
\"xclip\" : { \"xclip\" : {
\"origin\" : \"x11/xclip\" \"origin\" : \"x11/xclip\"
}, },
@@ -209,6 +210,7 @@ prefix: /
echo "/bin/python3 echo "/bin/python3
/usr/bin/sshync.py /usr/bin/sshync.py
/usr/bin/sshyp /usr/bin/sshyp
/usr/bin/sshyp.py
/usr/bin/sshypRemote.py /usr/bin/sshypRemote.py
/usr/share/doc/sshyp/changelog /usr/share/doc/sshyp/changelog
/usr/share/licenses/sshyp/license /usr/share/licenses/sshyp/license
@@ -230,7 +232,7 @@ pkgdesc='A light-weight, self-hosted, synchronized password manager'
url='https://github.com/rwinkhart/sshyp' url='https://github.com/rwinkhart/sshyp'
arch=('any') arch=('any')
license=('GPL3') license=('GPL3')
depends=(python gnupg openssh nano xclip wl-clipboard) depends=(python gnupg openssh xclip wl-clipboard)
source=(\""$source"\") source=(\""$source"\")
sha512sums=('"$sha512"') sha512sums=('"$sha512"')
Regular → Executable
+27 -33
View File
@@ -1,44 +1,38 @@
sshyp v1.0.0 sshyp v1.1.0
The Polished Trotters Update the sheecrets update
This release brings 'sshyp' in line with the original goals of the project. the main focus of this release was adding extension support (particularly for sshyp-mfa)
As such, it is being crowned "v1.0.0", and is considered a stable base for future expansions. and cleaning up the configuration experience
Features: compatibility-breaking changes:
- sshyp has been modified to run on Haiku, an open-source re-implementation of BeOS - the entry directory has been moved from ~/.password-pasture to ~/.local/share/sshyp
^ new package formats have been added for Haiku, FreeBSD, and Red Hat distributions (such as Fedora) as a means of complying with the XDG base directory spec
- the entry format has been changed to support entries created in pass/password-store ^ this requires the user to move their entry directory to the new location manually
^ this change requires existing entry libraries to be converted - see the sshyp v1.0.0 release page on GitHub for a conversion script ^ if this is not done, sshyp will not be able to find entries made prior to v1.1.0
- majorly overhauled visual experience for sshyp - due to a new configuration option (preferred text editor), 'sshyp tweak' must be run after updating
^ this includes a new entry list, a new entry readout, colored output, ascii art, and more!
- entries can now optionally be passed as arguments, directly
^ as opposed to the user being prompted for an entry name after running a command
Changes: user-facing features:
- corrected outdated licensing and copyright information - extension support
- 'if __name__=="__main__":' convention is now used ^ some of sshyp's critical functions can now be imported into other programs to extend
- help screens have been overhauled upon sshyp's functionality
- improved password generator to guarantee more secure results ^ the first usecase of this is sshyp-mfa, which can be found at https://github.com/rwinkhart/sshyp-labs
- fixed errors when trying to sync with a multi-word entry on the server - a new configuration experience
- sshync now uses 'scp' (via the sftp protocol), rather than 'sftp' ^ the 'sshyp tweak' menu has been made much more readable, and now the automatically generated
- rename function now properly allows for renaming folders gpg key has its id automatically input, making the process easier for the user
- renaming an entry/folder will now properly delete the old entry/folder from the server - customizable text editor
- entry previews at the time of entry creation/editing no longer require entering the gpg password more times than necessary ^ the text editor used for editing notes can now be set to any editor preferred by the user
- the clipboard now automatically clears after 30 seconds ^ this means that nano was dropped as a dependency
- sshyp server no longer needs to create copies of its libraries in ~/
- added more and improved existing exceptions fixes:
- the lock file is now decrypted to /dev/null
- ...and lots of minor (under the hood) changes! - to avoid exceptions on server devices, all arguments (apart from 'tweak') have been disabled on servers
- fixed entry readout not printing blank lines between fields under certain conditions
- various optimizations
<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
The full history of changes to "sshyp" can be found on the following page: The full history of changes to "sshyp" can be found on the following page:
https://raw.githubusercontent.com/rwinkhart/sshyp/main/extra/changelog-total https://raw.githubusercontent.com/rwinkhart/sshyp/main/extra/changelog-total
Legend:
- = feature/change implemented and pushed upstream
^ = note regarding the above feature/change