packages feed

propellor 5.17 → 5.18

raw patch · 12 files changed

+239/−308 lines, 12 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Propellor: (<>) :: Semigroup a => a -> a -> a
- Propellor: All :: Bool -> All
- Propellor: Alt :: f a -> Alt (f :: k -> Type) (a :: k)
- Propellor: Any :: Bool -> Any
- Propellor: Ap :: f a -> Ap (f :: k -> Type) (a :: k)
- Propellor: Dual :: a -> Dual a
- Propellor: Endo :: (a -> a) -> Endo a
- Propellor: First :: Maybe a -> First a
- Propellor: Last :: Maybe a -> Last a
- Propellor: Product :: a -> Product a
- Propellor: Sum :: a -> Sum a
- Propellor: [appEndo] :: Endo a -> a -> a
- Propellor: [getAll] :: All -> Bool
- Propellor: [getAlt] :: Alt (f :: k -> Type) (a :: k) -> f a
- Propellor: [getAny] :: Any -> Bool
- Propellor: [getAp] :: Ap (f :: k -> Type) (a :: k) -> f a
- Propellor: [getDual] :: Dual a -> a
- Propellor: [getFirst] :: First a -> Maybe a
- Propellor: [getLast] :: Last a -> Maybe a
- Propellor: [getProduct] :: Product a -> a
- Propellor: [getSum] :: Sum a -> a
- Propellor: class Semigroup a => Monoid a
- Propellor: infixr 6 <>
- Propellor: mappend :: Monoid a => a -> a -> a
- Propellor: mconcat :: Monoid a => [a] -> a
- Propellor: mempty :: Monoid a => a
- Propellor: newtype All
- Propellor: newtype Alt (f :: k -> Type) (a :: k)
- Propellor: newtype Any
- Propellor: newtype Ap (f :: k -> Type) (a :: k)
- Propellor: newtype Dual a
- Propellor: newtype Endo a
- Propellor: newtype First a
- Propellor: newtype Last a
- Propellor: newtype Product a
- Propellor: newtype Sum a
- Propellor.Property.SiteSpecific.JoeySites: USBDriveId :: String -> String -> USBDriveId
- Propellor.Property.SiteSpecific.JoeySites: USBHubPort :: String -> Int -> USBHubPort
- Propellor.Property.SiteSpecific.JoeySites: [driveProductId] :: USBDriveId -> String
- Propellor.Property.SiteSpecific.JoeySites: [driveVendorId] :: USBDriveId -> String
- Propellor.Property.SiteSpecific.JoeySites: [hubPort] :: USBHubPort -> Int
- Propellor.Property.SiteSpecific.JoeySites: [hubVendor] :: USBHubPort -> String
- Propellor.Property.SiteSpecific.JoeySites: autoMountDrive :: Label -> Maybe FilePath -> Property DebianLike
- Propellor.Property.SiteSpecific.JoeySites: autoMountDrive' :: [String] -> Label -> Maybe FilePath -> Property DebianLike
- Propellor.Property.SiteSpecific.JoeySites: autoMountDrivePort :: Label -> USBHubPort -> USBDriveId -> Maybe FilePath -> Property DebianLike
- Propellor.Property.SiteSpecific.JoeySites: data USBDriveId
- Propellor.Property.SiteSpecific.JoeySites: data USBHubPort
- Propellor.Property.SiteSpecific.JoeySites: githubBackup :: Property (HasInfo + DebianLike)
- Propellor.Property.SiteSpecific.JoeySites: githubKeys :: Property (HasInfo + UnixLike)
- Propellor.Property.SiteSpecific.JoeySites: homeNAS :: Property DebianLike
- Propellor.Property.SiteSpecific.JoeySites: podcatcher :: Property DebianLike
+ Propellor.Base: (-<.>) :: FilePath -> String -> FilePath
+ Propellor.Base: (<.>) :: FilePath -> String -> FilePath
+ Propellor.Base: (</>) :: FilePath -> FilePath -> FilePath
+ Propellor.Base: XdgState :: XdgDirectory
+ Propellor.Base: addExtension :: FilePath -> String -> FilePath
+ Propellor.Base: addTrailingPathSeparator :: FilePath -> FilePath
+ Propellor.Base: combine :: FilePath -> FilePath -> FilePath
+ Propellor.Base: dropDrive :: FilePath -> FilePath
+ Propellor.Base: dropExtension :: FilePath -> FilePath
+ Propellor.Base: dropExtensions :: FilePath -> FilePath
+ Propellor.Base: dropFileName :: FilePath -> FilePath
+ Propellor.Base: dropTrailingPathSeparator :: FilePath -> FilePath
+ Propellor.Base: equalFilePath :: FilePath -> FilePath -> Bool
+ Propellor.Base: extSeparator :: Char
+ Propellor.Base: getSearchPath :: IO [FilePath]
+ Propellor.Base: hasDrive :: FilePath -> Bool
+ Propellor.Base: hasExtension :: FilePath -> Bool
+ Propellor.Base: hasTrailingPathSeparator :: FilePath -> Bool
+ Propellor.Base: infixr 5 </>
+ Propellor.Base: infixr 7 <.>
+ Propellor.Base: isAbsolute :: FilePath -> Bool
+ Propellor.Base: isDrive :: FilePath -> Bool
+ Propellor.Base: isExtSeparator :: Char -> Bool
+ Propellor.Base: isExtensionOf :: String -> FilePath -> Bool
+ Propellor.Base: isPathSeparator :: Char -> Bool
+ Propellor.Base: isRelative :: FilePath -> Bool
+ Propellor.Base: isSearchPathSeparator :: Char -> Bool
+ Propellor.Base: isValid :: FilePath -> Bool
+ Propellor.Base: joinDrive :: FilePath -> FilePath -> FilePath
+ Propellor.Base: joinPath :: [FilePath] -> FilePath
+ Propellor.Base: makeRelative :: FilePath -> FilePath -> FilePath
+ Propellor.Base: makeValid :: FilePath -> FilePath
+ Propellor.Base: normalise :: FilePath -> FilePath
+ Propellor.Base: pathSeparator :: Char
+ Propellor.Base: pathSeparators :: [Char]
+ Propellor.Base: replaceBaseName :: FilePath -> String -> FilePath
+ Propellor.Base: replaceDirectory :: FilePath -> String -> FilePath
+ Propellor.Base: replaceExtension :: FilePath -> String -> FilePath
+ Propellor.Base: replaceExtensions :: FilePath -> String -> FilePath
+ Propellor.Base: replaceFileName :: FilePath -> String -> FilePath
+ Propellor.Base: searchPathSeparator :: Char
+ Propellor.Base: splitDirectories :: FilePath -> [FilePath]
+ Propellor.Base: splitDrive :: FilePath -> (FilePath, FilePath)
+ Propellor.Base: splitExtension :: FilePath -> (String, String)
+ Propellor.Base: splitExtensions :: FilePath -> (FilePath, String)
+ Propellor.Base: splitFileName :: FilePath -> (String, String)
+ Propellor.Base: splitPath :: FilePath -> [FilePath]
+ Propellor.Base: splitSearchPath :: String -> [FilePath]
+ Propellor.Base: stripExtension :: String -> FilePath -> Maybe FilePath
+ Propellor.Base: takeBaseName :: FilePath -> String
+ Propellor.Base: takeDirectory :: FilePath -> FilePath
+ Propellor.Base: takeDrive :: FilePath -> FilePath
+ Propellor.Base: takeExtension :: FilePath -> String
+ Propellor.Base: takeExtensions :: FilePath -> String
+ Propellor.Base: takeFileName :: FilePath -> FilePath
+ Propellor.Base: type FilePath = String
+ Propellor.Property.Git: daemonService :: FilePath -> Property Linux
+ Propellor.Property.SiteSpecific.JoeySites: epsolarUsbSetup :: Property UnixLike
+ Propellor.Property.SiteSpecific.JoeySites: thinkPadLedsOff :: Property Linux
+ Propellor.Types.Singletons: type family DemoteRep (kparam :: KProxy k)
+ Propellor.Utilities: XdgState :: XdgDirectory
- Propellor: (!) :: CheckCombinableNote x z (NoteFor ('Text "!")) => Props (MetaTypes x) -> RevertableProperty (MetaTypes y) (MetaTypes z) -> Props (MetaTypes (Combine x z))
+ Propellor: (!) :: forall {a} {k} (x :: [a]) (z :: [a]) (y :: k). CheckCombinableNote x z (NoteFor ('Text "!")) => Props (MetaTypes x) -> RevertableProperty (MetaTypes y) (MetaTypes z) -> Props (MetaTypes (Combine x z))
- Propellor: (&) :: (IsProp p, MetaTypes y ~ GetMetaTypes p, CheckCombinableNote x y (NoteFor ('Text "&"))) => Props (MetaTypes x) -> p -> Props (MetaTypes (Combine x y))
+ Propellor: (&) :: 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))
- Propellor.EnsureProperty: data OuterMetaTypesWitness metatypes
+ Propellor.EnsureProperty: data OuterMetaTypesWitness (metatypes :: k)
- Propellor.EnsureProperty: ensureProperty :: EnsurePropertyAllowed inner outer => OuterMetaTypesWitness outer -> Property (MetaTypes inner) -> Propellor Result
+ Propellor.EnsureProperty: ensureProperty :: forall (inner :: [MetaType]) (outer :: [MetaType]). EnsurePropertyAllowed inner outer => OuterMetaTypesWitness outer -> Property (MetaTypes inner) -> Propellor Result
- Propellor.EnsureProperty: property' :: SingI metatypes => Desc -> (OuterMetaTypesWitness metatypes -> Propellor Result) -> Property (MetaTypes metatypes)
+ Propellor.EnsureProperty: property' :: forall {k} (metatypes :: k). SingI metatypes => Desc -> (OuterMetaTypesWitness metatypes -> Propellor Result) -> Property (MetaTypes metatypes)
- Propellor.EnsureProperty: type family EnsurePropertyAllowed inner outer :: Constraint
+ Propellor.EnsureProperty: type family EnsurePropertyAllowed (inner :: [MetaType]) (outer :: [MetaType])
- Propellor.Info: setInfoProperty :: (MetaTypes metatypes' ~ (+) HasInfo metatypes, SingI metatypes') => Property metatypes -> Info -> Property (MetaTypes metatypes')
+ Propellor.Info: setInfoProperty :: forall {k} (metatypes' :: k) metatypes. (MetaTypes metatypes' ~ (HasInfo + metatypes), SingI metatypes') => Property metatypes -> Info -> Property (MetaTypes metatypes')
- Propellor.PropAccum: (!) :: CheckCombinableNote x z (NoteFor ('Text "!")) => Props (MetaTypes x) -> RevertableProperty (MetaTypes y) (MetaTypes z) -> Props (MetaTypes (Combine x z))
+ Propellor.PropAccum: (!) :: forall {a} {k} (x :: [a]) (z :: [a]) (y :: k). CheckCombinableNote x z (NoteFor ('Text "!")) => Props (MetaTypes x) -> RevertableProperty (MetaTypes y) (MetaTypes z) -> Props (MetaTypes (Combine x z))
- Propellor.PropAccum: (&) :: (IsProp p, MetaTypes y ~ GetMetaTypes p, CheckCombinableNote x y (NoteFor ('Text "&"))) => Props (MetaTypes x) -> p -> Props (MetaTypes (Combine x y))
+ Propellor.PropAccum: (&) :: 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))
- Propellor.PropAccum: (&^) :: (IsProp p, MetaTypes y ~ GetMetaTypes p, CheckCombinableNote x y (NoteFor ('Text "&^"))) => Props (MetaTypes x) -> p -> Props (MetaTypes (Combine x y))
+ Propellor.PropAccum: (&^) :: 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))
- Propellor.Property: class Checkable p i
+ Propellor.Property: class Checkable (p :: Type -> Type) i
- Propellor.Property: data OuterMetaTypesWitness metatypes
+ Propellor.Property: data OuterMetaTypesWitness (metatypes :: k)
- Propellor.Property: doNothing :: SingI t => Property (MetaTypes t)
+ Propellor.Property: doNothing :: forall {k} (t :: k). SingI t => Property (MetaTypes t)
- Propellor.Property: ensureProperty :: EnsurePropertyAllowed inner outer => OuterMetaTypesWitness outer -> Property (MetaTypes inner) -> Propellor Result
+ Propellor.Property: ensureProperty :: forall (inner :: [MetaType]) (outer :: [MetaType]). EnsurePropertyAllowed inner outer => OuterMetaTypesWitness outer -> Property (MetaTypes inner) -> Propellor Result
- Propellor.Property: impossible :: SingI t => String -> Property (MetaTypes t)
+ Propellor.Property: impossible :: forall {k} (t :: k). SingI t => String -> Property (MetaTypes t)
- Propellor.Property: pickOS :: HasCallStack => (SingKind ('KProxy :: KProxy ka), SingKind ('KProxy :: KProxy kb), DemoteRep ('KProxy :: KProxy ka) ~ [MetaType], DemoteRep ('KProxy :: KProxy kb) ~ [MetaType], SingI c) => Property (MetaTypes (a :: ka)) -> Property (MetaTypes (b :: kb)) -> Property (MetaTypes c)
+ Propellor.Property: pickOS :: forall {k} ka kb (c :: k) (a :: ka) (b :: kb). (HasCallStack, SingKind ('KProxy :: KProxy ka), SingKind ('KProxy :: KProxy kb), DemoteRep ('KProxy :: KProxy ka) ~ [MetaType], DemoteRep ('KProxy :: KProxy kb) ~ [MetaType], SingI c) => Property (MetaTypes a) -> Property (MetaTypes b) -> Property (MetaTypes c)
- Propellor.Property: property :: SingI metatypes => Desc -> Propellor Result -> Property (MetaTypes metatypes)
+ Propellor.Property: property :: forall {k} (metatypes :: k). SingI metatypes => Desc -> Propellor Result -> Property (MetaTypes metatypes)
- Propellor.Property: property' :: SingI metatypes => Desc -> (OuterMetaTypesWitness metatypes -> Propellor Result) -> Property (MetaTypes metatypes)
+ Propellor.Property: property' :: forall {k} (metatypes :: k). SingI metatypes => Desc -> (OuterMetaTypesWitness metatypes -> Propellor Result) -> Property (MetaTypes metatypes)
- Propellor.Property: withOS :: SingI metatypes => Desc -> (OuterMetaTypesWitness metatypes -> Maybe System -> Propellor Result) -> Property (MetaTypes metatypes)
+ Propellor.Property: withOS :: forall {k} (metatypes :: k). SingI metatypes => Desc -> (OuterMetaTypesWitness metatypes -> Maybe System -> Propellor Result) -> Property (MetaTypes metatypes)
- Propellor.Property.Atomic: atomicDirUpdate :: EnsurePropertyAllowed t t => SingI t => FilePath -> (FilePath -> Property (MetaTypes t)) -> Property (MetaTypes t)
+ Propellor.Property.Atomic: atomicDirUpdate :: forall (t :: [MetaType]). (EnsurePropertyAllowed t t, SingI t) => FilePath -> (FilePath -> Property (MetaTypes t)) -> Property (MetaTypes t)
- Propellor.Property.Atomic: atomicUpdate :: EnsurePropertyAllowed t t => SingI t => AtomicResourcePair a -> CheckAtomicResourcePair a -> SwapAtomicResourcePair a -> (a -> Property (MetaTypes t)) -> Property (MetaTypes t)
+ Propellor.Property.Atomic: atomicUpdate :: forall (t :: [MetaType]) a. (EnsurePropertyAllowed t t, SingI t) => AtomicResourcePair a -> CheckAtomicResourcePair a -> SwapAtomicResourcePair a -> (a -> Property (MetaTypes t)) -> Property (MetaTypes t)
- Propellor.Property.Atomic: type CheckAtomicResourcePair a = AtomicResourcePair a -> Propellor (AtomicResourcePair a)
+ Propellor.Property.Atomic: type CheckAtomicResourcePair a = AtomicResourcePair a -> Propellor AtomicResourcePair a
- Propellor.Property.Chroot: [Chroot] :: ChrootBootstrapper b => FilePath -> b -> InfoPropagator -> Host -> Chroot
+ Propellor.Property.Chroot: [Chroot] :: forall b. ChrootBootstrapper b => FilePath -> b -> InfoPropagator -> Host -> Chroot
- Propellor.Property.List: combineProperties :: SingI metatypes => Desc -> Props (MetaTypes metatypes) -> Property (MetaTypes metatypes)
+ Propellor.Property.List: combineProperties :: forall {k} (metatypes :: k). SingI metatypes => Desc -> Props (MetaTypes metatypes) -> Property (MetaTypes metatypes)
- Propellor.Property.List: propertyList :: SingI metatypes => Desc -> Props (MetaTypes metatypes) -> Property (MetaTypes metatypes)
+ Propellor.Property.List: propertyList :: forall {k} (metatypes :: k). SingI metatypes => Desc -> Props (MetaTypes metatypes) -> Property (MetaTypes metatypes)
- Propellor.Property.List: toProps :: [Property (MetaTypes metatypes)] -> Props (MetaTypes metatypes)
+ Propellor.Property.List: toProps :: forall {k} (metatypes :: k). [Property (MetaTypes metatypes)] -> Props (MetaTypes metatypes)
- Propellor.Property.SiteSpecific.JoeySites: homeRouter :: Interfaces -> String -> HostapdConfig -> Property DebianLike
+ Propellor.Property.SiteSpecific.JoeySites: homeRouter :: String -> Interfaces -> String -> HostapdConfig -> Property DebianLike
- Propellor.Types: class LiftPropellor m
+ Propellor.Types: class LiftPropellor (m :: Type -> Type)
- Propellor.Types: class TightenTargets p
+ Propellor.Types: class TightenTargets (p :: Type -> Type)
- Propellor.Types: property :: SingI metatypes => Desc -> Propellor Result -> Property (MetaTypes metatypes)
+ Propellor.Types: property :: forall {k} (metatypes :: k). SingI metatypes => Desc -> Propellor Result -> Property (MetaTypes metatypes)
- Propellor.Types: property'' :: SingI metatypes => Desc -> Maybe (Propellor Result) -> Property (MetaTypes metatypes)
+ Propellor.Types: property'' :: forall {k} (metatypes :: k). SingI metatypes => Desc -> Maybe (Propellor Result) -> Property (MetaTypes metatypes)
- Propellor.Types: tightenTargets :: (TightenTargets p, TightenTargetsAllowed untightened tightened, SingI tightened) => p (MetaTypes untightened) -> p (MetaTypes tightened)
+ Propellor.Types: tightenTargets :: forall (untightened :: [MetaType]) (tightened :: [MetaType]). (TightenTargets p, TightenTargetsAllowed untightened tightened, SingI tightened) => p (MetaTypes untightened) -> p (MetaTypes tightened)
- Propellor.Types: type ResultCombiner = Maybe (Propellor Result) -> Maybe (Propellor Result) -> Maybe (Propellor Result)
+ Propellor.Types: type ResultCombiner = Maybe Propellor Result -> Maybe Propellor Result -> Maybe Propellor Result
- Propellor.Types.Core: class LiftPropellor m
+ Propellor.Types.Core: class LiftPropellor (m :: Type -> Type)
- Propellor.Types.Info: [InfoEntry] :: (IsInfo v, Typeable v) => v -> InfoEntry
+ Propellor.Types.Info: [InfoEntry] :: forall v. (IsInfo v, Typeable v) => v -> InfoEntry
- Propellor.Types.MetaTypes: class SingI t
+ Propellor.Types.MetaTypes: class SingI (t :: k)
- Propellor.Types.MetaTypes: type MetaTypes = Sing
+ Propellor.Types.MetaTypes: type MetaTypes = Sing :: k -> Type
- Propellor.Types.ResultCheck: class Checkable p i
+ Propellor.Types.ResultCheck: class Checkable (p :: Type -> Type) i
- Propellor.Types.Singletons: class SingI t
+ Propellor.Types.Singletons: class SingI (t :: k)
- Propellor.Types.Singletons: class (kparam ~ 'KProxy) => SingKind (kparam :: KProxy k) where {
+ Propellor.Types.Singletons: class kparam ~ 'KProxy :: KProxy k => SingKind (kparam :: KProxy k) where {
- Propellor.Types.Singletons: fromSing :: SingKind kparam => Sing (a :: k) -> DemoteRep kparam
+ Propellor.Types.Singletons: fromSing :: forall (a :: k). SingKind kparam => Sing a -> DemoteRep kparam
- Propellor.Types.Singletons: type DemoteRep kparam :: Type;
+ Propellor.Types.Singletons: type DemoteRep (kparam :: KProxy k);
- Propellor.Types.ZFS: fromValue :: (Value a, IsString a) => String -> a
+ Propellor.Types.ZFS: fromValue :: Value a => String -> a
- Propellor.Utilities: bracket :: MonadMask m => m a -> (a -> m c) -> (a -> m b) -> m b
+ Propellor.Utilities: bracket :: (HasCallStack, MonadMask m) => m a -> (a -> m c) -> (a -> m b) -> m b
- Propellor.Utilities: bracketOnError :: MonadMask m => m a -> (a -> m c) -> (a -> m b) -> m b
+ Propellor.Utilities: bracketOnError :: (HasCallStack, MonadMask m) => m a -> (a -> m c) -> (a -> m b) -> m b
- Propellor.Utilities: bracket_ :: MonadMask m => m a -> m c -> m b -> m b
+ Propellor.Utilities: bracket_ :: (HasCallStack, MonadMask m) => m a -> m c -> m b -> m b
- Propellor.Utilities: catch :: (MonadCatch m, Exception e) => m a -> (e -> m a) -> m a
+ Propellor.Utilities: catch :: (MonadCatch m, HasCallStack, Exception e) => m a -> (e -> m a) -> m a
- Propellor.Utilities: catchAll :: MonadCatch m => m a -> (SomeException -> m a) -> m a
+ Propellor.Utilities: catchAll :: (HasCallStack, MonadCatch m) => m a -> (SomeException -> m a) -> m a
- Propellor.Utilities: catchIOError :: MonadCatch m => m a -> (IOError -> m a) -> m a
+ Propellor.Utilities: catchIOError :: (HasCallStack, MonadCatch m) => m a -> (IOError -> m a) -> m a
- Propellor.Utilities: catchIf :: (MonadCatch m, Exception e) => (e -> Bool) -> m a -> (e -> m a) -> m a
+ Propellor.Utilities: catchIf :: (HasCallStack, MonadCatch m, Exception e) => (e -> Bool) -> m a -> (e -> m a) -> m a
- Propellor.Utilities: catchJust :: (MonadCatch m, Exception e) => (e -> Maybe b) -> m a -> (b -> m a) -> m a
+ Propellor.Utilities: catchJust :: (HasCallStack, MonadCatch m, Exception e) => (e -> Maybe b) -> m a -> (b -> m a) -> m a
- Propellor.Utilities: catches :: (Foldable f, MonadCatch m) => m a -> f (Handler m a) -> m a
+ Propellor.Utilities: catches :: (HasCallStack, Foldable f, MonadCatch m) => m a -> f (Handler m a) -> m a
- Propellor.Utilities: finally :: MonadMask m => m a -> m b -> m a
+ Propellor.Utilities: finally :: (HasCallStack, MonadMask m) => m a -> m b -> m a
- Propellor.Utilities: generalBracket :: MonadMask m => m a -> (a -> ExitCase b -> m c) -> (a -> m b) -> m (b, c)
+ Propellor.Utilities: generalBracket :: (MonadMask m, HasCallStack) => m a -> (a -> ExitCase b -> m c) -> (a -> m b) -> m (b, c)
- Propellor.Utilities: handle :: (MonadCatch m, Exception e) => (e -> m a) -> m a -> m a
+ Propellor.Utilities: handle :: (HasCallStack, MonadCatch m, Exception e) => (e -> m a) -> m a -> m a
- Propellor.Utilities: handleAll :: MonadCatch m => (SomeException -> m a) -> m a -> m a
+ Propellor.Utilities: handleAll :: (HasCallStack, MonadCatch m) => (SomeException -> m a) -> m a -> m a
- Propellor.Utilities: handleIOError :: MonadCatch m => (IOError -> m a) -> m a -> m a
+ Propellor.Utilities: handleIOError :: (HasCallStack, MonadCatch m) => (IOError -> m a) -> m a -> m a
- Propellor.Utilities: handleIf :: (MonadCatch m, Exception e) => (e -> Bool) -> (e -> m a) -> m a -> m a
+ Propellor.Utilities: handleIf :: (HasCallStack, MonadCatch m, Exception e) => (e -> Bool) -> (e -> m a) -> m a -> m a
- Propellor.Utilities: handleJust :: (MonadCatch m, Exception e) => (e -> Maybe b) -> (b -> m a) -> m a -> m a
+ Propellor.Utilities: handleJust :: (HasCallStack, MonadCatch m, Exception e) => (e -> Maybe b) -> (b -> m a) -> m a -> m a
- Propellor.Utilities: mask :: MonadMask m => ((forall a. () => m a -> m a) -> m b) -> m b
+ Propellor.Utilities: mask :: (MonadMask m, HasCallStack) => ((forall a. () => m a -> m a) -> m b) -> m b
- Propellor.Utilities: mask_ :: MonadMask m => m a -> m a
+ Propellor.Utilities: mask_ :: (HasCallStack, MonadMask m) => m a -> m a
- Propellor.Utilities: onError :: MonadMask m => m a -> m b -> m a
+ Propellor.Utilities: onError :: (HasCallStack, MonadMask m) => m a -> m b -> m a
- Propellor.Utilities: onException :: MonadCatch m => m a -> m b -> m a
+ Propellor.Utilities: onException :: (HasCallStack, MonadCatch m) => m a -> m b -> m a
- Propellor.Utilities: throwM :: (MonadThrow m, Exception e) => e -> m a
+ Propellor.Utilities: throwM :: (MonadThrow m, HasCallStack, Exception e) => e -> m a
- Propellor.Utilities: try :: (MonadCatch m, Exception e) => m a -> m (Either e a)
+ Propellor.Utilities: try :: (HasCallStack, MonadCatch m, Exception e) => m a -> m (Either e a)
- Propellor.Utilities: tryJust :: (MonadCatch m, Exception e) => (e -> Maybe b) -> m a -> m (Either b a)
+ Propellor.Utilities: tryJust :: (HasCallStack, MonadCatch m, Exception e) => (e -> Maybe b) -> m a -> m (Either b a)
- Propellor.Utilities: uninterruptibleMask :: MonadMask m => ((forall a. () => m a -> m a) -> m b) -> m b
+ Propellor.Utilities: uninterruptibleMask :: (MonadMask m, HasCallStack) => ((forall a. () => m a -> m a) -> m b) -> m b
- Propellor.Utilities: uninterruptibleMask_ :: MonadMask m => m a -> m a
+ Propellor.Utilities: uninterruptibleMask_ :: (HasCallStack, MonadMask m) => m a -> m a

Files

CHANGELOG view
@@ -1,3 +1,16 @@+propellor (5.18) unstable; urgency=medium++  * Git.daemonRunning: Worked around git's unfortunate new+    requirement that safe.directory be set when running git-daemon from+    inetd.+  * Git.daemonService: New property which runs git-daemon as a systemd+    service.+  * Systemd.nspawned: Fix a bug that prevented propellor from running in+    a systemd machine due to /usr/local/propellor not being visible+    inside the machine.++ -- Joey Hess <id@joeyh.name>  Sun, 10 Aug 2025 11:24:34 -0400+ propellor (5.17) unstable; urgency=medium    * Apt: Enable the non-free-firmware section, when used with Debian
config-freebsd.hs view
@@ -59,7 +59,7 @@ -- A generic webserver in a Docker container. webserverContainer :: Docker.Container webserverContainer = Docker.container "webserver" (Docker.latestImage "debian") $ props-	& osDebian' KFreeBSD (Stable "bookworm") X86_64+	& osDebian' KFreeBSD (Stable "trixie") X86_64 	& Apt.stdSourcesList 	& Docker.publish "80:80" 	& Docker.volume "/var/www:/var/www"
debian/changelog view
@@ -1,3 +1,16 @@+propellor (5.18) unstable; urgency=medium++  * Git.daemonRunning: Worked around git's unfortunate new+    requirement that safe.directory be set when running git-daemon from+    inetd.+  * Git.daemonService: New property which runs git-daemon as a systemd+    service.+  * Systemd.nspawned: Fix a bug that prevented propellor from running in+    a systemd machine due to /usr/local/propellor not being visible+    inside the machine.++ -- Joey Hess <id@joeyh.name>  Sun, 10 Aug 2025 11:24:34 -0400+ propellor (5.17) unstable; urgency=medium    * Apt: Enable the non-free-firmware section, when used with Debian
joeyconfig.hs view
@@ -31,7 +31,6 @@ import qualified Propellor.Property.FlashKernel as FlashKernel import qualified Propellor.Property.Laptop as Laptop import qualified Propellor.Property.LightDM as LightDM-import qualified Propellor.Property.Debootstrap as Debootstrap import qualified Propellor.Property.HostingProvider.Linode as Linode import qualified Propellor.Property.SiteSpecific.GitHome as GitHome import qualified Propellor.Property.SiteSpecific.GitAnnexBuilder as GitAnnexBuilder@@ -48,7 +47,7 @@ 	, dragon 	, oyster 	, house-	, sky+	, gate 	, kite 	, sparrow 	, beaver@@ -62,47 +61,43 @@ darkstar :: Host darkstar = host "darkstar.kitenet.net" $ props 	& osDebian Unstable X86_64-	& ipv6 "2001:4830:1600:187::2" 	& Hostname.sane 	& Hostname.mailname 	& Apt.serviceInstalledRunning "swapspace" 	& Laptop.powertopAutoTuneOnBoot 	& Laptop.trimSSD-	& Grub.cmdline_Linux_default "i915.enable_psr=1" 	! Grub.cmdline_Linux_default "quiet" 	& User.hasGroup (User "joey") (Group "dialout")  	& JoeySites.dkimMilter 	& JoeySites.postfixSaslPasswordClient-	-- & JoeySites.alarmClock "*-*-* 7:30" (User "joey")-	--	"/usr/bin/timeout 45m /home/joey/bin/goodmorning" 	& JoeySites.laptopSoftware 	& JoeySites.userDirHtml+	& JoeySites.thinkPadLedsOff 	& Ssh.userKeys (User "joey") hostContext 		[ (SshEd25519, "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICfFntnesZcYz2B2T41ay45igfckXRSh5uVffkuCQkLv joey@darkstar") 		]-	& imageBuiltFor house+	! imageBuiltFor house 		(RawDiskImage "/srv/house.img") 		(Debootstrapped mempty)  dragon :: Host dragon = host "dragon.kitenet.net" $ props-	& ipv6 "2001:4830:1600:187::2" 	& JoeySites.dkimMilter 	& JoeySites.postfixSaslPasswordClient  oyster :: Host oyster = host "oyster.kitenet.net" $ props-	& standardSystem (Stable "buster") X86_64+	& standardSystem (Stable "bookworm") X86_64 		["Unreliable server. Anything here may be lost at any time!" ]-	& ipv4 "45.138.157.89"+	& ipv4 "45.87.155.118"  	& User.hasPassword (User "root") 	& Apt.unattendedUpgrades  house :: Host house = host "house.lan" $ props-	& standardSystem Testing ARMHF [ "House mail computer." ]+	& standardSystem (Stable "bookworm") ARMHF [ "House main computer." ] 	& Apt.removed ["rsyslog"] 	 	& cubietech_Cubietruck@@ -133,53 +128,69 @@ 		(Context "house.joeyh.name") 		(SshEd25519, "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMAmVYddg/RgCbIj+cLcEiddeFXaYFnbEJ3uGj9G/EyV joey@honeybee") 	-	-- Use iwd to connect to wifi on whatever usb wifi is connected.-	& Apt.installed ["iwd"]-	& File.hasContent "/var/lib/iwd/.known_network.freq"-		[ "[7085c93b-9e5f-5cdb-a13d-bf72db7c3adf]"-		, "name=/var/lib/iwd//hollow.open"-		, "list= 2457"-		]-	& File.hasContent "/var/lib/iwd/hollow.open"-		[ "[IPv4]"-		, "Address=10.1.1.2"-		, "Netmask=255.255.255.0"-		, "Gateway=10.1.1.1"-		, "Broadcast=10.1.1.255"-		, "DNS=10.1.1.1"+	-- Use network-manager to connect to wifi on whatever usb wifi is connected.+	& Apt.installed ["network-manager"]+	& File.hasContent "/etc/NetworkManager/system-connections/hollow.nmconnection"+		[ "[connection]"+		, "id=hollow"+		, "uuid=dfa672be-79d2-4151-9a26-ae7cb3729681"+		, "type=wifi"+		, "interface-name=wlan1"+		, ""+		, "[wifi]"+		, "mode=infrastructure"+		, "ssid=hollow"+		, ""+		, "[ipv4]"+		, "method=manual"+		, "addresses=10.1.1.2/24"+		, "gateway=10.1.1.1"+		, "dns=10.1.1.1"+		, ""+		, "[ipv6]"+		, "addr-gen-mode=default"+		, "method=auto"+		, ""+		, "[proxy]" 		]-	& Systemd.enabled "iwd"+	& Systemd.enabled "NetworkManager" 	& "/etc/resolv.conf" `File.containsLine` "search lan" -	& JoeySites.homeNAS 	& Apt.installed ["mtr-tiny", "iftop", "screen", "nmap"]-	-- Currently manually building the xr_usb_serial module.-	& Apt.installed ["linux-headers-armmp-lpae"] 	& Postfix.satellite -sky :: Host-sky = host "sky.lan" $ props-	& standardSystem Testing ARMHF [ "Wifi router." ]+-- This is a raspberry pi and is installed using https://raspi.debian.net/tested-images/+-- before propellor is run on it.+gate :: Host+gate = host "gate.lan" $ props+	& standardSystem (Stable "trixie") ARM64 [ "Wifi router." ] 	& Apt.removed ["rsyslog"] 	 	& Apt.installed ["raspi-firmware", "firmware-misc-nonfree"] 	& Apt.serviceInstalledRunning "systemd-timesyncd" -- no hardware clock +	& "/etc/systemd/network/10-gate.link" `File.containsLines`+		[ "[Match]"+		, "MACAddress=9c:ef:d5:fc:d6:f3" -- USB wifi adapter+		, ""+		, "[Link]"+		, "Name=" ++ JoeySites.wifiInterface ifs+		]+ 	& Ssh.hostKeys hostContext 		[ (SshEcdsa, "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBG19v7K59SzFp/OntM9iyhlKOj2pShFSPJeqR1aiYhPF2NqztcmsY6WvIDqh6jmaISnyV1IqZZ60zvGTVRoOyMY=") 		] 	& Ssh.userKeys (User "joey") hostContext 		[ (SshEd25519, "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOmwEfM5qTjA7xuJgygEgHfa1Y/WrRBpu7xBY8D82ul+") 		]+	& Postfix.satellite  	& JoeySites.connectStarlinkDish ifs-	& JoeySites.homeRouter ifs "hollow" JoeySites.hostapd2GhzConfig_mt76-	& Apt.installed ["mtr-tiny", "iftop", "screen", "nmap", "net-tools"]-	& Postfix.satellite+	& JoeySites.homeRouter "gate.lan" ifs "hollow" JoeySites.hostapd2GhzConfig_mt76   where 	ifs = JoeySites.Interfaces 		{ JoeySites.ethernetInterface = "eth0"-		, JoeySites.wifiInterface = "wlan0"+		, JoeySites.wifiInterface = "wlan-gate" 		, JoeySites.wifiInterfaceOld = "wlan1" 		} @@ -188,12 +199,9 @@ -- with propellor. kite :: Host kite = host "kite.kitenet.net" $ props-	& standardSystemUnhardened Testing X86_64 [ "Welcome to kite!" ]+	& standardSystemUnhardened (Stable "trixie") X86_64 [ "Welcome to kite!" ] 	& ipv4 "66.228.36.95"-	-- disabled because I've had complaints of mail bouncing, and it-	-- seems mail servers are only trying ipv6 and then bouncing if-	-- they have an ipv6 routing issue.-	-- & ipv6 "2600:3c03::f03c:91ff:fe73:b0d2"+	& ipv6 "2600:3c03::f03c:91ff:fe73:b0d2" 	& alias "kitenet.net" 	& Ssh.hostKeys (Context "kitenet.net") 		[ (SshDsa, "ssh-dss AAAAB3NzaC1kc3MAAACBAO9tnPUT4p+9z7K6/OYuiBNHaij4Nzv5YVBih1vMl+ALz0gYAj8RWJzXmqp5buFAyfgOoLw+H9s1bBS01Sy3i07Dm6cx1fWG4RXL/E/3w1tavX99GD2bBxDBu890ebA5Tp+eFRJkS9+JwSvFiF6CP7NbVjifCagoUO56Ig048RwDAAAAFQDPY2xM3q6KwsVQliel23nrd0rV2QAAAIEAga3hj1hL00rYPNnAUzT8GAaSP62S4W68lusErH+KPbsMwFBFY/Ib1FVf8k6Zn6dZLh/HH/RtJi0JwdzPI1IFW+lwVbKfwBvhQ1lw9cH2rs1UIVgi7Wxdgfy8gEWxf+QIqn62wG+Ulf/HkWGvTrRpoJqlYRNS/gnOWj9Z/4s99koAAACBAM/uJIo2I0nK15wXiTYs/NYUZA7wcErugFn70TRbSgduIFH6U/CQa3rgHJw9DCPCQJLq7pwCnFH7too/qaK+czDk04PsgqV0+Jc7957gU5miPg50d60eJMctHV4eQ1FpwmGGfXxRBR9k2ZvikWYatYir3L6/x1ir7M0bA9IzNU45")@@ -214,6 +222,7 @@ 	& Fail2Ban.installed -- since ssh password authentication is allowed 	-- Allow ssh -R to forward ports via kite 	& Ssh.setSshdConfig "GatewayPorts" "clientspecified"+	& Apt.installed ["wtmpdb"] 	& Apt.serviceInstalledRunning "ntp" 	& "/etc/timezone" `File.hasContent` ["US/Eastern"] 	@@ -274,9 +283,6 @@ 	& alias "kgb.kitenet.net" 	& JoeySites.kgbServer 	-	& alias "podcatcher.kitenet.net"-	& JoeySites.podcatcher- 	& alias "ns4.kitenet.net" 	& myDnsPrimary "kitenet.net" 		[ (RelDomain "mouse-onion", CNAME $ AbsDomain "htieo6yu2qtcn2j3.onion")@@ -313,11 +319,14 @@ 	& Systemd.nspawned (GitAnnexBuilder.autoBuilderContainer 		(GitAnnexBuilder.standardAutoBuilder True) 		Unstable X86_64 mempty Nothing (Cron.Times "15 * * * *") "2h")+	& Systemd.nspawned (GitAnnexBuilder.autoBuilderContainer+		(GitAnnexBuilder.standardAutoBuilder True)+		Unstable X86_32 mempty Nothing (Cron.Times "30 * * * *") "2h") 	& Apt.serviceInstalledRunning "swapspace"  sparrow :: Host sparrow = host "sparrow.kitenet.net" $ props-	& standardSystem Testing ARM64 [ "Welcome to sparrow!" ]+	& standardSystem (Stable "trixie") ARM64 [ "Welcome to sparrow!" ] 	& ipv4 "128.140.52.168" 	& ipv6 "2a01:4f8:c17:ed3a::1" 	& Apt.installed ["ssh"]@@ -326,16 +335,6 @@ 	& Apt.serviceInstalledRunning "swapspace"  	& Systemd.nspawned (GitAnnexBuilder.autoBuilderContainer-		-- qemu hangs running unattended-upgrades on i386-		-- so disable installing that-		(GitAnnexBuilder.standardAutoBuilder False)-		Unstable X86_32 mempty Nothing (Cron.Times "30 * * * *") "4h")-	& Systemd.nspawned (GitAnnexBuilder.autoBuilderContainer-		GitAnnexBuilder.stackAutoBuilder-		(Stable "jessie") X86_32 -		(Debootstrap.UseOldGpgKeyring Debootstrap.:+ Debootstrap.DebootstrapMirror "http://archive.debian.org/debian/")-		(Just "ancient") (Cron.Times "45 * * * *") "4h")-	& Systemd.nspawned (GitAnnexBuilder.autoBuilderContainer 		(GitAnnexBuilder.standardAutoBuilder True) 		Testing ARM64 mempty Nothing (Cron.Times "1 * * * *") "2h") 	& Systemd.nspawned (GitAnnexBuilder.autoBuilderContainer@@ -344,9 +343,21 @@ 		(Just "ancient") (Cron.Times "20 * * * *") "2h") 	& Systemd.nspawned (GitAnnexBuilder.autoBuilderContainer 		(GitAnnexBuilder.armAutoBuilder (GitAnnexBuilder.standardAutoBuilder True))-		Testing ARMEL mempty Nothing (Cron.Times "15 15 * * *") "2h")+		Testing ARMEL mempty Nothing (Cron.Times "40 * * * *") "2h") 	-	& Systemd.nspawned ancientKitenet+	! Systemd.nspawned ancientKitenet+	+	& Borg.backup "/root" (JoeySites.rsyncNetBorgRepo "sparrow.borg" []) Cron.Daily+		[ ]+		[ Borg.KeepDays 7+		, Borg.KeepWeeks 4+		, Borg.KeepMonths 3+		]+		`requires` Ssh.knownHost hosts "usw-s002.rsync.net" (User "root")+		`requires` Ssh.userKeys (User "root")+			(Context "sparrow.kitenet.net")+			[ (SshEd25519, "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID3mgBXLLzp+DnzZqH6Azly+0uZe/d2TK69oZYcFz+gN root@sparrow")+			]  beaver :: Host beaver = host "beaver.kitenet.net" $ props@@ -364,7 +375,6 @@  mouse :: Host mouse = host "mouse.kitenet.net" $ props-	& ipv4 "67.223.19.96" 	& Apt.installed ["ssh"] 	& Tor.installed 	& Tor.hiddenServiceAvailable "ssh" (Port 22)@@ -403,7 +413,7 @@ 	& alias "dist-bugs.kitenet.net" 	& alias "family.kitenet.net" -	& osDebian (Stable "buster") X86_32+	& osDebian (Stable "bookworm") X86_32 	& Apt.installed ["linux-image-686-pae"] 	& Apt.stdSourcesList `onChange` Apt.upgrade 	& Apt.unattendedUpgrades@@ -423,7 +433,7 @@ -- Exhibit: kite's 90's website on port 1994. ancientKitenet :: Systemd.Container ancientKitenet = Systemd.debContainer "ancient-kitenet" $ props-	& standardContainer (Stable "buster") ARM64+	& standardContainer (Stable "trixie") ARM64 	& alias hn 	& Git.cloned (User "root") "git://kitenet-net.branchable.com/" "/var/www/html" 		(Just "remotes/origin/old-kitenet.net")
propellor.cabal view
@@ -1,5 +1,5 @@ Name: propellor-Version: 5.17+Version: 5.18 Cabal-Version: 1.20 License: BSD2 Maintainer: Joey Hess <id@joeyh.name>
src/Propellor/Info.hs view
@@ -91,13 +91,13 @@ -- It also lets the type checker know that all the properties of the -- host must support Debian. ----- >	& osDebian (Stable "bookworm") X86_64+-- >	& osDebian (Stable "trixie") X86_64 osDebian :: DebianSuite -> Architecture -> Property (HasInfo + Debian) osDebian = osDebian' Linux  -- Use to specify a different `DebianKernel` than the default `Linux` ----- >	& osDebian' KFreeBSD (Stable "bookworm") X86_64+-- >	& osDebian' KFreeBSD (Stable "trixie") X86_64 osDebian' :: DebianKernel -> DebianSuite -> Architecture -> Property (HasInfo + Debian) osDebian' kernel suite arch = tightenTargets $ os (System (Debian kernel suite) arch) 
src/Propellor/Property/Git.hs view
@@ -4,6 +4,7 @@ import Propellor.Property.File import qualified Propellor.Property.Apt as Apt import qualified Propellor.Property.Service as Service+import qualified Propellor.Property.Systemd as Systemd  import Data.List @@ -40,12 +41,43 @@ 		, "nobody" 		, "/usr/bin/git" 		, "git"+		-- safe.directory configuration is needed by git for+		-- git-daemon to serve repositories owned by any other user.+		-- But since git-daemon is explicitly configured which+		-- repositories to operate on, it adds no security.+		, "-c", "safe.directory=*" 		, "daemon" 		, "--inetd" 		, "--export-all" 		, "--base-path=" ++ exportdir 		, exportdir 		]++-- | Exports all git repos in a directory (that user nobody can read)+-- using git-daemon, run as a systemd service.+daemonService :: FilePath -> Property Linux+daemonService exportdir = +	servicefile `onChange` +		Systemd.running "git-daemon"+  where+	servicefile = "/etc/systemd/system/git-daemon.service" `hasContent`+		[ "[Unit]"+		, "Description=Git Daemon"+		, "Documentation=man:git-daemon(1)"+		, ""+		, " [Service]"+		-- safe.directory configuration is needed by git for+		-- git-daemon to serve repositories owned by any other user.+		-- But since git-daemon is explicitly configured which+		-- repositories to operate on, it adds no security.+		, "Environment=GIT_CONFIG_COUNT='1' GIT_CONFIG_KEY_0='safe.directory' GIT_CONFIG_VALUE_0=\"*\""+		, "ExecStart=/usr/bin/git daemon --export-all --base-path=" ++ exportdir ++ " " ++ exportdir+		, "DynamicUser=yes"+		, ""+		, "[Install]"+		, "WantedBy=multi-user.target"+		]+		`describe` "git-daemon systemd service file"  installed :: Property DebianLike installed = Apt.installed ["git"]
src/Propellor/Property/Sbuild.hs view
@@ -21,7 +21,7 @@ Suggested usage in @config.hs@:  >  mybox = host "mybox.example.com" $ props->  	& osDebian (Stable "bookworm") X86_64+>  	& osDebian (Stable "trixie") X86_64 >  	& Apt.useLocalCacher >  	& sidSchrootBuilt >  	& Sbuild.usableBy (User "spwhitton")
src/Propellor/Property/SiteSpecific/GitAnnexBuilder.hs view
@@ -183,7 +183,7 @@ 			ARMEL -> "https://github.com/commercialhaskell/stack/releases/download/v1.9.3/stack-1.9.3-linux-arm.tar.gz" 			-- Newer version because older version did not 			-- support installing ghc on arm64-			ARM64 -> "https://github.com/commercialhaskell/stack/releases/download/v2.1.3/stack-2.1.3-linux-aarch64.tar.gz"+			ARM64 -> "https://github.com/commercialhaskell/stack/releases/download/v3.3.1/stack-3.3.1-linux-aarch64.tar.gz" 			-- Probably not available. 			a -> "https://www.stackage.org/stack/linux-" ++ architectureToDebianArchString a 	binstack = "/usr/bin/stack"
src/Propellor/Property/SiteSpecific/JoeySites.hs view
@@ -16,7 +16,6 @@ import qualified Propellor.Property.Service as Service import qualified Propellor.Property.User as User import qualified Propellor.Property.Group as Group-import qualified Propellor.Property.Sudo as Sudo import qualified Propellor.Property.Borg as Borg import qualified Propellor.Property.Apache as Apache import qualified Propellor.Property.Postfix as Postfix@@ -24,10 +23,8 @@ import qualified Propellor.Property.Network as Network import qualified Propellor.Property.Fail2Ban as Fail2Ban import qualified Propellor.Property.LetsEncrypt as LetsEncrypt-import qualified Propellor.Property.Mount as Mount import Utility.Split -import Data.List import System.Posix.Files  kgbServer :: Property (HasInfo + DebianLike)@@ -60,7 +57,7 @@ 	& Apt.installed ["kgb-client"] 	& File.hasPrivContentExposed "/etc/kgb-bot/kgb-client.conf" anyContext 		`requires` File.dirExists "/etc/kgb-bot/"-	& Git.daemonRunning "/srv/git"+	& Git.daemonService "/srv/git" 	& "/etc/cgitrc" `File.hasContent` 		[ "clone-url=https://git.joeyh.name/git/$CGIT_REPO_URL git://git.joeyh.name/$CGIT_REPO_URL" 		, "css=/cgit-css/cgit.css"@@ -216,38 +213,11 @@   where 	conf = "/home/znc/.znc/configs/znc.conf" -githubBackup :: Property (HasInfo + DebianLike)-githubBackup = propertyList "github-backup box" $ props-	& Apt.installed ["github-backup", "moreutils"]-	& githubKeys-	& Cron.niceJob "github-backup run" (Cron.Times "30 4 * * *") (User "joey")-		"/home/joey/lib/backup" backupcmd-  where-	backupcmd = intercalate "&&" $-		[ "mkdir -p github"-		, "cd github"-		, ". $HOME/.github-keys"-		, "github-backup joeyh"-		]--githubKeys :: Property (HasInfo + UnixLike)-githubKeys =-	let f = "/home/joey/.github-keys"-	in File.hasPrivContent f anyContext-		`onChange` File.ownerGroup f (User "joey") (Group "joey")-- rsyncNetBackup :: [Host] -> Property DebianLike rsyncNetBackup hosts = Cron.niceJob "rsync.net copied in daily" (Cron.Times "30 5 * * *") 	(User "joey") "/home/joey/lib/backup" "mkdir -p rsync.net && rsync --delete -az 2318@usw-s002.rsync.net: rsync.net" 	`requires` Ssh.knownHost hosts "usw-s002.rsync.net" (User "joey") -podcatcher :: Property DebianLike-podcatcher = Cron.niceJob "podcatcher run hourly" (Cron.Times "55 * * * *")-	(User "joey") "/home/joey/lib/sound/podcasts"-	"timeout 2h xargs git-annex importfeed -c annex.genmetadata=true < feeds; mr --quiet update"-	`requires` Apt.installed ["git-annex", "myrepos"]- spamdEnabled :: Property DebianLike spamdEnabled = Apt.serviceInstalledRunning "spamd" @@ -267,12 +237,19 @@ 		] 		`describe` "spamassassin configured" 		`requires` Apt.serviceInstalledRunning "cron"+	& "/etc/spamassassin/local.cf" `File.containsLines`+		-- dnswl.org started intentionally sending false responses+		-- for spam senders when they don't like the amount of+		-- queries being made. This causes spamassassin to skip+		-- all other checks and miss flagging spam. This is+		-- irresponsible behavior, and so they should not be used.+		[ "dns_query_restriction deny dnswl.org"+		]  kiteMailServer :: Property (HasInfo + DebianLike) kiteMailServer = propertyList "kitenet.net mail server" $ props 	& Postfix.installed 	& Apt.installed ["postfix-pcre"]-	& Apt.serviceInstalledRunning "postgrey" 	& spamassassinConfigured 	& Apt.serviceInstalledRunning "spamass-milter" 	-- Add -m to prevent modifying messages Subject or body.@@ -281,20 +258,6 @@ 		`onChange` Service.restarted "spamass-milter" 		`describe` "spamass-milter configured" -	& Apt.serviceInstalledRunning "amavisd-milter"-	& "/etc/default/amavisd-milter" `File.containsLines`-		[ "# Propellor deployed"-		, "MILTERSOCKET=/var/spool/postfix/amavis/amavis.sock"-		, "MILTERSOCKETOWNER=\"postfix:postfix\""-		, "MILTERSOCKETMODE=\"0660\""-		]-		`onChange` Service.restarted "amavisd-milter"-		`describe` "amavisd-milter configured for postfix"-	& Apt.serviceInstalledRunning "clamav-freshclam"-	-- Workaround https://bugs.debian.org/569150-	& Cron.niceJob "amavis-expire" Cron.Daily (User "root") "/"-		"find /var/lib/amavis/virusmails/ -type f -ctime +2 -delete"- 	& dkimInstalled  	& Postfix.saslAuthdInstalled@@ -309,11 +272,8 @@ 		[ "# Global maildrop filter file (deployed with propellor)" 		, "DEFAULT=\"$HOME/Maildir\"" 		, "MAILBOX=\"$DEFAULT/.\""-		, "# Filter spam to a spam folder, unless .keepspam exists" 		, "if (/^X-Spam-Status: Yes/)" 		, "{"-		, "  `test -e \"$HOME/.keepspam\"`"-		, "  if ( $RETURNCODE != 0 )" 		, "  to ${MAILBOX}spam" 		, "}" 		]@@ -372,11 +332,11 @@ 		, "smtpd_sasl_security_options = noanonymous" 		, "smtpd_sasl_local_domain = kitenet.net" -		, "# Enable postgrey and sasl auth and client certs."-		, "smtpd_recipient_restrictions = permit_tls_clientcerts,permit_sasl_authenticated,,permit_mynetworks,reject_unauth_destination,check_policy_service inet:127.0.0.1:10023"+		, "# Enable sasl auth and client certs."+		, "smtpd_recipient_restrictions = permit_tls_clientcerts,permit_sasl_authenticated,,permit_mynetworks,reject_unauth_destination" -		, "# Enable spamass-milter, amavis-milter (opendkim is not enabled because it causes mails forwarded from eg gmail to be rejected)"-		, "smtpd_milters = unix:/spamass/spamass.sock unix:amavis/amavis.sock"+		, "# Enable spamass-milter (opendkim is not enabled because it causes mails forwarded from eg gmail to be rejected)"+		, "smtpd_milters = unix:/spamass/spamass.sock" 		, "# opendkim is used for outgoing mail" 		, "non_smtpd_milters = inet:localhost:8891" 		, "milter_connect_macros = j {daemon_name} v {if_name} _"@@ -389,7 +349,7 @@ 		, "smtpd_tls_key_file = /etc/ssl/private/postfix.pem" 		, "smtpd_tls_loglevel = 1" 		, "smtpd_tls_received_header = yes"-		, "smtpd_use_tls = yes"+		, "smtpd_tls_security_level = may" 		, "smtpd_tls_ask_ccert = yes" 		, "smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache" @@ -398,7 +358,7 @@ 		, "smtp_tls_cert_file = /etc/ssl/certs/postfix.pem" 		, "smtp_tls_key_file = /etc/ssl/private/postfix.pem" 		, "smtp_tls_loglevel = 1"-		, "smtp_use_tls = yes"+		, "smtp_tls_security_level = may" 		, "smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache"  		, "# Allow larger attachments, up to 200 mb."@@ -422,6 +382,14 @@ 		"!include auth-passwdfile.conf.ext" 		`onChange` Service.restarted "dovecot" 		`describe` "dovecot auth.conf"+	& "/etc/dovecot/conf.d/10-ssl.conf" `File.containsLines`+		[ "ssl_cert = </etc/letsencrypt/live/kitenet.net/fullchain.pem"+		, "ssl_key = </etc/letsencrypt/live/kitenet.net/privkey.pem"+		]+		`onChange` Service.reloaded "dovecot"+		`describe` "dovecot ssl.conf using letsencrypt"+	& Cron.niceJob "dovecot restarts to update letsenctypt certificate"+		Cron.Daily (User "root") "/" "systemctl restart dovecot" 	& File.hasPrivContent dovecotusers ctx 		`onChange` (dovecotusers `File.mode` 			combineModes [ownerReadMode, groupReadMode])@@ -519,7 +487,7 @@ 		, "smtp_sasl_auth_enable = yes" 		, "smtp_tls_security_level = encrypt" 		, "smtp_sasl_tls_security_options = noanonymous"-		, "relayhost = kitenet.net:587"+		, "relayhost = kitenet.net:465" 		, "smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd" 		] 		`onChange` Postfix.reloaded@@ -607,7 +575,7 @@ 		, "rewriterule (.*) http://joeyh.name$1 [r]" 		] 	& alias "house.joeyh.name"-	& apacheSite "house.joeyh.name"+	& Apache.httpsVirtualHost' "house.joeyh.name" "/srv/web/house.joeyh.name" letos 		[ "DocumentRoot /srv/web/house.joeyh.name" 		, "<Directory /srv/web/house.joeyh.name>" 		, "  Options Indexes ExecCGI"@@ -729,6 +697,29 @@ 	& "/etc/systemd/logind.conf" `ConfFile.containsIniSetting` 		("Login", "LidSwitchIgnoreInhibited", "no") +epsolarUsbSetup :: Property UnixLike+epsolarUsbSetup = propertyList "epsolar usb setup" $ props+	& "/etc/udev/rules.d/99-epsolar-usb.rules" `File.hasContent`+		-- Each epever usb cable has its own serial number and gets+		-- its own stable device name.+		[ "SUBSYSTEM==\"tty\", ATTRS{idVendor}==\"04e2\", ATTRS{idProduct}==\"1411\", ATTRS{serial}==\"G9107376441\", SYMLINK+=\"epsolar1\""+		, "SUBSYSTEM==\"tty\", ATTRS{idVendor}==\"1a86\", ATTRS{idProduct}==\"55d3\", ATTRS{serial}==\"5876071260\", SYMLINK+=\"epsolar2\""+		, "SUBSYSTEM==\"tty\", ATTRS{idVendor}==\"1a86\", ATTRS{idProduct}==\"55d3\", ATTRS{serial}==\"5875017909\", SYMLINK+=\"epsolar3\""+		, "SUBSYSTEM==\"tty\", ATTRS{idVendor}==\"1a86\", ATTRS{idProduct}==\"55d3\", ATTRS{serial}==\"5876071261\", SYMLINK+=\"epsolar4\""+		, "SUBSYSTEM==\"tty\", ATTRS{idVendor}==\"1a86\", ATTRS{idProduct}==\"55d3\", ATTRS{serial}==\"5875017825\", SYMLINK+=\"epsolar5\""+		, "SUBSYSTEM==\"tty\", ATTRS{idVendor}==\"1a86\", ATTRS{idProduct}==\"55d3\", ATTRS{serial}==\"589A039325\", SYMLINK+=\"epsolar6\""+		]+	-- The xr-serial module wants to handle the epever usb cables+	-- (at least older ones, new ones are handled by ACM), but+	-- it does not work in linux 6.1. Instead I am currently installing+	-- an out of tree module, xr_usb_serial_common. In linux 6.6,+	-- the xr-serial module has been reported to work,+	-- see https://github.com/kasbert/epsolar-tracer/pull/61#issuecomment-2056442735+	& "/etc/modprobe.d/blacklist.conf" `File.containsLines`+		[ "blacklist xr-serial"+		, "blacklist xr-serial"+		]+ house :: IsContext c => User -> [Host] -> c -> (SshKeyType, Ssh.PubKeyText) -> Property (HasInfo + DebianLike) house user hosts ctx sshkey = propertyList "home automation" $ props 	& Apache.installed@@ -736,6 +727,7 @@ 	& Git.cloned user "https://git.joeyh.name/git/joey/house.git" d Nothing 	& websitesymlink 	& build+	& epsolarUsbSetup 	& Systemd.enabled setupservicename 		`requires` setupserviceinstalled 		`onChange` Systemd.started setupservicename@@ -767,7 +759,6 @@ 			user (userGroup user) 		`requires` File.dirExists (takeDirectory sshkeyfile) 		`requires` Ssh.knownHost hosts "kitenet.net" user-	& File.hasPrivContentExposed "/etc/darksky-forecast-url" anyContext   where 	d = "/home/joey/house" 	sshkeyfile = d </> ".ssh/key"@@ -804,7 +795,7 @@ 		, "" 		, "[Install]" 		, "WantedBy=multi-user.target"-		, "WantedBy=house-controller.target"+		, "WantedBy=house-controller.service" 		] 	controllerservicename = "house-controller" 	controllerservicefile = "/etc/systemd/system/" ++ controllerservicename ++ ".service"@@ -853,9 +844,9 @@ 		, "" 		, "[Install]" 		, "WantedBy=multi-user.target"-		, "WantedBy=house-poller.target"-		, "WantedBy=house-controller.target"-		, "WantedBy=house-watchdog.target"+		, "WantedBy=house-poller.service"+		, "WantedBy=house-controller.service"+		, "WantedBy=house-watchdog.service" 		] 	-- Any changes to the rsync command will need my .authorized_keys 	-- rsync server command to be updated too.@@ -878,16 +869,15 @@ connectStarlinkDish :: Interfaces -> Property DebianLike connectStarlinkDish ifs = propertyList "connected via starlink dish" $ props 	-- Use dhcpcd for ipv6 prefix delegation to the wifi interface.-	& Apt.installed ["dhcpcd"]-	-- dhcpcd is run by ifup on boot. When the daemon was enabled,-	-- that somehow prevented prefix delegation from happening,-	-- so disable the daemon from being run by systemd.-	& Systemd.stopped "dhcpcd"-	& Systemd.masked "dhcpcd"+	& Apt.installed ["dhcpcd-base"]+	& Apt.removed ["isc-dhcp-client"] 	& "/etc/dhcpcd.conf" `File.containsLine` 		("ia_pd 1 " ++ wifiInterface ifs) 	& "/etc/dhcpcd.conf" `File.lacksLine` 		("ia_pd 1 " ++ wifiInterfaceOld ifs)+	-- Avoid using dns servers sent by starlink.+	& "/etc/dhcpcd.conf" `File.containsLine`+		"nohook resolv.conf" 	& Network.dhcp (ethernetInterface ifs) 		`requires` Network.cleanInterfacesFile @@ -902,8 +892,8 @@ 		`requires` Network.cleanInterfacesFile  -- My home router, running hostapd and dnsmasq.-homeRouter :: Interfaces -> String -> HostapdConfig -> Property DebianLike-homeRouter ifs wifinetworkname (HostapdConfig hostapdconfig) = propertyList "home router" $ props+homeRouter :: String -> Interfaces -> String -> HostapdConfig -> Property DebianLike+homeRouter hn ifs wifinetworkname (HostapdConfig hostapdconfig) = propertyList "home router" $ props 	& File.notPresent (Network.interfaceDFile (wifiInterfaceOld ifs)) 	& Network.static (wifiInterface ifs) (IPv4 "10.1.1.1") Nothing 		`requires` Network.cleanInterfacesFile@@ -916,8 +906,9 @@ 		`requires` File.hasContent "/etc/default/hostapd" 			[ "DAEMON_CONF=/etc/hostapd/hostapd.conf" ] 		`onChange` Service.running "hostapd"+	! Systemd.masked "hostapd" 	& Systemd.enabled "hostapd"-	& File.hasContent "/etc/resolv.conf"+	& File.hasContent "/etc/resolv.conf.head" 		[ "domain kitenet.net" 		, "search kitenet.net" 		, "nameserver 8.8.8.8"@@ -933,7 +924,7 @@ 		-- controller wants to know when clients disconnect 		, "dhcp-range=10.1.1.100,10.1.1.150,10m" 		, "no-hosts"-		, "address=/sky.lan/10.1.1.1"+		, "address=/" ++ hn ++ "/10.1.1.1" 		, "address=/house.lan/10.1.1.2" 		-- allow accessing starlink dish when it's not online yet 		, "address=/dishy.starlink.com/192.168.100.1"@@ -960,7 +951,11 @@ 		, "};" 		] 		`onChange` Service.restarted "radvd"-	& "/etc/sysctl.conf" `File.containsLine` "net.ipv6.conf.all.forwarding=1"+	& "/etc/sysctl.d/ip-forwarding.conf" +		`File.containsLine` "net.ipv6.conf.all.forwarding=1"+	-- Programs that I find useful on a router.+	& Apt.installed ["net-tools", "wireless-tools"]+	& Apt.installed ["mtr-tiny", "iftop", "screen", "nmap"]  -- | Enable IP masqerading, on whatever other interfaces come up, besides the -- provided intif.@@ -987,11 +982,12 @@ laptopSoftware = Apt.installed 	[ "intel-microcode", "acpi" 	, "procmeter3", "xfce4", "procmeter3", "unclutter-xfixes"-	, "mplayer", "fbreader", "firefox", "chromium"+	, "vlc", "mpv", "mplayer", "fbreader", "foliate", "firefox", "chromium"+	, "yt-dlp" 	, "libdatetime-event-sunrise-perl", "libtime-duration-perl" 	, "network-manager", "network-manager-openvpn-gnome", "openvpn"-	, "gtk-redshift", "powertop"-	, "gimp", "gthumb", "inkscape", "sozi", "xzgv", "hugin"+	, "powertop"+	, "gimp", "gthumb", "inkscape", "xzgv", "hugin" 	, "mpc", "mpd", "ncmpc", "sonata", "mpdtoys" 	, "bsdgames", "nethack-console" 	, "xmonad", "libghc-xmonad-dev", "libghc-xmonad-contrib-dev"@@ -999,23 +995,25 @@ 	, "mairix", "offlineimap", "mutt", "slrn" 	, "mtr", "nmap", "whois", "wireshark", "tcpdump", "iftop" 	, "pmount", "tree", "pv"-	, "arbtt", "hledger", "bc"+	, "hledger", "bc" 	, "apache2", "ikiwiki", "libhighlight-perl" 	, "avahi-daemon", "avahi-discover"-	, "pal"+	, "pal", "ncal" 	, "yeahconsole", "xkbset", "xinput" 	, "assword", "pumpa" 	, "vorbis-tools", "audacity"-	, "ekiga"-	, "bluez-firmware", "blueman", "pulseaudio-module-bluetooth"+	, "bluez-firmware", "blueman", "pulseaudio-module-bluetooth" +	, "pulseaudio-utils" 	, "fwupd"-	, "xul-ext-ublock-origin", "xul-ext-pdf.js", "xul-ext-status4evar"+	, "webext-ublock-origin-firefox", "webext-ublock-origin-chromium" 	, "vim-syntastic", "vim-fugitive" 	, "adb", "gthumb" 	, "w3m", "sm", "weechat" 	, "borgbackup", "wipe", "smartmontools", "libgfshare-bin" 	, "units"-	, "virtualbox", "virtualbox-guest-additions-iso", "qemu-kvm"+	, "libnotify-bin"+	, "libmodbus-dev"+	, "qemu-kvm" 	] 	`requires` baseSoftware 	`requires` devSoftware@@ -1023,17 +1021,17 @@ baseSoftware :: Property DebianLike baseSoftware = Apt.installed 	[ "bash", "bash-completion", "vim", "screen", "less", "moreutils"-	, "git", "mr", "etckeeper", "git-annex", "ssh", "vim-vimoutliner"+	, "git", "mr", "etckeeper", "git-annex", "ssh" --, "vim-vimoutliner" 	]  devSoftware :: Property DebianLike devSoftware = Apt.installed 	[ "build-essential", "debhelper", "devscripts" 	, "ghc", "cabal-install", "haskell-stack"-	, "hothasktags", "hdevtools", "hlint"+	, "hasktags", "hlint" 	, "gdb", "time" 	, "dpkg-repack", "lintian"-	, "pristine-tar", "github-backup"+	, "pristine-tar" 	]  cubieTruckOneWire :: Property DebianLike@@ -1072,161 +1070,6 @@ 		, "};" 		] --- My home networked attached storage server.-homeNAS :: Property DebianLike-homeNAS = propertyList "home NAS" $ props-	& Apt.installed ["uhubctl"]-	& "/etc/udev/rules.d/52-startech-hub.rules" `File.hasContent`-		[ "# let users power control startech hub with uhubctl"-		, "ATTR{idVendor}==\"" ++ hubvendor ++ "\", ATTR{idProduct}==\"005a\", MODE=\"0666\""-		]-	& autoMountDrivePort "archive-10"-		(USBHubPort hubvendor 1)-		(USBDriveId wd "1230")-		(Just "archive-oldest")-	& autoMountDrivePort "archive-11"-		(USBHubPort hubvendor 2)-		(USBDriveId wd "25ee")-		(Just "archive-older")-	& autoMountDrivePort "archive-12"-		(USBHubPort hubvendor 3)-		(USBDriveId seagate "3322")-		(Just "archive-old")-	& autoMountDrivePort "archive-13"-		(USBHubPort hubvendor 4)-		(USBDriveId wd "25a3")-		(Just "archive")-	& autoMountDrivePort "archive-14"-		(USBHubPort hubvendor 2)-		(USBDriveId wd "25a3")-		Nothing-	& autoMountDrive "passport" Nothing-	& Apt.installed ["git-annex", "borgbackup"]-  where-	hubvendor = "0409"-	wd = "1058"-	seagate = "0bc2"--data USBHubPort = USBHubPort-	{ hubVendor :: String-	, hubPort :: Int-	}--data USBDriveId = USBDriveId-	{ driveVendorId :: String-	, driveProductId :: String-	}---- Makes a USB drive with the given label automount, and unmount after idle--- for a while.------ The hub port is turned on and off automatically as needed, using--- uhubctl.-autoMountDrivePort :: Mount.Label -> USBHubPort -> USBDriveId -> Maybe FilePath -> Property DebianLike-autoMountDrivePort label hp drive malias = propertyList desc $ props-	& File.hasContent ("/etc/systemd/system/" ++ hub)-		[ "[Unit]"-		, "Description=Startech usb hub port " ++ show (hubPort hp) ++ " vendor " ++ driveVendorId drive ++ " driveid " ++ driveProductId drive-		, "PartOf=" ++ mount-		, "[Service]"-		, "Type=oneshot"-		, "RemainAfterExit=true"-		, "ExecStart=/bin/sh -c 'uhubctl -a on " ++ selecthubport ++ "'"-		, "ExecStop=/bin/sh -c 'uhubctl -a off " ++ selecthubport-			-- Powering off the port does not remove device-			-- files, so ask udev to remove the devfile; it will-			-- be added back after the drive next spins up-			-- and so avoid mount happening before the drive is-			-- spun up.-			-- (This only works when the devfile is in-			-- by-label.)-			++ "; udevadm trigger --action=remove " ++ devfile ++ " || true'"-		, "[Install]"-		, "WantedBy="-		]-		`onChange` Systemd.daemonReloaded-	& autoMountDrive' -		[ "Requires=" ++ hub-		, "After=" ++ hub-		] label malias-  where-	devfile = "/dev/disk/by-label/" ++ label-	mountpoint = "/media/joey/" ++ label-	desc = "auto mount with hub port power control " ++ mountpoint-	hub = "startech-hub-port-" ++ show (hubPort hp) ++ "-vendor-" ++ driveVendorId drive ++ "-drivedid-" ++ driveProductId drive ++ ".service"-	mount = svcbase ++ ".mount"-	svcbase = Systemd.escapePath mountpoint-	selecthubport = unwords-		[ "-p", show (hubPort hp)-		, "-n", hubVendor hp-		, "-l", concat-			-- The hub's location id, eg "1-1.4", does not seem-			-- as stable as uhubctl claims it will be,-			-- and the vendor is not sufficient since I have 2-			-- hubs from the same vendor. So search for the-			-- drive lsusb to find that. This works even if the-			-- port is powered off, as long as it's been on at-			-- some point before.-			[ "$(lsusb -tvv | perl -lne \"if (\\\\$h && m!/sys/bus/usb/devices/(.*?) !) {\\\\$v=\\\\$1}; if (m/Hub/) { \\\\$h=1 } else { \\\\$h=0 }; if (/"-			, driveVendorId drive ++ ":" ++ driveProductId drive-			++ "/) { print \\\\$v; last}\")"-			]-		]---- Makes a USB drive with the given label automount, and unmount after idle--- for a while.-autoMountDrive :: Mount.Label -> Maybe FilePath -> Property DebianLike-autoMountDrive = autoMountDrive' []--autoMountDrive' :: [String] -> Mount.Label -> Maybe FilePath -> Property DebianLike-autoMountDrive' mountunitadd label malias = propertyList desc $ props-	& File.ownerGroup mountpoint (User "joey") (Group "joey")-		`requires` File.dirExists mountpoint-	& case malias of-		Just t -> ("/media/joey/" ++ t) `File.isSymlinkedTo`-			File.LinkTarget mountpoint-		Nothing -> doNothing <!> doNothing-	& File.hasContent ("/etc/systemd/system/" ++ mount)-		([ "[Unit]"-		, "Description=" ++ label-		] ++ mountunitadd ++-		[ "[Mount]"-		-- avoid mounting whenever the block device is available,-		-- only want to automount on demand-		, "Options=noauto"-		, "What=" ++ devfile-		, "Where=" ++ mountpoint-		, "[Install]"-		, "WantedBy="-		])-		`onChange` Systemd.daemonReloaded-	& File.hasContent ("/etc/systemd/system/" ++ automount)-		[ "[Unit]"-		, "Description=Automount " ++ label-		, "[Automount]"-		, "Where=" ++ mountpoint-		, "TimeoutIdleSec=300"-		, "[Install]"-		, "WantedBy=multi-user.target"-		]-		`onChange` Systemd.daemonReloaded-	& Systemd.enabled automount-	& Systemd.started automount-	& Sudo.sudoersDFile ("automount-" ++ label)-		[ "joey ALL= NOPASSWD: " ++ sudocommands-		]-  where-	devfile = "/dev/disk/by-label/" ++ label-	mountpoint = "/media/joey/" ++ label-	desc = "auto mount " ++ mountpoint-	automount = svcbase ++ ".automount"-	mount = svcbase ++ ".mount"-	svcbase = Systemd.escapePath mountpoint-	sudocommands = intercalate " , " $ map (\c -> "/bin/systemctl " ++ c)-		[ "stop " ++ mountpoint-		, "start " ++ mountpoint-		]- rsyncNetBorgRepo :: String -> [Borg.BorgRepoOpt] -> Borg.BorgRepo rsyncNetBorgRepo d os = Borg.BorgRepoUsing os' ("2318@usw-s002.rsync.net:" ++ d)   where@@ -1252,7 +1095,7 @@ -- For wifi adapters such as the Alfa AWUS036ACHM -- -- Note that for maximum speed, this needs channel 5 or 6.--- This should make it be capable of 150 Mb/s.+-- This should make it be capable of 300 Mb/s. hostapd2GhzConfig_mt76 :: HostapdConfig hostapd2GhzConfig_mt76 = HostapdConfig $ c ++  	[ "ht_capab=[HT40+][HT40-][GF][SHORT-GI-20][SHORT-GI-40]"@@ -1270,4 +1113,18 @@ 	, "ieee80211ac=1" 	, "wmm_enabled=1" 	]++thinkPadLedsOff :: Property Linux+thinkPadLedsOff =+	"/etc/systemd/system/thinkpad_leds_off.service" `File.hasContent`+		[ "[Unit]"+		, "After=suspend.target"+		, "[Service]"+		, "Type=simple"+		, "ExecStart=sh -c 'echo 0 > /sys/devices/platform/thinkpad_acpi/leds/tpacpi::lid_logo_dot/brightness; echo 0 > /sys/devices/platform/thinkpad_acpi/leds/tpacpi::power/brightness'"+		, "[Install]"+		, "WantedBy=suspend.target"+		]+		`onChange` (Systemd.daemonReloaded+			`before` Systemd.enabled "thinkpad_leds_off") 
src/Propellor/Property/Systemd.hs view
@@ -314,6 +314,12 @@ 		, "--boot" 		, "--directory=" ++ containerDir name 		, "--machine=" ++ name+		-- While propellChroot bind mounts the propellor directory,+		-- when systemd stops and then restarts the machine, it+		-- will mask any existing bind mounts to the directory.+		-- So, tell systemd to bind mount the propellor directory+		-- as well.+		, "--bind=" ++ localdir 		] ++ nspawnServiceParams cfg  	overrideconfigured = File.hasContent overridefile overridecontent
src/Propellor/Types/OS.hs view
@@ -59,7 +59,7 @@ 	deriving (Show, Eq)  -- | Debian has several rolling suites, and a number of stable releases,--- such as Stable "bookworm".+-- such as Stable "trixie". data DebianSuite = Experimental | Unstable | Testing | Stable Release 	deriving (Show, Eq)