Author: Lisa

Calculating Cost Of Material For Craft Projects — Crocheting and Knitting

Calculating the cost of materials for a knitting or crocheting project is a little more complex than calculating soap material costs. Because there’s not a recipe. Your pattern may say 150 grams of a specific yarn, but you don’t use exactly 150 grams. You use what you use, and whatever is left over is left over.

As with soap oils, the price per skein of yarn may include a tax or shipping component. You can follow the same process to determine the yarn as a percentage of the order weight for prorated shipping and the yarn cost as a percentage of the order cost for prorated tax.

With yarn projects, though, you need to know how much yarn actually went into the project. How? Weigh the skeins. Weigh them before you use them or just accept the manufacturer’s weight as accurate. If you’ve got a little kitchen scale, weigh the yarns when you finish using it on the project (this assumes you don’t concurrently use the same yarn on multiple projects!). The difference is the amount of yarn in the project. You can then calculate the percentage of the yarn which was used ( (weight at start – weight at end) / weigh at start). The material cost is that percentage of the yarn cost (price per skein * number of skeins used). Add up the material cost for each yarn in the project, and you know the material cost for the project.

A spreadsheet is good for these sort of repetitive calculations. You can estimate a project’s total cost by entering 0 into column ‘G’ and basing column ‘C’ on the pattern’s yarn requirements.

The spreadsheet allows you to check out how different yarns will impact the project material cost too – what happens if I use a more expensive yarn? It’s nice to know before looking at your shopping cart total in absolute shock 🙂

Calculating Cost Of Material For Craft Projects — Soap

Tax accounting doesn’t require you to track per-item cost of materials. Money for inventory on a shelf v/s money for inventory used in products  – same diff. But if you want to know how much you are profiting for each sale, it helps to track the cost of goods per item.

Soap is fairly easy because there’s a recipe — you know how much of a certain oil you’ve used in the end product because you weighted it out to the nearest tenth gram to start with. You still have to know how much you paid for that oil (well, close enough – prices change and I’m not interested in the level of accuracy which would require knowing from *which* order the oil I’m using *this* time came). But did you pay shipping and/or tax? If not, then the cost per gram is simply the price you paid divided by the number of grams purchased. If you *did* pay shipping or tax — then you need to calculate each item as a percentage of the order. You can do this just as a percentage of the total order cost ($ for item / $ for total order) and use that for both tax (accurate) and shipping (accurate if shipping is assessed by order value, probably close enough if shipping is assessed by weight). Or you can calculate the percentage of total order cost and the percentage of order weight (item weight / sum of weight of all items ordered). Use the order cost % for for tax calculations and the order weight % for shipping calculations. Then multiply tax and shipping charges by their respective % values.

A 35# pail of coconut oil for 43.78$ with no shipping but 3.28$ in tax as the only item ordered = 47.06$. 35# is 560 ounces (I trust the supplier’s scale here). Which is 0.084 cents per ounce. My 20% superfat coconut oil soap recipe uses 66 ounces. That’s 5.54$ of coconut oil.

Lye cost is a more complex calculation because I order it along with other specialty items. It was 50$ for the whole order of lye. The lye was 85% of the previous order’s weight. Shipping was 17.79. So the lye’s portion of the shipping was 0.85 * 17.79 = 15.12$. The lye was 61% of the order’s value. Tax was 6.19. The lye’s portion of tax was 0.61 * 6.19 = 3.78$. The sum of these (50+15.12+3.78) is 68.9. Each container is 27 ounces of lye and I got 5 containers — 27 * 5 is 135 ounces. Divide the lye cost by ounces received, 68.9/135, and I paid 0.51$ per ounce of lye. There are 9.6 ounces of lye in my recipe, which adds 4.90$ for lye.

Lavender essential oil was 33.54$ for 16.6 ounces. 16.07$ shipping, and the EO was 7% of the order weight. That’s another 1.12$ for shipping. Total cost for the EO is 34.66. Divide by ounces and it is 2.03$ per ounce. I used 4 ounces of the EO – 8.37$

The lavender scented, uncoloured batch of my coconut oil soap costs 5.54 + 4.90 + 8.37 = 18.82$ to produce. Now this is a 6# batch of soap, 24 3-oz bars. To calculate a per-bar cost, divide the total batch cost by the number of bars cut. 18.82 / 24 = 0.78$.

This is the sort of thing databases and spreadsheets are really good at doing. It’s the same formula over and over again. There are specialty accounting packages specifically for individual craft segments (i.e. a single piece of software that tracks inventory and unit cost thereof, recipes, sales and unit costs thereof and can analyze the data all sorts of ways). 100$ for the software, which is deductible against business income – it’s an investment in the business and saving yourself time. But for an initial foray into marketing a product, a simple spreadsheet will do. SoapCostCalculator is a spreadsheet I built to get an idea of how much each bar of soap was costing me.

ZoneMinder After Upgrade

We recently updated from ZoneMinder 1.30 to 1.34 – easy as can be, ran the DB update script and everything came right online. Except … our home automation system hasn’t been able to access the system. OpenHAB reports that the bridge is offline. And we’re getting 404 errors in all of the /zm/api calls in access_log.

Turns out the API was offline because when the new package came down … there was a zoneminder.conf.rpmnew in the Apache conf.d directory. Can’t even say I found this intentionally – I wanted to check the Apache config file to see if it had anything about the api directory, did a directory listing, and said oooooh!

[lisa@fedora01 conf.d]# ll zone*
-rw-r–r– 1 root root 1990 Jul 29 18:13 zoneminder.conf
-rw-r–r– 1 root root 1990 Aug 28 22:34 zoneminder.conf.rpmnew

They’ve changed a few of the sub-directories and added components to the config. As soon as I renamed zoneminder.conf to zoneminder.conf.old, copied zoneminder.conf.rpmnew to zoneminder.conf, and repeated a few config tweaks we had made for the original installation … restarted Apache and voila, we can fetch /zm/api/host/getVersion.json and get values. So if you’re getting odd 404 errors and CakePHP “/zm/api” not found errors maybe you forgot to update your config with changes from the rpmnew file.

Cleaning Up Old OpenHAB Persistence Tables

So my husband asked for a program that would go out to the OpenHAB persistence database and identify all of the item tables that are no longer associated with active items. If you rename or delete an item from OpenHAB, the associated data is retained in the persistence database. Might be a good thing – maybe you wanted that data. But if it’s useless fluff … well, no need to keep the state changes from a door sensor that’s no longer around.

Wrote the code, and asked him how many days old he wanted the last update to be before the item table got dropped … and he told me this was a useless way to do it and maybe something really hadn’t updated in six months or three years and age of last update is no way to be identifying tables to be removed. Which, yeah, then why ask for it!? Then I needed to write something that takes a list of items from OpenHAB and identifies everything in the items table that does not appear in the OpenHAB list so those tables can be deleted. But I figured I’d post the original code too in case anyone else could use it. Both in perl, and neither in particularly well written perl. I trust the data and don’t want to protect against insertion attacks.

Drop tables for items that no longer appear in OpenHAB:

use strict;
use DBI;

my %strItemsFromOpenHAB = ();
open(INPUT,"./openhabItemList.txt");
while(<INPUT>){
        chomp();
        my $strCurrentItem = $_;
        $strItemsFromOpenHAB{$strCurrentItem}++;
}
close INPUT;

my $dbh = DBI->connect('DBI:mysql:openhabdb;host=DBHOST', 'DBUID', 'DBPassword', { RaiseError => 1 } );

my $sth = $dbh->prepare("SELECT * FROM items");
$sth->execute();
while (my @row = $sth->fetchrow_array) {
        my $strItemID = $row[0];
        my $strItemName = $row[1];
        if(! $strItemsFromOpenHAB{$strItemName} ){              # If the current item name is not in the list of items from OpenHAB
#               print "DELETE FROM items where ItemID = $strItemID\n";
                print "DROP TABLE Item$strItemID;  # $strItemName \n";
        }
}
$sth->finish();

$dbh->disconnect();
close OUTPUT;

 

Identify tables that have not been updated in iTooOldInDays days:

use strict;
use DBI;
use Date::Parse;
use Time::Local;

my $iTooOldInDays = 365;

my $iCurrentEpochTime = time();

my @strItems = ();
my $iItems = 0;

my $dbh = DBI->connect('DBI:mysql:openhabdb;host=DBHOST', 'DBUID', 'DBPassword', { RaiseError => 1 } );

my $sth = $dbh->prepare("SELECT * FROM Items");
$sth->execute();
while (my @row = $sth->fetchrow_array) {
        $strItems[$iItems++] = $row[0];
}
$sth->finish();

for(my $i = 0; $i < $iItems; $i++){ my $strTableName = 'Item' . $strItems[$i]; my $sth = $dbh->prepare("SELECT * FROM $strTableName ORDER BY Time DESC LIMIT 1");
        $sth->execute();
        while (my @row = $sth->fetchrow_array) {
                my $strUpdateTime = $row[0];
                my @strDateTimeBreakout = split(/ /,$strUpdateTime);
                my $strDate = $strDateTimeBreakout[0];
                my $strTime = $strDateTimeBreakout[1];

                my @strDateBreakout = split(/-/,$strDate);
                my @strTimeBreakout = split(/:/,$strTime);

                my $iUpdateEpochTime = timelocal($strTimeBreakout[2],$strTimeBreakout[1],$strTimeBreakout[0], $strDateBreakout[2],$strDateBreakout[1]-1,$strDateBreakout[0]);
                my $iTableAge = $iCurrentEpochTime - $iUpdateEpochTime;

                if($iTableAge > ($iTooOldInDays * 86400) ){
                        print "$strTableName last updated $strUpdateTime - $iUpdateEpochTime\n";
                }
        }
        $sth->finish();
}

$dbh->disconnect();
close OUTPUT;

Configuring and Using RPZ

I realized today what, while I had written about why response policy zones are useful, I never indicated how to configure one! So here’s a quick document outlining how to set it up in ISC Bind. In your named.conf file, add a response policy to your options section:

        response-policy {
                zone “rpz”;
        };
Then add the correspondingly named zone at the end of the file. For purposes of testing, I added a zone as a forward only zone so I could perform a network capture to see what exactly transpires when a name in the RPZ is resolved.
zone “rpz” {
      type master;
      file “rpz.db”;
      allow-query { none; };
      allow-transfer { none; };
};
zone “example.com” {
    type forward;
    forward only;
    forwarders { 8.8.8.8; };
};
Then you just have to make a rpz.db where you store your named files:
$TTL 60
$ORIGIN rpz.
@            IN    SOA  localhost. root.localhost.  (
                          2   ; serial
                          3H  ; refresh
                          1H  ; retry
                          1W  ; expiry
                          1H) ; minimum
                  IN    NS    localhost.

www.example.com    CNAME    www.yahoo.com.
Restarted named and ran “rndc flush” to avoid serving cached content instead of the RPZ host data. Then ran a few tests and confirmed that the resolution configured in the rpz zone:
[lisa@fedora02 named]# dig +short www.example.com @localhost
www.yahoo.com.
atsv2-fp.wg1.b.yahoo.com.
98.139.183.24
98.138.252.30
98.139.180.149
98.138.253.109
[lisa@fedora02 named]# dig +short www.google.com @localhost
216.58.218.228
In this process, I learnt something interesting about ICS’s implementation of RPZ: it still performs the query and then overrides the results. Odd waste of cycles, but the resolution that was subsequently turned into yahoo’s address from the rpz zone. Looking up a host that isn’t in my RPZ and I got another query out to 8.8.8.8 which was expected. Query to something not in the forward zone and not in the rpz zone and I get no traffic to 8.8.8.8 (because it follows my normal forwarding which is to our ISP’s DNS).
I was curious if this meant rpz could not be used to publish a bad hostname locally – but attempting to resolve a bad hostname (added abadhost.example.com with the same CNAME to Yahoo and reloaded my zone) worked just fine.

[root@fedora02 ~]# dig abadhost.example.com @localhost

; <<>> DiG 9.11.1-P2-RedHat-9.11.1-2.P2.fc26 <<>> abadhost.example.com @localhost
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 8382
;; flags: qr rd ra; QUERY: 1, ANSWER: 6, AUTHORITY: 4, ADDITIONAL: 3

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: 1aa34751c5df7f78857a921259a8706fb5e1741a46eb5352 (good)
;; QUESTION SECTION:
;abadhost.example.com. IN A

;; ANSWER SECTION:
abadhost.example.com. 5 IN CNAME www.yahoo.com.
www.yahoo.com. 1800 IN CNAME atsv2-fp.wg1.b.yahoo.com.
atsv2-fp.wg1.b.yahoo.com. 60 IN A 98.139.180.149
atsv2-fp.wg1.b.yahoo.com. 60 IN A 98.138.253.109
atsv2-fp.wg1.b.yahoo.com. 60 IN A 98.139.183.24
atsv2-fp.wg1.b.yahoo.com. 60 IN A 98.138.252.30

;; AUTHORITY SECTION:
wg1.b.yahoo.com. 172800 IN NS yf3.a1.b.yahoo.net.
wg1.b.yahoo.com. 172800 IN NS yf4.a1.b.yahoo.net.
wg1.b.yahoo.com. 172800 IN NS yf1.yahoo.com.
wg1.b.yahoo.com. 172800 IN NS yf2.yahoo.com.

;; ADDITIONAL SECTION:
yf1.yahoo.com. 86400 IN A 68.142.254.15
yf2.yahoo.com. 86400 IN A 68.180.130.15

;; Query time: 1204 msec
;; SERVER: ::1#53(::1)
;; WHEN: Thu Aug 31 16:24:15 EDT 2017
;; MSG SIZE rcvd: 315

But there is a query that goes out to the name server and a ‘no such name’ result returned. Odd.

 

Cooperation

Read an interesting take on Mueller’s coordination with NY AG Schneiderman: Trump might manage to shut down Mueller’s investigation (or enough Republicans may OK the 6 month time limit). Justice can suppress documents from the investigation (or oopse them), but it would be a gross abuse of federal power to confiscate (or even assume to control the release of) investigative materials in possession of the NY AG.

Which isn’t to say Trump & co wouldn’t *try* … but something not a lot of Americans seem to know about the lead up to the Revolutionary war: there was quite a bit of debate in Parliament about the same grievances the American colonialists had. Not because Britons had some altruistic concern for the socio-political well-being of Colonials, but if some set of offensive rules apply to THOSE crown citizens today, what is to stop them from applying to ME tomorrow? Point being – it’s one thing to treat women horrendously, screw over poor people, take the David Duke approach to race relations. But if the feds can come in and overrule state criminal investigations in NY today … well, here’s hoping people who like to prance around screaming about states rights have some line they aren’t OK with Trump crossing.

Pardons

When a law enforcement officer refuses to enforce the law and can rely on the president to pardon the infraction, does that not elevate the executive branch above all others? Trump encouraged law enforcement officers to abuse people in their custody (full on Jerry Seinfeld why are they so careful getting the guy in the back of the cruiser?). Can police officers expect to be pardoned when charged with brutality? Will they be guaranteed their jobs even after killing a suspect? What is the point of having laws (or a court system) if one branch of the government can decide what should be legal by pardoning infractions they don’t like.

Sure there are mitigating circumstances that may diminish the severity of the infraction, mandatory sentencing produced people whose ‘time’ in no way fit their ‘crime’, some people are amazingly reformed after the commission of their crime. Hell, the law could change five years into your term rendering your infraction a perfectly legal activity. Some people deserve a break. Would it not be better to allow the judicial system to redress these scenarios? Mechanisms to do so are already in place — parole reduces the effective sentence, but the individual retains a criminal record. Expunction seals the record and generally means the individual does not need to disclose the conviction.

And not that I believe Trump is in the least bit concerned about people whose rights were trampled by Arpaio? What redress do they have? The purported ideology does not care who is damaged in the pursuit of illegal immigrants. And that is what makes this particular pardon so offensive. In the ‘war on drugs’, cops abused people in poor neighborhoods. Evidence was planted to convict those who were known to be dealers. What would have happened if Reagan had just pardoned those officers?

We watched a lot of movies during some M&A activity. A lot. We were splitting an eDirectory tree, and a couple of servers each night would be moved. I ran the support bridge for the split, and needed a way to get a good number of people to volunteer to work 6p-2a six days a week with no overtime. Accomplished this by having free dinner, snacks, and movies. I didn’t own that many DVDs, so people volunteered to bring their collection too. It was a lot of fun (some of the techs ringing into the bridge would get the same DVD going and watch with us), and I got to see a lot of movies I wouldn’t have known existed. One of those movies was an early 70’s movie based on a mid 60’s book: Colossus: The Forbin Project. Pardoning Arpaio makes me think of the movie — the premise was essentially that the US government commissioned a computer system to control the country’s nuclear arsenal with the goal of preserving world peace because computers do not suffer human foibles such as irrational emotional responses. The computer accomplished it’s goal … by using the nuclear arsenal to blackmail humanity into peace (either you stop fighting or I start nuc’ing you until you concede). Same lack of constraint or morality. It doesn’t matter how many people are tortured or killed, as long as the objective is met.

Avocado Chocolate Mousse

Mmmmm – I made an avocado chocolate mousse that a friend of a friend sent out. I modified the original recipe significantly: used a lot of carob in place of the cocoa, used maple syrup in place of honey, and used coconut milk in place of almond milk. 

Ingredients:

2 avocados (room temperature)
6 Tbsp coconut milk
5 Tbsp carob powder
1 Tbsp cocoa powder (I used KAF’s triple cocoa powder)
1/2 tsp vanilla extract
2 Tbsp maple syrup

Method:

Dice up the avocado into large chunks. Combine everything in a food processor and process until smooth and creamy. Eat 🙂

DMARC and DKIM

Microsoft’s latest security newsletter included the fact that more than 90% of Fortune 500 companies have not fully implemented DMARC. Wow — that’s something I do at home! Worse still, the Fortune 500 company for which I work is in that 90% … a fact I hope to rectify this week. SPF is just some DNS entries that indicate the source IPs that are expected to be sending email from your domain. Lots of SPF record generators online.

DKIM is a little more involved, but it’s a lot easier now that packages for DKIM are available on Linux distro repositories. You still *can* build it from source, but it’s easier to install the OpenDKIM package.

Once the package is installed, generate the key(s) to be used with your domain(s).

cd /etc/opendkim/keys/
openssl genrsa -out dkim.private 2048
openssl rsa -in dkim.private -out dkim.public -pubout -outform PEM
# secure private key file
chown opendkim:opendkim dkim.private
chmod go-r dkim.private

Decide on the selector you are using — I use ‘mail’ as my selector. At work, I use ‘2017Q3Key’ — this allows us to change to a new key without in-transit mail being impacted. Old mail was sent with the 2017Q2 selector and *that* public key is in DNS. New mail comes across with 2017Q3 and uses the new DNS record to verify. I do *not* share these keys – anyone else sending mail from our domain needs to generate their own key (or I make one for them), use their own unique selector, and I will create the DNS records for their selector. When marketing engages a third party to send e-mails on our behalf, we have a 2017VendorName selector too.

Edit /etc/opendkim.conf. The socket line is not necessary – I just tend away from default ports as a habit. Since it’s bound to localhost, not such a big deal.

Mode sv
Socket   inet:8895@localhost
Selector mail
KeyFile /etc/opendkim/keys/dkim.private
KeyTable /etc/opendkim/KeyTable
SigningTable refile:/etc/opendkim/SigningTable
InternalHosts refile:/etc/opendkim/TrustedHosts

There’s a config option to “SendReports” — it’s a boolean that indicates if you want your system to send failure reports when the sender indicates they want such reports and provide a reporting address. Especially for testing purposes, I recommend indicating your domain wants reports — it is helpful in case you’ve got something configured not quite right and are failing delivery on some messages. As such, configure my installation to send reports. It’s additional overhead in cases where verification fails; I don’t see all that many failures, and it isn’t a lot of extra load. Since I know my installation will send detailed failure information, I can use my domain when testing new implementations.

Once you have the base configuration set, edit /etc/opendkim/SigningTable and add your domain(s) and the appropriate selector

*@rushworth.us mail._domainkey.rushworth.us
*@lisa.rushworth.us mail._domainkey.lisa.rushworth.us
*@scott.rushworth.us mail._domainkey.scott.rushworth.us
*@anya.rushworth.us mail._domainkey.anya.rushworth.us

Edit /etc/opendkim/KeyTable and map each selector from the SigningTable to a key file

mail._domainkey.rushworth.us rushworth.us:default:/etc/opendkim/keys/dkim.private
mail._domainkey.lisa.rushworth.us lisa.rushworth.us:default:/etc/opendkim/keys/lisa.dkim.private
mail._domainkey.scott.rushworth.us scott.rushworth.us:default:/etc/opendkim/keys/scott.dkim.private
mail._domainkey.anya.rushworth.us anya.rushworth.us:default:/etc/opendkim/keys/anya.dkim.private

Edit /etc/opendkim/TrustedHosts and add the internal IPs that relay your domain’s mail through the server (IP addresses or subnets)

Create DNS TXT records – the part after p= is the content of the public key file for that selector. When you are first setting up DKIM, use t=y (yes, we are just testing this). Once you confirm everything is functional, you can change to y=n (nope, really pay attention to our DKIM signature and policy). The policy is an individual preference. I use ‘all’ (all mail from my domain will be signed) and “o=-” (again all mail from my domain will be signed). You can use “o=~” (some mail from my domain is signed, some isn’t … who knows) and “dkim=unknown” (again, some is signed). You can use “dkim=discardable” (don’t just consider the message as more likely to be spam if it is not signed … you can outright drop the message). As a business, I don’t use this *just in case*. Something crazy happens – the dkim service falls over, your key gets mangled – and receiving parties can start dropping your messages. Using “dkim=all” means they are more apt to quarantine them as spam, but someone can go and get the messages. And hopefully notice something odd is happening.

mail._domainkey.domain.tld  TXT k=rsa;t=y;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzTnpc7tHfyH1zgT3Jx/JHmGSz8WCy1jvzu5QsYvDBmimKEHRY4Kz4mya5bOYsDQuJ/sz+BJo6xDwsUXCuyEkykIlgqP+7E9oK2EcW0dZms87SGmNEnNBN5iTe0pdzk1lXx2js3QdOWswO+cmA9F1Z8OzSR+2u79huugPFBHl79zFvOEHbigrmeHEfo0KHWpeNomf/xKx+wyYr1n3R5gS+28CeC3abSyKgmaYYRLoZsjrCLbEM0m2YPJRKd1ZGOObBMa4PZWj7pT07ISEjoNnXQ27BtcL/QjKKeLkbJ0UGEOSdPEJKuEpAUvYU9lA5hbtzrqiwdlPxWYocDVPrcqAHwIDAQAB
_adsp._domiankey.domain.tld TXT dkim=all
_domainkey.rushworth.us TXT t=y;o=-;r=dkim@lisa.rushworth.us
_ssp._domainkey.rushworth.us TXT t=y;dkim=all

 

Edit /etc/mail/sendmail.mc (using the port defined in /etc/opendkim.conf

INPUT_MAIL_FILTER(`dkim-filter’, `S=inet:8895@localhost’)

Make your sendmail.mc to sendmail.cf and verify that you’ve got the dkim-filter line

Xdkim-filter, S=inet:8895@localhost

Start opendkim, then restart sendmail. Now test it — inbound mail should have *their* DKIM signatures verified, outbound mail should be signed with the appropriate key.

Once you have verified your DKIM is functioning properly — well, first of all you can update your DNS records to remove testing mode. Then create your DMARC record:

_dmarc.rushworth.us     v=DMARC1; p=quarantine; sp=quarantine; rua=mailto:dmarc-rua@lisa.rushworth.us!10m; ruf=mailto:dmarc-ruf@lisa.rushworth.us!10m; rf=afrf; pct=100; ri=172800
 Again, you don’t need to use quarantine — ‘reject’ would recommend mail be dropped or ‘none’ recommends no action (good for testing). The rua (aggregate reporting email address) and ruf (address to recieve failing samples for analysis) should be in your domain.
You could add either/both “adkim=s” or “aspf=s” to indicate your DKIM or SPF adhere to strict standards. I use relaxed (default, do not need to specify it in the TXT record).
If you want the reports delivered to an address outside of your domain, that domain needs to publish a DNS record authorizing receipt of the reports:
     rushworth.us._report._dmarc.lisa.rushworth.us     v=DMARC1