aboutsummaryrefslogtreecommitdiffstats
path: root/src/routes/blog/posts/alpine-ssh-early-initfs.svx
blob: f284caf862b0d8fa0a283cf72224131169acf520 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
---
title:  "Launching SSH during early boot with mkinitfs"
blurb:  "Replacing the early init with our own script to launch SSH, killing it in early userspace, and allowing remote disk decryption in the mean time"
author: "7222e800"
slug:   "alpine-ssh-early-initfs-disk-decryption"
id:     1768406136

# Timestamps are in ISO8601 UTC (`date -u +%Y-%m-%dT%H:%M:%SZ`)
created: "2026-01-14T15:53:57Z"
updated: "2026-01-14T15:53:57Z"
published: false
---

For a while, this one's been meaning to setup an early-boot SSH environment for
Alpine Linux on systems that are using a
[System Disk](https://wiki.alpinelinux.org/wiki/System_Disk_Mode) installation
mode.

<!-- TODO: APKOVL boot article -->
In [Data Disk](https://wiki.alpinelinux.org/wiki/Data_Disk_Mode) mode, it can
be handled in `boot`, or if needed, `sysinit`. This can even be nicely netbooted
via a netbooted apkovl - article on that eventually. (for now, if you're
interested in that, here's a good starting point:
[alpine/mkinitfs#cc4954b/initramfs-init.in](https://gitlab.alpinelinux.org/alpine/mkinitfs/-/blob/cc4954bc73cf55833b48624232b9c42ca3abc390/initramfs-init.in#L647))

On System Disk installations, with tooling like [dracut](https://wiki.gentoo.org/wiki/Dracut),
this would also be trivial. Unfortunately, this one's a masochist and like
staying close to the intended upstream Alpine installation 

> **Note**<br/>
> Alpine does have
> [a package](https://pkgs.alpinelinux.org/package/v3.23/community/x86_64/dracut)
> for dracut, and the reader may want to look into using it instead.

## mkinitfs and it's challenges

Alpine's [mkinitfs](https://gitlab.alpinelinux.org/alpine/mkinitfs/) allows us
to do things like including files or kernel modules in the image, via their
[features.d](https://gitlab.alpinelinux.org/alpine/mkinitfs/-/tree/master/features.d).
This is nice and all, but on it's own, we can only really give the kernel a
module, or a file we manually call by spamming enter through the encryption
password prompts and running via the 'Emergency Shell'.

## some code

```ts
/*
  Copyright (C) 2024-2026 memdmp

  This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by Affero, Inc., at version 1.

  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the Affero General Public License for more details.

  You should have received a copy of the Affero General Public License along with this program.  If not, see <https://spdx.org/licenses/AGPL-1.0-only>.
*/
import { Animation, MultiObjectKeyframe } from '@memdmp/keyframegen';
import {
  biosStepInterval,
  biosSteps,
  getDelay,
  login,
  ttyLines,
} from './shared.ts';
import fs from 'node:fs';
import esbuild from 'esbuild';
console.log('hi');
const anim = new Animation();
let ttyCtr = 0;
const stages = [
  anim.selector('.anmroot #bios'),
  anim.selector('.anmroot #grub'),
  anim.selector('.anmroot #grub-term'),
  anim.selector('.anmroot #openrc'),
  ...ttyLines.flatMap((v) =>
    v.kind === 'clear' ? [anim.selector('.anmroot #tty-' + ttyCtr++)] : [],
  ),
];
const handleSteps: Step[] = [
  // (n) => {
  //   toStage(0);
  //   anim.in(500, n);
  // },
  ...biosStepHandlers,
  ...grubStepHandlers,
  ...openrcStepHandlers(1),
  ...ttyStepHandlers,
  (n) => {
    const s = anim.selector('.anmroot #app .hidden-after-anim');
    s.style(visibleStyles);
    anim._internal_timeline.now += 1;
    s.style(hiddenStyles);
    anim.in(1000, n);
  },
];
fs.writeFileSync(
  'src/routes/anim.css',
  `${comment}
${esbuild.buildSync({
    stdin: {
      contents: `${exported}
${tail}`,
      loader: 'css',
    },
    write: false,
    minify: false,
  }).outputFiles![0].text
  }`,
);
```