Skip to content

Commit

Permalink
WIP - anaconda mode
Browse files Browse the repository at this point in the history
  • Loading branch information
mvollmer committed Nov 23, 2023
1 parent 50cfbb4 commit fc2eaea
Show file tree
Hide file tree
Showing 21 changed files with 304 additions and 39 deletions.
84 changes: 84 additions & 0 deletions doc/anaconda.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
Cockpit Storage in Anaconda Mode
================================

Anaconda (the OS Installer) can open the Cockpit "storaged" page for
advanced setup of the target storage devices. When this is done,
storaged is in a special "Anaconda mode" and behaves significantly
different.

In essence, the storaged page restricts itself to working with the
target environment. It will hide the real root filesystem (on the USB
stick that the Live environment was booted from, say), but let the
user create a "fake" root filesystem on some block device.

Entering Anaconda mode
----------------------

The "storaged" page is put into Anaconda mode by storing a
"cockpit_anaconda" item in its `window.localStorage`. The value
should be a JSON encoded object, the details of which are explained
below.

Since both Anaconda and the storaged page are served from the same
origin, Anaconda can just execute something like this:

```
window.localStorage.setItem("cockpit_anaconda",
JSON.stringify({
"mount_point_prefix": "/sysroot",
"ignore_devices": [ "/dev/sr0", "/dev/loop0" ]
}));
window.open("/cockpit/@localhost/storage/index.html", "storage-tab");
```

Ignoring storage devices
------------------------

Anaconda needs to tell Cockpit which devices can not be used to
install the OS on. This is done with the "ignore_devices" entry, which
is a array of strings.

```
{
"ignore_devices": [ "/dev/sda" ]
}
```

Entries in that array can contain pathname of block devices, names of
LVM2 volume groups, and names of Stratis pools.

Mount point prefix
------------------

Cockpit can be put into a kind of "chroot" environment by giving it a
mount point prefix like so:

```
{
"mount_point_prefix": "/sysroot"
}
```

This works at the UI level: filesystems that have mount points outside
of "/sysroot" are hidden from the user, and when letting the user work
with mount points below "/sysroot", the "/sysroot" prefix is
omitted. So when the user says to create a filesystem on "/var", they
are actually creating one on "/sysroot/var".

However, Cockpit (via UDisks2) will still write the new mount point
configuration into the real /etc/fstab (_not_ /sysroot/etc/fstab).

In addition to that, Cockpit will also store the mount points in the
`"cockpit_mount_points"` item in `window.localStorage`, as a JSON
encoded object, for the benefit of Anaconda.

This is a simple map from mount point to block device, like

```
{
"/boot": "/dev/vda1",
"/": "/dev/vda2"
}
```

The mount points do not include the mount point prefix.
42 changes: 42 additions & 0 deletions pkg/storaged/anaconda.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* This file is part of Cockpit.
*
* Copyright (C) 2023 Red Hat, Inc.
*
* Cockpit is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* Cockpit 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 GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
*/

import cockpit from "cockpit";
import React from "react";
import client from "./client.js";

import { StackItem } from "@patternfly/react-core/dist/esm/layouts/Stack/index.js";
import { Alert } from "@patternfly/react-core/dist/esm/components/Alert/index.js";

const _ = cockpit.gettext;

export const AnacondaAdvice = () => {
if (!client.in_anaconda_mode())
return null;

return (
<StackItem>
<Alert isInline
variant='info'
title={_("What you need to do")}>
<p>Anaconda will tell us here what is wrong with the current config.</p>
</Alert>
</StackItem>
);
};
3 changes: 2 additions & 1 deletion pkg/storaged/block/create-pages.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,5 +103,6 @@ export function make_block_page(parent, block, card) {
}
}

new_page(parent, card);
if (card)
new_page(parent, card);
}
31 changes: 22 additions & 9 deletions pkg/storaged/block/format-dialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,19 @@ const _ = cockpit.gettext;
export function initial_tab_options(client, block, for_fstab) {
const options = { };

get_parent_blocks(client, block.path).forEach(p => {
// "nofail" is the default for new filesystems with Cockpit so
// that a failure to mount one of them will not prevent
// Cockpit from starting. This allows people to debug and fix
// these failures with Cockpit itself.
//
// "nofail" is the default for new filesystems with Cockpit so
// that a failure to mount one of them will not prevent
// Cockpit from starting. This allows people to debug and fix
// these failures with Cockpit itself.
//
// In Anaconda mode however, we don't make "nofail" the
// default since people will be creating the core filesystems
// like "/", "/var", etc.

if (!client.in_anaconda_mode())
options.nofail = true;

get_parent_blocks(client, block.path).forEach(p => {
if (is_netdev(client, p)) {
options._netdev = true;
}
Expand Down Expand Up @@ -142,10 +147,10 @@ export function format_dialog(client, path, start, size, enable_dos_extended) {
return false;
})
.then(version => {
format_dialog_internal(client, path, start, size, enable_dos_extended, version);
return format_dialog_internal(client, path, start, size, enable_dos_extended, version);
});
} else {
format_dialog_internal(client, path, start, size, enable_dos_extended);
return format_dialog_internal(client, path, start, size, enable_dos_extended);
}
}

Expand Down Expand Up @@ -242,6 +247,10 @@ function format_dialog_internal(client, path, start, size, enable_dos_extended,
if (old_opts == undefined)
old_opts = initial_mount_options(client, block);

old_dir = client.strip_mount_point_prefix(old_dir);
if (old_dir === false)
return Promise.reject(_("This device can not be used for the installation target."));

const split_options = parse_options(old_opts);
extract_option(split_options, "noauto");
const opt_ro = extract_option(split_options, "ro");
Expand Down Expand Up @@ -279,7 +288,10 @@ function format_dialog_internal(client, path, start, size, enable_dos_extended,
visible: is_filesystem,
value: old_dir || "",
validate: (val, values, variant) => {
return is_valid_mount_point(client, block, val, variant == "nomount");
return is_valid_mount_point(client,
block,
client.add_mount_point_prefix(val),
variant == "nomount");
}
}),
SelectOne("type", _("Type"),
Expand Down Expand Up @@ -474,6 +486,7 @@ function format_dialog_internal(client, path, start, size, enable_dos_extended,
if (mount_point != "") {
if (mount_point[0] != "/")
mount_point = "/" + mount_point;
mount_point = client.add_mount_point_prefix(mount_point);

config_items.push(["fstab", {
dir: { t: 'ay', v: encode_filename(mount_point) },
Expand Down
4 changes: 4 additions & 0 deletions pkg/storaged/block/other.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import cockpit from "cockpit";
import React from "react";
import client from "../client.js";

import { DescriptionList } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js";
import { CardBody } from "@patternfly/react-core/dist/esm/components/Card/index.js";
Expand All @@ -32,6 +33,9 @@ import { make_block_page } from "../block/create-pages.jsx";
const _ = cockpit.gettext;

export function make_other_page(parent, block) {
if (client.should_ignore_block(block))
return;

const other_card = new_card({
title: _("Block device"),
next: null,
Expand Down
62 changes: 62 additions & 0 deletions pkg/storaged/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,7 @@ client.update = (first_time) => {
client.ready = true;
if (client.ready) {
update_indices();
client.export_mount_point_mapping();
reset_pages();
make_overview_page();
client.dispatchEvent("changed");
Expand Down Expand Up @@ -741,6 +742,15 @@ function init_model(callback) {
).then(() => info);
}

try {
client.anaconda = JSON.parse(window.localStorage.getItem("cockpit_anaconda"));
} catch {
console.warn("Can't parse cockpit_anaconda configuration as JSON");
client.anaconda = null;
}

console.log("ANACONDA", client.anaconda);

pull_time().then(() => {
read_os_release().then(os_release => {
client.os_release = os_release;
Expand Down Expand Up @@ -1484,4 +1494,56 @@ client.get_config = (name, def) => {
}
};

client.in_anaconda_mode = () => !!client.anaconda;

client.strip_mount_point_prefix = (dir) => {
const mpp = client.anaconda?.mount_point_prefix;

if (dir && mpp) {
if (dir.indexOf(mpp) != 0)
return false;

dir = dir.substr(mpp.length);
if (dir == "")
dir = "/";
}

return dir;
};

client.add_mount_point_prefix = (dir) => {
const mpp = client.anaconda?.mount_point_prefix;
if (mpp) {
if (dir == "/")
dir = mpp;
else
dir = mpp + dir;
}
return dir;
};

client.should_ignore_device = (devname) => {
return client.anaconda?.ignore_devices && client.anaconda.ignore_devices.indexOf(devname) != -1;
};

client.should_ignore_block = (block) => {
return client.should_ignore_device(utils.decode_filename(block.PreferredDevice));
};

client.export_mount_point_mapping = () => {
const mpm = { };
for (const p in client.blocks) {
const b = client.blocks[p];
for (const c of b.Configuration) {
if (c[0] == "fstab") {
const dir = client.strip_mount_point_prefix(utils.decode_filename(c[1].dir.v));
if (dir)
mpm[dir] = utils.decode_filename(b.PreferredDevice);
}
}
}

window.localStorage.setItem("cockpit_mount_points", JSON.stringify(mpm));
};

export default client;
11 changes: 9 additions & 2 deletions pkg/storaged/dialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -1103,7 +1103,8 @@ export const BlockingMessage = (usage) => {
pvol: _("physical volume of LVM2 volume group"),
"mdraid-member": _("member of MDRAID device"),
vdo: _("backing device for VDO device"),
"stratis-pool-member": _("member of Stratis pool")
"stratis-pool-member": _("member of Stratis pool"),
mounted: _("Filesystem outside the target"),
};

const rows = [];
Expand Down Expand Up @@ -1193,9 +1194,15 @@ export const TeardownMessage = (usage, expect_single_unmount) => {
const name = (fsys
? fsys.Devnode
: block_name(client.blocks[use.block.CryptoBackingDevice] || use.block));
let location = use.location;
if (use.usage == "mounted") {
location = client.strip_mount_point_prefix(location);
if (location === false)
location = _("(Not part of target)");
}
rows.push({
columns: [name,
use.location || "-",
location || "-",
use.actions.length ? use.actions.join(", ") : "-",
{
title: <UsersPopover users={use.users || []} />,
Expand Down
3 changes: 3 additions & 0 deletions pkg/storaged/drive/drive.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ export function make_drive_page(parent, drive) {
if (!block)
return;

if (client.should_ignore_block(block))
return;

const drive_card = new_card({
title: _("Drive"),
next: null,
Expand Down
12 changes: 7 additions & 5 deletions pkg/storaged/filesystem/filesystem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,13 @@ export function make_filesystem_card(next, backing_block, content_block, fstab_c
const mounted = content_block && is_mounted(client, content_block);

let mp_text;
if (mount_point && mounted)
mp_text = mount_point;
else if (mount_point && !mounted)
mp_text = mount_point + " " + _("(not mounted)");
else
if (mount_point) {
mp_text = client.strip_mount_point_prefix(mount_point);
if (mp_text == false)
return null;
if (!mounted)
mp_text = mp_text + " " + _("(not mounted)");
} else
mp_text = _("(not mounted)");

return new_card({
Expand Down
Loading

0 comments on commit fc2eaea

Please sign in to comment.