Compare commits
10 Commits
4909bb9c96
...
3d09446bed
Author | SHA1 | Date | |
---|---|---|---|
3d09446bed | |||
70b883cfe0 | |||
2514d5befd | |||
3863ddf42e | |||
19c6801d12 | |||
66cf9d4600 | |||
5072fb4df4 | |||
d713f9b4e6 | |||
a1edace600 | |||
5354c950fa |
4
LICENSE
4
LICENSE
@ -1,4 +1,6 @@
|
||||
Copyright 2022 James Eversole (james@eversole.co)
|
||||
ISC LICENSE
|
||||
|
||||
Copyright James Eversole (james@eversole.co)
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
cabal-version: 1.12
|
||||
|
||||
name: Purr
|
||||
version: 0.3.0
|
||||
version: 0.5.0
|
||||
description: https://git.eversole.co/Purr
|
||||
author: James Eversole
|
||||
maintainer: james@eversole.co
|
||||
@ -16,8 +16,7 @@ extra-source-files:
|
||||
executable Purr
|
||||
main-is: Main.hs
|
||||
hs-source-dirs:
|
||||
app
|
||||
, src
|
||||
src
|
||||
default-extensions:
|
||||
ConstraintKinds
|
||||
DeriveGeneric
|
||||
@ -64,5 +63,4 @@ executable Purr
|
||||
Feature.Sharing.HTTP
|
||||
Feature.Sharing.SQLite
|
||||
Feature.Sharing.Templates
|
||||
Lib
|
||||
default-language: Haskell2010
|
||||
|
51
README
51
README
@ -1,51 +0,0 @@
|
||||
purr
|
||||
-----
|
||||
|
||||
https://purr.eversole.co
|
||||
a work-in-progress web application offering customizable password generation
|
||||
and time-limited sharing of secrets.
|
||||
|
||||
TECH STACK
|
||||
|
||||
- Haskell and Scotty backend
|
||||
- HTMX frontend
|
||||
- SQLite database
|
||||
|
||||
GOALS
|
||||
|
||||
- Generate sufficiently memorable but secure passwords for use with accounts
|
||||
that don't offer better authentication methods.
|
||||
|
||||
- Share text secrets with others without disclosing the secret in the
|
||||
message itself.
|
||||
|
||||
- Provide a minimal and clean interface for generating and sharing passwords.
|
||||
|
||||
- Maintain a clean and organized codebase that can be extended to include more
|
||||
utilities than originally anticipated.
|
||||
|
||||
- Be really cute compared to the competition.
|
||||
|
||||
WHY TRUST YOU?
|
||||
|
||||
You shouldn't. This is free and open-source software which you can run on your
|
||||
own hardware.
|
||||
|
||||
DEPLOYMENT
|
||||
|
||||
Only Nix build instructions targeting containers are provided below,
|
||||
but this project can be built and run without containers or Nix using Cabal.
|
||||
|
||||
- Clone this repository
|
||||
- Build the container image (with flakes enabled): `nix build .#purr-container`
|
||||
- Load the container image
|
||||
- podman load -i result
|
||||
- Use the provided docker stack example to deploy the container if desired
|
||||
- docker stack deploy -c docker-stack.yml purr
|
||||
|
||||
DEVELOPMENT & SUPPORT
|
||||
|
||||
Per the permissive ISC license, you are free to do what you wish with this software. I hold
|
||||
no liability for any defects and no guarantees are made to its usability.
|
||||
|
||||
Copyright James Eversole (james@eversole.co)
|
79
README.md
Normal file
79
README.md
Normal file
@ -0,0 +1,79 @@
|
||||
# purr
|
||||
|
||||
[purr.eversole.co](https://purr.eversole.co)
|
||||
|
||||
a simple web application offering customizable password generation
|
||||
and time-limited sharing of secrets.
|
||||
|
||||
## TECH STACK
|
||||
|
||||
- [Haskell](https://www.haskell.org)
|
||||
- [Scotty](https://hackage.haskell.org/package/scotty)
|
||||
- [HTMX](https://htmx.org)
|
||||
- [SQLite](https://www.sqlite.org)
|
||||
|
||||
## GOALS
|
||||
|
||||
- Provide a minimal and clean interface for generating and sharing passwords.
|
||||
- Generate sufficiently memorable but secure passwords for use with accounts
|
||||
that don't offer better authentication methods.
|
||||
- Share text secrets with others without disclosing the secret in the
|
||||
message itself.
|
||||
- Maintain a clean and organized codebase that can be extended to include more
|
||||
utilities than originally anticipated.
|
||||
|
||||
## WHY TRUST PURR.EVERSOLE.CO?
|
||||
|
||||
You shouldn't. This is free and open-source software which you can run on your
|
||||
own hardware!
|
||||
|
||||
## DEPLOYMENT
|
||||
|
||||
Only Nix build instructions are provided below.
|
||||
|
||||
### No Containers
|
||||
1) Clone this repository
|
||||
2) Build the application (with flakes enabled): `nix build '.#'`
|
||||
3) Set the environment variables
|
||||
- File: `cp examples/.env.example ./.env; $EDITOR ./.env`
|
||||
- If you want to set them in a different way, you already know how.
|
||||
4) Run the application: `./result/bin/Purr`
|
||||
|
||||
### Containers
|
||||
1) Clone this repository
|
||||
2) Build the container image (with flakes enabled): `nix build .#purr-container`
|
||||
3) Load the container image: `podman load -i result`
|
||||
4) Pick option 5, 6, or use your favorite process to manage containers and ENV
|
||||
5) NixOS configuration:
|
||||
```
|
||||
virtualisation.oci-containers.containers.purr = {
|
||||
image = "purr";
|
||||
ports = [ "${PURR_EXTERNAL_PORT}:3000" ];
|
||||
volumes = [
|
||||
"/PATH/TO/PURR/data:/app/data"
|
||||
];
|
||||
environment = {
|
||||
PURRNOFILE = "true";
|
||||
ENVIRONMENT = "production";
|
||||
APPLICATIONHOST = "localhost";
|
||||
APPLICATIONPORT = "3000";
|
||||
DATADIR = "/app/";
|
||||
LINKLENGTH = "24";
|
||||
ADMINEMAIL = "${YOUR_EMAIL}";
|
||||
};
|
||||
};
|
||||
```
|
||||
6) Docker Stack
|
||||
1) Copy the docker-stack.yml example and edit as needed.
|
||||
1) `cp examples/docker-stack.yml ./; $EDITOR ./docker-stack.yml`
|
||||
2) Set environment variables:
|
||||
- `cp examples/.env.example ./.env; $EDITOR ./.env`
|
||||
- Or set them in the docker-stack.yml environment declaration.
|
||||
3) Deploy: `docker stack deploy -c docker-stack.yml purr`
|
||||
|
||||
## DEVELOPMENT & SUPPORT
|
||||
|
||||
Per the permissive ISC license, you are free to do what you wish with this
|
||||
software. No guarantees are made to its usability, security, or functionality.
|
||||
|
||||
Copyright James Eversole (james@eversole.co)
|
@ -1,7 +0,0 @@
|
||||
module Main where
|
||||
|
||||
import Prelude
|
||||
import qualified Lib
|
||||
|
||||
main :: IO ()
|
||||
main = Lib.main
|
1
data/assets/public/htmx.js
Normal file
1
data/assets/public/htmx.js
Normal file
File diff suppressed because one or more lines are too long
@ -1,21 +1,48 @@
|
||||
module Core.Configuration where
|
||||
module Core.Configuration ( adminEmail, appPort
|
||||
, confLinkLength, dataPath, dbPath
|
||||
, encKey, getRuntimeEnvironment
|
||||
, keyFileInit, init) where
|
||||
|
||||
import qualified Data.ByteString as B
|
||||
|
||||
import Control.Monad (mapM)
|
||||
import Configuration.Dotenv
|
||||
import Core.Types
|
||||
import Crypto.Saltine.Core.SecretBox (newKey)
|
||||
import Crypto.Saltine.Class (encode)
|
||||
import Configuration.Dotenv
|
||||
import Prelude hiding (init)
|
||||
import System.Directory (doesFileExist)
|
||||
import System.Environment (getEnv, lookupEnv)
|
||||
|
||||
-- Make the dotenv file configuration available if PURRNOFILE is not present
|
||||
main :: IO ()
|
||||
main = do
|
||||
envFile <- lookupEnv "PURRNOFILE"
|
||||
case envFile of
|
||||
Nothing -> loadFile defaultConfig
|
||||
_ -> putStrLn "Not using dotenv file"
|
||||
-- Load environment variables from dotenv file if required
|
||||
init :: IO ()
|
||||
init = do
|
||||
reqEnvLookup <- getRequiredEnv
|
||||
if (Nothing `elem` reqEnvLookup)
|
||||
then checkEnvFile requiredEnvVars
|
||||
else pure ()
|
||||
where
|
||||
getRequiredEnv :: IO [Maybe String]
|
||||
getRequiredEnv = mapM (\s -> lookupEnv s) requiredEnvVars
|
||||
|
||||
checkEnvFile :: [String] -> IO ()
|
||||
checkEnvFile requiredEnv = do
|
||||
dotEnvExists <- doesFileExist "./.env"
|
||||
if dotEnvExists
|
||||
then do
|
||||
loadFile defaultConfig
|
||||
fromEnvFile <- getRequiredEnv
|
||||
if (Nothing `elem` fromEnvFile)
|
||||
then error $ missingEnvMsg requiredEnv
|
||||
else pure ()
|
||||
else error $ "Cannot find .env file in application directory.\n"
|
||||
++ missingEnvMsg requiredEnv
|
||||
|
||||
missingEnvMsg :: [String] -> String
|
||||
missingEnvMsg required =
|
||||
"Missing required environment variable(s).\n"
|
||||
++ "All required environment variables:\n"
|
||||
++ unlines required
|
||||
|
||||
-- Check if an encryption key exists on the filesystem and create one if not
|
||||
keyFileInit :: IO ()
|
||||
@ -39,6 +66,9 @@ encKey = do
|
||||
adminEmail :: IO String
|
||||
adminEmail = getEnv "ADMINEMAIL"
|
||||
|
||||
getRuntimeEnvironment :: IO String
|
||||
getRuntimeEnvironment = getEnv "ENVIRONMENT"
|
||||
|
||||
appPort :: IO String
|
||||
appPort = getEnv "APPLICATIONPORT"
|
||||
|
||||
@ -50,3 +80,8 @@ dbPath = "data/Purr.sqlite"
|
||||
|
||||
confLinkLength :: IO String
|
||||
confLinkLength = getEnv "LINKLENGTH"
|
||||
|
||||
requiredEnvVars :: [String]
|
||||
requiredEnvVars = [ "ADMINEMAIL", "APPLICATIONHOST", "APPLICATIONPORT"
|
||||
, "DATADIR", "ENVIRONMENT", "LINKLENGTH"
|
||||
]
|
||||
|
@ -1,22 +1,28 @@
|
||||
module Core.HTTP ( app ) where
|
||||
|
||||
import Core.Configuration (adminEmail, confLinkLength)
|
||||
import Core.Configuration ( adminEmail
|
||||
, confLinkLength
|
||||
, getRuntimeEnvironment)
|
||||
import Core.Types
|
||||
|
||||
import Core.Templates (renderIndex, renderStyle)
|
||||
import Feature.Generation.HTTP as Generation
|
||||
import Feature.Sharing.HTTP as Sharing
|
||||
|
||||
import Control.Monad (void)
|
||||
import Control.Monad.Trans (liftIO)
|
||||
import Data.Maybe (Maybe (Nothing))
|
||||
import Network.Wai.Middleware.RequestLogger (logStdoutDev)
|
||||
import Network.Wai.Middleware.RequestLogger (logStdout, logStdoutDev)
|
||||
import Network.Wai.Middleware.Static
|
||||
import Web.Scotty
|
||||
|
||||
app :: PurrApp ()
|
||||
app = do
|
||||
app :: String -> PurrApp ()
|
||||
app env = do
|
||||
-- Middleware that are processed on every request
|
||||
middleware logStdoutDev
|
||||
case env of
|
||||
"production" -> middleware logStdout
|
||||
"prod" -> middleware logStdout
|
||||
_ -> middleware logStdoutDev
|
||||
middleware $ staticPolicy (noDots >-> addBase "data/assets/public")
|
||||
|
||||
-- Core Routes
|
||||
|
@ -6,12 +6,13 @@ import Core.Types
|
||||
import Data.ByteString as B
|
||||
import Database.SQLite.Simple
|
||||
import Database.SQLite.Simple.FromRow
|
||||
import Prelude hiding (init)
|
||||
|
||||
import qualified Data.Text as T
|
||||
|
||||
-- Set up SQLite database table when Purr starts if it doesn't already exist
|
||||
main :: IO ()
|
||||
main = do
|
||||
init :: IO ()
|
||||
init = do
|
||||
conn <- open dbPath
|
||||
execute_ conn
|
||||
"CREATE TABLE IF NOT EXISTS pws\
|
||||
|
@ -1,4 +1,4 @@
|
||||
module Lib ( main ) where
|
||||
module Main ( main ) where
|
||||
|
||||
import qualified Core.Configuration as Configuration
|
||||
import qualified Core.HTTP as HTTP
|
||||
@ -16,16 +16,16 @@ main = do
|
||||
-- Initialize the RNG used for sodium encryption (Saltine library)
|
||||
sodiumInit
|
||||
{- Initialize our dotenv configuration which reads from a .env configuration
|
||||
file unless the PURRNOFILE env var exists already. -}
|
||||
Configuration.main
|
||||
if any required variables are missing from the provided environment -}
|
||||
Configuration.init
|
||||
{- Initialize the encryption key file if it doesn't
|
||||
exist yet or use the existing key -}
|
||||
Configuration.keyFileInit
|
||||
{- Initialize our database by ensuring the SQLite file exists
|
||||
and has tables setup as the application expects -}
|
||||
DB.main
|
||||
DB.init
|
||||
{- Get the configured port to run on and start the Scotty webserver app
|
||||
defined in HTTP.app -}
|
||||
appPortStr <- Configuration.appPort
|
||||
let appPort = read appPortStr :: Int
|
||||
scotty appPort HTTP.app
|
||||
port <- Configuration.appPort
|
||||
env <- Configuration.getRuntimeEnvironment
|
||||
scotty (read port) (HTTP.app env)
|
@ -70,6 +70,7 @@ input[type=number]
|
||||
padding: 1em 0.5em 0.5em 1.5em
|
||||
background-color: #{colorTwo}
|
||||
box-shadow: 8px 8px 12px #ccc
|
||||
overflow-x: scroll
|
||||
|
||||
.generators .numberInput
|
||||
background-color: #{colorTwo}
|
||||
@ -276,6 +277,9 @@ input[type=number]
|
||||
.mainButton
|
||||
width: 80%
|
||||
|
||||
.generators
|
||||
font-size: 12px
|
||||
|
||||
.genButton
|
||||
width: 80%
|
||||
|
||||
|
@ -4,14 +4,14 @@ $doctype 5
|
||||
<head>
|
||||
<title>purr
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<script src="https://unpkg.com/htmx.org@1.7.0" integrity="sha384-EzBXYPt0/T6gxNp0nuPtLkmRpmDBbjg6WmCUZRLXBBwYYmwAUxzlSGej0ARHX0Bo" crossorigin="anonymous">
|
||||
<script src="/htmx.js" integrity="sha384-wS5l5IKJBvK6sPTKa2WZ1js3d947pvWXbPJ1OmWfEuxLgeHcEbjUUA5i9V5ZkpCw">
|
||||
<script src="/copyButtons.js" integrity="sha384-eNQZr7QWPQmi/EWi4lVVFOavm+Eibmh7iDvDptgE0j5fI3xycLssbDBZbKphi8pk">
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
|
||||
<body>
|
||||
|
||||
<header>
|
||||
<a href="https://git.eversole.co/purr">
|
||||
<a href="https://git.eversole.co">
|
||||
made with ♥
|
||||
|
|
||||
<a href="mailto:#{email}">contact
|
||||
|
Loading…
x
Reference in New Issue
Block a user