summaryrefslogtreecommitdiff
path: root/doc/todo
diff options
context:
space:
mode:
Diffstat (limited to 'doc/todo')
-rw-r--r--doc/todo/Bug_in_Property.Ssh.authorizedKey.mdwn8
-rw-r--r--doc/todo/File.containsConfPair___38___LightDM.autoLogin_properties.mdwn25
-rw-r--r--doc/todo/File.containsConfPair___38___LightDM.autoLogin_properties/comment_1_c8240ba3abf5cf458eba8ed7e31eaccf._comment25
-rw-r--r--doc/todo/File.containsConfPair___38___LightDM.autoLogin_properties/comment_2_9303138a3be2fb639498737afe60b87d._comment11
-rw-r--r--doc/todo/File.containsConfPair___38___LightDM.autoLogin_properties/comment_3_92c583f883fae2b447c1598356efade2._comment41
-rw-r--r--doc/todo/File.containsConfPair___38___LightDM.autoLogin_properties/comment_4_2049a1ce601ba77f4139f844d0fd91b2._comment13
-rw-r--r--doc/todo/File.containsConfPair___38___LightDM.autoLogin_properties/comment_5_4caff287eb767d481bb7ef87e62c508b._comment10
-rw-r--r--doc/todo/HostingProvider_for_AWS.mdwn1
-rw-r--r--doc/todo/HostingProvider_for_AWS/comment_1_9db50a3f4fef8e10261e3e29dbd90e73._comment22
-rw-r--r--doc/todo/Manage_DNS_with_Route53.mdwn1
-rw-r--r--doc/todo/Manage_DNS_with_Route53/comment_1_dfa93678644b72781afda4fdc9d0da31._comment21
-rw-r--r--doc/todo/Manage_DNS_with_Route53/comment_2_a6c1ace47d5387d0b1559266ca124525._comment8
-rw-r--r--doc/todo/Manage_DNS_with_Route53/comment_3_a521a1b875526d8b65e76f11ed367a36._comment8
-rw-r--r--doc/todo/Propellor.Property.Ssh:_it_should_be_possible_to_call_permitRootLogin_with___34__forced-commands-only__34___and___34__without-password__34__.mdwn5
-rw-r--r--doc/todo/Push_2.4.0_to_Hackage.mdwn4
-rw-r--r--doc/todo/Wishlist:_User.hasLoginShell.mdwn9
-rw-r--r--doc/todo/Wishlist:_User.hasLoginShell/comment_1_c02e8783b91c3c0326bf1b317be4694f._comment59
-rw-r--r--doc/todo/bytes_in_privData__63__.mdwn17
-rw-r--r--doc/todo/bytes_in_privData__63__/comment_1_42c107179b091f74ef55aff1fc240c5e._comment19
-rw-r--r--doc/todo/bytes_in_privData__63__/comment_2_60f577b476adc6ee1e4f18e11843df90._comment7
-rw-r--r--doc/todo/bytes_in_privData__63__/comment_3_55f34128de77b7947d32fac71071e033._comment7
-rw-r--r--doc/todo/bytes_in_privData__63__/comment_4_f34a8f82c7bce7224e4edc59410c741f._comment19
-rw-r--r--doc/todo/bytes_in_privData__63__/comment_5_f4db6ffad054feb7eb299708fcd7d05c._comment15
-rw-r--r--doc/todo/bytes_in_privData__63__/comment_6_545e1c26a042b9f8347496a1bfb61548._comment48
-rw-r--r--doc/todo/bytes_in_privData__63__/comment_7_d6c4c2645696eac448e906d812c2de62._comment25
-rw-r--r--doc/todo/detect_and_use___96__GHC__95__PACKAGE__95__PATH__96__.mdwn9
-rw-r--r--doc/todo/detect_and_use___96__GHC__95__PACKAGE__95__PATH__96__/comment_1_892385793c38976d0c446906dd004772._comment10
-rw-r--r--doc/todo/docker_todo_list.mdwn2
-rw-r--r--doc/todo/dynamic_Info.mdwn2
-rw-r--r--doc/todo/editor_for_privdata__63__.mdwn4
-rw-r--r--doc/todo/editor_for_privdata__63__/comment_2_4fcbdf36f32ca7cf82593a8992167aff._comment9
-rw-r--r--doc/todo/etckeeper.mdwn1
-rw-r--r--doc/todo/etckeeper/comment_1_8766da27c69bbae357d497e0e557fad2._comment9
-rw-r--r--doc/todo/fail_if_modification_not_commited_when_using_--spin.mdwn3
-rw-r--r--doc/todo/fail_if_modification_not_commited_when_using_--spin/comment_1_7267d62ccc8db44bccb935836536e8a1._comment30
-rw-r--r--doc/todo/fail_if_modification_not_commited_when_using_--spin/comment_2_e4d170a14d689bef5d9174b251a4fe6f._comment7
-rw-r--r--doc/todo/fail_if_modification_not_commited_when_using_--spin/comment_3_c69eaa9c6ae5b07b5c2dd2591de965a3._comment19
-rw-r--r--doc/todo/git_push_over_propellor_ssh_channel.mdwn13
-rw-r--r--doc/todo/info_propigation_out_of_nested_properties.mdwn109
-rw-r--r--doc/todo/issue_after_upgrading_shared_library.mdwn25
-rw-r--r--doc/todo/issue_after_upgrading_shared_library/comment_1_8d9144d57871cb5d234710d1ab1b7183._comment20
-rw-r--r--doc/todo/issue_after_upgrading_shared_library/comment_2_01a3d5e006158302e12862cacee3327e._comment7
-rw-r--r--doc/todo/issue_after_upgrading_shared_library/comment_2_6025ec35330fbac220f2888e60be1e78._comment17
-rw-r--r--doc/todo/lxc_containers_support.mdwn1
-rw-r--r--doc/todo/missing_dependencies.mdwn39
-rw-r--r--doc/todo/missing_dependencies/comment_1_826a75052e87c04489aa07c3d322a54f._comment15
-rw-r--r--doc/todo/onChange_failure_handling.mdwn41
-rw-r--r--doc/todo/port_info_for_properties_for_firewall.mdwn24
-rw-r--r--doc/todo/publish_propellor_as_library_to_hackage.mdwn4
-rw-r--r--doc/todo/publish_propellor_as_library_to_hackage/comment_1_00a865bf7977c0e49f54a365f4b60ce8._comment27
-rw-r--r--doc/todo/publish_propellor_as_library_to_hackage/comment_2_29cc276929020e68eae8ae04110a3f5f._comment17
-rw-r--r--doc/todo/publish_propellor_as_library_to_hackage/comment_3_efbe0ef77be957c37e745ec64452ae99._comment10
-rw-r--r--doc/todo/publish_propellor_as_library_to_hackage/comment_4_6ebf2e30596ddf6eba91717576837019._comment8
-rw-r--r--doc/todo/publish_propellor_as_library_to_hackage/comment_5_4a4e94c637e0380adc1a43ec3d0633e1._comment8
-rw-r--r--doc/todo/publish_propellor_as_library_to_hackage/comment_6_19470170c3ef461f446b0af1d8501640._comment8
-rw-r--r--doc/todo/revertable_Ssh.authorizedKey.mdwn1
-rw-r--r--doc/todo/revertable_Ssh.authorizedKey/comment_1_6c11976a814a7f4a830bc11ae9bf534e._comment11
-rw-r--r--doc/todo/spin_and_ipv6_addresses.mdwn1
-rw-r--r--doc/todo/ssh__95__user_+_sudo/comment_4_7fc635a8d6e4c903eaefa7383d2c37ac._comment8
-rw-r--r--doc/todo/type_level_port_conflict_detection.mdwn5
60 files changed, 926 insertions, 26 deletions
diff --git a/doc/todo/Bug_in_Property.Ssh.authorizedKey.mdwn b/doc/todo/Bug_in_Property.Ssh.authorizedKey.mdwn
new file mode 100644
index 00000000..7a59fc20
--- /dev/null
+++ b/doc/todo/Bug_in_Property.Ssh.authorizedKey.mdwn
@@ -0,0 +1,8 @@
+If Ssh.authorizedKey in propellor 2.0.0 is used to create .ssh/authorized_keys for
+a user other than root, it will be owned by root:root and won't
+work for the user. Adding a key to an existing authorized_keys
+file doesn't change its ownership and therefore works fine.
+
+-- weinzwang
+
+> Thanks, [[fixed|done]] this and will make a release.
diff --git a/doc/todo/File.containsConfPair___38___LightDM.autoLogin_properties.mdwn b/doc/todo/File.containsConfPair___38___LightDM.autoLogin_properties.mdwn
new file mode 100644
index 00000000..57cbc343
--- /dev/null
+++ b/doc/todo/File.containsConfPair___38___LightDM.autoLogin_properties.mdwn
@@ -0,0 +1,25 @@
+# `File.containsConfPair` property
+
+A property to set `key = value` pairs under particular `[sections]` in config files. For example, in stock Debian Jessie `/etc/lightdm/lightdm.conf` contains the lines
+
+ [SeatDefaults]
+ #autologin-user=
+
+With the property
+
+ "/etc/lightdm/lightdm.conf" `File.containsConfPair` ("SeatDefaults", "autologin-user", "swhitton")
+
+this will get set to
+
+ [SeatDefaults]
+ autologin-user=swhitton
+
+# `LightDM.autoLogin` property
+
+An application of `File.containsConfPair` to edit `/etc/lightdm/lightdm.conf` to enable autologin for a specified user: a property encapsulating the above example.
+
+# Patches
+
+Please see the two commits in branch `confpairs` in the repo at `git@github.com:spwhitton/propellor.git`.
+
+> [[merged|done]] --[[Joey]]
diff --git a/doc/todo/File.containsConfPair___38___LightDM.autoLogin_properties/comment_1_c8240ba3abf5cf458eba8ed7e31eaccf._comment b/doc/todo/File.containsConfPair___38___LightDM.autoLogin_properties/comment_1_c8240ba3abf5cf458eba8ed7e31eaccf._comment
new file mode 100644
index 00000000..a5a2b80c
--- /dev/null
+++ b/doc/todo/File.containsConfPair___38___LightDM.autoLogin_properties/comment_1_c8240ba3abf5cf458eba8ed7e31eaccf._comment
@@ -0,0 +1,25 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 1"""
+ date="2015-08-04T14:23:33Z"
+ content="""
+Thanks for submitting these patches!
+
+Looking at `containsConfPair`, it assumes an ini-style file,
+so is a little misplaced in Property.File. which is otherwise about generic
+text files.
+
+So, it would probably make sense to move it to a new Property.IniFile
+module.
+
+However, [[forum/parsing_a_config_file]] recently pointed out that
+the tor config file has a similar need. It's not ini format, but
+shares the same basic idea of a "section" line which is followed by
+lines setting things specific to that section.
+
+So, it would be great if `containsConfPair` could be generalized to also
+cover that tor config file use case. I think this would be pretty easy;
+just make it take one string containing the whole section line (including
+square brackets for ini file, or whatever for tor config file), and a
+second string containing the whole setting line.
+"""]]
diff --git a/doc/todo/File.containsConfPair___38___LightDM.autoLogin_properties/comment_2_9303138a3be2fb639498737afe60b87d._comment b/doc/todo/File.containsConfPair___38___LightDM.autoLogin_properties/comment_2_9303138a3be2fb639498737afe60b87d._comment
new file mode 100644
index 00000000..7b01dd71
--- /dev/null
+++ b/doc/todo/File.containsConfPair___38___LightDM.autoLogin_properties/comment_2_9303138a3be2fb639498737afe60b87d._comment
@@ -0,0 +1,11 @@
+[[!comment format=mdwn
+ username="spwhitton"
+ subject="comment 2"
+ date="2015-08-05T21:29:04Z"
+ content="""
+Thanks for the input!
+
+I agree that generalising to lines under sections is a good idea, but I don't think it can be as simple as a property taking the full section header and the full settings line. That's because there is a need to update the values of keys under sections: in the example LightDM case, the line `autologin-user=someone` must *replace* any `autologin-user=someone_else`. So the function needs to know the key, not just the whole line.
+
+So to generalise containsConfPair, it might take a section header, key, value and a specification of what kind of config file it is. That specification would be a type containing the comment character, the formatting of section headers and the use of spaces, colons or equals signs between keys and values. What do you think to this?
+"""]]
diff --git a/doc/todo/File.containsConfPair___38___LightDM.autoLogin_properties/comment_3_92c583f883fae2b447c1598356efade2._comment b/doc/todo/File.containsConfPair___38___LightDM.autoLogin_properties/comment_3_92c583f883fae2b447c1598356efade2._comment
new file mode 100644
index 00000000..a45bc921
--- /dev/null
+++ b/doc/todo/File.containsConfPair___38___LightDM.autoLogin_properties/comment_3_92c583f883fae2b447c1598356efade2._comment
@@ -0,0 +1,41 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 3"""
+ date="2015-08-06T14:54:14Z"
+ content="""
+I'd suggest making it take some helper functions.
+
+Something like these:
+
+ type SectionStart = String -> Bool -- ^ find the line that is the start of the wanted section (eg, == "<Foo>")
+ type SectionEnd = String -> Bool -- ^ find a line that is within the section, but that indicates the end of the section (eg == "</Foo>")
+ type SectionPast = String -> Bool -- ^ find a line that indicates we are past the section (eg, a new section header)
+ type AdjustSection = [String] -> [String] -- ^ run on all lines in the section, including the SectionStart line and any SectionEnd line; can add/delete/modify lines, or even delete entire section
+ type InsertSection = [String] -> [String] -- ^ if SectionStart does not find the section in the file, this is used to insert the section somewhere within it
+
+ adjustSection :: SectionStart -> SectionEnd -> AdjustSection -> InsertSection -> FilePath -> Property
+
+Which seems sufficiently generic; it can even be used to delete entire sections!
+
+Let's see..
+
+ iniHeader header = '[':header++"]"
+
+ adjustIniSection :: String -> AdjustSection -> InsertSection -> Property
+ adjustIniSection header = adjustSection
+ (== iniHeader header)
+ (const False)
+ ("[" `isPrefixOf`)
+
+ containsConfPair header key value = adjustIniSection header
+ go
+ (++ [confheader, confline]) -- add missing section at end
+ where
+ confheader = iniHeader header
+ confline = key ++ "=" ++ value
+ go ls = undefined -- TODO find key= line and change it, or add confline
+
+ removeSection header = adjustIniSection header
+ (const []) -- remove all lines of section
+ id -- add no lines if section is missing
+"""]]
diff --git a/doc/todo/File.containsConfPair___38___LightDM.autoLogin_properties/comment_4_2049a1ce601ba77f4139f844d0fd91b2._comment b/doc/todo/File.containsConfPair___38___LightDM.autoLogin_properties/comment_4_2049a1ce601ba77f4139f844d0fd91b2._comment
new file mode 100644
index 00000000..f4e0921f
--- /dev/null
+++ b/doc/todo/File.containsConfPair___38___LightDM.autoLogin_properties/comment_4_2049a1ce601ba77f4139f844d0fd91b2._comment
@@ -0,0 +1,13 @@
+[[!comment format=mdwn
+ username="spwhitton"
+ subject="comment 4"
+ date="2015-08-17T00:57:54Z"
+ content="""
+Thanks for the ideas. I've implemented them as a new commit to my confpairs branch. Please take a look.
+
+Two points:
+
+1. I dropped the SectionEnd helper function. My implementation of adjustSection didn't need it and I couldn't think up a case where it would be needed.
+
+2. I'm using a tuple `(section, key, value)` as the second argument to `ConfFile.containsIniPair`, rather than just using four arguments as you suggested. If `ConfFile.containsIniPair` takes four arguments, then it cannot be used infix when attached to other properties with the `&` operator, without using extra brackets.
+"""]]
diff --git a/doc/todo/File.containsConfPair___38___LightDM.autoLogin_properties/comment_5_4caff287eb767d481bb7ef87e62c508b._comment b/doc/todo/File.containsConfPair___38___LightDM.autoLogin_properties/comment_5_4caff287eb767d481bb7ef87e62c508b._comment
new file mode 100644
index 00000000..40f14ec2
--- /dev/null
+++ b/doc/todo/File.containsConfPair___38___LightDM.autoLogin_properties/comment_5_4caff287eb767d481bb7ef87e62c508b._comment
@@ -0,0 +1,10 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 5"""
+ date="2015-08-20T14:37:43Z"
+ content="""
+And merged, thanks.
+
+The SectionEnd would be useful for eg, bind-style or apache-style config
+files. However, those probably need a better parser than this one anyway.
+"""]]
diff --git a/doc/todo/HostingProvider_for_AWS.mdwn b/doc/todo/HostingProvider_for_AWS.mdwn
new file mode 100644
index 00000000..fc381afe
--- /dev/null
+++ b/doc/todo/HostingProvider_for_AWS.mdwn
@@ -0,0 +1 @@
+I'd really love to be able to use propellor to manage my AWS services.
diff --git a/doc/todo/HostingProvider_for_AWS/comment_1_9db50a3f4fef8e10261e3e29dbd90e73._comment b/doc/todo/HostingProvider_for_AWS/comment_1_9db50a3f4fef8e10261e3e29dbd90e73._comment
new file mode 100644
index 00000000..71ded884
--- /dev/null
+++ b/doc/todo/HostingProvider_for_AWS/comment_1_9db50a3f4fef8e10261e3e29dbd90e73._comment
@@ -0,0 +1,22 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 1"""
+ date="2015-05-14T16:19:00Z"
+ content="""
+So there's something here that propellor doesn't yet have a concept of,
+and that's spinning up a VM. Propellor can deploy itself to an existing VM
+pretty well, but getting the VM running isn't something it tries to do.
+
+I imagine that --spin could be extended to support this though.
+Make a Property like `vm AWS`, which tells propellor that the host
+is a VM, and that the VM is hosted on AWS. Then when you run propellor
+--spin, it could set up the VM if it doesn't exist yet.
+
+I don't use AWS currently, so don't have plans to work on this myself,
+although I think it would be a great direction to move in. Happy to help
+with advice, code review, etc.
+
+<http://hackage.haskell.org/package/aws>
+or <http://hackage.haskell.org/package/amazonka>
+are good haskell libraries for working with AWS.
+"""]]
diff --git a/doc/todo/Manage_DNS_with_Route53.mdwn b/doc/todo/Manage_DNS_with_Route53.mdwn
new file mode 100644
index 00000000..b35a37cb
--- /dev/null
+++ b/doc/todo/Manage_DNS_with_Route53.mdwn
@@ -0,0 +1 @@
+I currently use Route53 to manage the DNS for my service. I'd really like to use Propellor to take care of that for me.
diff --git a/doc/todo/Manage_DNS_with_Route53/comment_1_dfa93678644b72781afda4fdc9d0da31._comment b/doc/todo/Manage_DNS_with_Route53/comment_1_dfa93678644b72781afda4fdc9d0da31._comment
new file mode 100644
index 00000000..8836beaa
--- /dev/null
+++ b/doc/todo/Manage_DNS_with_Route53/comment_1_dfa93678644b72781afda4fdc9d0da31._comment
@@ -0,0 +1,21 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 1"""
+ date="2015-05-14T16:18:37Z"
+ content="""
+I think this would be great. Patches accepted.
+
+If I were going to implement this, I'd use
+<http://hackage.haskell.org/package/amazonka-route53>
+to write the propellor Property.
+
+A question is, what host would the Property be attached to?
+One way to do it would be to make the property be called something like
+`route53Controller`. So then you pick a host, or hosts, and give them this
+property for a domain, and those hosts then take care of making the
+necessary API calls to route53. Presumably some API keys will be needed
+on those hosts, which can be provided via the privdata.
+
+I'm happy to offer advice on implementation, but don't plan to code this up
+myself, as I'm happily self-hosting my DNS servers.
+"""]]
diff --git a/doc/todo/Manage_DNS_with_Route53/comment_2_a6c1ace47d5387d0b1559266ca124525._comment b/doc/todo/Manage_DNS_with_Route53/comment_2_a6c1ace47d5387d0b1559266ca124525._comment
new file mode 100644
index 00000000..9b5150bf
--- /dev/null
+++ b/doc/todo/Manage_DNS_with_Route53/comment_2_a6c1ace47d5387d0b1559266ca124525._comment
@@ -0,0 +1,8 @@
+[[!comment format=mdwn
+ username="https://launchpad.net/~jml"
+ nickname="jml"
+ subject="comment 2"
+ date="2015-05-15T08:53:34Z"
+ content="""
+Glad you think so. I had a very quick poke around and also discovered [aws-ec2](https://hackage.haskell.org/package/aws-ec2) as well as the amazonka package. Any particular reason for preferring amazonka to aws-ec2?
+"""]]
diff --git a/doc/todo/Manage_DNS_with_Route53/comment_3_a521a1b875526d8b65e76f11ed367a36._comment b/doc/todo/Manage_DNS_with_Route53/comment_3_a521a1b875526d8b65e76f11ed367a36._comment
new file mode 100644
index 00000000..00bb6b04
--- /dev/null
+++ b/doc/todo/Manage_DNS_with_Route53/comment_3_a521a1b875526d8b65e76f11ed367a36._comment
@@ -0,0 +1,8 @@
+[[!comment format=mdwn
+ username="mithrandi@311efa1b2b5c4999c2edae7da06fb825899e8a82"
+ nickname="mithrandi"
+ subject="comment 3"
+ date="2015-06-08T01:22:14Z"
+ content="""
+aws-ec2 doesn't seem to support Route53, unless I'm missing something.
+"""]]
diff --git a/doc/todo/Propellor.Property.Ssh:_it_should_be_possible_to_call_permitRootLogin_with___34__forced-commands-only__34___and___34__without-password__34__.mdwn b/doc/todo/Propellor.Property.Ssh:_it_should_be_possible_to_call_permitRootLogin_with___34__forced-commands-only__34___and___34__without-password__34__.mdwn
new file mode 100644
index 00000000..f02ff328
--- /dev/null
+++ b/doc/todo/Propellor.Property.Ssh:_it_should_be_possible_to_call_permitRootLogin_with___34__forced-commands-only__34___and___34__without-password__34__.mdwn
@@ -0,0 +1,5 @@
+It should be possible to call Propellor.Property.Ssh.permitRootLogin with "forced-commands-only" and "without-password", in addition to "True" or "False". It requires to change the type of the function (and maybe to create a new datatype?)...
+
+ permitRootLogin :: Bool -> Property NoInfo
+
+> [[done]] --[[Joey]]
diff --git a/doc/todo/Push_2.4.0_to_Hackage.mdwn b/doc/todo/Push_2.4.0_to_Hackage.mdwn
new file mode 100644
index 00000000..a176f416
--- /dev/null
+++ b/doc/todo/Push_2.4.0_to_Hackage.mdwn
@@ -0,0 +1,4 @@
+https://propellor.branchable.com/news/version_2.4.0/ says that version 2.4.0, but as of today, 2.3.0 is the latest version on Hackage: http://hackage.haskell.org/package/propellor
+
+> Seems the upload must have failed and I didn't notice. re-uploaded;
+> [[done]] --[[Joey]]
diff --git a/doc/todo/Wishlist:_User.hasLoginShell.mdwn b/doc/todo/Wishlist:_User.hasLoginShell.mdwn
new file mode 100644
index 00000000..cf8aa73c
--- /dev/null
+++ b/doc/todo/Wishlist:_User.hasLoginShell.mdwn
@@ -0,0 +1,9 @@
+As far as I can tell there is no easy way to set a user's
+login shell. A Property User.hasLoginShell, which ensures
+that a user has a specified login shell and that said shell
+is in /etc/shells would be really helpful. Sadly, I lack the
+skills to put this together myself :(
+
+-- weinzwang
+
+> patched in and so [[done]] --[[Joey]]
diff --git a/doc/todo/Wishlist:_User.hasLoginShell/comment_1_c02e8783b91c3c0326bf1b317be4694f._comment b/doc/todo/Wishlist:_User.hasLoginShell/comment_1_c02e8783b91c3c0326bf1b317be4694f._comment
new file mode 100644
index 00000000..52043406
--- /dev/null
+++ b/doc/todo/Wishlist:_User.hasLoginShell/comment_1_c02e8783b91c3c0326bf1b317be4694f._comment
@@ -0,0 +1,59 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 1"""
+ date="2015-04-19T16:07:24Z"
+ content="""
+Propellor makes it very easy to put together a property like this.
+
+Let's start with a property that combines the two properties you mentioned:
+
+ hasLoginShell :: UserName -> FilePath -> Property
+ hasLoginShell user shell = shellSetTo user shell `requires` shellEnabled shell
+
+The shellEnabled property can be easily written using propellor's file
+manipulation properties.
+
+ -- Need to add an import to the top of the source file.
+ import qualified Propellor.Property.File as File
+
+ shellEnabled :: FilePath -> Property
+ shellEnabled shell = "/etc/shells" `File.containsLine` shell
+
+And then, we want to actually change the user's shell. The `chsh(1)`
+program can do that, so we can simply tell propellor the command line to
+run:
+
+ shellSetTo :: UserName -> FilePath -> Property
+ shellSetTo user shell = cmdProperty "chsh" ["--shell", shell, user]
+
+The only remaining problem with this is that shellSetTo runs chsh every
+time, and propellor will always display that it's made a change each time
+it runs, even when it didn't really do much. Now, there's an easy way to
+avoid that problem, we could just tell propellor that it's a trivial
+property, and then it will run chsh every time and not think it made any
+change:
+
+ shellSetTo :: UserName -> FilePath -> Property
+ shellSetTo user shell = trivial $
+ cmdProperty "chsh" ["--shell", shell, user]
+
+But, it's not much harder to do this right. Let's make the property
+check if the user's shell is already set to the desired value and avoid
+doing anything in that case.
+
+ shellSetTo :: UserName -> FilePath -> Property
+ shellSetTo user shell = check needchangeshell $
+ cmdProperty "chsh" ["--shell", shell, user]
+ where
+ needchangeshell = do
+ currshell <- userShell <$> getUserEntryForName user
+ return (currshell /= shell)
+
+And that will probably all work, although I've not tested it. You might
+want to throw in some uses of `describe` to give the new properties
+more useful descriptions.
+
+I hope this has been helpful as an explanation of how to add properties to
+Propellor, and if you get these properties to work, a patch adding them
+to Propellor.User would be happily merged.
+"""]]
diff --git a/doc/todo/bytes_in_privData__63__.mdwn b/doc/todo/bytes_in_privData__63__.mdwn
new file mode 100644
index 00000000..27297fd5
--- /dev/null
+++ b/doc/todo/bytes_in_privData__63__.mdwn
@@ -0,0 +1,17 @@
+It seems like I can't set the content of a PrivFile to arbitrary bytes.
+
+ $ propellor --set 'PrivFile "mysecret.key"' 'mycontext' < ~/mysecret.key
+ find . | grep -v /.git/ | grep -v /tmp/ | grep -v /dist/ | grep -v /doc/ | egrep '\.hs$' | xargs hothasktags | perl -ne 'print; s/Propellor\.Property\.//; print' | sort > tags 2>/dev/null || true
+ cabal build
+ Building propellor-2.2.1...
+ Preprocessing library propellor-2.2.1...
+ In-place registering propellor-2.2.1...
+ Preprocessing executable 'propellor' for propellor-2.2.1...
+ Preprocessing executable 'propellor-config' for propellor-2.2.1...
+ [70 of 70] Compiling Main ( src/config.hs, dist/build/propellor-config/propellor-config-tmp/Main.o )
+ Linking dist/build/propellor-config/propellor-config ...
+ ln -sf dist/build/propellor-config/propellor-config propellor
+
+
+ Enter private data on stdin; ctrl-D when done:
+ propellor: <stdin>: hGetContents: invalid argument (invalid byte sequence)
diff --git a/doc/todo/bytes_in_privData__63__/comment_1_42c107179b091f74ef55aff1fc240c5e._comment b/doc/todo/bytes_in_privData__63__/comment_1_42c107179b091f74ef55aff1fc240c5e._comment
new file mode 100644
index 00000000..5c1508fd
--- /dev/null
+++ b/doc/todo/bytes_in_privData__63__/comment_1_42c107179b091f74ef55aff1fc240c5e._comment
@@ -0,0 +1,19 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 1"""
+ date="2015-04-20T01:04:26Z"
+ content="""
+I imagine that adding `fileEncoding stdin` to setPrivData will fix
+this crash, but I'd expect there are also other problems with encodings
+for privdata that haskell doesn't like. Similar fixes would probably
+be needed in several other places.
+
+Probably cleaner and better to convert
+`PrivData` from a String to a ByteString, and so avoid encodings
+being applied to it. I think this could be done without changing the
+file format; the privdata file uses Read/Show for serialization,
+and happily ByteString uses the same Read/Show format as String does.
+
+So, changing the type and following the compile errors should get you
+there, I think!
+"""]]
diff --git a/doc/todo/bytes_in_privData__63__/comment_2_60f577b476adc6ee1e4f18e11843df90._comment b/doc/todo/bytes_in_privData__63__/comment_2_60f577b476adc6ee1e4f18e11843df90._comment
new file mode 100644
index 00000000..10ff956a
--- /dev/null
+++ b/doc/todo/bytes_in_privData__63__/comment_2_60f577b476adc6ee1e4f18e11843df90._comment
@@ -0,0 +1,7 @@
+[[!comment format=mdwn
+ username="gueux"
+ subject="comment 2"
+ date="2015-04-21T12:59:42Z"
+ content="""
+Would you accept a patch converting PrivData from String to ByteString?
+"""]]
diff --git a/doc/todo/bytes_in_privData__63__/comment_3_55f34128de77b7947d32fac71071e033._comment b/doc/todo/bytes_in_privData__63__/comment_3_55f34128de77b7947d32fac71071e033._comment
new file mode 100644
index 00000000..a1c7f62f
--- /dev/null
+++ b/doc/todo/bytes_in_privData__63__/comment_3_55f34128de77b7947d32fac71071e033._comment
@@ -0,0 +1,7 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 3"""
+ date="2015-04-21T16:52:06Z"
+ content="""
+Absolutely. Thought that went w/o saying. ;)
+"""]]
diff --git a/doc/todo/bytes_in_privData__63__/comment_4_f34a8f82c7bce7224e4edc59410c741f._comment b/doc/todo/bytes_in_privData__63__/comment_4_f34a8f82c7bce7224e4edc59410c741f._comment
new file mode 100644
index 00000000..bd7a0618
--- /dev/null
+++ b/doc/todo/bytes_in_privData__63__/comment_4_f34a8f82c7bce7224e4edc59410c741f._comment
@@ -0,0 +1,19 @@
+[[!comment format=mdwn
+ username="gueux"
+ subject="comment 4"
+ date="2015-04-23T09:21:07Z"
+ content="""
+I tried to do the conversion, but then it started a kind of chain reaction... (PrivData=ByteString to writeFileProtected to Line=ByteString to ... to readProcess to ...) Should I use FilePath=String? ... To be honest, the patch became a lot bigger that what I am comfortable with. :-)
+
+I guess you should have a look at it...
+
+At least, I think there is a type bug in Propellor.Property.File:
+
+ hasPrivContent' :: (IsContext c, IsPrivDataSource s) => (String -> FilePath -> IO ()) -> s -> FilePath -> c -> Property HasInfo
+
+but it should be
+
+ hasPrivContent' :: (IsContext c, IsPrivDataSource s) => (FilePath -> String -> IO ()) -> s -> FilePath -> c -> Property HasInfo
+
+(it is hidden by FilePath = String)
+"""]]
diff --git a/doc/todo/bytes_in_privData__63__/comment_5_f4db6ffad054feb7eb299708fcd7d05c._comment b/doc/todo/bytes_in_privData__63__/comment_5_f4db6ffad054feb7eb299708fcd7d05c._comment
new file mode 100644
index 00000000..45c97b97
--- /dev/null
+++ b/doc/todo/bytes_in_privData__63__/comment_5_f4db6ffad054feb7eb299708fcd7d05c._comment
@@ -0,0 +1,15 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 5"""
+ date="2015-04-23T13:25:50Z"
+ content="""
+Can you put the patch up somewhere? I'll take a look. Might see a way to
+short-curcuit the bytestring before everything becomes one..
+
+One way might be:
+
+ writeFileProtected :: FileContent content => FilePath -> content -> IO ()
+
+Which would also at least partly avoid foot-shooting over which parameter is which.
+(Fixed that type signature.)
+"""]]
diff --git a/doc/todo/bytes_in_privData__63__/comment_6_545e1c26a042b9f8347496a1bfb61548._comment b/doc/todo/bytes_in_privData__63__/comment_6_545e1c26a042b9f8347496a1bfb61548._comment
new file mode 100644
index 00000000..29b07e5c
--- /dev/null
+++ b/doc/todo/bytes_in_privData__63__/comment_6_545e1c26a042b9f8347496a1bfb61548._comment
@@ -0,0 +1,48 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 6"""
+ date="2015-04-28T19:24:12Z"
+ content="""
+I've followed the same path in the wip-bytestring-privdata branch.
+
+It needs to round trip through String anyway to handle Read/Show
+serialization the same as before. I think this is doable without falling
+over on invalid encodings, but it's certianly ugly.
+
+And yeah, changing Line to ByteString and all the other follow-on changes
+just don't seem right. Everything that uses withPrivData would need to deal
+with it being a ByteString, and would need to worry about encoding problems
+when it needed to convert to a String, or Text, or whatever.
+
+So this feels like kicking the can down the road in the wrong direction...
+
+----
+
+Maybe it would be better to handle this by adding a type to wrap up an
+encoded ByteString in the PrivData. Could use base64 or something like
+that for the encoding. Then only consumers of these ByteStrings would be a
+little complicated by needing to unwrap it.
+
+Then it would be handly to give --set, --dump and --edit some
+special handling of fields encoded like that. They could operate on raw
+ByteStrings when handling such fields, and take care of the encoding
+details.
+
+Add a new constructor to PrivDataField for binary files:
+
+ | PrivBinaryFile FilePath
+
+And a function to get the encoder and decoder:
+
+ type Encoder = ByteString -> PrivData
+ type Decoder = PrivData -> ByteString
+
+ privDataEncoding :: PrivDataField -> Maybe (Encoder, Decoder)
+
+Then --set, --dump, and --edit could use that to encode and decode the
+data.
+
+And finally, a `withBinaryPrivData` that uses ByteString.
+
+(Maybe this could be made more type safe though..)
+"""]]
diff --git a/doc/todo/bytes_in_privData__63__/comment_7_d6c4c2645696eac448e906d812c2de62._comment b/doc/todo/bytes_in_privData__63__/comment_7_d6c4c2645696eac448e906d812c2de62._comment
new file mode 100644
index 00000000..07bc8145
--- /dev/null
+++ b/doc/todo/bytes_in_privData__63__/comment_7_d6c4c2645696eac448e906d812c2de62._comment
@@ -0,0 +1,25 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""mostly done"""
+ date="2015-09-15T00:26:10Z"
+ content="""
+A recent change converted PrivData to a newtype.
+There are no longer any things that directly use PrivData; all use
+should be via accessor functions like privDataLines and privDataVal.
+Which helps with this.
+
+So, I've instead implemented a `privDataByteString :: PrivData -> ByteString`,
+and I've adjusted the privdata serialization so it shouldn't crash
+on arbitrarily encoded data when eg, a binary file is fed into `propellor --set`.
+
+(Note that I was wrong earlier when I said it'd be safe to change the
+serialization to use ByteString; it must use String. While `"foo"`
+can be Read as a ByteString same as a string, `"foo\1000"`,
+when Read as a ByteString, truncates the big unicode character to
+a single byte. So, PrivData is still stored as Strings internally.)
+
+The final step would be to make `hasPrivContent` use `privDataByteString`
+instead of `privDataLines`. Which needs some more work to add Properties to
+ensure a file contains a ByteString. This should be pretty easy to do,
+but I lost steam..
+"""]]
diff --git a/doc/todo/detect_and_use___96__GHC__95__PACKAGE__95__PATH__96__.mdwn b/doc/todo/detect_and_use___96__GHC__95__PACKAGE__95__PATH__96__.mdwn
new file mode 100644
index 00000000..2973e662
--- /dev/null
+++ b/doc/todo/detect_and_use___96__GHC__95__PACKAGE__95__PATH__96__.mdwn
@@ -0,0 +1,9 @@
+Detecting and using `GHC_PACKAGE_PATH` would allow "stack exec" support. This way propellor would be able to be built with
+
+ stack build
+
+and run with
+
+ stack exec -- propellor ...
+
+see [[https://github.com/yesodweb/yesod/issues/1018]] and [[https://github.com/yesodweb/yesod/commit/a7cccf2a7c5df8b26da9ea4fdcb6bac5ab3a3b75]]
diff --git a/doc/todo/detect_and_use___96__GHC__95__PACKAGE__95__PATH__96__/comment_1_892385793c38976d0c446906dd004772._comment b/doc/todo/detect_and_use___96__GHC__95__PACKAGE__95__PATH__96__/comment_1_892385793c38976d0c446906dd004772._comment
new file mode 100644
index 00000000..3154a895
--- /dev/null
+++ b/doc/todo/detect_and_use___96__GHC__95__PACKAGE__95__PATH__96__/comment_1_892385793c38976d0c446906dd004772._comment
@@ -0,0 +1,10 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 1"""
+ date="2015-06-29T20:25:10Z"
+ content="""
+I don't entirely understand this, though
+<https://github.com/haskell/cabal/pull/2270> seems to give some background.
+Patches welcome I suppose, although would't it be better to fix the tooling
+and not things like propellor that just use the tools?
+"""]]
diff --git a/doc/todo/docker_todo_list.mdwn b/doc/todo/docker_todo_list.mdwn
index 72ded426..1321445d 100644
--- a/doc/todo/docker_todo_list.mdwn
+++ b/doc/todo/docker_todo_list.mdwn
@@ -1,5 +1,3 @@
* There is no way for a property of a docker container to require
some property be met outside the container. For example, some servers
need ntp installed for a good date source.
-* The SimpleSh was added before `docker exec` existed, and could probably
- be eliminated by using that.
diff --git a/doc/todo/dynamic_Info.mdwn b/doc/todo/dynamic_Info.mdwn
new file mode 100644
index 00000000..cfe0eebb
--- /dev/null
+++ b/doc/todo/dynamic_Info.mdwn
@@ -0,0 +1,2 @@
+nomeata suggested using Data.Dynamic for Info, so there doesn't need to be
+a big record centralizing all sorts of info. --[[Joey]]
diff --git a/doc/todo/editor_for_privdata__63__.mdwn b/doc/todo/editor_for_privdata__63__.mdwn
new file mode 100644
index 00000000..8b91338c
--- /dev/null
+++ b/doc/todo/editor_for_privdata__63__.mdwn
@@ -0,0 +1,4 @@
+Would adding a way to call $EDITOR to edit privdata be possible?
+It would make sense for editing data like logcheck files.
+
+> [[done]]
diff --git a/doc/todo/editor_for_privdata__63__/comment_2_4fcbdf36f32ca7cf82593a8992167aff._comment b/doc/todo/editor_for_privdata__63__/comment_2_4fcbdf36f32ca7cf82593a8992167aff._comment
new file mode 100644
index 00000000..bbe93fe3
--- /dev/null
+++ b/doc/todo/editor_for_privdata__63__/comment_2_4fcbdf36f32ca7cf82593a8992167aff._comment
@@ -0,0 +1,9 @@
+[[!comment format=mdwn
+ username="http://joeyh.name/"
+ subject="comment 2"
+ date="2014-11-11T21:16:09Z"
+ content="""
+Already exists in `propellor --edit`
+
+Documentation patches accepted! :)
+"""]]
diff --git a/doc/todo/etckeeper.mdwn b/doc/todo/etckeeper.mdwn
new file mode 100644
index 00000000..7dc80cef
--- /dev/null
+++ b/doc/todo/etckeeper.mdwn
@@ -0,0 +1 @@
+It would be cool to have an etckeeper module :-).
diff --git a/doc/todo/etckeeper/comment_1_8766da27c69bbae357d497e0e557fad2._comment b/doc/todo/etckeeper/comment_1_8766da27c69bbae357d497e0e557fad2._comment
new file mode 100644
index 00000000..f080f70e
--- /dev/null
+++ b/doc/todo/etckeeper/comment_1_8766da27c69bbae357d497e0e557fad2._comment
@@ -0,0 +1,9 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 1"""
+ date="2014-11-06T15:46:56Z"
+ content="""
+All I've needed for this is `& Apt.installed ["etckeeper"]`
+
+Patches welcome, I suppose.
+"""]]
diff --git a/doc/todo/fail_if_modification_not_commited_when_using_--spin.mdwn b/doc/todo/fail_if_modification_not_commited_when_using_--spin.mdwn
new file mode 100644
index 00000000..046f4a6f
--- /dev/null
+++ b/doc/todo/fail_if_modification_not_commited_when_using_--spin.mdwn
@@ -0,0 +1,3 @@
+Sometimes I forget to commit a modification, and running "propellor --spin" automatically commits this stuff. It would be better if "propellor --spin" failed (or, even better, warned the user) that there are uncommited changes, and "propellor --spin" would just always add an empty commit.
+
+> --merge added; [[done]] --[[Joey]]
diff --git a/doc/todo/fail_if_modification_not_commited_when_using_--spin/comment_1_7267d62ccc8db44bccb935836536e8a1._comment b/doc/todo/fail_if_modification_not_commited_when_using_--spin/comment_1_7267d62ccc8db44bccb935836536e8a1._comment
new file mode 100644
index 00000000..19b2fab6
--- /dev/null
+++ b/doc/todo/fail_if_modification_not_commited_when_using_--spin/comment_1_7267d62ccc8db44bccb935836536e8a1._comment
@@ -0,0 +1,30 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 1"""
+ date="2014-11-23T18:41:40Z"
+ content="""
+Letting --spin commit is part of my workflow. It's great when you're just
+changing config.hs to quickly blast out the changes.
+
+Granted, it is not so nice when doing Property development, as changes get
+fragmented across the spins used to test them. I'd be happy to find some
+way to improve that. Perhaps a way could be found to get this structure of
+git commits:
+
+ manual commit------------------------->manual commit--merge
+ \--spin--spin--spin--spin--spin------------/
+
+Where the second manual commit has an identical tree committed as does the
+spin just underneath it, and so the following merge doesn't change any files,
+just grafts the two branches back together.
+
+I guess that could be handled by haing a checkpoint command, that squashes
+all the previous spins since the last checkpoint together into one commit,
+lets the user edit the commit message of that, and the juggles the branches
+into place and creates the merge commit -- which then becomes the new last
+checkpoint.
+
+I'll take patches for such a thing, or more simply a way to configure --spin's
+auto-committing behavior. However, I don't want to change the default
+behavior to not commit.
+"""]]
diff --git a/doc/todo/fail_if_modification_not_commited_when_using_--spin/comment_2_e4d170a14d689bef5d9174b251a4fe6f._comment b/doc/todo/fail_if_modification_not_commited_when_using_--spin/comment_2_e4d170a14d689bef5d9174b251a4fe6f._comment
new file mode 100644
index 00000000..3e8e5f62
--- /dev/null
+++ b/doc/todo/fail_if_modification_not_commited_when_using_--spin/comment_2_e4d170a14d689bef5d9174b251a4fe6f._comment
@@ -0,0 +1,7 @@
+[[!comment format=mdwn
+ username="gueux"
+ subject="comment 2"
+ date="2014-11-23T20:23:24Z"
+ content="""
+Your solution seems a lot better :-).
+"""]]
diff --git a/doc/todo/fail_if_modification_not_commited_when_using_--spin/comment_3_c69eaa9c6ae5b07b5c2dd2591de965a3._comment b/doc/todo/fail_if_modification_not_commited_when_using_--spin/comment_3_c69eaa9c6ae5b07b5c2dd2591de965a3._comment
new file mode 100644
index 00000000..8ad6ab49
--- /dev/null
+++ b/doc/todo/fail_if_modification_not_commited_when_using_--spin/comment_3_c69eaa9c6ae5b07b5c2dd2591de965a3._comment
@@ -0,0 +1,19 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 3"""
+ date="2014-11-23T21:12:19Z"
+ content="""
+Here's a almost-script to do it, which worked when it did it by hand:
+
+<pre>
+get old-head (git show-ref HEAD -s)
+get curr-branch (refs/heads/master eg)
+find old-commit (look back through git log for the first commit that was not "propellor spin")
+git reset old-commit
+git commit -a # user gets to edit commit message for all the spins and any staged changes here
+git merge -S -s ours old-head
+get current-commit (result of merge)
+git update-ref curr-branch current-commit
+git checkout curr-branch
+</pre>
+"""]]
diff --git a/doc/todo/git_push_over_propellor_ssh_channel.mdwn b/doc/todo/git_push_over_propellor_ssh_channel.mdwn
new file mode 100644
index 00000000..c6d42fcf
--- /dev/null
+++ b/doc/todo/git_push_over_propellor_ssh_channel.mdwn
@@ -0,0 +1,13 @@
+Propellor currently needs a central git server. And it has a special-cased
+protocol during bootstrap that transfers the git repo over to a new host,
+using the ssh connection that will be used to run propellor.
+
+This could be improved by making a git push be done whenever
+`propellor spin $host` runs. The remote propellor runs `git receive-pack`;
+the local one runs `git send-pack`.
+
+Then there would be no need for a central git repo. Although still very
+useful if you have multiple propellor driven hosts and you want to just git
+commit and let cron sort them out.
+
+> [[done]]! --[[Joey]]
diff --git a/doc/todo/info_propigation_out_of_nested_properties.mdwn b/doc/todo/info_propigation_out_of_nested_properties.mdwn
index e6427069..536d6719 100644
--- a/doc/todo/info_propigation_out_of_nested_properties.mdwn
+++ b/doc/todo/info_propigation_out_of_nested_properties.mdwn
@@ -1,36 +1,97 @@
> Now [[fixed|done]]!! --[[Joey]]
-Currently, Info about a Host's Properties is manually gathered and
-propigated. propertyList combines the Info of the Properties in the list.
-Docker.docked extracts relevant Info from the Properties of the container
-(but not al of it, intentionally!).
+Currently, Info about a Host's Properties is propigated to the host by
+examining the tree of Properties.
-This works, but it's error-prone. Consider this example:
+This works, but there's one problem. Consider this example:
withOS desc $ \o -> case o of
(Just (System (Debian Unstable) _)) -> ensureProperty foo
_ -> ensureProperty bar
Here, the Info of `foo` is not propigated out. Nor is `bar`'s Info.
-Of course, only one of them will be run, and only its info should be propigated
-out..
+It's not really clear if just one Info, or both should be propigated out.
-This commonly afflicts eg, privData. For example, `User.hasPassword'`
-has this problem, and this prevents --list-fields from listing privdata
-that's not set from that property.
+----
One approach might be to make the Propellor monad be able to be run in two
-modes. In one mode, it actually perform IO, etc. In the other mode, all
-liftIO is a no-op, but all Info encountered is accumulated using a Reader
-monad. This might need two separate monad definitions.
-
-That is surely doable, but the withOS example above shows a problem with it --
-the OS is itself part of a Host's info, so won't be known until all its
-properties have been examined for info!
-
-Perhaps that can be finessed. We don't really need to propigate out OS info.
-Just DNS and PrivDataField Info. So info could be collected in 2 passes,
-first as it's done now by static propertyInfo values. Then take that
-and use it as the Info when running the Properties in the Reader monad.
-Combine what the Reader accumulates with the static info to get the full
-info.
+modes. In run mode, it actually performs IO, etc. In introspection mode, all
+liftIO is a no-op, but all Info encountered is accumulated using a Reader.
+This might need two separate monad definitions.
+
+That is surely doable, but consider this example:
+
+ property "demo" = do
+ needfoo <- liftIO checkFoo
+ if needfoo
+ then ensureProperty foo
+ else ensureProperty . bar =<< liftIO (getBarParam)
+
+In introspection mode, the liftIO is a no-op, but needs to return a Bool.
+That seems unlikely (how to pick which?), but even if some defaulting is
+used, only one of foo or bar's info will be seen.
+
+Worse, the bar property is not fully known until IO can be performed to get
+its parameter.
+
+----
+
+Another approach could be something like this:
+
+ withInfoFrom foo $ \callfoo ->
+ withInfoFrom bar $ \callbar ->
+ property "demo" = do
+ needfoo <- liftIO checkFoo
+ if needfoo
+ then callfoo
+ else callbar
+
+Here withInfoFrom adds foo and bar as child properties of the demo property
+that (may) call them.
+
+This approach is not fully type safe; it would be possible to call
+withInfoFrom in a way that didn't let it propigate the info.
+
+And again this doesn't solve the problem that IO can be needed to get
+a parameter of a child property.
+
+----
+
+Another approach would be to add a new SimpleProperty, which is a property
+that has no Info. Only allow calling ensureProperty on this new type.
+
+(Or, remove propertyInfo from Property, and add a new InfoProperty that
+has the info.)
+
+But, propertyList can only contain one type at a time,
+not a mixed list of Property and SimpleProperty.
+
+Could a GADT be used instead?
+
+ {-# LANGUAGE GADTs #-}
+ {-# LANGUAGE EmptyDataDecls #-}
+
+ data HasInfo
+ data NoInfo
+
+ data Property = IProperty (GProperty HasInfo) | SProperty (GProperty NoInfo)
+
+ data GProperty i where
+ GIProperty :: Desc -> Propellor Result -> Info -> GProperty HasInfo
+ GSProperty :: Desc -> Propellor Result -> GProperty NoInfo
+
+ ensureProperty :: GProperty NoInfo -> Propellor Result
+ ensureProperty (GSProperty d r) = r
+
+That works. I made a `gadtwip` git branch that elaborated on that,
+to the point that Property.File compiles, but is otherwise
+unfinished. Most definitions of `Property` need to be changed to
+`GProperty NoInfo`, so that ensureProperty can call them. It's a big,
+intrusive change, and it may complicate propellor too much.
+
+I've tried to make this change a couple times now, and not been completely
+successful so far.
+
+(I may need to make instances of Prop for `GProperty NoInfo` and `GProperty
+HasInfo`, if that's possible, and make more Property combinators work on
+Prop.)
diff --git a/doc/todo/issue_after_upgrading_shared_library.mdwn b/doc/todo/issue_after_upgrading_shared_library.mdwn
new file mode 100644
index 00000000..52e72d4a
--- /dev/null
+++ b/doc/todo/issue_after_upgrading_shared_library.mdwn
@@ -0,0 +1,25 @@
+After upgrading my server to jessie, I noticed that propellor does not work anymore. The issue seems to be that, libffi was upgraded from libffi5:amd64 to libffi6:amd64
+
+ $ ./propellor --spin myserver
+ Building propellor-2.2.1...
+ Preprocessing library propellor-2.2.1...
+ In-place registering propellor-2.2.1...
+ Preprocessing executable 'propellor' for propellor-2.2.1...
+ Preprocessing executable 'propellor-config' for propellor-2.2.1...
+ Propellor build ... done
+
+ You need a passphrase to unlock the secret key for
+ user: bla
+
+ [master 2aabb40] propellor spin
+ Git commit ... done
+ Counting objects: 1, done.
+ Writing objects: 100% (1/1), 852 bytes | 0 bytes/s, done.
+ Total 1 (delta 0), reused 0 (delta 0)
+ To root@myserver:/var/lib/git/private/propellor.git
+ b16f1a6..2aabb40 master -> master
+ Push to central git repository ... done
+ ./propellor: error while loading shared libraries: libffi.so.5: cannot open shared object file: No such file or directory
+ propellor: user error (ssh ["-o","ControlPath=/home/myuser/.ssh/propellor/myserver.sock","-o","ControlMaster=auto","-o","ControlPersist=yes","root@myserver","sh -c 'if [ ! -d /usr/local/propellor/.git ] ; then (if ! git --version >/dev/null; then apt-get update && apt-get --no-install-recommends --no-upgrade -y install git; fi && echo STATUSNeedGitClone) || echo STATUSNeedPrecompiled ; else cd /usr/local/propellor && if ! test -x ./propellor; then ( apt-get update ; apt-get --no-upgrade --no-install-recommends -y install gnupg ; apt-get --no-upgrade --no-install-recommends -y install ghc ; apt-get --no-upgrade --no-install-recommends -y install cabal-install ; apt-get --no-upgrade --no-install-recommends -y install libghc-async-dev ; apt-get --no-upgrade --no-install-recommends -y install libghc-missingh-dev ; apt-get --no-upgrade --no-install-recommends -y install libghc-hslogger-dev ; apt-get --no-upgrade --no-install-recommends -y install libghc-unix-compat-dev ; apt-get --no-upgrade --no-install-recommends -y install libghc-ansi-terminal-dev ; apt-get --no-upgrade --no-install-recommends -y install libghc-ifelse-dev ; apt-get --no-upgrade --no-install-recommends -y install libghc-network-dev ; apt-get --no-upgrade --no-install-recommends -y install libghc-quickcheck2-dev ; apt-get --no-upgrade --no-install-recommends -y install libghc-mtl-dev ; apt-get --no-upgrade --no-install-recommends -y install libghc-monadcatchio-transformers-dev ; cabal update ; cabal install --only-dependencies ) || true && cabal configure && cabal build && ln -sf dist/build/propellor-config/propellor-config propellor; fi && ./propellor --boot myserver ; fi'"] exited 127)
+
+> [[fixed|done]] --[[Joey]]
diff --git a/doc/todo/issue_after_upgrading_shared_library/comment_1_8d9144d57871cb5d234710d1ab1b7183._comment b/doc/todo/issue_after_upgrading_shared_library/comment_1_8d9144d57871cb5d234710d1ab1b7183._comment
new file mode 100644
index 00000000..77c7df83
--- /dev/null
+++ b/doc/todo/issue_after_upgrading_shared_library/comment_1_8d9144d57871cb5d234710d1ab1b7183._comment
@@ -0,0 +1,20 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 1"""
+ date="2015-04-02T01:14:06Z"
+ content="""
+I think I saw this once myself (have no servers older than jessie left
+now).
+
+I believe the problem can be worked around by running make clean
+in /usr/local/propellor on the server.
+
+I'm not clear yet on a good way for --spin to detect that propellor
+has failed due to this, rather than some other problem, and try
+a clean and rebuild.
+
+Hmm, xmonad should have a similar problem, since it builds a haskell
+program locally. I wonder how the debian package deals with it there.
+
+Note there's a libffi6, so this will presumably happen again..
+"""]]
diff --git a/doc/todo/issue_after_upgrading_shared_library/comment_2_01a3d5e006158302e12862cacee3327e._comment b/doc/todo/issue_after_upgrading_shared_library/comment_2_01a3d5e006158302e12862cacee3327e._comment
new file mode 100644
index 00000000..3f7a7bbc
--- /dev/null
+++ b/doc/todo/issue_after_upgrading_shared_library/comment_2_01a3d5e006158302e12862cacee3327e._comment
@@ -0,0 +1,7 @@
+[[!comment format=mdwn
+ username="gueux"
+ subject="comment 2"
+ date="2015-04-02T09:24:07Z"
+ content="""
+Indeed, \"make clean\" on the server worked. I don't know it could be made more robust to this kind of upgrade...
+"""]]
diff --git a/doc/todo/issue_after_upgrading_shared_library/comment_2_6025ec35330fbac220f2888e60be1e78._comment b/doc/todo/issue_after_upgrading_shared_library/comment_2_6025ec35330fbac220f2888e60be1e78._comment
new file mode 100644
index 00000000..bc89ad7f
--- /dev/null
+++ b/doc/todo/issue_after_upgrading_shared_library/comment_2_6025ec35330fbac220f2888e60be1e78._comment
@@ -0,0 +1,17 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 2"""
+ date="2015-04-02T14:27:26Z"
+ content="""
+So I thought of two approaches.
+
+1. Propellor could copy in all the shared libraries. It already contains
+ code to do this. But, this would add overhead to every build. And it
+ might not guard against all snafus.
+
+2. Make propellor --check that should exit 0. Make --spin check that
+ propellor works and rebuild if not. Also make the runPropellor cron job
+ do that.
+
+I've gone with option #2.
+"""]]
diff --git a/doc/todo/lxc_containers_support.mdwn b/doc/todo/lxc_containers_support.mdwn
new file mode 100644
index 00000000..5e9da306
--- /dev/null
+++ b/doc/todo/lxc_containers_support.mdwn
@@ -0,0 +1 @@
+Adding lxc containers support would be great, as an alternative to docker, chroot, or systemd containers.
diff --git a/doc/todo/missing_dependencies.mdwn b/doc/todo/missing_dependencies.mdwn
new file mode 100644
index 00000000..55490a86
--- /dev/null
+++ b/doc/todo/missing_dependencies.mdwn
@@ -0,0 +1,39 @@
+After upgrading to 2.4.0, I get this error:
+
+ ./propellor --spin myserver
+ Building propellor-2.4.0...
+ Preprocessing library propellor-2.4.0...
+ In-place registering propellor-2.4.0...
+ Preprocessing executable 'propellor' for propellor-2.4.0...
+ Preprocessing executable 'propellor-config' for propellor-2.4.0...
+ Propellor build ... done
+ Git commit ... done
+ Enter passphrase for /home/user/.ssh/id_rsa:
+ Identity added: /home/user/.ssh/id_rsa (/home/user/.ssh/id_rsa)
+ Counting objects: 253, done.
+ Delta compression using up to 4 threads.
+ Compressing objects: 100% (253/253), done.
+ Writing objects: 100% (253/253), 173.59 KiB | 0 bytes/s, done.
+ Total 253 (delta 172), reused 0 (delta 0)
+ To root@myserver:/var/lib/git/private/propellor.git
+ d81fb7d..6f7f041 master -> master
+ Push to central git repository ... done
+ From myserver:/var/lib/git/private/propellor
+ d81fb7d..6f7f041 master -> origin/master
+ Pull from central git repository ... done
+ ** warning: git branch origin/master is not signed with a trusted gpg key; refusing to deploy it! (Running with previous configuration instead.)
+ Sending privdata (87652 bytes) to myserver ... done
+ From .
+ * branch HEAD -> FETCH_HEAD
+ Sending git update to myserver ... done
+ Warning: The package list for 'hackage.haskell.org' is 47 days old.
+ Run 'cabal update' to get the latest list of available packages.
+ Resolving dependencies...
+ Configuring propellor-2.4.0...
+ cabal: At least the following dependencies are missing:
+ exceptions -any
+ propellor: failed to make dist/setup-config
+ Shared connection to myserver closed.
+ propellor: remote propellor failed
+
+As in https://propellor.branchable.com/todo/issue_after_upgrading_shared_library/, manually running "make clean" on the server fixed the issue
diff --git a/doc/todo/missing_dependencies/comment_1_826a75052e87c04489aa07c3d322a54f._comment b/doc/todo/missing_dependencies/comment_1_826a75052e87c04489aa07c3d322a54f._comment
new file mode 100644
index 00000000..2ccb179d
--- /dev/null
+++ b/doc/todo/missing_dependencies/comment_1_826a75052e87c04489aa07c3d322a54f._comment
@@ -0,0 +1,15 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 1"""
+ date="2015-05-22T18:13:15Z"
+ content="""
+`exceptions` is indeed a new dependency.
+
+This is supposed to be handled by Propellor.Bootstrap.checkDepsCommand
+which is run by --spin.
+
+Maybe check if your propellor.cabal includes the `exceptions` dependency,
+and check if `cabal configure` fails. If it does, it seems like that code
+would fire, and should install the missing dependency. It worked when I
+upgraded my systems with it, is all I know.
+"""]]
diff --git a/doc/todo/onChange_failure_handling.mdwn b/doc/todo/onChange_failure_handling.mdwn
new file mode 100644
index 00000000..46a81caf
--- /dev/null
+++ b/doc/todo/onChange_failure_handling.mdwn
@@ -0,0 +1,41 @@
+> Please consider the following three properties
+> - p1,
+> - p2 and
+> - p3 = onChange p1 p2.
+>
+> If p1 returns MadeChange and p2 FailedChange, then p3 is FailedChange.
+> If we apply this property again without any changes, then p3 is
+> NoChange.
+>
+> This behavior could create problematic situations since p3 can be
+> required by another property which thinks that p3 has been applied
+> whereas it's not the case...
+>
+> -- Antoine
+
+Very well stated.
+
+I looked over existing uses of onChange in propellor, and many of them
+seem safe.
+
+The safe ones are where there's eg, a daemon, with a Property that it's
+running, and another Property that configures it in some way with
+onChange restart. If the restart fails, then the daemon is presumably
+left not running (unless it failed to stop the daemon somehow); a state
+that the former Property will attempt to take care of (or at least
+continue to indicate failure on) the next time propellor runs.
+
+Hmm, there are also lots of uses of onChange reloaded. If the new
+configuration of a daemon is broken, this can fail to reload it, and
+leave the daemon running with the old configuration. So that's more
+problimatic, and then there are some more problimatic yet uses of
+onChange, like the one that runs apt-get update after a change to
+sources.list.
+
+--[[Joey]]
+
+----
+
+The `onChangeFlagOnFail` combinator is a safer alternative to `onChange`
+that avoids this problem. But, it can be difficult to come up with unique
+names for the flag files it uses.
diff --git a/doc/todo/port_info_for_properties_for_firewall.mdwn b/doc/todo/port_info_for_properties_for_firewall.mdwn
new file mode 100644
index 00000000..efaaba05
--- /dev/null
+++ b/doc/todo/port_info_for_properties_for_firewall.mdwn
@@ -0,0 +1,24 @@
+The firewall module could be improved if properties that set up a service
+on a port included info (see Propellor.Info and Propellor.Types.Info)
+about the port(s) used.
+
+While currently the ports have to be explicitly listed:
+
+ & Apache.installed
+ & Firewall.installed
+ & Firewall.addRule (Rule INPUT ACCEPT (Proto TCP :- Port 80))
+ & Firewall.addRule (Rule INPUT ACCEPT (Proto TCP :- Port 443))
+
+Instead the ports would be derived from the installed services.
+
+ & Apache.installed
+ & Firewall.installed
+
+There could also be some combinators to adjust the exposed
+ports of a property.
+
+ & localOnly Apache.installed
+ & exposedPorts [443,80] (Apt.serviceInstalledRunning "apache2")
+
+Such port enformation is also going to be needed as a basis of
+[[type_level_port_conflict_detection]]. --[[Joey]]
diff --git a/doc/todo/publish_propellor_as_library_to_hackage.mdwn b/doc/todo/publish_propellor_as_library_to_hackage.mdwn
new file mode 100644
index 00000000..709ee35b
--- /dev/null
+++ b/doc/todo/publish_propellor_as_library_to_hackage.mdwn
@@ -0,0 +1,4 @@
+Currently, AFAIK, one needs to fork propellor repo, add its own configuration and compile propellor binary from all the source tree.
+It would be handy and more modular to allow one to have a propellor configuration linked to propellor as a library, hosted on hackage.
+
+> [[done]] --[[Joey]]
diff --git a/doc/todo/publish_propellor_as_library_to_hackage/comment_1_00a865bf7977c0e49f54a365f4b60ce8._comment b/doc/todo/publish_propellor_as_library_to_hackage/comment_1_00a865bf7977c0e49f54a365f4b60ce8._comment
new file mode 100644
index 00000000..8d56f0f1
--- /dev/null
+++ b/doc/todo/publish_propellor_as_library_to_hackage/comment_1_00a865bf7977c0e49f54a365f4b60ce8._comment
@@ -0,0 +1,27 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 1"""
+ date="2015-02-28T15:01:24Z"
+ content="""
+Unusual as it is for propellor's configuration git repo to include the full
+source code to propellor, I like this approach. It lets users change any
+existing property that is not generic enough, or makes assumptions they
+don't like, or needs porting to their OS of choice.
+
+But still, propellor is
+[on hackage](http://hackage.haskell.org/package/propellor), as
+a library. It can be used that way if you want to.
+
+I don't think that any of propellor's code cares how it's distributed,
+except for src/wrapper.hs (which cabal will install as
+~/.cabal/bin/propellor), which sets up the ~/.propellor/ repository. You
+can bypass using that wrapper if you like, and cabal install propellor and
+create your own ~/.propellor/ repository containing only your own
+config.hs, and build and use propellor that way.
+
+Where that approach becomes a problem is that propellor --spin currently
+relies on propellor's Makefile being in the repository, when bootstrapping
+propellor on a remote host. So you'll need to include a copy of that in
+your repo for --spin to work. I'd like to get rid of the need for the
+Makefile. (Only the build and deps targets are used by --spin.)
+"""]]
diff --git a/doc/todo/publish_propellor_as_library_to_hackage/comment_2_29cc276929020e68eae8ae04110a3f5f._comment b/doc/todo/publish_propellor_as_library_to_hackage/comment_2_29cc276929020e68eae8ae04110a3f5f._comment
new file mode 100644
index 00000000..af61b1db
--- /dev/null
+++ b/doc/todo/publish_propellor_as_library_to_hackage/comment_2_29cc276929020e68eae8ae04110a3f5f._comment
@@ -0,0 +1,17 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 2"""
+ date="2015-02-28T17:08:28Z"
+ content="""
+Ok, I got --spin to not use the Makefile any more. So with the 2.2.0
+release, if you want to make ~/.propellor contain only a config.hs
+file and a foo.cabal file, that will work. The cabal file would contain
+something like:
+
+<pre>
+Executable propellor-config
+ Main-Is: config.hs
+ GHC-Options: -Wall -threaded -O0
+ Build-Depends: propellor, base >= 4.5, base < 5
+</pre>
+"""]]
diff --git a/doc/todo/publish_propellor_as_library_to_hackage/comment_3_efbe0ef77be957c37e745ec64452ae99._comment b/doc/todo/publish_propellor_as_library_to_hackage/comment_3_efbe0ef77be957c37e745ec64452ae99._comment
new file mode 100644
index 00000000..09628e53
--- /dev/null
+++ b/doc/todo/publish_propellor_as_library_to_hackage/comment_3_efbe0ef77be957c37e745ec64452ae99._comment
@@ -0,0 +1,10 @@
+[[!comment format=mdwn
+ username="https://www.google.com/accounts/o8/id?id=AItOawmtnXa0F3OsNh8H7yf5EEbtuufPZG-3StI"
+ nickname="Arnaud"
+ subject="You rocks!"
+ date="2015-03-05T15:24:49Z"
+ content="""
+Apologies for wrong information, I did not check if propellor was on hackage. Anyway, thanks a lot for caring to \"fix\" that, will give it a try this week and keep you posted.
+
+Thanks a lot
+"""]]
diff --git a/doc/todo/publish_propellor_as_library_to_hackage/comment_4_6ebf2e30596ddf6eba91717576837019._comment b/doc/todo/publish_propellor_as_library_to_hackage/comment_4_6ebf2e30596ddf6eba91717576837019._comment
new file mode 100644
index 00000000..737e7066
--- /dev/null
+++ b/doc/todo/publish_propellor_as_library_to_hackage/comment_4_6ebf2e30596ddf6eba91717576837019._comment
@@ -0,0 +1,8 @@
+[[!comment format=mdwn
+ username="https://www.google.com/accounts/o8/id?id=AItOawmtnXa0F3OsNh8H7yf5EEbtuufPZG-3StI"
+ nickname="Arnaud"
+ subject="Propellor 2.2.0 not on hackage"
+ date="2015-03-08T20:21:42Z"
+ content="""
+So I cannot depend on it right now. Do you know when it will be available there?
+"""]]
diff --git a/doc/todo/publish_propellor_as_library_to_hackage/comment_5_4a4e94c637e0380adc1a43ec3d0633e1._comment b/doc/todo/publish_propellor_as_library_to_hackage/comment_5_4a4e94c637e0380adc1a43ec3d0633e1._comment
new file mode 100644
index 00000000..85f95c17
--- /dev/null
+++ b/doc/todo/publish_propellor_as_library_to_hackage/comment_5_4a4e94c637e0380adc1a43ec3d0633e1._comment
@@ -0,0 +1,8 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 5"""
+ date="2015-03-09T17:00:35Z"
+ content="""
+SImply because 2.2.0 had not been released yet. (UNRELEASED in
+changelog..)
+"""]]
diff --git a/doc/todo/publish_propellor_as_library_to_hackage/comment_6_19470170c3ef461f446b0af1d8501640._comment b/doc/todo/publish_propellor_as_library_to_hackage/comment_6_19470170c3ef461f446b0af1d8501640._comment
new file mode 100644
index 00000000..143f1dea
--- /dev/null
+++ b/doc/todo/publish_propellor_as_library_to_hackage/comment_6_19470170c3ef461f446b0af1d8501640._comment
@@ -0,0 +1,8 @@
+[[!comment format=mdwn
+ username="https://www.google.com/accounts/o8/id?id=AItOawmtnXa0F3OsNh8H7yf5EEbtuufPZG-3StI"
+ nickname="Arnaud"
+ subject="comment 6"
+ date="2015-03-10T06:28:52Z"
+ content="""
+Sorry, I did not read the changelog. Thanks for all the hard work on propellor.
+"""]]
diff --git a/doc/todo/revertable_Ssh.authorizedKey.mdwn b/doc/todo/revertable_Ssh.authorizedKey.mdwn
new file mode 100644
index 00000000..f92eaee8
--- /dev/null
+++ b/doc/todo/revertable_Ssh.authorizedKey.mdwn
@@ -0,0 +1 @@
+I recently lost the security key I store my primary SSH key on, and had to remove that key from all authorized_keys files I had access to. It would be great if Ssh.authorizedKey was revertable, so that this could be done simply by adding a ! before existing Ssh.authorizedKey lines.
diff --git a/doc/todo/revertable_Ssh.authorizedKey/comment_1_6c11976a814a7f4a830bc11ae9bf534e._comment b/doc/todo/revertable_Ssh.authorizedKey/comment_1_6c11976a814a7f4a830bc11ae9bf534e._comment
new file mode 100644
index 00000000..e03f1143
--- /dev/null
+++ b/doc/todo/revertable_Ssh.authorizedKey/comment_1_6c11976a814a7f4a830bc11ae9bf534e._comment
@@ -0,0 +1,11 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 1"""
+ date="2015-10-06T19:01:13Z"
+ content="""
+This should be easy enough to implement; just implement a property that
+ensures the file lacks an authorized key (probably using `File.lacksLine`)
+and then combine the two properties with `<!>` to get a RevertableProperty.
+
+I'd accept a patch that does that.
+"""]]
diff --git a/doc/todo/spin_and_ipv6_addresses.mdwn b/doc/todo/spin_and_ipv6_addresses.mdwn
index 8693f16e..602d311b 100644
--- a/doc/todo/spin_and_ipv6_addresses.mdwn
+++ b/doc/todo/spin_and_ipv6_addresses.mdwn
@@ -6,3 +6,4 @@ using short names for such hosts with --spin. And, propellor only looks at
configured ipv4 properties of a host when deciding if the DNS hostname is
out of date, and falling back to contacting the host by IPv6 address.
+> [[fixed|done]] --[[Joey]]
diff --git a/doc/todo/ssh__95__user_+_sudo/comment_4_7fc635a8d6e4c903eaefa7383d2c37ac._comment b/doc/todo/ssh__95__user_+_sudo/comment_4_7fc635a8d6e4c903eaefa7383d2c37ac._comment
new file mode 100644
index 00000000..af5e120a
--- /dev/null
+++ b/doc/todo/ssh__95__user_+_sudo/comment_4_7fc635a8d6e4c903eaefa7383d2c37ac._comment
@@ -0,0 +1,8 @@
+[[!comment format=mdwn
+ username="https://launchpad.net/~jml"
+ nickname="jml"
+ subject="comment 4"
+ date="2015-05-15T08:57:04Z"
+ content="""
+Just want to add that it's not only a security issue: it's also a convenience issue. Many machines are configured by default to not allow remote root logins, but to allow user logins followed by sudo. If propellor can't do that, then there's an extra step in the whole process that can't be easily automated within propellor.
+"""]]
diff --git a/doc/todo/type_level_port_conflict_detection.mdwn b/doc/todo/type_level_port_conflict_detection.mdwn
new file mode 100644
index 00000000..5aec5775
--- /dev/null
+++ b/doc/todo/type_level_port_conflict_detection.mdwn
@@ -0,0 +1,5 @@
+See <http://stackoverflow.com/questions/26027765/using-types-to-prevent-conflicting-port-numbers-in-a-list> --[[Joey]]
+
+Needs ghc newer than 7.6.3. It may be possible to port Data.Type.Equality
+and Data.Type.Bool to older versions; I got them to compile but they didn't
+work right. --[[Joey]]