<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Filling a fresh Infisical — bulk-importing from Vaultwarden, and using tags to keep it sane]]></title><description><![CDATA[<p dir="auto">Following up on my earlier notes about the Infisical package. Getting it <em>installed</em> turned out to be the easy part; getting our scattered credentials <em>into</em> it was the real work. Here is what worked, including the bits I'd do differently.</p>
<h2>The shape of the problem</h2>
<p dir="auto">Credentials were spread across: plain text files in a sync folder, <code>~/.config/*token*</code><br />
files, shell profile exports, app containers themselves, the Cloudron box database, and many hundreds of items in a self-hosted Vaultwarden. Nobody remembers all of that. The only way I<br />
found to make it tractable was to stop treating it as "move my secrets" and start treating it as "discover, verify, then move".</p>
<h2>a) Using an AI assistant to do the filling</h2>
<p dir="auto">**1. Be careful about including financial details.</p>
<p dir="auto"><strong>2. Verify liveness before trusting anything.</strong> Cheap probes, per credential type:</p>
<pre><code class="language-bash">curl -sS -o /dev/null -w '%{http_code}' -H "Authorization: token $T" https://&lt;forge&gt;/api/v1/user
curl -sSI -H "Authorization: token $T" https://api.github.com/user | grep -i expiration
# Porkbun: POST /api/json/v3/ping with {"apikey":…,"secretapikey":…} -&gt; {"status":"SUCCESS"}
</code></pre>
<p dir="auto">The GitHub expiry header is the single most useful one — it turns "a token" into "a<br />
token that dies on a known date".</p>
<p dir="auto"><strong>3. Read the licence gating from the source, not the docs.</strong> The community tier's<br />
feature set is a literal table in the shipped backend<br />
(<code>/app/code/src/ee/services/license/license-fns.ts</code>, <code>getDefaultOnPremFeatures</code>), which<br />
you can read via the app's terminal. That settled several questions faster than<br />
searching, and corrected one belief I had — secret <em>versioning</em> is available on the<br />
free tier, whatever older posts say.</p>
<h2>b) Migrating from Vaultwarden</h2>
<p dir="auto">The Bitwarden CLI (<code>bw</code>) is the tool, and it self-hosts against Vaultwarden fine:</p>
<pre><code class="language-bash">bw config server https://&lt;your-vaultwarden&gt;
bw login &lt;email&gt;              # interactive; use `bw login` not `bw unlock` if the token expired
export BW_SESSION=$(bw unlock --raw)
bw sync
</code></pre>
<p dir="auto">Two things that cost me time and may save you some:</p>
<p dir="auto"><strong>Credentials hide in three different places</strong> per item — <code>.login.password</code>,<br />
<code>.notes</code>, and <strong>custom fields</strong>. In our vault the newest keys had been appended as<br />
custom fields at the bottom of existing entries, so anything that only read<br />
<code>.login.password</code> would have silently missed most of them. This finds all three:</p>
<pre><code class="language-bash">bw list items --session "$BW_SESSION" | jq -r '
  .[] | select((.fields // []) | length &gt; 0)
  | "\(.name)  ||  \((.fields // []) | map(.name) | join(", "))"'
</code></pre>
<p dir="auto"><strong>Dedupe by value, not by name.</strong> The same key was frequently in several entries under<br />
different names. I hashed every candidate and compared against what was already stored:</p>
<pre><code class="language-bash">fp() { sha256sum | cut -c1-8; }   # compare fingerprints, never values
</code></pre>
<p dir="auto">That turned ~35 candidates into 25 genuinely new secrets, and revealed two services<br />
that were unknowingly sharing one API key — worth knowing on its own.</p>
<p dir="auto">Since <code>bw get</code> needs an exact name or ID, drive the import from item <strong>IDs</strong> collected<br />
programmatically rather than names typed by hand.</p>
<h2>c) Tagging</h2>
<p dir="auto">Infisical's tags are free on the community tier and are the thing that stops a flat<br />
list of secrets becoming unusable. There's no CLI command to create them, but the API<br />
is simple:</p>
<pre><code class="language-bash">curl -X POST "$URL/api/v1/workspace/&lt;projectId&gt;/tags" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"slug":"vaultwarden-import","color":"#3b82f6"}'
</code></pre>
<p dir="auto">then apply with the CLI (note: tags <strong>replace</strong> existing ones on update, so pass the<br />
full set each time):</p>
<pre><code class="language-bash">infisical secrets set --silent "NAME=$VALUE" --path /ai \
  --tag ai-service --tag third-party --tag unverified
</code></pre>
<p dir="auto">The scheme I settled on, after a false start tagging by <em>service</em> (too granular, one<br />
tag per secret, useless for filtering):</p>
<ul>
<li><strong>What it is</strong> — <code>git-forge</code>, <code>ai-service</code>, <code>dns</code>, <code>cloudron-app</code>, <code>backup</code></li>
<li><strong>Where it runs</strong> — <code>self-hosted</code> vs <code>third-party</code>. Surprisingly the most useful<br />
axis: "what breaks if this box dies" vs "what needs an account somewhere".</li>
<li><strong>Trust state</strong> — <code>verified-live</code>, <code>unverified</code>, <code>invalid-or-expired</code>. Being honest<br />
that most imported credentials are <em>unverified</em> is more useful than pretending<br />
otherwise, and it gives you a work queue.</li>
<li><strong>Time bombs</strong> — a tag naming the expiry month (<code>expires-YYYY-MM</code>) for anything with<br />
a known death date. Grep-able reminders beat calendar entries you'll ignore.</li>
<li><strong>Provenance</strong> — <code>vaultwarden-import</code>, so you can tell migrated from native.</li>
</ul>
<p dir="auto">Folders answer "where does this belong", tags answer "what do I need to look at".</p>
<h2>Things worth knowing before you start</h2>
<ul>
<li><strong>Back up before you fill it.</strong> The instance's encryption key lives in <code>/app/data/</code><br />
and everything in Postgres is encrypted with it. On a fresh install, before the first<br />
backup runs, that key exists in exactly one place. Take a backup first.</li>
<li><strong>Memory.</strong> The package sets a 1 GB limit and mine sat at ~90% of it near-idle. Raise<br />
it before doing bulk work.</li>
<li><strong>The bootstrap problem is real.</strong> Machine identities (Universal Auth) work well for<br />
headless retrieval, but the client secret has to live <em>somewhere</em> on each host.<br />
Infisical reduces N secrets to 1; it doesn't reduce them to 0, and any design that<br />
claims otherwise is hiding the root credential rather than removing it.</li>
<li><strong>Cloudron integration is push, not pull, for stock apps.</strong> They can only receive<br />
values via <code>cloudron env set</code>, which restarts the app. Packages you build yourself<br />
can pull natively at start-up, which is much nicer.</li>
</ul>
]]></description><link>https://forum.cloudron.io/topic/15739/filling-a-fresh-infisical-bulk-importing-from-vaultwarden-and-using-tags-to-keep-it-sane</link><generator>RSS for Node</generator><lastBuildDate>Sun, 16 Aug 2026 19:29:58 GMT</lastBuildDate><atom:link href="https://forum.cloudron.io/topic/15739.rss" rel="self" type="application/rss+xml"/><pubDate>Tue, 28 Jul 2026 13:33:26 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to Filling a fresh Infisical — bulk-importing from Vaultwarden, and using tags to keep it sane on Tue, 28 Jul 2026 18:50:42 GMT]]></title><description><![CDATA[<p dir="auto">We just had our first update to Infisical v0.162.14.</p>
<p dir="auto">All went well. Thank you for packaging this for Cloudron!</p>
]]></description><link>https://forum.cloudron.io/post/127343</link><guid isPermaLink="true">https://forum.cloudron.io/post/127343</guid><dc:creator><![CDATA[LoudLemur]]></dc:creator><pubDate>Tue, 28 Jul 2026 18:50:42 GMT</pubDate></item></channel></rss>