Code Monkey home page Code Monkey logo

livefs-editor's Introduction

A tool to "edit" Ubuntu live server CDs

There are a few reasons the Ubuntu live server ISOs as shipped may not quite be what you want. Here are some examples:

  • You want to make an ISO that does a completely automated install

  • You want packages that are not in the package repository by default to be available during install, even when there is no network (or only a very isolated network).

  • You want to add an argument to the default kernel command line.

  • You want to inject a new version of the subiquity snap for testing.

This script aims to help you making modified versions of the distributed ISOs with changes such as those above.

Some of the things it does probably work on desktop installer ISOs too but I haven't thought very hard about that side of things yet.

A warning

Although this program was written by a Canonical employee on company time, it started out as essentially an overgrown shell script used as part of installer developement. It is not a Canonical product and is not supported as such. The "this program is distributed in the hope that it will be useful" part of the GPL stanza is as true as ever but the "without even the implied warranty of FITNESS FOR A PARTICULAR PURPOSE" is perhaps even more true than usual.

Dependencies

This script is pretty Linux-dependent and requires xorriso and mksquashfs to be available on $PATH. Some actions require the python3-debian package to be installed and gpg command to be available. Operating on ISOs from some Ubuntu releases will also need lz4cat (to unpack the initrd) which is found in liblz4-tool.

It needs to be run as root (although possibly using FUSE variants for all the mounts would allow it to run as a regular user, maybe).

General invocation

The basic idea behind this tool is that you tell it where to find the source ISO, where to put the modified ISO and a list of actions that make up the modifications. So an invocation always looks somewhat like this:

# livefs-edit $source.iso $dest.iso [actions]

Actions can be specified two ways: on the command line or in a YAML file. Each action has a name and many of them take arguments.

On the command line, actions and arguments are specified like:

--action-name arg1 arg2 --next-action-name

Arguments that are interpreted as boolean interpret 'on', 'yes', 'true' (case insensitively) as true.

Alternatively (if shell quoting starts to get painful), the actions can be passed as a YAML file, using the --action-yaml flag:

# livefs-edit $source.iso $dest.iso --action-yaml examples/example.yaml

The YAML file should be a list of mappings. Each mapping names the actions with name and lists any arguments by name, for example:

- name: add-cmdline-arg
  arg: autoinstall
  persist: false
- name: shell
- name: add-packages-to-pool
  packages:
    - casper
    - valgrind

Directory structure

This script does all its work in a temporary directory. Within that directory the original ISO is mounted at old/iso and what will be packed into the new ISO is present at new/iso (the script uses a lot of overlayfs mounts to avoid copying large amounts of data around, and also only repack things when there are changes).

Many actions require a writable emulation of the root filesystem that the installer will run in. By default this is created at rootfs in the main temporary directory, but this can be customized.

In general, things from the original ISO are mounted in old/ and are either read-only or should be treated as such. Writable versions for the new ISO live in new/ (mostly).

Actions

setup-rootfs

argument: target (default: "rootfs")

This action sets up a writable emulation of the root filesystem that the installer will run in at the directory named by target. Changes to this rootfs will be present in the rootfs used by installer on the modified ISO.

Many actions will do this implicitly but it may be clearer to be explicit about the target directory name if later shell or cp actions refer to paths in the rootfs.

shell

argument: command (default: null)

Runs a shell (bash) in the main temporary directory. If command is present, this is the command that is run. If not, an interactive shell is run. If the shell (command or interactive) exits with a non-zero code, that aborts the run.

cp

argument: source

argument: dest

Copy a file. The paths source and dest are interpreted in a special way:

  • Absolute paths (i.e. paths starting with '/') are handled as is.
  • A magic prefix of '$LAYER[n]' is handled by mounting the n'th layer if needed and interpreting the remainer of the path relative to the base of that layer.
  • Other paths are interpreted relative to the main temporary directory.

So something like this:

--cp /my/custom/initrd new/iso/casper/initrd

to replace the initrd. And something like this:

--cp /path/to/sources.list '$LAYER[0]/etc/apt/sources.list'

to overwrite sources.list in the base layer.

rm

argument: path

Remove a file or directory. path is interpreted the same way as the paths passed to --cp.

--rm new/iso/casper

to remove the directory casper.

inject-snap

argument: snap

argument: channel (default: "stable")

Inject the passed snap into the rootfs the installer runs in. This is used to test new versions of subiquity. If there is an assert alongside the snap, this will be copied into the ISO too and the snap set up to track the passed channel, otherwise it is installed unasserted.

add-snap-from-store

argument: snap_name

argument: channel (default: "stable")

A wrapper around --inject-snap that downloads the specified snap from the store first.

edit-squashfs

argument: squash_name

argument: add_sys_mounts (default: true)

Mount the squashfs named squash_name at new/{name} and arrange for it be repacked if there are any changes before the new ISO is made.

add_sys_mounts controls whether the usual chroot setup stuff is done (mounting /dev, /proc/ etc, setting up /etc/resolv.conf).

add-cmdline-arg

argument: arg

argument: persist (default: true)

Add an argument to the default kernel command line. If persist is true, it will be present on the default kernel command line of the installed system as well.

add-autoinstall-config

argument: autoinstall_config

Add the provided autoinstall config to the ISO so it is used by default. This also adds "autoinstall" to the default kernel command line.

autoinstall_config is the path to a YAML file which can contain either the autoinstall config directly or a cloud-init user-data file (in which case it can contain other configuration for the live installer session, such as ssh keys to be used for the installer user).

add-xorriso-args

argument: list of xorriso argument(s)

Add one or more extra arguments for xorriso, after the ones that livefs-editor already supplies.

resign-pool

This will generate a new Ed25519 GPG key, sign the package repository on the ISO with it, arrange for the public part to end up in /etc/apt/trusted.gpg.d/custom-iso-key.gpg in the installed system, and then throw the private part away. You should be aware of this change to the default apt configuration! Deleting this file in an autoinstall late-command would be a reasonable thing to do, unless you want the option of using the ISO as an apt repository later on.

add-debs-to-pool

argument: list of deb files

Add the passed deb files to the repository on the CD so that they are available for installation while the installer is running, even if the install is done offline.

This calls resign-pool so do read the note about GPG in the description of that action.

add-packages-to-pool

argument: packages (list of package names)

This is a wrapper around add-debs-to-pool which takes package names rather than deb files. It downloads the listed packages and any others needed to satisfy their dependencies from the main Ubuntu archive and passes them to add-debs-to-pool. Do read the note about GPG in the description of resign-pool.

unpack-initrd

argument: target (default: "new/initrd")

Unpack the initrd using unmkinitramfs into target (contents will likely end up in subdirectories called things like early, early2 and main, at least on amd64) and arrange for these to be repacked into a replacement initrd for the modified ISO if any changes are made.

add-apt-repository

argument: repo

Run add-apt-repository <repo> in the base layer.

install-debs

argument: list of deb files

Install the listed deb files in the installer environment.

install-packages

argument: packages (list of deb files)

Install the listed packages in the base layer.

python

argument: <cmd> (optional)

Execute <cmd> in a namespace that contains the context object as ctxt or start an interactive shell if <cmd> is not passed.

replace-kernel

argument: <flavor>

Modify the ISO so the kernel installed via the metapackage linux-<flavor> will be used to boot the ISO and be installed in the target system.

mount-all-squashfses

Mount all squashfses in new/iso/casper at old/{squash_name} (this is mostly to make poking at ISOs in a subsequent interactive --shell action easier).

livefs-editor's People

Contributors

dbungert avatar lord2y avatar medicalwei avatar mwhudson avatar niraj-fortum avatar phreakazoid21 avatar shigmas avatar variabledeclared avatar xypron avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

livefs-editor's Issues

user-data file not included in iso

Shouldn't the user-data file used on the command line be added to the iso?

sudo python3 -m livefs_edit /home/jonasb/Downloads/ubuntu-22.04.1-live-server-amd64.iso ubuntu-22.04-ai.iso --add-autoinstall-config ./user-data
set up loop device /dev/loop3 backing /home/jonasb/Downloads/ubuntu-22.04.1-live-server-amd64.iso
found live iso9660 filesystem on /dev/loop3p1
running add-autoinstall-config with arguments {'autoinstall_config': './user-data'}
  running setup-rootfs with arguments {}
    running unpack-initrd with arguments {}
  running add-cmdline-arg with arguments {'arg': 'autoinstall', 'persist': False}
    rewriting /tmp/tmpzm67qie5/new/iso/boot/grub/grub.cfg
dr-xr-xr-x 1 root  2048 Apr 16 00:08 boot/
-r--r--r-- 1 root  2048 Aug  9 18:48 boot.catalog
dr-xr-xr-x 1 root  4096 Sep 29 14:49 casper/
dr-xr-xr-x 1 root  2048 Aug  9 18:48 dists/
dr-xr-xr-x 1 root  2048 Apr 16 00:08 EFI/
dr-xr-xr-x 1 root  2048 Aug  9 18:48 install/
-r--r--r-- 1 root 11408 Aug  9 18:48 md5sum.txt
dr-xr-xr-x 1 root  2048 Aug  9 18:48 pool/
lr-xr-xr-x 1 root     1 Aug  9 18:48 ubuntu -> ./

Specifying package versions does not work

When i specify a specific package version i get the error: raise KeyError('The cache has no package named %r' % key)

Traceback (most recent call last):
  File "<redacted>/__main__.py", line 65, in main
    func(ctxt, **kw)
  File "/usr/local/lib/python3.10/dist-packages/livefs_edit/actions.py", line 41, in impl
    return func(ctxt, **kw)
  File "/usr/local/lib/python3.10/dist-packages/livefs_edit/actions.py", line 443, in add_packages_to_pool
    cache[p].mark_install()
  File "/usr/lib/python3/dist-packages/apt/cache.py", line 283, in __getitem__
    raise KeyError('The cache has no package named %r' % key)
KeyError: "The cache has no package named 'tpm2-tools=5.2-1build1'"

any suggestions?

subprocess.CalledProcessError: Command '['losetup', '--show', '--find', '--partscan', 'ubuntu-22.04.1-live-server-amd64.iso']' returned non-zero exit status 1.

What am I doing wrong?

python3 -m livefs_edit ubuntu-22.04.1-live-server-amd64.iso ubuntu-22.04-autoinstall-server-amd64.iso --add-autoinstall-config user-data
Traceback (most recent call last):
  File "/usr/lib/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/home/jonasb/.local/lib/python3.10/site-packages/livefs_edit/__main__.py", line 74, in <module>
    main(sys.argv[1:])
  File "/home/jonasb/.local/lib/python3.10/site-packages/livefs_edit/__main__.py", line 60, in main
    ctxt.mount_source()
  File "/home/jonasb/.local/lib/python3.10/site-packages/livefs_edit/context.py", line 228, in mount_source
    source_loop = self.add_loop(self.source_path)
  File "/home/jonasb/.local/lib/python3.10/site-packages/livefs_edit/context.py", line 85, in add_loop
    cp = run_capture(['losetup', '--show', '--find', '--partscan', file])
  File "/home/jonasb/.local/lib/python3.10/site-packages/livefs_edit/__init__.py", line 11, in run_capture
    return run(
  File "/home/jonasb/.local/lib/python3.10/site-packages/livefs_edit/__init__.py", line 7, in run
    return subprocess.run(cmd, check=check, **kw)
  File "/usr/lib/python3.10/subprocess.py", line 524, in run
    raise CalledProcessError(retcode, process.args,
subprocess.CalledProcessError: Command '['losetup', '--show', '--find', '--partscan', 'ubuntu-22.04.1-live-server-amd64.iso']' returned non-zero exit status 1.

Error: upper fs does not support RENAME_WHITEOUT

Hi,

I'm trying to copy a modified grub.cfg file to the ISO file of the latest ubuntu distribution: ubuntu-22.10-live-server-amd64.iso

I installed livefs-editor from github (so it's the latest version)

When I run the command livefs-edit ../$ORIG_ISO ../$MODDED_ISO --cp /tmp/grub.cfg I get an error:
subprocess.CalledProcessError: Command '['mount', '-t', 'overlay', 'overlay', '-o', 'lowerdir=/tmp/tmpit56cbff/old/iso,upperdir=/tmp/tmpit56cbff/.tmp/tmp03znjpuj,workdir=/tmp/tmpit56cbff/.tmp/tmpk5en_7du', '/tmp/tmpit56cbff/new/iso']' returned non-zero exit status 32.

looking at the messages output by dmesg I get:

[191790.930557] loop4: detected capacity change from 0 to 3208264
[191790.930802]  loop4: p1 p2 p3
[191790.931877]  loop4: p1 p2 p3
[191791.038643] ISO 9660 Extensions: Microsoft Joliet Level 3
[191791.038692] ISO 9660 Extensions: RRIP_1991A
[191791.045626] ISO 9660 Extensions: Microsoft Joliet Level 3
[191791.045665] ISO 9660 Extensions: RRIP_1991A
[191791.049904] overlayfs: upper fs does not support RENAME_WHITEOUT.
[191791.049926] overlayfs: upper fs missing required features.

I am running:

Distributor ID:	Ubuntu
Description:	Ubuntu 22.10
Release:	22.10
Codename:	kinetic

Here the complete output from livefs-edit

root@# ls ..
grub.cfg  livefs-editor  ubuntu-22.10-live-server-amd64.iso

root@# echo $ORIG_ISO
ubuntu-22.10-live-server-amd64.iso

root@# echo $MODDED_ISO
ubuntu-22.10-live-server-amd64-modded.iso

root@# livefs-edit ../$ORIG_ISO ../$MODDED_ISO --cp /tmp/grub.cfg new/iso/boot/grub/grub.cfg

set up loop device /dev/loop4 backing ../ubuntu-22.10-live-server-amd64.iso
found live iso9660 filesystem on /dev/loop4p1
Traceback (most recent call last):
  File "/usr/local/lib/python3.10/dist-packages/livefs_edit/__main__.py", line 62, in main
    ctxt.mount_source()
  File "/usr/local/lib/python3.10/dist-packages/livefs_edit/context.py", line 246, in mount_source
    self._source_overlay = self.add_overlay(
  File "/usr/local/lib/python3.10/dist-packages/livefs_edit/context.py", line 164, in add_overlay
    mountpoint=self.add_mount(
  File "/usr/local/lib/python3.10/dist-packages/livefs_edit/context.py", line 111, in add_mount
    run_capture(cmd)
  File "/usr/local/lib/python3.10/dist-packages/livefs_edit/__init__.py", line 11, in run_capture
    return run(
  File "/usr/local/lib/python3.10/dist-packages/livefs_edit/__init__.py", line 7, in run
    return subprocess.run(cmd, check=check, **kw)
  File "/usr/lib/python3.10/subprocess.py", line 524, in run
    raise CalledProcessError(retcode, process.args,
subprocess.CalledProcessError: Command '['mount', '-t', 'overlay', 'overlay', '-o', 'lowerdir=/tmp/tmpit56cbff/old/iso,upperdir=/tmp/tmpit56cbff/.tmp/tmp03znjpuj,workdir=/tmp/tmpit56cbff/.tmp/tmpk5en_7du', '/tmp/tmpit56cbff/new/iso']' returned non-zero exit status 32.

Stderr:

mount: /tmp/tmpit56cbff/new/iso: wrong fs type, bad option, bad superblock on overlay, missing codepage or helper program, or other error.
       dmesg(1) may have more information after failed mount system call.

object not callable

I am trying to use livefs-edit and get an error

livefs-edit ubuntu-22.04.3-live-server-amd64.iso test.iso --cp grub.cfg /boot/grub/grub.cfg
Traceback (most recent call last):
File "/usr/local/bin/livefs-edit", line 8, in
sys.exit(main())
TypeError: 'module' object is not callable

All I want to do is replace the grub.cfg file on the ubuntu live server cd.

I did "pip3 install livefs-editor", and then ran the above command.
What do I do with this error ?

Jerry

Cross-arch iso modification

Currently, livefs-editor is directly chroot-ing into the given livefs which isn't working in the case of cross platform modification (of a raspberry pi image would be a good example).

Here is the log:

โžœ livefs_edit ubuntu-23.10.1-desktop-arm64+x13s.iso modified.iso --install-debs /tmp/e394e7c3b27d.efdj/*.deb
set up loop device /dev/loop26 backing ubuntu-23.10.1-desktop-arm64+x13s.iso
found live iso9660 filesystem on /dev/loop26p1
running install-debs with arguments {'debs': ['/tmp/e394e7c3b27d.efdj/1.deb', '/tmp/e394e7c3b27d.efdj/2.deb', '/tmp/e394e7c3b27d.efdj/3.deb', '/tmp/e394e7c3b27d.efdj/4.deb']}
  running setup-rootfs with arguments {}
chroot: failed to run command โ€˜dpkgโ€™: Exec format error
Traceback (most recent call last):
  File "/home/thibf/canonical/livefs-editor/livefs_edit/__main__.py", line 87, in main
    func(ctxt, **kw)
  File "/home/thibf/canonical/livefs-editor/livefs_edit/actions.py", line 57, in impl
    return func(ctxt, **kw)
           ^^^^^^^^^^^^^^^^
  File "/home/thibf/canonical/livefs-editor/livefs_edit/actions.py", line 189, in install_debs
    ctxt.run(['chroot', rootfs, 'dpkg', '-i', deb_name])
  File "/home/thibf/canonical/livefs-editor/livefs_edit/context.py", line 81, in run
    cp = subprocess.run(cmd, check=check, **kw)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/subprocess.py", line 571, in run
    raise CalledProcessError(retcode, process.args,
subprocess.CalledProcessError: Command '['chroot', '/tmp/tmplhyge94k/rootfs', 'dpkg', '-i', 'foo.deb']' returned non-zero exit status 126.
umount: /tmp/tmplhyge94k/old/iso: target is busy.

Maybe a first step toward this problem would be to fail gracefully by detecting the host arch and the image type, then rely on qemu to execute embedded binaries.

yaml.load needs to be updated to yaml.safe_load

Hello,

when installing livefs-edit from source, the following error is thrown:

(livefs-edit) root@allowing-monkfish:~# livefs-edit /home/pjds/Downloads/ubuntu-20.04.5-live-server-amd64.iso custom-server.iso --action-yaml ./actions.yaml                                                       
Traceback (most recent call last):                                                                                                                                                                                 
  File "/root/.virtualenvs/livefs-edit/bin/livefs-edit", line 8, in <module>                                                                                                                                       
    sys.exit(__main__.main())                                                                                                                                                                                      
  File "/root/.virtualenvs/livefs-edit/lib/python3.8/site-packages/livefs_edit/__main__.py", line 49, in main                                                                                                      
    spec = yaml.load(fp)                                                                                                                                                                                           
TypeError: load() missing 1 required positional argument: 'Loader'

The error is due to the missing loader argument. yaml.load (with loader args) was deprecated, in favor of yaml.safe_load. Chanigng the line to yaml.safe_load works around this issue.

Thanks,
Peter

--cp command does not work

Created ISO with this command:

cd /var/tmp/remaster.2L0ZrQo &&
sudo PYTHONPATH=/m/src/remaster1/subiquity/livefs-editor
python3 -m livefs_edit --debug
/var/tmp/images/ubuntu-22.04.2-live-server-amd64.iso
/var/tmp/builds/ubuntu-remaster/my.iso
--inject-snap /m/src/remaster1/subiquity/subiquity-test.snap stable
--cp /m/src/remaster1/patches/instfiles/installer/etc/default/console-setup /etc/default/console-setup
--cp /m/src/remaster1/patches/instfiles/installer/etc/default/keyboard /etc/default/keyboard

But the files meant to be copied still have the original contents (US keyboard/language instead of german)

resolv.conf is not correctly restored after add-autoinstall-config

Today I used this script to add a package and an autoinstall config to the installer. During the installation I noticed that the name resolution was not working and looking into /etc showed me an incorrectly static resolv.conf and a resolv.conf.tmp symlink.

I looked into the source code and tracked it down to the setup_rootfs() function. This function calls the context method add_sys_mounts() which adds a _pre_repack callback, to restore the original resolv.conf. After this setup_rootfs() addes a _pre_repack callback too, which creates the custom squashfs. Since pre_repack hooks are later called in reverse by the repack() method, the custom squashfs is build before the original resolv.conf is restored.
For myself I fixed it by adding the pre_repack hook from setup_rootfs to the front of the list. I don't know if there are any side effects doing it this way if a more complicated action.yaml is used, because mine is very simple:

- name: add-debs-to-pool
  debs:
    - helper/subiquity-speed-up_1_all.deb

- name: add-autoinstall-config
  autoinstall_config: user-data

Interest in human friendly errors?

After I added support for adding specific package versions to pool, this has resulted in a few interesting error messages when a specific package is no longer available (for whichever reason a package is removed from a repository)

Problem
The error message is difficult to interpret for a user not intimately familiar with livefs-editor

Example - problem

Traceback (most recent call last):
  File "/usr/lib/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/usr/local/lib/python3.10/dist-packages/livefs_edit/__main__.py", line 100, in <module>
    main(sys.argv[1:])
  File "/usr/local/lib/python3.10/dist-packages/livefs_edit/__main__.py", line 82, in main
    func(ctxt, **kw)
  File "/usr/local/lib/python3.10/dist-packages/livefs_edit/actions.py", line 58, in impl
    return func(ctxt, **kw)
  File "/usr/local/lib/python3.10/dist-packages/livefs_edit/actions.py", line 487, in add_packages_to_pool
    package.candidate = package.versions.get(package_version)
  File "/usr/lib/python3/dist-packages/apt/package.py", line 1086, in candidate
    self._pcache._depcache.set_candidate_ver(self._pkg, version._cand)
AttributeError: 'NoneType' object has no attribute '_cand'

Suggested solution
Make a custom error message which provides more helpful output to the user

Example: "Could not satisfy version requirement. Package has no version "

  • Wrap the logic related to marking a specific version as installable in try/catch and throw a custom exception
  • Add a new branch to try/catch in __main__ to print more human friendly output

Would this be an addition that could be of interest so that it could be accepted? If so, I'll submit a PR shortly ๐Ÿ™‚
Or would you prefer a different approach?

umount error running example.yaml: target is busy

I'm trying to get this up and running - looks very promising for my use case.
(needed to install python-apt-dev, initramfs-tools and python3-debian)

I hopefully have an environment setup correctly. I cloned this repo today (561e3d), built the wheel and installed into a venv (as root)

I am working with the 18.04.6 server live image on a 22.04.1 host.

Running the example.yaml actions.

The add-packages-to-pool action runs and then I this this error:

    running resign-pool with arguments {}                                                                                                                              
gpg: WARNING: unsafe permissions on homedir '/tmp/tmpxcdchbi0/.tmp/tmp94dn9ngp'                                                                                        gpg: keybox '/tmp/tmpxcdchbi0/.tmp/tmp94dn9ngp/pubring.kbx' created
gpg: /tmp/tmpxcdchbi0/.tmp/tmp94dn9ngp/trustdb.gpg: trustdb created                                                                                                    gpg: key 1D9479026C00BBA2 marked as ultimately trusted                                                                                                                
gpg: directory '/tmp/tmpxcdchbi0/.tmp/tmp94dn9ngp/openpgp-revocs.d' created                                                                                            gpg: revocation certificate stored as '/tmp/tmpxcdchbi0/.tmp/tmp94dn9ngp/openpgp-revocs.d/BACF010B2B102F162E3C14D01D9479026C00BBA2.rev'                                
gpg: WARNING: unsafe permissions on homedir '/tmp/tmpxcdchbi0/.tmp/tmp94dn9ngp'                                                                                             
 squashfs 'filesystem' now mounted at '/tmp/tmpxcdchbi0/new/filesystem'                                                                                          
umount: /tmp/tmpxcdchbi0/.tmp/tmpr327mgwo: target is busy.                                                                                                             
Traceback (most recent call last):                                                                                                                                       
File "/home/x/xx/venv/lib/python3.10/site-packages/livefs_edit/__main__.py", line 65, in main                                                                  func(ctxt, **kw)                                                                                                                                                    
File "/home/x/xx/venv/lib/python3.10/site-packages/livefs_edit/actions.py", line 41, in impl                                                                   return func(ctxt, **kw)                                                                                                                                             
File "/home/x/xx/venv/lib/python3.10/site-packages/livefs_edit/actions.py", line 450, in add_packages_to_pool                                                  add_debs_to_pool(ctxt, debs=debs)                                                                                                                                   
File "/home/x/xx/venv/lib/python3.10/site-packages/livefs_edit/actions.py", line 41, in impl                                                                   return func(ctxt, **kw)
File "/home/x/xx/venv/lib/python3.10/site-packages/livefs_edit/actions.py", line 404, in add_debs_to_pool
    resign_pool(ctxt)
File "/home/x/xx/venv/lib/python3.10/site-packages/livefs_edit/actions.py", line 41, in impl
    return func(ctxt, **kw)
File "/home/x/xx/venv/lib/python3.10/site-packages/livefs_edit/actions.py", line 359, in resign_pool
    new_fs = ctxt.edit_squashfs(get_squash_names(ctxt)[0])
File "/home/x/xx/venv/lib/python3.10/site-packages/livefs_edit/context.py", line 201, in edit_squashfs
    self.add_sys_mounts(target)
File "/home/x/xx/venv/lib/python3.10/site-packages/livefs_edit/context.py", line 121, in add_sys_mounts
    mnts.append(self.add_mount(typ, typ, f'{mountpoint}/{relpath}'))
File "/home/x/xx/venv/lib/python3.10/site-packages/livefs_edit/context.py", line 103, in add_mount
    os.makedirs(mountpoint)
File "/usr/lib/python3.10/os.py", line 225, in makedirs
    mkdir(name, mode)
PermissionError: [Errno 1] Operation not permitted: '/tmp/tmpxcdchbi0/new/filesystem/sys/kernel/security'

cgroup2 unified

Hi,

could you please give me hint how to fix following issue

root@ubuntu-test:/home/test# livefs-edit ubuntu-20.04.5-live-server-amd64.iso custom.iso --action-yaml example.yaml 
[{'name': 'add-cmdline-arg', 'arg': 'autoinstall', 'persist': False}, {'name': 'replace-kernel', 'flavor': 'linux-hwe'}, {'name': 'add-packages-to-pool', 'packages': ['casper', 'valgrind']}]
set up loop device /dev/loop9 backing ubuntu-20.04.5-live-server-amd64.iso
found live iso9660 filesystem on /dev/loop9p1
running add-cmdline-arg with arguments {'arg': 'autoinstall', 'persist': False}
  rewriting /tmp/tmpaj8i2cl6/new/iso/boot/grub/grub.cfg
  rewriting /tmp/tmpaj8i2cl6/new/iso/isolinux/txt.cfg
running replace-kernel with arguments {'flavor': 'linux-hwe'}
  running unpack-initrd with arguments {}
  squashfs 'filesystem' now mounted at '/tmp/tmpaj8i2cl6/new/filesystem'
Traceback (most recent call last):
  File "/usr/local/bin/livefs-edit", line 8, in <module>
    sys.exit(__main__.main())
  File "/usr/local/lib/python3.8/dist-packages/livefs_edit/__main__.py", line 82, in main
    func(ctxt, **kw)
  File "/usr/local/lib/python3.8/dist-packages/livefs_edit/actions.py", line 58, in impl
    return func(ctxt, **kw)
  File "/usr/local/lib/python3.8/dist-packages/livefs_edit/actions.py", line 576, in replace_kernel
    base = ctxt.edit_squashfs(get_squash_names(ctxt)[0])
  File "/usr/local/lib/python3.8/dist-packages/livefs_edit/context.py", line 235, in edit_squashfs
    self.add_sys_mounts(target)
  File "/usr/local/lib/python3.8/dist-packages/livefs_edit/context.py", line 146, in add_sys_mounts
    mnts.append(self.add_mount(
  File "/usr/local/lib/python3.8/dist-packages/livefs_edit/context.py", line 127, in add_mount
    os.makedirs(mountpoint)
  File "/usr/lib/python3.8/os.py", line 223, in makedirs
    mkdir(name, mode)
OSError: [Errno 30] Read-only file system: '/tmp/tmpaj8i2cl6/new/filesystem/sys/fs/cgroup/unified'

host os: ubuntu 20.04.5 LTS in Qemu

root@ubuntu-test:/home/test# mount | grep sys
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)
securityfs on /sys/kernel/security type securityfs (rw,nosuid,nodev,noexec,relatime)
tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,mode=755)
cgroup2 on /sys/fs/cgroup/unified type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate)
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,name=systemd)
pstore on /sys/fs/pstore type pstore (rw,nosuid,nodev,noexec,relatime)
none on /sys/fs/bpf type bpf (rw,nosuid,nodev,noexec,relatime,mode=700)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls,net_prio)
cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,rdma)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
systemd-1 on /proc/sys/fs/binfmt_misc type autofs (rw,relatime,fd=28,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=15566)
debugfs on /sys/kernel/debug type debugfs (rw,nosuid,nodev,noexec,relatime)
tracefs on /sys/kernel/tracing type tracefs (rw,nosuid,nodev,noexec,relatime)
fusectl on /sys/fs/fuse/connections type fusectl (rw,nosuid,nodev,noexec,relatime)
configfs on /sys/kernel/config type configfs (rw,nosuid,nodev,noexec,relatime)
binfmt_misc on /proc/sys/fs/binfmt_misc type binfmt_misc (rw,nosuid,nodev,noexec,relatime)

TypeError: 'module' object is not callable

Hi folks, I will probably figure this out at some point, but I need some help with getting the code to run.

I cloned the repo on an Ubuntu 20.04 system, then installed livefs-editor with sudo pip3 install ./livefs-editor, and then tried to run it, but I'm getting this error:

% livefs-edit
Traceback (most recent call last):
  File "/usr/local/bin/livefs-edit", line 9, in <module>
    sys.exit(__main__())
TypeError: 'module' object is not callable

As far as I've been able to figure out this may be a pip version issue.

ubuntux86(livefs-editor)> python3 --version
Python 3.8.10
ubuntux86(livefs-editor)> pip3 --version
pip 20.0.2 from /usr/lib/python3/dist-packages/pip (python 3.8)

Any assistance would be greatly appreciated! Thank you.

replace-kernel: unmounting fails

xorriso : UPDATE :  88.01% done
ISO image produced: 1033350 sectors
Written to medium : 1033350 sectors at LBA 0
Writing to 'stdio:new.iso' completed successfully.

mount: (hint) your fstab has been modified, but systemd still uses
       the old version; use 'systemctl daemon-reload' to reload.
mount: (hint) your fstab has been modified, but systemd still uses
       the old version; use 'systemctl daemon-reload' to reload.
umount: /tmp/tmpx8_mo_oq/.tmp/tmph5ya1u24: target is busy.
Traceback (most recent call last):
  File "/usr/local/bin/livefs-edit", line 8, in <module>
    sys.exit(__main__.main())
  File "/usr/local/lib/python3.10/dist-packages/livefs_edit/__main__.py", line 80, in main
    ctxt.teardown()
  File "/usr/local/lib/python3.10/dist-packages/livefs_edit/context.py", line 218, in teardown
    run(['umount', '-R', mount])
  File "/usr/local/lib/python3.10/dist-packages/livefs_edit/__init__.py", line 7, in run
    return subprocess.run(cmd, check=check, **kw)
  File "/usr/lib/python3.10/subprocess.py", line 526, in run
    raise CalledProcessError(retcode, process.args,
subprocess.CalledProcessError: Command '['umount', '-R', '/tmp/tmpx8_mo_oq/.tmp/tmph5ya1u24']' returned non-zero exit status 32.
make[1]: *** [Makefile:29: new] Error 1

The following mounts exist at this time

/dev/loop12p1 on /tmp/tmpx8_mo_oq/old/iso type iso9660 (ro,relatime,nojoliet,check=s,map=n,blocksize=2048,iocharset=utf8)
overlay on /tmp/tmpx8_mo_oq/new/iso type overlay (rw,relatime,lowerdir=/tmp/tmpx8_mo_oq/old/iso,upperdir=/tmp/tmpx8_mo_oq/.tmp/tmpeu8y9ths,workdir=/tmp/tmpx8_mo_oq/.tmp/tmpm5_55sf2,xino=off)
overlay on /tmp/tmpx8_mo_oq/new/initrd type overlay (rw,relatime,lowerdir=/tmp/tmpx8_mo_oq/old/initrd,upperdir=/tmp/tmpx8_mo_oq/.tmp/tmp9mwbz4ni,workdir=/tmp/tmpx8_mo_oq/.tmp/tmpi8zt3e2z)
/tmp/tmpx8_mo_oq/old/iso/casper/ubuntu-server-minimal.squashfs on /tmp/tmpx8_mo_oq/old/ubuntu-server-minimal type squashfs (ro,relatime,errors=continue)
overlay on /tmp/tmpx8_mo_oq/new/ubuntu-server-minimal type overlay (rw,relatime,lowerdir=/tmp/tmpx8_mo_oq/old/ubuntu-server-minimal,upperdir=/tmp/tmpx8_mo_oq/.tmp/tmpj65ygrpa,workdir=/tmp/tmpx8_mo_oq/.tmp/tmp7nyrdp5a,xino=off)
/tmp/tmpx8_mo_oq/old/iso/casper/ubuntu-server-minimal.ubuntu-server.squashfs on /tmp/tmpx8_mo_oq/old/ubuntu-server-minimal.ubuntu-server type squashfs (ro,relatime,errors=continue)
/tmp/tmpx8_mo_oq/old/iso/casper/ubuntu-server-minimal.ubuntu-server.installer.squashfs on /tmp/tmpx8_mo_oq/old/ubuntu-server-minimal.ubuntu-server.installer type squashfs (ro,relatime,errors=continue)
/tmp/tmpx8_mo_oq/old/iso/casper/ubuntu-server-minimal.ubuntu-server.installer.generic.squashfs on /tmp/tmpx8_mo_oq/old/ubuntu-server-minimal.ubuntu-server.installer.generic type squashfs (ro,relatime,errors=continue)
overlay on /tmp/tmpx8_mo_oq/.tmp/tmph5ya1u24 type overlay (rw,relatime,lowerdir=/tmp/tmpx8_mo_oq/old/ubuntu-server-minimal.ubuntu-server.installer:/tmp/tmpx8_mo_oq/old/ubuntu-server-minimal.ubuntu-server:/tmp/tmpx8_mo_oq/new/ubuntu-server-minimal,upperdir=/tmp/tmpx8_mo_oq/.tmp/tmpxb6sqsqd,workdir=/tmp/tmpx8_mo_oq/.tmp/tmpmkgap73e,xino=off)

Modifying relative path of --cp <source>

Context

I have to run livefs-editor from a script since the options I pass to it dynamically change.

for example, within my script i start livefs-editor as follows:
$LIVE_FS $ISO_FILE $ISO_FILE_MOD --action-yaml $LIVEFS_CONFIG

One of the options i'm using in the action-yaml is cp

- name: cp
  source: <some grub.cfg file>
  dest: new/iso/boot/grub/grub.cfg # relative path to livefs temp folder

problem:

I cannot put a fixed full path as source, because this path might change depending on the environment i'm running in.

What i really want, is the source path to be relative to the action-yaml file. This way I could specify my source in the yaml as simply ./grub.cfg.

Can you assist me in showing me which function i should modify for this behavior? I would need to modify the cp function?

@register_action()

I'm not really sure the best way to go about this.

Thanks for any advice!

Option add_autoinstall doesn't work on the desktop ISO

It bails out on this error

livefs-editor/livefs_edit/actions.py", line 361, in add_autoinstall_config
    with open(os.path.join(rootfs, seed_dir, 'user-data'), 'w') as fp:
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: '/tmp/tmpiu6jbhku/rootfs/var/lib/cloud/seed/nocloud/user-data'

The project description states it's about the server ISO but it would be nice if it worked also on the desktop one since it's similar enough

Updated packages not used during curtin installation

Hello @mwhudson,

My update.yaml has

- name: install-packages
  packages:
  - efivar
  - libefiboot1
  - libefivar1

to update efivar which has a problem on the target system.

grub-install fails due to using the old efivar.

This is what I find on the file system:

root@ubuntu-server:/# ls /lib/riscv64-linux-gnu/libefivar.so.1* -la
lrwxrwxrwx 1 root root     17 Aug  2 17:52 /lib/riscv64-linux-gnu/libefivar.so.1 -> libefivar.so.1.37
-rw-r--r-- 1 root root 138872 Aug  2 17:52 /lib/riscv64-linux-gnu/libefivar.so.1.37
root@ubuntu-server:/# ls /target/lib/riscv64-linux-gnu/libefivar.so.1* -la
lrwxrwxrwx 1 root root     17 Oct  7  2021 target/lib/riscv64-linux-gnu/libefivar.so.1 -> libefivar.so.1.37
-rw-r--r-- 1 root root 126352 Oct  7  2021 target/lib/riscv64-linux-gnu/libefivar.so.1.37

The root file-system has the new package, but the target system is still using the old version.

My expectation was that the target would receive the updated packages before GRUB is installed.

Best regards

Heinrich

Read-only file system error

I am getting an error when the script is trying to unpack initrd. Below the trace.

Install the snap with:
   snap ack ../../../../tmp/tmpn7_6wfd1/.tmp/tmpmrgs1fnz/dl.assert
   snap install ../../../../tmp/tmpn7_6wfd1/.tmp/tmpmrgs1fnz/dl.snap
  running inject-snap with arguments {'snap': '/tmp/tmpn7_6wfd1/.tmp/tmpmrgs1fnz/dl.snap', 'channel': 'edge'}
    running setup-rootfs with arguments {}
      running unpack-initrd with arguments {}
Traceback (most recent call last):
  File "/root/.pyenv/versions/3.6.15/lib/python3.6/runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "/root/.pyenv/versions/3.6.15/lib/python3.6/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/home/administrator/subiquity/scripts/livefs-editor/livefs_edit/__main__.py", line 100, in <module>
    main(sys.argv[1:])
  File "/home/administrator/subiquity/scripts/livefs-editor/livefs_edit/__main__.py", line 82, in main
    func(ctxt, **kw)
  File "/home/administrator/subiquity/scripts/livefs-editor/livefs_edit/actions.py", line 58, in impl
    return func(ctxt, **kw)
  File "/home/administrator/subiquity/scripts/livefs-editor/livefs_edit/actions.py", line 297, in add_snap_from_store
    ctxt, snap=download_snap(ctxt, snap_name, channel), channel=channel)
  File "/home/administrator/subiquity/scripts/livefs-editor/livefs_edit/actions.py", line 58, in impl
    return func(ctxt, **kw)
  File "/home/administrator/subiquity/scripts/livefs-editor/livefs_edit/actions.py", line 232, in inject_snap
    rootfs = setup_rootfs(ctxt)
  File "/home/administrator/subiquity/scripts/livefs-editor/livefs_edit/actions.py", line 45, in impl
    r = ctxt._cache[key] = func(ctxt, **kw)
  File "/home/administrator/subiquity/scripts/livefs-editor/livefs_edit/actions.py", line 58, in impl
    return func(ctxt, **kw)
  File "/home/administrator/subiquity/scripts/livefs-editor/livefs_edit/actions.py", line 117, in setup_rootfs
    ctxt.add_sys_mounts(target)
  File "/home/administrator/subiquity/scripts/livefs-editor/livefs_edit/context.py", line 148, in add_sys_mounts
    options=fs['options']))
  File "/home/administrator/subiquity/scripts/livefs-editor/livefs_edit/context.py", line 127, in add_mount
    os.makedirs(mountpoint)
  File "/root/.pyenv/versions/3.6.15/lib/python3.6/os.py", line 220, in makedirs
    mkdir(name, mode)
OSError: [Errno 30] Read-only file system: '/tmp/tmpm9je7le5/rootfs/sys/fs/cgroup/unified'

Ubuntu Server 22.04 with replace-kernel and add-autoinstall-config stuck on `mouting /dev/sr0 on /cdrom failed: No such device`

Current behavior

After replacing the bundled kernel with the hwe version on Ubuntu 22.04 (using replace-kernel) and adding an autoinstall script (using add-autoinstall-config), the installer gets stuck after casper-premount.

Error message: 'mount: mounting /dev/sr0 on /cdrom failed: No such device'

If I remove either of add-autoinstall-config or replace-kernel, booting and installation works fine as expected. However, the combination results in installation getting stuck on this error.

Expected behavior

Being able to both replace the kernel and use autoinstall together.

Reproduce

- name: replace-kernel
  flavor: generic-hwe-22.04

- name: add-autoinstall-config
  autoinstall_config: autoinstall.yaml

pre-seed.yaml:

#cloud-config
autoinstall:
  user-data:
    disable_root: true
    timezone: UTC
    package_upgrade: false
    users:
      - name: user
        primary_group: users
        groups: audio video
        lock_passwd: false
        # echo <PASSWORD> | openssl passwd -1 -stdin
        passwd: "adfasdfadsfadsfadsfasdfasdfadsfad"
        shell: /bin/bash
        chpasswd: { expire: False }
        sudo: false

image

Meta

Original ISO Version: Ubuntu Server 22.04 (daily build, downloaded 31.01.2023)
livefs-editor hash: 2dd0179

License?

Thanks for all your hard work on making this surprisingly well-functioning tool!

I'm unable to find any license information in the repository. Would it be possible to state the license somewhere?

add-debs-to-pool does not overwrite old packages

Hello @mwhudson

In my work directory I have:

libefivar1_37-6ubuntu3_riscv64.deb
libefiboot1_37-6ubuntu3_riscv64.deb

My update.yaml contains

- name: add-debs-to-pool
  debs:
  - libefivar1_37-6ubuntu3_riscv64.deb
  - libefiboot1_37-6ubuntu3_riscv64.deb

After the update I see:

/pool/main/e/efivar/libefiboot1_37-6ubuntu2_riscv64.deb
/pool/main/e/efivar/libefivar1_37-6ubuntu2_riscv64.deb
/pool/main/libefiboot1_37-6ubuntu3_riscv64.deb
/pool/main/libefivar1_37-6ubuntu3_riscv64.deb

There should never be two versions of the same package in the pool.

Best regards

Heinrich

Is it possible to add package for other architecture?

I would add i386 packages in a amd64 env

 marking gcc-12-base:amd64 for installation
  marking gcc-12-base:i386 for installation
umount: /tmp/tmp6jxb37wm/.tmp/tmpq3fmbvnn: target is busy.
umount: /tmp/tmp6jxb37wm/old/iso: target is busy.
Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/apt/cache.py", line 281, in __getitem__
    rawpkg = self._cache[key]
KeyError: 'gcc-12-base:i386'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/home/arloba/livefs-editor/livefs_edit/__main__.py", line 100, in <module>
    main(sys.argv[1:])
  File "/home/arloba/livefs-editor/livefs_edit/__main__.py", line 82, in main
    func(ctxt, **kw)
  File "/home/arloba/livefs-editor/livefs_edit/actions.py", line 58, in impl
    return func(ctxt, **kw)
  File "/home/arloba/livefs-editor/livefs_edit/actions.py", line 484, in add_packages_to_pool
    cache[p].mark_install()
  File "/usr/lib/python3/dist-packages/apt/cache.py", line 283, in __getitem__
    raise KeyError('The cache has no package named %r' % key)
KeyError: "The cache has no package named 'gcc-12-base:i386'"

Ubuntu could download the file:


#~/livefs-editor$ apt download gcc-12-base:i386
Holen:1 http://archive.ubuntu.com/ubuntu jammy-updates/main i386 gcc-12-base i386 12.1.0-2ubuntu1~22.04 [19,0 kB]
Es wurden 19,0 kB in 1 s geholt (23,8 kB/s).

Could livefs-editor configured to use "foreign-architectures"

I would be great if you can look at it.

Greets Michael

How to use this tool behind a proxy

I keep getting unknown certificate authorities errors when running your tool. I am behind a proxy. Hoping to see if this is a host issue, or if the tool supports a proxy argument. I have my OS variables/environment configured with the proxy details, every other tool works besides this one.

Thank you.

Andres

setup_rootfs and runtime kernel command line args

If one uses livefs-editor to inject a snap, or presumably anything that uses setup_rootfs, there is a layerfs-path arg added to the kernel command line.
If one then takes the resulting iso, and attempts to boot it with kvm and supply arguments with -append, that nice custom layer is not present.
A workaround would be to use add_cmdline_arg to build in the arguments that would be supplied with kvm -append, which is arguably more convenient in the first place, but it's not clear that this would be needed without a bit of head scratching on why the setup_rootfs operation didn't seem to take.

question about enabling cloud-init with u18.04

hiya

I'm trying to create custom bootcd's from ubuntus 18 20 and 22 that I could use as live-only boot supports
I've been kind of successful with 20 and 22 so far, using a hand-made shell script of my own brew - before I stumbled onto this repo which I will probably leverage as it's much cleaner than my own stuff :)

regardless, my approach was working fine with 20 and 22, but fails to work with 18 and I am suspecting the presence of /etc/cloud/cloud-init.disabled to be the root problem

to be more specific:

  • with 20 and 22 I expose another ISO volume named cidata and that is enough for cloud-init to spot it; I can tell that cloud-init performs because my custom ssh keys get installed
  • with 18, there's no .ssh/ created, there is no file named as /var/log/*cloud*, nor does journalctl -u cloud-init.service report anything at all

so I guess I have 3 questions

  1. am I right to suspect the presence of this file to be the root cause of my problem ?
  2. can I use livefs-editor to remove that file in /etc/ ?
  3. more generally, I've taken good note of your disclaimer in the readme :), but in your experience, is it safe to consider editing an image as old as u18.04 with livefs-editor ?

thanks !

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.