From: Cameron Otsuka Date: Tue, 30 Jul 2024 19:30:41 +0000 (-0700) Subject: initial commit with 11ty X-Git-Tag: v1.0.0~101 X-Git-Url: https://git.otsuka.systems/?a=commitdiff_plain;h=50d6458a40f9b3185c0db13e8ed7e835651c813e;p=cotsuka.github.io initial commit with 11ty --- 50d6458a40f9b3185c0db13e8ed7e835651c813e diff --git a/.github/workflows/deploy-to-ghpages.yml b/.github/workflows/deploy-to-ghpages.yml new file mode 100644 index 0000000..1beadd4 --- /dev/null +++ b/.github/workflows/deploy-to-ghpages.yml @@ -0,0 +1,38 @@ +name: Deploy to GitHub Pages + +on: + push: + branches: + - main + pull_request: + +jobs: + deploy: + runs-on: ubuntu-22.04 + permissions: + contents: write + concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + steps: + - uses: actions/checkout@v3 + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: "18" + + - name: Persist npm cache + uses: actions/cache@v3 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ hashFiles('**/package.json') }} + + - run: npm install + - run: npm run build-ghpages + + - name: Deploy + uses: peaceiris/actions-gh-pages@v3 + if: github.ref == 'refs/heads/main' + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./_site \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..19ded4c --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +_site/ +node_modules/ +package-lock.json +.cache \ No newline at end of file diff --git a/_data/contributions.json b/_data/contributions.json new file mode 100644 index 0000000..9a07d31 --- /dev/null +++ b/_data/contributions.json @@ -0,0 +1,14 @@ +[ + { + "title": "ArchWiki", + "url": "https://wiki.archlinux.org/title/Special:Contributions/Cotsuka" + }, + { + "title": "Arch User Repository", + "url": "https://aur.archlinux.org/packages?O=0&SeB=M&K=cotsuka&outdated=&SB=p&SO=d&PP=50&submit=Go" + }, + { + "title": "Wikipedia", + "url": "https://en.wikipedia.org/wiki/Special:Contributions/Cotsuka" + } +] \ No newline at end of file diff --git a/_data/metadata.json b/_data/metadata.json new file mode 100644 index 0000000..76714df --- /dev/null +++ b/_data/metadata.json @@ -0,0 +1,11 @@ +{ + "title": "Cameron Otsuka", + "url": "https://otsuka.haus/", + "language": "en", + "description": "The collection of Cameron's thoughts.", + "author": { + "name": "Cameron Otsuka", + "email": "cameron@otsuka.haus", + "url": "https://otsuka.haus" + } +} \ No newline at end of file diff --git a/_data/socials.json b/_data/socials.json new file mode 100644 index 0000000..8dff999 --- /dev/null +++ b/_data/socials.json @@ -0,0 +1,22 @@ +[ + { + "title": "Twitter", + "url": "https://twitter.com/CameronOtsuka" + }, + { + "title": "LinkedIn", + "url": "https://www.linkedin.com/in/cotsuka" + }, + { + "title": "Mastodon", + "url": "https://social.otsuka.haus/cameron" + }, + { + "title": "Nostr", + "url": "nostr:npub1hzssq7wewjztvglpdvku92htx3sv2x5r9ycvqhvl9xrtt5fn629s3np693" + }, + { + "title": "GitHub", + "url": "https://github.com/cotsuka" + } +] \ No newline at end of file diff --git a/_includes/articleslist.njk b/_includes/articleslist.njk new file mode 100644 index 0000000..2741911 --- /dev/null +++ b/_includes/articleslist.njk @@ -0,0 +1,6 @@ +
+{% for article in articleslist | reverse %} +
{{ article.data.title }}
+
{{ article.data.description }}
+{% endfor %} +
\ No newline at end of file diff --git a/_includes/css/prism-okaidia.css b/_includes/css/prism-okaidia.css new file mode 100644 index 0000000..28abb2f --- /dev/null +++ b/_includes/css/prism-okaidia.css @@ -0,0 +1,123 @@ +/** + * okaidia theme for JavaScript, CSS and HTML + * Loosely based on Monokai textmate theme by http://www.monokai.nl/ + * @author ocodia + */ + +code[class*="language-"], +pre[class*="language-"] { + color: #f8f8f2; + background: none; + text-shadow: 0 1px rgba(0, 0, 0, 0.3); + font-family: monospace; + font-size: 0.75rem; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1rem; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: .5em 0; + overflow: auto; + border-radius: 0.3em; +} + +:not(pre) > code[class*="language-"], +pre[class*="language-"] { + background: #272822; +} + +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: .1em; + border-radius: .3em; + white-space: normal; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: #8292a2; +} + +.token.punctuation { + color: #f8f8f2; +} + +.token.namespace { + opacity: .7; +} + +.token.property, +.token.tag, +.token.constant, +.token.symbol, +.token.deleted { + color: #f92672; +} + +.token.boolean, +.token.number { + color: #ae81ff; +} + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin, +.token.inserted { + color: #a6e22e; +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string, +.token.variable { + color: #f8f8f2; +} + +.token.atrule, +.token.attr-value, +.token.function, +.token.class-name { + color: #e6db74; +} + +.token.keyword { + color: #66d9ef; +} + +.token.regex, +.token.important { + color: #fd971f; +} + +.token.important, +.token.bold { + font-weight: bold; +} +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} diff --git a/_includes/css/reset.css b/_includes/css/reset.css new file mode 100644 index 0000000..18c251d --- /dev/null +++ b/_includes/css/reset.css @@ -0,0 +1,27 @@ +/* + Josh's Custom CSS Reset + https://www.joshwcomeau.com/css/custom-css-reset/ +*/ +*, *::before, *::after { + box-sizing: border-box; +} +* { + margin: 0; +} +body { + line-height: 1.5; + -webkit-font-smoothing: antialiased; +} +img, picture, video, canvas, svg { + display: block; + max-width: 100%; +} +input, button, textarea, select { + font: inherit; +} +p, h1, h2, h3, h4, h5, h6 { + overflow-wrap: break-word; +} +#root, #__next { + isolation: isolate; +} \ No newline at end of file diff --git a/_includes/css/style.css b/_includes/css/style.css new file mode 100644 index 0000000..a80908f --- /dev/null +++ b/_includes/css/style.css @@ -0,0 +1,77 @@ +html { + background-color: #ffffff; + color: #000000; + font-family: sans-serif; + font-size: 100%; + line-height: 1.1rem; +} +body { + max-width: 45rem; + margin-left: auto; + margin-right: auto; + font-size: 1rem; +} +nav { + font-size: 2rem; + font-weight: bold; +} +nav > a { + text-decoration: none; +} +details { + margin-bottom: 1rem; +} +p { + margin-bottom: 1rem; +} +dl, menu, ol, ul { + margin-bottom: 1rem; +} +ul ul, ul ol, ul dl, ul menu, +ol ul, ol ol, ol dl, ol menu, +dl ul, dl ol, dl dl, dl menu, +menu ul, menu ol, menu dl, menu menu { + /* nested lists won't have margins*/ + margin-bottom: 0; +} +dd { + margin-bottom: 1rem; + padding-left: 1.5rem; +} +blockquote { + margin-bottom: 1rem; + padding-left: 0.5rem; + border-left: 0.3rem solid; +} +figure { + margin-bottom: 1rem; +} +img { + height: auto; +} +figcaption { + font-style: italic; +} +table { + margin-bottom: 1rem; + border: 0.1rem solid; + border-collapse: collapse; +} +th, td { + border: 0.1rem solid; + text-align: center; + vertical-align: middle; + padding: 0.5rem; +} +footer { + text-align: center; + font-style: italic; +} +footer > address > menu { + margin-inline-start: 0; + padding-inline-start: 0; +} +footer > address > menu > li { + display: inline; + padding-right: 1rem; +} \ No newline at end of file diff --git a/_includes/layouts/article.njk b/_includes/layouts/article.njk new file mode 100644 index 0000000..5aab2ea --- /dev/null +++ b/_includes/layouts/article.njk @@ -0,0 +1,18 @@ +--- +layout: layouts/base.njk +--- + +

{{ title }}

+ +
+ Metadata + +
+ +
+ {{ content | safe }} +
diff --git a/_includes/layouts/base.njk b/_includes/layouts/base.njk new file mode 100644 index 0000000..7923172 --- /dev/null +++ b/_includes/layouts/base.njk @@ -0,0 +1,47 @@ + + + + + + {{ title }} | {{ metadata.title }} + + + + {% set css %} + {% include "css/reset.css" %} + {% include "css/style.css" %} + {% include "css/prism-okaidia.css" %} + {% endset %} + + + +
+ +
+
+ {{ content | safe }} +
+
+ + + \ No newline at end of file diff --git a/_includes/layouts/home.njk b/_includes/layouts/home.njk new file mode 100644 index 0000000..def68d2 --- /dev/null +++ b/_includes/layouts/home.njk @@ -0,0 +1,22 @@ +--- +layout: layouts/base.njk +--- + +
+ {{ content | safe }} +
+ +
+

Articles

+ {% set articleslist = collections.articles %} + {% include "articleslist.njk" %} +
+ +
+

Contributions

+ +
\ No newline at end of file diff --git a/content/404.njk b/content/404.njk new file mode 100644 index 0000000..e0d382f --- /dev/null +++ b/content/404.njk @@ -0,0 +1,8 @@ +--- +layout: layouts/base.njk +title: 404 +permalink: 404.html +date: 2024-07-30 +--- +

404 Not Found

+

Oops! The page you requested is not found.

\ No newline at end of file diff --git a/content/articles/articles.11tydata.js b/content/articles/articles.11tydata.js new file mode 100644 index 0000000..0e85111 --- /dev/null +++ b/content/articles/articles.11tydata.js @@ -0,0 +1,6 @@ +module.exports = { + tags: [ + "articles" + ], + "layout": "layouts/article.njk", +}; diff --git a/content/articles/minimum-utxo-value/index.md b/content/articles/minimum-utxo-value/index.md new file mode 100644 index 0000000..374d309 --- /dev/null +++ b/content/articles/minimum-utxo-value/index.md @@ -0,0 +1,113 @@ +--- +title: Minimum UTXO Value +date: 2023-08-04 +modified: 2024-01-26 +permalink: 'articles/2023-008-04-minimum-utxo-value/' +description: A look at what Bitcoin dust is, historical fee rates, how fees are calculated, and a decision on a minimum UTXO value to stay above the dust threshold. +tags: +- bitcoin +--- + +Due to recent high fee rate periods, many were advocating for consolidating low-value UTXOs to ensure they wouldn't become dust. I was curious whether I had UTXOs that were in danger of becoming dust. + +While there is a [technical definition](https://bitcoin.stackexchange.com/a/41082) of dust built into Bitcoin Core's code, I am more broadly referring to the colloquial definition of dust: UTXOs with a value less than the transaction fees required to spend them, which have become uneconomical to move. + +Because fee rates are the primary variable determining the dust threshold, whether a UTXO is considered dust can change over time. I took a look at historical fee rates, how fees are calculated, and included qualitative projections of future block space demand to determine a UTXO value that will stay above the dust threshold for myself. + +> **TL;DR;** +> +> Taproot UTXO, 65k sats + +### Historical Fee Rates +The highest fee periods in Bitcoin history have been 2018, 2021, and 2023: + +
+ {% image "./minimum-utxo-value_mempool_vbytes_history.svg", "Historical graph of Bitcoin mempool by vBytes" %} +
Source: mempool.space
+
+ +I've selected 4 blocks to deep dive, based on their proximity to mempool peaks in those three years, as well as a more "normal" current block: +- 504000: January 12, 2018 +- 680000: April 21, 2021 +- 782400: March 24, 2023 +- 801171: July 31, 2023 + +
+ {% image "./minimum-utxo-value_box-whisker-overall.svg", "Box and whisker plot of fee rates across blocks" %} +
Outliers removed
+
+ +| **Fee Rate (sats/vB)** | **504000** | **680000** | **782400** | **801171** | +|:----------------------:|:----------:|:----------:|:----------:|:----------:| +| Minimum | 11 | 19 | 5 | 5 | +| Median | 119 | 269 | 27 | 12 | +| Mode | 118 | 269 | 27 | 12 | +| Maximum | 1099 | 3578 | 301 | 1280 | + +The first visible difference based on the box and whisker plot is the noticeable decrease in fee rate variance, likely attributable to better fee estimations. Second, is the 86% decline in median fee rate with an average 194 sats/vB between blocks 504000 and 680000 vs. 27 sats/vB in block 782400. + +> **Note** +> +> I'm taking a naïve view of transactions, so transactions pushed through using CPFP aren't coalesced into a single transaction with a blended, "effective" fee rate. In theory, this should reduce fee rate spread, but will be relatively centered around the median. + +I don't expect to see major differences in fee rates by script type, but since I have the data available here's what that looks like (it's as expected): + +
+ {% image "./minimum-utxo-value_box-whisker-fee-rate-script-type.svg", "Box and whiskper plot of fee rates by script type" %} +
Fee Rate axis capped at 50 sats/vB to better show the differences between script types.
+
+ +Transactions are shifting towards the newer P2WPKH, P2WSH, and P2TR script types: + +| **Transactions (%)** | **504000** | **680000** | **782400** | **801171** | +|:--------------------:|:----------:|:----------:|:----------:|:----------:| +| P2PKH | 97% | 51% | 22% | 32% | +| P2SH | 3% | 41% | 28% | 26% | +| P2WPKH | 0% | 7% | 38% | 38% | +| P2WSH | 0% | 0% | 1% | 1% | +| P2TR | 0% | 0% | 12% | 3% | + +Where the script types do differ is in the total size of the transaction and therefore, the total fees paid. + +
+ {% image "./minimum-utxo-value_box-whisker-fees-script-type.svg", "Box and whisker plot of total fees by script type" %} +
Fee Rate axis capped at 300000 sats/vB to better show the differences between script types.
+
+ +P2WSH and P2TR transactions appear to be paying higher median fees than other script types. + +
+ {% image "./minimum-utxo-value_box-whisker-fees-script-type-zoomed.svg", "Box and whisker plot of total fees by script type, zoomed" %} +
Fee Rate axis capped at 18000 sats/vB to better show the differences between script types.
+
+ +To better understand the cause of this, we have to know how vBytes are allocated during the construction of a transaction. + +### Calculating Transaction Size +The total fees paid to move a given UTXO depend on the vBytes needed to construct a transaction. Jameson Lopp has a [transaction size calculator](https://jlopp.github.io/bitcoin-transaction-size-calculator/) you can use to see how differing script types, numbers of inputs/outputs, and other variables will impact the resulting transaction size. Bitcoin OpTech has a [transaction size calculator](https://bitcoinops.org/en/tools/calc-size/) that breaks out how each component of a transaction relates to an amount of vBytes. + +The table below shows the amount of vBytes required by a transaction consisting of a single input being spent to a single output. + +| **Output →{% raw %}
{% endraw %}Input ↓** | **P2PKH** | **P2SH** | **P2WPKH** | **P2WSH** | **P2TR** | +|:--------------------:|:---------:|:--------:|:----------:|:---------:|:--------:| +| P2PKH | 192 | 190 | 189 | 201 | 201 | +| P2SH (2-of-3) | 341 | 339 | 338 | 350 | 350 | +| P2WPKH | 112.5 | 110.5 | 109.5 | 121.5 | 121.5 | +| P2WSH (2-of-3) | 149 | 147 | 146 | 158 | 158 | +| P2TR (Keypath) | 102 | 100 | 99 | 111 | 111 | + +So in ideal dust spending conditions, you would hold a P2TR UTXO and spend it to a P2WPKH output. An input requires more vBytes than an output, so the ideal will change if you are spending few inputs to many outputs. + +> **Note** +> +> Surprisingly, despite P2SH and P2PKH inputs being the most expensive (in terms of total fees), the fee data above doesn't bear that out. Without doing further digging, I assume it's due to multi-sig being more common with other script types. + +### Choosing a Minimum UTXO Value +Putting everything together, here are my assumptions for choosing my minimum UTXO value: +1. I want to be able to send the UTXO to any type of output: assume I'm spending to a P2WSH or P2TR output which are the most expensive +1. Although median fee rates have come down since the last peaks, I'll assume a pretty high fee rate: 300 sats/vB + 1. Additional Bitcoin adoption in the future will put upward pressure on block space demand. Current adoption is estimated in the low single-digit percentage (~2.5%) [of the world's population](https://bitcoinmagazine.com/markets/an-objective-look-at-bitcoin-adoption). + 1. Non-Bitcoin changes could also affect block space demand (e.g., Ordinals) +1. Bitcoin changes slowly and updates that reduce vBytes needed to spend a UTXO may not come. They could require **more** vBytes. + +Given the above, I want to hold a P2TR UTXO so my projected dust threshold would be 33,301 sats. That's just the threshold at which I'm able to spend the UTXO without requiring an additional input. To make the UTXO useful, it requires excess value. I've settled on 65,000 sats as a minimum UTXO value for a round number that (at current USD exchange rates) would leave me with ~$10 in spending power. \ No newline at end of file diff --git a/content/articles/minimum-utxo-value/minimum-utxo-value_box-whisker-fee-rate-script-type.svg b/content/articles/minimum-utxo-value/minimum-utxo-value_box-whisker-fee-rate-script-type.svg new file mode 100644 index 0000000..0b58151 --- /dev/null +++ b/content/articles/minimum-utxo-value/minimum-utxo-value_box-whisker-fee-rate-script-type.svg @@ -0,0 +1 @@ +Script TypeP2PKHP2SHP2WPKHP2WSHP2TRFee Rate (sats/vB)05101520253035404550Fee Rates by Script TypeBlock 782400 \ No newline at end of file diff --git a/content/articles/minimum-utxo-value/minimum-utxo-value_box-whisker-fees-script-type-zoomed.svg b/content/articles/minimum-utxo-value/minimum-utxo-value_box-whisker-fees-script-type-zoomed.svg new file mode 100644 index 0000000..8f1635d --- /dev/null +++ b/content/articles/minimum-utxo-value/minimum-utxo-value_box-whisker-fees-script-type-zoomed.svg @@ -0,0 +1 @@ +P2PKHP2SHP2WPKHP2WSHP2TRScript Type020004000600080001000012000140001600018000Total Fee (sats)Total Fees by Script TypeBlock 782400 \ No newline at end of file diff --git a/content/articles/minimum-utxo-value/minimum-utxo-value_box-whisker-fees-script-type.svg b/content/articles/minimum-utxo-value/minimum-utxo-value_box-whisker-fees-script-type.svg new file mode 100644 index 0000000..321b76f --- /dev/null +++ b/content/articles/minimum-utxo-value/minimum-utxo-value_box-whisker-fees-script-type.svg @@ -0,0 +1 @@ +P2PKHP2SHP2WPKHP2WSHP2TRScript Type050000100000150000200000250000300000Total Fee (sats)Total Fees by Script TypeBlock 782400 \ No newline at end of file diff --git a/content/articles/minimum-utxo-value/minimum-utxo-value_box-whisker-overall.svg b/content/articles/minimum-utxo-value/minimum-utxo-value_box-whisker-overall.svg new file mode 100644 index 0000000..a35bceb --- /dev/null +++ b/content/articles/minimum-utxo-value/minimum-utxo-value_box-whisker-overall.svg @@ -0,0 +1 @@ +504000680000782400801171Block Height0200400600800100012001400Fee Rate (sats/vB)Fee Rates by Block \ No newline at end of file diff --git a/content/articles/minimum-utxo-value/minimum-utxo-value_mempool_vbytes_history.svg b/content/articles/minimum-utxo-value/minimum-utxo-value_mempool_vbytes_history.svg new file mode 100644 index 0000000..7b545f9 --- /dev/null +++ b/content/articles/minimum-utxo-value/minimum-utxo-value_mempool_vbytes_history.svg @@ -0,0 +1,393 @@ + + + + + + + + + + + + + + + + + + +0 MvB +50 MvB +100 MvB +150 MvB +200 MvB +250 MvB +300 MvB +350 MvB + +2017 + +2018 + +2019 + +2020 + +2021 + +2022 + +2023 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/content/articles/monetary-system-enshittification/index.md b/content/articles/monetary-system-enshittification/index.md new file mode 100644 index 0000000..209662e --- /dev/null +++ b/content/articles/monetary-system-enshittification/index.md @@ -0,0 +1,30 @@ +--- +title: Monetary System Enshittification +date: 2024-03-01 +permalink: 'articles/2024-03-01-monetary-system-enshittification/' +description: Is the US monetary system experience enshittification? And how do we solve it? +tags: +- bitcoin +--- + +{% image "./monetary-system-enshittification_honeybadger.jpg", "Bitcoin honeybadger stomping on the US monetary system." %} + +Is the US monetary system experiencing enshittification? And how do we solve it? If we think about monetary systems as platforms which facilitate value transfer, I think some of [Cory Doctorow's](https://twitter.com/doctorow) ideas on preventing platform decay can apply. + +> "First, they are good to their users; then they abuse their users to make things better for their business customers; finally, they abuse those business customers to claw back all the value for themselves. Then, they die." - Cory Doctorow + +Ultimately, enshittification is a rent-seeking problem: the platform is no longer incentivized to maximize value for the users since they're locked into the network. Rent-seeking continues to drain value from debtors to creditors and leads to heavy stratification of society. + +Currently, the dollar is king. With the world locked into the US monetary system, the US governmental apparatus (policymakers, interest groups, etc.) is not incentivized to improve the system of tariffs, subsidies, and regulations for the net benefit of society. + +The Jubilee, as described in Leviticus, attempted to solve societal stratification as value accreted to creditors over time. Every 50 years, debts were forgiven and property was returned to its original owner, "resetting" the scale between creditors and debtors. + +There are two principles Doctorow believes can solve platform decay: +- End-to-End Principle: the role of a network is to reliably deliver data from willing senders to willing receivers. +- Right of Exit: users can easily go elsewhere if they are dissatisfied with it. + +The end-to-end principle, when applied to monetary systems, is about the reliable delivery of information about value. Distorting the value information about a good or service through monetary policy, regulation, and laws violates this principle. + +With the invention of Bitcoin, there is now a way to easily move off the US monetary system and satisfy the Right of Exit principle, by limiting exposure to trusted third-parties, and self-custodying personal wealth. + +While Bitcoin is just part of the solution to the US monetary system's enshittification problem, it is necessary to maintain its value for the people, and not the rent-seekers. \ No newline at end of file diff --git a/content/articles/monetary-system-enshittification/monetary-system-enshittification_honeybadger.jpg b/content/articles/monetary-system-enshittification/monetary-system-enshittification_honeybadger.jpg new file mode 100644 index 0000000..1862895 Binary files /dev/null and b/content/articles/monetary-system-enshittification/monetary-system-enshittification_honeybadger.jpg differ diff --git a/content/articles/removing-a-drive-from-a-btrfs-array.md b/content/articles/removing-a-drive-from-a-btrfs-array.md new file mode 100644 index 0000000..f206227 --- /dev/null +++ b/content/articles/removing-a-drive-from-a-btrfs-array.md @@ -0,0 +1,114 @@ +--- +title: 'Removing a Drive from a Btrfs Array' +date: 2023-07-05 +permalink: 'articles/2023-07-05-removing-a-drive-from-a-btrfs-array/' +modified: 2024-01-26 +description: If a drive is failing in an array, Btrfs could block attempts at removing the drive due to corrupted files. A quick write-up of how to get it removed. +tags: +- linux +--- + +I recently ran into a situation where I knew one of the hard drives in my Btrfs array was failing. Simply following the [Btrfs wiki removing devices instructions](https://archive.kernel.org/oldwiki/btrfs.wiki.kernel.org/index.php/Using_Btrfs_with_Multiple_Devices.html#Removing_devices) was unsuccessful as the `btrfs delete device` command would throw an error, cancelling the device deletion process. + +At this point, I was okay with losing any corrupted/unrecoverable files. You should already be past the point of restoring from backup, attempting other recovery methods, etc. I just wanted to get the dying drive removed from the array since I knew I didn't have critical data on the array. + +> **Warning** +> +> Make sure you've exhausted all other resources before continuing. Check the [ArchWiki](https://wiki.archlinux.org/title/Btrfs), [Btrfs mailing list](https://archive.kernel.org/oldwiki/btrfs.wiki.kernel.org/index.php/Btrfs_mailing_list.html), etc. This will lead to data loss! + +### Scrub the Array +Start a scrub to identify files blocking the device from getting deleted: + +```console +[cameron@host ~]$ sudo btrfs scrub start /mnt/btrfs +``` + +The scrub will take a while. In my case, it took nearly 27 hours: + +```console +[cameron@host ~]$ sudo btrfs scrub status /mnt/btrfs +UUID: 16df0a0e-1dad-439f-aee1-f9961122fe59 +Scrub started: Tue Jul 4 11:27:56 2023 +Status: finished +Duration: 26:55:47 +Total to scrub: 31.20TiB +Rate: 338.01MiB/s +Error summary: read=256 + Corrected: 238 + Uncorrectable: 18 + Unverified: 0 +``` + +### Remove Uncorrectable Errors +Btrfs was able to correct many of the errors it came across, but in this instance I was left with 18 uncorrectable errors. As the scrub identifies these, it will report them into the kernel ring buffer (`dmesg`). I found it nearly impossible to search through that, since the messages would either fall out of the log by the time I came back to the terminal to search. Instead, `journalctl` keeps a full enough history to search through: + +```console +[cameron@host ~]$ sudo journalctl --dmesg --grep 'unable to fixup' +Jul 04 13:56:48 host kernel: BTRFS error (device sdh): unable to fixup (regular) error at logical 32574079762432 on dev /dev/sdd physical 1251936043008 +Jul 04 13:58:27 host kernel: BTRFS error (device sdh): unable to fixup (regular) error at logical 32615643807744 on dev /dev/sdd physical 1265582800896 +Jul 04 14:15:53 host kernel: BTRFS error (device sdh): unable to fixup (regular) error at logical 33092168253440 on dev /dev/sdd physical 1424279666688 +Jul 04 14:16:08 host kernel: BTRFS error (device sdh): unable to fixup (regular) error at logical 33092168646656 on dev /dev/sdd physical 1424280059904 +Jul 04 14:16:08 host kernel: BTRFS error (device sdh): unable to fixup (regular) error at logical 33092168515584 on dev /dev/sdd physical 1424279928832 +Jul 04 14:16:23 host kernel: BTRFS error (device sdh): unable to fixup (regular) error at logical 33092169498624 on dev /dev/sdd physical 1424280911872 +Jul 04 14:16:23 host kernel: BTRFS error (device sdh): unable to fixup (regular) error at logical 33092169760768 on dev /dev/sdd physical 1424281174016 +Jul 04 14:16:30 host kernel: BTRFS error (device sdh): unable to fixup (regular) error at logical 33092169891840 on dev /dev/sdd physical 1424281305088 +Jul 04 14:16:45 host kernel: BTRFS error (device sdh): unable to fixup (regular) error at logical 33092171005952 on dev /dev/sdd physical 1424282419200 +Jul 04 14:16:45 host kernel: BTRFS error (device sdh): unable to fixup (regular) error at logical 33092171137024 on dev /dev/sdd physical 1424282550272 +Jul 04 14:16:53 host kernel: BTRFS error (device sdh): unable to fixup (regular) error at logical 33092172382208 on dev /dev/sdd physical 1424283795456 +Jul 04 14:17:00 host kernel: BTRFS error (device sdh): unable to fixup (regular) error at logical 33092201283584 on dev /dev/sdd physical 1424312696832 +Jul 04 14:17:07 host kernel: BTRFS error (device sdh): unable to fixup (regular) error at logical 33092202528768 on dev /dev/sdd physical 1424313942016 +Jul 04 14:17:19 host kernel: BTRFS error (device sdh): unable to fixup (regular) error at logical 33092203773952 on dev /dev/sdd physical 1424315187200 +Jul 04 14:17:19 host kernel: BTRFS error (device sdh): unable to fixup (regular) error at logical 33092203773952 on dev /dev/sdd physical 1424315187200 +Jul 04 14:17:26 host kernel: BTRFS error (device sdh): unable to fixup (regular) error at logical 33092205019136 on dev /dev/sdd physical 1424316432384 +Jul 04 14:17:38 host kernel: BTRFS error (device sdh): unable to fixup (regular) error at logical 33092209999872 on dev /dev/sdd physical 1424321413120 +Jul 04 14:17:38 host kernel: BTRFS error (device sdh): unable to fixup (regular) error at logical 33092209999872 on dev /dev/sdd physical 1424321413120 +``` + +Now we need to translate those logicals into files we can actually attempt removing: + +```console +[cameron@host ~]$ sudo btrfs inspect-internal logical-resolve 32574079762432 /mnt/btrfs +/mnt/btrfs/P6300090.mov +``` + +The output shows logical `32574079762432` refers to `/mnt/btrfs/P6300090.mov`. Remove the file, doing the same for all other logicals reported as uncorrectable errors. Several of the errors I received seemed to go away/not refer to a file. I just attempted removing any files that were identified, ignoring logicals that didn't resolve to anything. + +### Delete the Device +Now remount the Btrfs array, marking it as degraded. Then, delete the device you want to remove (in my case `/dev/sdd`): + +```console +[cameron@host ~]$ sudo mount -o degraded /dev/sdd /mnt/btrfs +[cameron@host ~]$ sudo btrfs device delete /dev/sdd /mnt/btrfs +``` + +The device deletion will also take a while, but now you should see the used data total on the device you're deleting reduce over time: + +```console +[cameron@host ~]$ sudo btrfs fi show +Label: none uuid: 16df0a0e-1dad-439f-aee1-f9961122fe59 + Total devices 7 FS bytes used 31.16TiB + devid 1 size 14.55TiB used 13.96TiB path /dev/sdh + devid 2 size 0.00B used 962.01GiB path /dev/sdd + devid 3 size 12.73TiB used 12.14TiB path /dev/sda + devid 4 size 12.73TiB used 1.05TiB path /dev/sdb + devid 5 size 12.73TiB used 1.05TiB path /dev/sdc + devid 6 size 12.73TiB used 1.05TiB path /dev/sdf + devid 7 size 12.73TiB used 1.05TiB path /dev/sdg +``` + +And here's the same a while later: + +```console +[cameron@host ~]$ sudo btrfs fi show +Label: none uuid: 16df0a0e-1dad-439f-aee1-f9961122fe59 + Total devices 7 FS bytes used 31.16TiB + devid 1 size 14.55TiB used 13.96TiB path /dev/sdh + devid 2 size 0.00B used 874.01GiB path /dev/sdd + devid 3 size 12.73TiB used 12.14TiB path /dev/sda + devid 4 size 12.73TiB used 1.07TiB path /dev/sdb + devid 5 size 12.73TiB used 1.07TiB path /dev/sdc + devid 6 size 12.73TiB used 1.07TiB path /dev/sdf + devid 7 size 12.73TiB used 1.07TiB path /dev/sdg +``` + +Let that complete, and the device should now be removed from the Btrfs array. You're now missing several files that were unrecoverable, so hopefully those were backed up somewhere else or they weren't critical. \ No newline at end of file diff --git a/content/index.md b/content/index.md new file mode 100644 index 0000000..21b1fac --- /dev/null +++ b/content/index.md @@ -0,0 +1,7 @@ +--- +layout: layouts/home.njk +title: Home +date: 2024-07-30 +--- + +Hello, world! \ No newline at end of file diff --git a/eleventy.config.js b/eleventy.config.js new file mode 100644 index 0000000..02154ab --- /dev/null +++ b/eleventy.config.js @@ -0,0 +1,101 @@ +const cleanCSS = require("clean-css"); +const { DateTime } = require("luxon"); +const eleventyFeedPlugin = require("@11ty/eleventy-plugin-rss"); +const eleventyImagePlugin = require("@11ty/eleventy-img"); +const eleventySyntaxHighlightPlugin = require("@11ty/eleventy-plugin-syntaxhighlight"); +const path = require("path"); + +function relativeToInputPath(inputPath, relativeFilePath) { + let split = inputPath.split("/"); + split.pop(); + return path.resolve(split.join(path.sep), relativeFilePath); +} + +function isFullUrl(url) { + try { + new URL(url); + return true; + } catch(e) { + return false; + } +} + +module.exports = function (eleventyConfig) { + eleventyConfig.addFilter("cssmin", function (code) { + return new cleanCSS({}).minify(code).styles; + }); + eleventyConfig.addFilter("htmlDateString", (dateObj) => { + // dateObj input: https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-date-string + return DateTime.fromJSDate(dateObj, {zone: 'utc'}).toFormat('yyyy-LL-dd'); + }); + eleventyConfig.addFilter("readableDate", (dateObj, format, zone) => { + // Formatting tokens for Luxon: https://moment.github.io/luxon/#/formatting?id=table-of-tokens + return DateTime.fromJSDate(dateObj, { zone: zone || "utc" }).toFormat(format || "dd LLLL yyyy"); + }); + eleventyConfig.addPassthroughCopy({ "./static/": "/" }); + eleventyConfig.addPlugin(eleventyFeedPlugin, { + type: "atom", + outputPath: "/feed.xml", + collection: { + name: "articles", + limit: 10, + }, + metadata: { + language: "en", + title: "Cameron Otsuka", + subtitle: "The collection of Cameron's thoughts.", + base: "https://otsuka.haus/", + author: { + name: "Cameron Otsuka", + email: "cameron@otsuka.haus", + url: "https://otsuka.haus" + } + } + }); + eleventyConfig.addPlugin(eleventySyntaxHighlightPlugin); + eleventyConfig.addShortcode("image", async function (src, alt) { + let input; + if(isFullUrl(src)) { + input = src; + } else { + input = relativeToInputPath(this.page.inputPath, src); + } + + let metadata = await eleventyImagePlugin(input, { + widths: [360, 720], + formats: ["svg", "avif", "jpeg"], + urlPath: "/img/", + outputDir: path.join(eleventyConfig.dir.output, "img"), + }); + + let imageAttributes = { + alt, + sizes: "360w, 720w", + loading: "lazy", + decoding: "async", + }; + + let options = { + pictureAttributes: {}, + whitespaceMode: "inline", + }; + + return eleventyImagePlugin.generateHTML(metadata, imageAttributes, options); + }); + return { + templateFormats: [ + "md", + "njk", + "html" + ], + markdownTemplateEngine: "njk", + htmlTemplateEngine: "njk", + dir: { + input: "content", + includes: "../_includes", + data: "../_data", + output: "_site" + }, + pathPrefix: "/" + }; +}; \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..c83426e --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "cotsuka.github.io", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "build-ghpages": "npx @11ty/eleventy" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "", + "dependencies": { + "@11ty/eleventy": "^3.0.0-alpha.17", + "@11ty/eleventy-img": "^4.0.2", + "@11ty/eleventy-plugin-rss": "^2.0.1", + "@11ty/eleventy-plugin-syntaxhighlight": "^5.0.0", + "clean-css": "^5.3.3", + "luxon": "^3.4.4" + } +} diff --git a/static/.well-known/nostr.json b/static/.well-known/nostr.json new file mode 100644 index 0000000..3774d56 --- /dev/null +++ b/static/.well-known/nostr.json @@ -0,0 +1,5 @@ +{ + "names": { + "cameron": "b8a10079d97484b623e16b2dc2aaeb3460c51a832930c05d9f2986b5d133d28b" + } +} \ No newline at end of file diff --git a/static/CNAME b/static/CNAME new file mode 100644 index 0000000..1e758b5 --- /dev/null +++ b/static/CNAME @@ -0,0 +1 @@ +otsuka.haus \ No newline at end of file diff --git a/static/favicon.ico b/static/favicon.ico new file mode 100644 index 0000000..67f8b77 Binary files /dev/null and b/static/favicon.ico differ diff --git a/static/public/banner.jpg b/static/public/banner.jpg new file mode 100644 index 0000000..ac52151 Binary files /dev/null and b/static/public/banner.jpg differ diff --git a/static/public/pfp.jpg b/static/public/pfp.jpg new file mode 100644 index 0000000..d313136 Binary files /dev/null and b/static/public/pfp.jpg differ