    {-# LANGUAGE TypeFamilies #-}

    module Propellor.Property.Tor where

    import Propellor.Base
    import qualified Propellor.Property.File as File
    import qualified Propellor.Property.Apt as Apt
    import qualified Propellor.Property.Service as Service
    import qualified Propellor.Property.ConfFile as ConfFile
    import Utility.DataUnits

    import System.Posix.Files
    import Data.Char
    import Data.List

    type HiddenServiceName = String

    type NodeName = String

    -- | Sets up a tor bridge. (Not a relay or exit node.)
    --
    -- Uses port 443
    isBridge :: Property DebianLike
    isBridge :: Property DebianLike
    isBridge = [(String, String)] -> Property DebianLike
    configured
    	[ (String
    "BridgeRelay", String
    "1")
    	, (String
    "Exitpolicy", String
    "reject *:*")
    	, (String
    "ORPort", String
    "443")
    	]
    	Property DebianLike -> String -> Property DebianLike
    forall p. IsProp p => p -> String -> p
    `describe` String
    "tor bridge"
    	Property DebianLike
    -> Property DebianLike
    -> CombinedType (Property DebianLike) (Property DebianLike)
    forall x y. Combines x y => x -> y -> CombinedType x y
    `requires` Property DebianLike
    server

    -- | Sets up a tor relay.
    --
    -- Uses port 443
    isRelay :: Property DebianLike
    isRelay :: Property DebianLike
    isRelay = [(String, String)] -> Property DebianLike
    configured
    	[ (String
    "BridgeRelay", String
    "0")
    	, (String
    "Exitpolicy", String
    "reject *:*")
    	, (String
    "ORPort", String
    "443")
    	]
    	Property DebianLike -> String -> Property DebianLike
    forall p. IsProp p => p -> String -> p
    `describe` String
    "tor relay"
    	Property DebianLike
    -> Property DebianLike
    -> CombinedType (Property DebianLike) (Property DebianLike)
    forall x y. Combines x y => x -> y -> CombinedType x y
    `requires` Property DebianLike
    server

    -- | Makes the tor node be named, with a known private key.
    --
    -- This can be moved to a different IP without needing to wait to
    -- accumulate trust.
    named :: NodeName -> Property (HasInfo + DebianLike)
    named :: String -> Property (HasInfo + DebianLike)
    named String
    n = [(String, String)] -> Property DebianLike
    configured [(String
    "Nickname", String
    n')]
    	Property DebianLike -> String -> Property DebianLike
    forall p. IsProp p => p -> String -> p
    `describe` (String
    "tor node named " String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ String
    n')
    	Property DebianLike
    -> Property
         (MetaTypes
            '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish])
    -> CombinedType
         (Property DebianLike)
         (Property
            (MetaTypes
               '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish]))
    forall x y. Combines x y => x -> y -> CombinedType x y
    `requires` Context -> Property (HasInfo + DebianLike)
    torPrivKey (String -> Context
    Context (String
    "tor " String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ String
    n))
      where
    	n' :: String
    n' = String -> String
    saneNickname String
    n

    -- | Configures tor with secret_id_key, ed25519_master_id_public_key,
    -- and ed25519_master_id_secret_key from privdata.
    torPrivKey :: Context -> Property (HasInfo + DebianLike)
    torPrivKey :: Context -> Property (HasInfo + DebianLike)
    torPrivKey Context
    context = [Property
       (MetaTypes
          '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
             'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD])]
    -> Property
         (MetaTypes
            '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
               'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD])
    forall a. Monoid a => [a] -> a
    mconcat ((String
     -> Property
          (MetaTypes
             '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
                'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD]))
    -> [String]
    -> [Property
          (MetaTypes
             '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
                'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD])]
    forall a b. (a -> b) -> [a] -> [b]
    map String
    -> CombinedType
         (Property
            (MetaTypes
               '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
                  'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD]))
         (Property UnixLike)
    String
    -> Property
         (MetaTypes
            '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
               'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD])
    go [String]
    keyfiles)
    	Property
      (MetaTypes
         '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
            'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD])
    -> Property DebianLike
    -> CombinedType
         (Property
            (MetaTypes
               '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
                  'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD]))
         (Property DebianLike)
    forall x y. Combines x y => x -> y -> CombinedType x y
    `onChange` Property DebianLike
    restarted
    	CombinedType
      (Property
         (MetaTypes
            '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
               'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD]))
      (Property DebianLike)
    -> Property DebianLike
    -> CombinedType
         (CombinedType
            (Property
               (MetaTypes
                  '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
                     'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD]))
            (Property DebianLike))
         (Property DebianLike)
    forall x y. Combines x y => x -> y -> CombinedType x y
    `requires` Property DebianLike
    torPrivKeyDirExists
      where
    	keyfiles :: [String]
    keyfiles = (String -> String) -> [String] -> [String]
    forall a b. (a -> b) -> [a] -> [b]
    map (String
    torPrivKeyDir String -> String -> String
    </>)
    		[ String
    "secret_id_key"
    		, String
    "ed25519_master_id_public_key"
    		, String
    "ed25519_master_id_secret_key"
    		]
    	go :: String
    -> CombinedType
         (Property
            (MetaTypes
               '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
                  'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD]))
         (Property UnixLike)
    go String
    f = String
    f String -> Context -> Property (HasInfo + UnixLike)
    forall c.
    IsContext c =>
    String -> c -> Property (HasInfo + UnixLike)
    `File.hasPrivContent` Context
    context
    		Property
      (MetaTypes
         '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
            'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD])
    -> Property UnixLike
    -> CombinedType
         (Property
            (MetaTypes
               '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
                  'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD]))
         (Property UnixLike)
    forall x y. Combines x y => x -> y -> CombinedType x y
    `onChange` String -> User -> Group -> Property UnixLike
    File.ownerGroup String
    f User
    user (User -> Group
    userGroup User
    user)

    torPrivKeyDirExists :: Property DebianLike
    torPrivKeyDirExists :: Property DebianLike
    torPrivKeyDirExists = String -> Property UnixLike
    File.dirExists String
    torPrivKeyDir
    	Property UnixLike
    -> Property UnixLike
    -> CombinedType (Property UnixLike) (Property UnixLike)
    forall x y. Combines x y => x -> y -> CombinedType x y
    `onChange` Property UnixLike
    setperms
    	Property UnixLike
    -> Property DebianLike
    -> CombinedType (Property UnixLike) (Property DebianLike)
    forall x y. Combines x y => x -> y -> CombinedType x y
    `requires` Property DebianLike
    installed
      where
    	setperms :: CombinedType (Property UnixLike) (Property UnixLike)
    setperms = String -> User -> Group -> Property UnixLike
    File.ownerGroup String
    torPrivKeyDir User
    user (User -> Group
    userGroup User
    user)
    		Property UnixLike
    -> Property UnixLike
    -> CombinedType (Property UnixLike) (Property UnixLike)
    forall x y. Combines x y => x -> y -> CombinedType x y
    `before` String -> FileMode -> Property UnixLike
    File.mode String
    torPrivKeyDir FileMode
    0O2700

    torPrivKeyDir :: FilePath
    torPrivKeyDir :: String
    torPrivKeyDir = String
    "/var/lib/tor/keys"

    -- | A tor server (bridge, relay, or exit)
    -- Don't use if you just want to run tor for personal use.
    server :: Property DebianLike
    server :: Property DebianLike
    server = [(String, String)] -> Property DebianLike
    configured [(String
    "SocksPort", String
    "0")]
    	Property DebianLike
    -> Property DebianLike
    -> CombinedType (Property DebianLike) (Property DebianLike)
    forall x y. Combines x y => x -> y -> CombinedType x y
    `requires` Property DebianLike
    installed
    	Property DebianLike
    -> Property DebianLike
    -> CombinedType (Property DebianLike) (Property DebianLike)
    forall x y. Combines x y => x -> y -> CombinedType x y
    `requires` [String] -> Property DebianLike
    Apt.installed [String
    "ntp"]
    	Property DebianLike -> String -> Property DebianLike
    forall p. IsProp p => p -> String -> p
    `describe` String
    "tor server"

    installed :: Property DebianLike
    installed :: Property DebianLike
    installed = [String] -> Property DebianLike
    Apt.installed [String
    "tor"]

    -- | Specifies configuration settings. Any lines in the config file
    -- that set other values for the specified settings will be removed,
    -- while other settings are left as-is. Tor is restarted when
    -- configuration is changed.
    configured :: [(String, String)] -> Property DebianLike
    configured :: [(String, String)] -> Property DebianLike
    configured [(String, String)]
    settings = String -> ([String] -> [String]) -> String -> Property UnixLike
    forall c.
    (FileContent c, Eq c) =>
    String -> (c -> c) -> String -> Property UnixLike
    File.fileProperty String
    "tor configured" [String] -> [String]
    go String
    mainConfig
    	Property UnixLike
    -> Property DebianLike
    -> CombinedType (Property UnixLike) (Property DebianLike)
    forall x y. Combines x y => x -> y -> CombinedType x y
    `onChange` Property DebianLike
    restarted
      where
    	ks :: [String]
    ks = ((String, String) -> String) -> [(String, String)] -> [String]
    forall a b. (a -> b) -> [a] -> [b]
    map (String, String) -> String
    forall a b. (a, b) -> a
    fst [(String, String)]
    settings
    	go :: [String] -> [String]
    go [String]
    ls = [String] -> [String]
    forall a. Ord a => [a] -> [a]
    sort ([String] -> [String]) -> [String] -> [String]
    forall a b. (a -> b) -> a -> b
    $ ((String, String) -> String) -> [(String, String)] -> [String]
    forall a b. (a -> b) -> [a] -> [b]
    map (String, String) -> String
    toconfig ([(String, String)] -> [String]) -> [(String, String)] -> [String]
    forall a b. (a -> b) -> a -> b
    $
    		((String, String) -> Bool)
    -> [(String, String)] -> [(String, String)]
    forall a. (a -> Bool) -> [a] -> [a]
    filter (\(String
    k, String
    _) -> String
    k String -> [String] -> Bool
    forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
    `notElem` [String]
    ks) ((String -> (String, String)) -> [String] -> [(String, String)]
    forall a b. (a -> b) -> [a] -> [b]
    map String -> (String, String)
    fromconfig [String]
    ls)
    		[(String, String)] -> [(String, String)] -> [(String, String)]
    forall a. [a] -> [a] -> [a]
    ++ [(String, String)]
    settings
    	toconfig :: (String, String) -> String
    toconfig (String
    k, String
    v) = String
    k String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ String
    " " String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ String
    v
    	fromconfig :: String -> (String, String)
    fromconfig = (Char -> Bool) -> String -> (String, String)
    forall a. (a -> Bool) -> [a] -> ([a], [a])
    separate (Char -> Char -> Bool
    forall a. Eq a => a -> a -> Bool
    == Char
    ' ')

    data BwLimit
    	= PerSecond String
    	| PerDay String
    	| PerMonth String

    -- | Limit incoming and outgoing traffic to the specified
    -- amount each.
    --
    -- For example, PerSecond "30 kibibytes" is the minimum limit
    -- for a useful relay.
    bandwidthRate :: BwLimit -> Property DebianLike
    bandwidthRate :: BwLimit -> Property DebianLike
    bandwidthRate (PerSecond String
    s) = String -> ByteSize -> Property DebianLike
    bandwidthRate' String
    s ByteSize
    1
    bandwidthRate (PerDay String
    s) = String -> ByteSize -> Property DebianLike
    bandwidthRate' String
    s (ByteSize
    24ByteSize -> ByteSize -> ByteSize
    forall a. Num a => a -> a -> a
    *ByteSize
    60ByteSize -> ByteSize -> ByteSize
    forall a. Num a => a -> a -> a
    *ByteSize
    60)
    bandwidthRate (PerMonth String
    s) = String -> ByteSize -> Property DebianLike
    bandwidthRate' String
    s (ByteSize
    31ByteSize -> ByteSize -> ByteSize
    forall a. Num a => a -> a -> a
    *ByteSize
    24ByteSize -> ByteSize -> ByteSize
    forall a. Num a => a -> a -> a
    *ByteSize
    60ByteSize -> ByteSize -> ByteSize
    forall a. Num a => a -> a -> a
    *ByteSize
    60)

    bandwidthRate' :: String -> Integer -> Property DebianLike
    bandwidthRate' :: String -> ByteSize -> Property DebianLike
    bandwidthRate' String
    s ByteSize
    divby = case [Unit] -> String -> Maybe ByteSize
    readSize [Unit]
    dataUnits String
    s of
    	Just ByteSize
    sz -> let v :: String
    v = ByteSize -> String
    forall a. Show a => a -> String
    show (ByteSize
    sz ByteSize -> ByteSize -> ByteSize
    forall a. Integral a => a -> a -> a
    `div` ByteSize
    divby) String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ String
    " bytes"
    		in [(String, String)] -> Property DebianLike
    configured [(String
    "BandwidthRate", String
    v)]
    			Property DebianLike -> String -> Property DebianLike
    forall p. IsProp p => p -> String -> p
    `describe` (String
    "tor BandwidthRate " String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ String
    v)
    	Maybe ByteSize
    Nothing -> String -> Propellor Result -> Property DebianLike
    forall {k} (metatypes :: k).
    SingI metatypes =>
    String -> Propellor Result -> Property (MetaTypes metatypes)
    property (String
    "unable to parse " String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ String
    s) Propellor Result
    noChange

    -- | Enables a hidden service for a given port.
    --
    -- If used without `hiddenServiceData`, tor will generate a new
    -- private key.
    hiddenService :: HiddenServiceName -> Port -> Property DebianLike
    hiddenService :: String -> Port -> Property DebianLike
    hiddenService String
    hn Port
    port = String -> [Port] -> Property DebianLike
    hiddenService' String
    hn [Port
    port]

    hiddenService' :: HiddenServiceName -> [Port] -> Property DebianLike
    hiddenService' :: String -> [Port] -> Property DebianLike
    hiddenService' String
    hn [Port]
    ports = String
    -> SectionStart
    -> SectionStart
    -> ([String] -> [String])
    -> ([String] -> [String])
    -> String
    -> Property UnixLike
    ConfFile.adjustSection
    	([String] -> String
    unwords [String
    "hidden service", String
    hn, String
    "available on ports", String -> [String] -> String
    forall a. [a] -> [[a]] -> [a]
    intercalate String
    "," ((Port -> String) -> [Port] -> [String]
    forall a b. (a -> b) -> [a] -> [b]
    map Port -> String
    forall t. ConfigurableValue t => t -> String
    val [Port]
    ports')])
    	(String -> SectionStart
    forall a. Eq a => a -> a -> Bool
    == String
    oniondir)
    	(Bool -> Bool
    not (Bool -> Bool) -> SectionStart -> SectionStart
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . String -> SectionStart
    forall a. Eq a => [a] -> [a] -> Bool
    isPrefixOf String
    "HiddenServicePort")
    	([String] -> [String] -> [String]
    forall a b. a -> b -> a
    const (String
    oniondir String -> [String] -> [String]
    forall a. a -> [a] -> [a]
    : [String]
    onionports))
    	([String] -> [String] -> [String]
    forall a. [a] -> [a] -> [a]
    ++ String
    oniondir String -> [String] -> [String]
    forall a. a -> [a] -> [a]
    : [String]
    onionports)
    	String
    mainConfig
    	Property UnixLike
    -> Property DebianLike
    -> CombinedType (Property UnixLike) (Property DebianLike)
    forall x y. Combines x y => x -> y -> CombinedType x y
    `onChange` Property DebianLike
    restarted
      where
    	oniondir :: String
    oniondir = [String] -> String
    unwords [String
    "HiddenServiceDir", String
    varLib String -> String -> String
    </> String
    hn]
    	onionports :: [String]
    onionports = (Port -> String) -> [Port] -> [String]
    forall a b. (a -> b) -> [a] -> [b]
    map Port -> String
    forall t. ConfigurableValue t => t -> String
    onionport [Port]
    ports'
    	ports' :: [Port]
    ports' = [Port] -> [Port]
    forall a. Ord a => [a] -> [a]
    sort [Port]
    ports
    	onionport :: t -> String
    onionport t
    port = [String] -> String
    unwords [String
    "HiddenServicePort", t -> String
    forall t. ConfigurableValue t => t -> String
    val t
    port, String
    "127.0.0.1:" String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ t -> String
    forall t. ConfigurableValue t => t -> String
    val t
    port]

    -- | Same as `hiddenService` but also causes propellor to display
    -- the onion address of the hidden service.
    hiddenServiceAvailable :: HiddenServiceName -> Port -> Property DebianLike
    hiddenServiceAvailable :: String -> Port -> Property DebianLike
    hiddenServiceAvailable String
    hn Port
    port = String -> [Port] -> Property DebianLike
    hiddenServiceAvailable' String
    hn [Port
    port]

    hiddenServiceAvailable' :: HiddenServiceName -> [Port] -> Property DebianLike
    hiddenServiceAvailable' :: String -> [Port] -> Property DebianLike
    hiddenServiceAvailable' String
    hn [Port]
    ports = Property DebianLike -> Property DebianLike
    hiddenServiceHostName (Property DebianLike -> Property DebianLike)
    -> Property DebianLike -> Property DebianLike
    forall a b. (a -> b) -> a -> b
    $ String -> [Port] -> Property DebianLike
    hiddenService' String
    hn [Port]
    ports
      where
    	hiddenServiceHostName :: Property DebianLike -> Property DebianLike
    hiddenServiceHostName Property DebianLike
    p =  Property DebianLike
    -> (Propellor Result -> Propellor Result) -> Property DebianLike
    forall metatypes.
    Property metatypes
    -> (Propellor Result -> Propellor Result) -> Property metatypes
    adjustPropertySatisfy Property DebianLike
    p ((Propellor Result -> Propellor Result) -> Property DebianLike)
    -> (Propellor Result -> Propellor Result) -> Property DebianLike
    forall a b. (a -> b) -> a -> b
    $ \Propellor Result
    satisfy -> do
    		r <- Propellor Result
    satisfy
    		mh <- liftIO $ tryIO $ readFile (varLib </> hn </> "hostname")
    		case mh of
    			Right String
    h -> [String] -> Propellor ()
    forall (m :: * -> *). MonadIO m => [String] -> m ()
    infoMessage [String
    "hidden service hostname:", String
    h]
    			Left IOException
    _e -> String -> Propellor ()
    forall (m :: * -> *). MonadIO m => String -> m ()
    warningMessage String
    "hidden service hostname not available yet"
    		return r

    -- | Load the private key for a hidden service from the privdata.
    hiddenServiceData :: IsContext c => HiddenServiceName -> c -> Property (HasInfo + DebianLike)
    hiddenServiceData :: forall c.
    IsContext c =>
    String -> c -> Property (HasInfo + DebianLike)
    hiddenServiceData String
    hn c
    context = String
    -> Props
         (MetaTypes
            '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish])
    -> Property
         (MetaTypes
            '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish])
    forall {k} (metatypes :: k).
    SingI metatypes =>
    String
    -> Props (MetaTypes metatypes) -> Property (MetaTypes metatypes)
    combineProperties String
    desc (Props
       (MetaTypes
          '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish])
     -> Property
          (MetaTypes
             '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish]))
    -> Props
         (MetaTypes
            '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish])
    -> Property
         (MetaTypes
            '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish])
    forall a b. (a -> b) -> a -> b
    $ Props UnixLike
    props
    	Props UnixLike
    -> Property
         (MetaTypes
            '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish])
    -> Props
         (Sing
            (Combine
               '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
                  'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD]
               '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish]))
    forall {a} p (y :: [a]) (x :: [a]).
    (IsProp p, MetaTypes y ~ GetMetaTypes p,
     CheckCombinableNote x y (NoteFor ('Text "&"))) =>
    Props (MetaTypes x) -> p -> Props (MetaTypes (Combine x y))
    & String -> Property (HasInfo + DebianLike)
    installonion String
    "hostname"
    	Props
      (Sing
         (Combine
            '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
               'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD]
            '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish]))
    -> Property
         (MetaTypes
            '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish])
    -> Props
         (MetaTypes
            (Combine
               (Combine
                  '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
                     'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD]
                  '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish])
               '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish]))
    forall {a} p (y :: [a]) (x :: [a]).
    (IsProp p, MetaTypes y ~ GetMetaTypes p,
     CheckCombinableNote x y (NoteFor ('Text "&"))) =>
    Props (MetaTypes x) -> p -> Props (MetaTypes (Combine x y))
    & String -> Property (HasInfo + DebianLike)
    installonion String
    "private_key"
      where
    	desc :: String
    desc = [String] -> String
    unwords [String
    "hidden service data available in", String
    varLib String -> String -> String
    </> String
    hn]
    	installonion :: FilePath -> Property (HasInfo + DebianLike)
    	installonion :: String -> Property (HasInfo + DebianLike)
    installonion String
    basef =
    		let f :: String
    f = String
    varLib String -> String -> String
    </> String
    hn String -> String -> String
    </> String
    basef
    		in PrivDataField
    -> c
    -> (((PrivData -> Propellor Result) -> Propellor Result)
        -> Property (HasInfo + DebianLike))
    -> Property (HasInfo + DebianLike)
    forall c s metatypes.
    (IsContext c, IsPrivDataSource s,
     IncludesInfo metatypes ~ 'True) =>
    s
    -> c
    -> (((PrivData -> Propellor Result) -> Propellor Result)
        -> Property metatypes)
    -> Property metatypes
    withPrivData (String -> PrivDataField
    PrivFile String
    f) c
    context ((((PrivData -> Propellor Result) -> Propellor Result)
      -> Property (HasInfo + DebianLike))
     -> Property (HasInfo + DebianLike))
    -> (((PrivData -> Propellor Result) -> Propellor Result)
        -> Property (HasInfo + DebianLike))
    -> Property (HasInfo + DebianLike)
    forall a b. (a -> b) -> a -> b
    $ \(PrivData -> Propellor Result) -> Propellor Result
    getcontent ->
    		String
    -> (OuterMetaTypesWitness
          '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish]
        -> Propellor Result)
    -> Property
         (MetaTypes
            '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish])
    forall {k} (metatypes :: k).
    SingI metatypes =>
    String
    -> (OuterMetaTypesWitness metatypes -> Propellor Result)
    -> Property (MetaTypes metatypes)
    property' String
    desc ((OuterMetaTypesWitness
        '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish]
      -> Propellor Result)
     -> Property
          (MetaTypes
             '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish]))
    -> (OuterMetaTypesWitness
          '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish]
        -> Propellor Result)
    -> Property
         (MetaTypes
            '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish])
    forall a b. (a -> b) -> a -> b
    $ \OuterMetaTypesWitness
      '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish]
    w -> (PrivData -> Propellor Result) -> Propellor Result
    getcontent ((PrivData -> Propellor Result) -> Propellor Result)
    -> (PrivData -> Propellor Result) -> Propellor Result
    forall a b. (a -> b) -> a -> b
    $ \PrivData
    privcontent ->
    			Propellor Bool
    -> (Propellor Result, Propellor Result) -> Propellor Result
    forall (m :: * -> *) a. Monad m => m Bool -> (m a, m a) -> m a
    ifM (IO Bool -> Propellor Bool
    forall a. IO a -> Propellor a
    forall (m :: * -> *) a. MonadIO m => IO a -> m a
    liftIO (IO Bool -> Propellor Bool) -> IO Bool -> Propellor Bool
    forall a b. (a -> b) -> a -> b
    $ String -> IO Bool
    doesFileExist String
    f)
    				( Propellor Result
    noChange
    				, OuterMetaTypesWitness
      '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish]
    -> Property UnixLike -> Propellor Result
    forall (inner :: [MetaType]) (outer :: [MetaType]).
    EnsurePropertyAllowed inner outer =>
    OuterMetaTypesWitness outer
    -> Property (MetaTypes inner) -> Propellor Result
    ensureProperty OuterMetaTypesWitness
      '[ 'WithInfo, 'Targeting 'OSDebian, 'Targeting 'OSBuntish]
    w (Property UnixLike -> Propellor Result)
    -> Property UnixLike -> Propellor Result
    forall a b. (a -> b) -> a -> b
    $ String -> Props UnixLike -> Property UnixLike
    forall {k} (metatypes :: k).
    SingI metatypes =>
    String
    -> Props (MetaTypes metatypes) -> Property (MetaTypes metatypes)
    propertyList String
    desc (Props UnixLike -> Property UnixLike)
    -> Props UnixLike -> Property UnixLike
    forall a b. (a -> b) -> a -> b
    $ [Property UnixLike] -> Props UnixLike
    forall {k} (metatypes :: k).
    [Property (MetaTypes metatypes)] -> Props (MetaTypes metatypes)
    toProps
    					[ String -> Propellor Result -> Property UnixLike
    forall {k} (metatypes :: k).
    SingI metatypes =>
    String -> Propellor Result -> Property (MetaTypes metatypes)
    property String
    desc (Propellor Result -> Property UnixLike)
    -> Propellor Result -> Property UnixLike
    forall a b. (a -> b) -> a -> b
    $ IO () -> Propellor Result
    makeChange (IO () -> Propellor Result) -> IO () -> Propellor Result
    forall a b. (a -> b) -> a -> b
    $ do
    						Bool -> String -> IO ()
    createDirectoryIfMissing Bool
    True (String -> String
    takeDirectory String
    f)
    						String -> String -> IO ()
    writeFileProtected String
    f ([String] -> String
    unlines (PrivData -> [String]
    privDataLines PrivData
    privcontent))
    					, String -> FileMode -> Property UnixLike
    File.mode (String -> String
    takeDirectory String
    f) (FileMode -> Property UnixLike) -> FileMode -> Property UnixLike
    forall a b. (a -> b) -> a -> b
    $ [FileMode] -> FileMode
    combineModes
    						[FileMode
    ownerReadMode, FileMode
    ownerWriteMode, FileMode
    ownerExecuteMode]
    					, String -> User -> Group -> Property UnixLike
    File.ownerGroup (String -> String
    takeDirectory String
    f) User
    user (User -> Group
    userGroup User
    user)
    					, String -> User -> Group -> Property UnixLike
    File.ownerGroup String
    f User
    user (User -> Group
    userGroup User
    user)
    					]
    				)

    restarted :: Property DebianLike
    restarted :: Property DebianLike
    restarted = String -> Property DebianLike
    Service.restarted String
    "tor"

    mainConfig :: FilePath
    mainConfig :: String
    mainConfig = String
    "/etc/tor/torrc"

    varLib :: FilePath
    varLib :: String
    varLib = String
    "/var/lib/tor"

    varRun :: FilePath
    varRun :: String
    varRun = String
    "/var/run/tor"

    user :: User
    user :: User
    user = String -> User
    User String
    "debian-tor"

    type NickName = String

    -- | Convert String to a valid tor NickName.
    saneNickname :: String -> NickName
    saneNickname :: String -> String
    saneNickname String
    s
    	| SectionStart
    forall a. [a] -> Bool
    forall (t :: * -> *) a. Foldable t => t a -> Bool
    null String
    n = String
    "unnamed"
    	| Bool
    otherwise = String
    n
      where
    	legal :: Char -> Bool
    legal Char
    c = Char -> Bool
    isNumber Char
    c Bool -> Bool -> Bool
    || Char -> Bool
    isAsciiUpper Char
    c Bool -> Bool -> Bool
    || Char -> Bool
    isAsciiLower Char
    c
    	n :: String
    n = Int -> String -> String
    forall a. Int -> [a] -> [a]
    take Int
    19 (String -> String) -> String -> String
    forall a b. (a -> b) -> a -> b
    $ (Char -> Bool) -> String -> String
    forall a. (a -> Bool) -> [a] -> [a]
    filter Char -> Bool
    legal String
    s