fabrica

hanna/fabrica

feat(web): inline SVG icon set and drop the footer

04276f5 · hanna committed on 2026-07-25

Vendor Lucide (ISC) UI glyphs and Simple Icons (CC0) brand marks as an
inline-SVG `icons` module so no page makes a network request (§9.5), and
replace the emoji glyphs in the top bar, drawer, and tree listing. Remove
the page footer per operator preference.

The full glyph set is vendored up front; a temporary `allow(dead_code)`
covers the variants the profile, explore, and settings views wire in next.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed by hannaSSH key fingerprint: SHA256:4g9cWhkAAw8gwqhJUcVbSnGEvdGwklW+1Aa/fMUu59k
8 files changed · +273 −41UnifiedSplit
assets/base.css +33 −12
@@ -51,6 +51,14 @@ code, pre, .mono {
51 font-family: var(--fb-font-mono);51 font-family: var(--fb-font-mono);
52}52}
5353
54/* Inline icons inherit colour and scale to the current font size. */
55.icon {
56 width: 1em;
57 height: 1em;
58 flex: none;
59 vertical-align: -0.125em;
60}
61
54/* Accessibility: visible focus, skip link. */62/* Accessibility: visible focus, skip link. */
55:focus-visible {63:focus-visible {
56 outline: 2px solid var(--fb-accent);64 outline: 2px solid var(--fb-accent);
@@ -126,6 +134,10 @@ code, pre, .mono {
126.icon-btn:hover {134.icon-btn:hover {
127 background: var(--fb-bg-inset);135 background: var(--fb-bg-inset);
128}136}
137.icon-btn .icon {
138 width: 1.15rem;
139 height: 1.15rem;
140}
129141
130/* ---- Drawer (off-canvas nav) ---- */142/* ---- Drawer (off-canvas nav) ---- */
131.drawer-backdrop {143.drawer-backdrop {
@@ -161,12 +173,27 @@ body.drawer-open .drawer-backdrop {
161}173}
162.drawer nav a,174.drawer nav a,
163.drawer .drawer-item {175.drawer .drawer-item {
164 display: block;176 display: flex;
177 align-items: center;
178 gap: 0.6rem;
179 width: 100%;
165 padding: 0.4rem 0.5rem;180 padding: 0.4rem 0.5rem;
166 border-radius: var(--fb-radius);181 border-radius: var(--fb-radius);
167 color: var(--fb-fg);182 color: var(--fb-fg);
183 font: inherit;
184 text-align: left;
185}
186.drawer .drawer-item {
187 border: none;
188 background: none;
189 cursor: pointer;
190}
191.drawer nav a .icon,
192.drawer .drawer-item .icon {
193 color: var(--fb-fg-muted);
168}194}
169.drawer nav a:hover {195.drawer nav a:hover,
196.drawer .drawer-item:hover {
170 background: var(--fb-bg-inset);197 background: var(--fb-bg-inset);
171 text-decoration: none;198 text-decoration: none;
172}199}
@@ -398,6 +425,10 @@ table.tree td {
398.tree-icon {425.tree-icon {
399 width: 1.5rem;426 width: 1.5rem;
400 text-align: center;427 text-align: center;
428 color: var(--fb-fg-muted);
429}
430.tree-icon .icon {
431 vertical-align: -0.15em;
401}432}
402433
403.blob-header {434.blob-header {
@@ -567,16 +598,6 @@ td.diff-empty {
567 }598 }
568}599}
569600
570/* ---- Footer ---- */
571.footer {
572 max-width: 76rem;
573 margin: 2rem auto 1rem;
574 padding: 1rem var(--fb-gap) 0;
575 border-top: 1px solid var(--fb-border);
576 color: var(--fb-fg-muted);
577 font-size: 0.85em;
578}
579
580/* ---- Responsive: single breakpoint at 48rem ---- */601/* ---- Responsive: single breakpoint at 48rem ---- */
581@media (max-width: 48rem) {602@media (max-width: 48rem) {
582 .topbar .tabs {603 .topbar .tabs {
config.toml +73 −0
@@ -0,0 +1,73 @@
1[instance]
2name = "fabrica"
3url = "http://localhost:8080"
4description = ""
5default_branch = "main"
6allow_anonymous = true
7
8[server]
9address = "0.0.0.0"
10port = 8080
11behind_proxy = false
12graceful_shutdown_secs = 20
13max_body_bytes = 1048576
14
15[ssh]
16enabled = true
17address = "0.0.0.0"
18port = 2222
19host_key_path = "data/host_key"
20clone_host = "localhost"
21clone_port = 22
22
23[storage]
24data_dir = "data/fabrica"
25repo_dir = "data/repos"
26
27[database]
28url = "sqlite://data/fabrica.db"
29max_connections = 16
30
31[auth]
32session_ttl_days = 30
33token_ttl_days = 90
34invite_ttl_hours = 72
35cookie_name = "fabrica_session"
36cookie_secure = true
37argon2_m_cost = 19456
38argon2_t_cost = 2
39argon2_p_cost = 1
40
41[mail]
42backend = "stdout"
43from = "fabrica@localhost"
44host = ""
45port = 587
46username = ""
47encryption = "starttls"
48timeout_secs = 15
49
50[ui]
51theme = "dark"
52themes_dir = "data/themes"
53assets_dir = "data/assets"
54allow_theme_choice = true
55show_runs = false
56diff_context_lines = 3
57max_highlight_bytes = 1048576
58max_diff_bytes = 5242880
59tree_history_limit = 1000
60
61[git]
62binary = "git"
63transport_timeout_secs = 3600
64max_pack_size_bytes = 536870912
65
66[search]
67concurrency = 4
68timeout_secs = 10
69max_results = 200
70
71[log]
72level = "info"
73format = "pretty"
crates/web/src/icons.rs +137 −0
@@ -0,0 +1,137 @@
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Inline SVG icons.
6//!
7//! Vendored from [Lucide](https://lucide.dev) (ISC) for UI glyphs and
8//! [Simple Icons](https://simpleicons.org) (CC0) for brands, inlined so no page
9//! makes a network request. Icons inherit `currentColor` and size to `1em`.
10
11use maud::{Markup, PreEscaped, html};
12
13/// A vendored icon.
14// The full glyph set is vendored up front; the profile, explore, and settings
15// views wire in the remaining variants. Removed once every variant is consumed.
16#[allow(dead_code)]
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum Icon {
19 PanelLeft,
20 House,
21 Folder,
22 File,
23 GitBranch,
24 Tag,
25 Search,
26 Copy,
27 ChevronRight,
28 Box,
29 Link,
30 User,
31 Settings,
32 LogOut,
33 Upload,
34 Plus,
35 Dashboard,
36 Compass,
37 MapPin,
38 Globe,
39 Download,
40 AtSign,
41 Github,
42 Mastodon,
43}
44
45impl Icon {
46 /// Whether the icon is a filled brand glyph rather than a stroked Lucide one.
47 fn brand(self) -> bool {
48 matches!(self, Icon::Github | Icon::Mastodon)
49 }
50
51 /// The inner SVG geometry.
52 fn geometry(self) -> &'static str {
53 match self {
54 Icon::PanelLeft => {
55 r#"<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M9 3v18"/>"#
56 }
57 Icon::House => {
58 r#"<path d="M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8"/><path d="M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>"#
59 }
60 Icon::Folder => {
61 r#"<path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/>"#
62 }
63 Icon::File => {
64 r#"<path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"/><path d="M14 2v5a1 1 0 0 0 1 1h5"/>"#
65 }
66 Icon::GitBranch => {
67 r#"<path d="M15 6a9 9 0 0 0-9 9V3"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/>"#
68 }
69 Icon::Tag => {
70 r#"<path d="M12.586 2.586A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.426 2.426 0 0 0 3.42 0l6.58-6.58a2.426 2.426 0 0 0 0-3.42z"/><circle cx="7.5" cy="7.5" r=".5" fill="currentColor"/>"#
71 }
72 Icon::Search => r#"<path d="m21 21-4.34-4.34"/><circle cx="11" cy="11" r="8"/>"#,
73 Icon::Copy => {
74 r#"<rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>"#
75 }
76 Icon::ChevronRight => r#"<path d="m9 18 6-6-6-6"/>"#,
77 Icon::Box => {
78 r#"<path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"/><path d="m3.3 7 8.7 5 8.7-5"/><path d="M12 22V12"/>"#
79 }
80 Icon::Link => {
81 r#"<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>"#
82 }
83 Icon::User => {
84 r#"<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>"#
85 }
86 Icon::Settings => {
87 r#"<path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915"/><circle cx="12" cy="12" r="3"/>"#
88 }
89 Icon::LogOut => {
90 r#"<path d="m16 17 5-5-5-5"/><path d="M21 12H9"/><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/>"#
91 }
92 Icon::Upload => {
93 r#"<path d="M12 3v12"/><path d="m17 8-5-5-5 5"/><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>"#
94 }
95 Icon::Plus => r#"<path d="M5 12h14"/><path d="M12 5v14"/>"#,
96 Icon::Dashboard => {
97 r#"<rect width="7" height="9" x="3" y="3" rx="1"/><rect width="7" height="5" x="14" y="3" rx="1"/><rect width="7" height="9" x="14" y="12" rx="1"/><rect width="7" height="5" x="3" y="16" rx="1"/>"#
98 }
99 Icon::Compass => {
100 r#"<circle cx="12" cy="12" r="10"/><path d="m16.24 7.76-1.804 5.411a2 2 0 0 1-1.265 1.265L7.76 16.24l1.804-5.411a2 2 0 0 1 1.265-1.265z"/>"#
101 }
102 Icon::MapPin => {
103 r#"<path d="M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"/><circle cx="12" cy="10" r="3"/>"#
104 }
105 Icon::Globe => {
106 r#"<circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/>"#
107 }
108 Icon::Download => {
109 r#"<path d="M12 15V3"/><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m7 10 5 5 5-5"/>"#
110 }
111 Icon::AtSign => {
112 r#"<circle cx="12" cy="12" r="4"/><path d="M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-4 8"/>"#
113 }
114 Icon::Github => {
115 r#"<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/>"#
116 }
117 Icon::Mastodon => {
118 r#"<path d="M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054c1.517.363 3.072.546 4.632.546.376 0 .75 0 1.125-.01 1.57-.044 3.224-.124 4.768-.422.038-.008.077-.015.11-.024 2.435-.464 4.753-1.92 4.989-5.604.008-.145.03-1.52.03-1.67.002-.512.167-3.63-.024-5.545zm-3.748 9.195h-2.561V8.29c0-1.309-.55-1.976-1.67-1.976-1.23 0-1.846.79-1.846 2.35v3.403h-2.546V8.663c0-1.56-.617-2.35-1.848-2.35-1.112 0-1.668.668-1.67 1.977v6.218H4.822V8.102c0-1.31.337-2.35 1.011-3.12.696-.77 1.608-1.164 2.74-1.164 1.311 0 2.302.5 2.962 1.498l.638 1.06.638-1.06c.66-.999 1.65-1.498 2.96-1.498 1.13 0 2.043.395 2.74 1.164.675.77 1.012 1.81 1.012 3.12z"/>"#
119 }
120 }
121 }
122}
123
124/// Render an icon inheriting `currentColor` and sized to `1em`.
125#[must_use]
126pub fn icon(name: Icon) -> Markup {
127 let brand = name.brand();
128 html! {
129 svg class="icon" viewBox="0 0 24 24" width="1em" height="1em"
130 fill=(if brand { "currentColor" } else { "none" })
131 stroke=(if brand { "none" } else { "currentColor" })
132 stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
133 aria-hidden="true" focusable="false" {
134 (PreEscaped(name.geometry()))
135 }
136 }
137}
crates/web/src/layout.rs +14 −22
@@ -2,7 +2,7 @@
2// License, v. 2.0. If a copy of the MPL was not distributed with this2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44
5//! The page shell: `<head>`, top bar, off-canvas drawer, theme picker, footer.5//! The page shell: `<head>`, top bar, off-canvas drawer, theme picker.
6//!6//!
7//! Every full page is wrapped by [`page`]; htmx partials return fragments instead.7//! Every full page is wrapped by [`page`]; htmx partials return fragments instead.
8//! Structure is server-rendered and works without JavaScript — the drawer is a8//! Structure is server-rendered and works without JavaScript — the drawer is a
@@ -13,6 +13,7 @@ use maud::{DOCTYPE, Markup, html};
13use model::User;13use model::User;
1414
15use crate::assets::{Scheme, ThemeInfo};15use crate::assets::{Scheme, ThemeInfo};
16use crate::icons::{Icon, icon};
1617
17/// The per-request chrome data the shell needs.18/// The per-request chrome data the shell needs.
18pub struct Chrome {19pub struct Chrome {
@@ -59,7 +60,6 @@ pub fn page(chrome: &Chrome, title: &str, body: Markup) -> Markup {
59 (drawer(chrome))60 (drawer(chrome))
60 div class="drawer-backdrop" {}61 div class="drawer-backdrop" {}
61 main id="main" class="page" { (body) }62 main id="main" class="page" { (body) }
62 (footer(chrome))
63 }63 }
64 }64 }
65 }65 }
@@ -70,10 +70,11 @@ fn topbar(chrome: &Chrome) -> Markup {
70 html! {70 html! {
71 header class="topbar" {71 header class="topbar" {
72 button class="icon-btn" type="button" data-drawer-toggle aria-label="Menu"72 button class="icon-btn" type="button" data-drawer-toggle aria-label="Menu"
73 aria-controls="drawer" aria-expanded="false" { "☰" }73 aria-controls="drawer" aria-expanded="false" { (icon(Icon::PanelLeft)) }
74 span class="slug" { a href="/" { (chrome.instance_name) } }74 span class="slug" { a href="/" { (chrome.instance_name) } }
75 nav class="tabs" aria-label="Primary" {}75 nav class="tabs" aria-label="Primary" {}
76 a class="icon-btn" href="/" aria-label="Home" { "⌂" }76 a class="icon-btn" href="/search" aria-label="Search" { (icon(Icon::Search)) }
77 a class="icon-btn" href="/" aria-label="Home" { (icon(Icon::House)) }
77 }78 }
78 }79 }
79}80}
@@ -84,9 +85,12 @@ fn drawer(chrome: &Chrome) -> Markup {
84 aside id="drawer" class="drawer" aria-label="Navigation" {85 aside id="drawer" class="drawer" aria-label="Navigation" {
85 h2 { (chrome.instance_name) }86 h2 { (chrome.instance_name) }
86 nav {87 nav {
87 a href="/" { "Home" }
88 @if let Some(user) = &chrome.user {88 @if let Some(user) = &chrome.user {
89 a href={ "/" (user.username) } { "Your repositories" }89 a href="/" { (icon(Icon::Dashboard)) span { "Dashboard" } }
90 a href="/explore" { (icon(Icon::Compass)) span { "Explore" } }
91 a href={ "/" (user.username) } { (icon(Icon::User)) span { "Your profile" } }
92 } @else {
93 a href="/explore" { (icon(Icon::Compass)) span { "Explore" } }
90 }94 }
91 }95 }
92 hr;96 hr;
@@ -96,16 +100,15 @@ fn drawer(chrome: &Chrome) -> Markup {
96 }100 }
97 nav {101 nav {
98 @if chrome.user.is_some() {102 @if chrome.user.is_some() {
99 a href="/settings" { "Settings" }103 a href="/settings" { (icon(Icon::Settings)) span { "Settings" } }
100 form method="post" action="/logout" {104 form method="post" action="/logout" {
101 input type="hidden" name="_csrf" value=(chrome.csrf);105 input type="hidden" name="_csrf" value=(chrome.csrf);
102 button class="drawer-item" type="submit"106 button class="drawer-item" type="submit" {
103 style="width:100%;text-align:left;border:none;background:none;color:inherit;cursor:pointer" {107 (icon(Icon::LogOut)) span { "Log out" }
104 "Log out"
105 }108 }
106 }109 }
107 } @else {110 } @else {
108 a href="/login" { "Sign in" }111 a href="/login" { (icon(Icon::User)) span { "Sign in" } }
109 }112 }
110 }113 }
111 }114 }
@@ -131,14 +134,3 @@ fn theme_picker(chrome: &Chrome) -> Markup {
131 }134 }
132 }135 }
133}136}
134
135/// The page footer.
136fn footer(chrome: &Chrome) -> Markup {
137 html! {
138 footer class="footer" {
139 span { (chrome.instance_name) }
140 " · "
141 a href="/version" { "version" }
142 }
143 }
144}
crates/web/src/lib.rs +1 −0
@@ -14,6 +14,7 @@ mod assets;
14mod diff;14mod diff;
15mod error;15mod error;
16mod git_http;16mod git_http;
17mod icons;
17mod layout;18mod layout;
18mod markdown;19mod markdown;
19mod pages;20mod pages;
crates/web/src/repo.rs +7 −6
@@ -24,6 +24,7 @@ use serde::Deserialize;
24use crate::AppState;24use crate::AppState;
25use crate::diff::{self, RenderedFile};25use crate::diff::{self, RenderedFile};
26use crate::error::{AppError, AppResult};26use crate::error::{AppError, AppResult};
27use crate::icons::{Icon, icon};
27use crate::layout::page;28use crate::layout::page;
28use crate::markdown;29use crate::markdown;
29use crate::pages::build_chrome;30use crate::pages::build_chrome;
@@ -1096,7 +1097,7 @@ fn tree_table(
1096 tbody {1097 tbody {
1097 @for entry in entries {1098 @for entry in entries {
1098 tr {1099 tr {
1099 td class="tree-icon" { (entry_icon(entry.kind)) }1100 td class="tree-icon" { (icon(entry_icon(entry.kind))) }
1100 td { a href=(href(&entry.name, entry.kind)) { (entry.name) } }1101 td { a href=(href(&entry.name, entry.kind)) { (entry.name) } }
1101 }1102 }
1102 }1103 }
@@ -1106,12 +1107,12 @@ fn tree_table(
1106}1107}
11071108
1108/// An icon glyph for a tree entry kind.1109/// An icon glyph for a tree entry kind.
1109fn entry_icon(kind: git::EntryKind) -> &'static str {1110fn entry_icon(kind: git::EntryKind) -> Icon {
1110 match kind {1111 match kind {
1111 git::EntryKind::Directory => "📁",1112 git::EntryKind::Directory => Icon::Folder,
1112 git::EntryKind::Submodule => "📦",1113 git::EntryKind::Submodule => Icon::Box,
1113 git::EntryKind::Symlink => "🔗",1114 git::EntryKind::Symlink => Icon::Link,
1114 git::EntryKind::File => "📄",1115 git::EntryKind::File => Icon::File,
1115 }1116 }
1116}1117}
11171118
data/fabrica/secret +1 −1
@@ -0,0 +1 @@
0 \ No newline at end of file13cIBhLUwUan-vB0uok4Ajb2uEJOzapkIB9BwdLB9lXA
data/host_key +7 −0
@@ -0,0 +1,7 @@
1-----BEGIN OPENSSH PRIVATE KEY-----
2b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
3QyNTUxOQAAACDtfdqbDHDx719TofjNfw7zjd2rZtQAace+caQQ1fYOPgAAAIgq2mooKtpq
4KAAAAAtzc2gtZWQyNTUxOQAAACDtfdqbDHDx719TofjNfw7zjd2rZtQAace+caQQ1fYOPg
5AAAEDpF5CAVCkyKaFFhvhp2OLdmWtIH33H8XFaS8GhVscL5u192psMcPHvX1Oh+M1/DvON
63atm1ABpx75xpBDV9g4+AAAAAAECAwQF
7-----END OPENSSH PRIVATE KEY-----