Merge branch 'master' of github.com:robot-tools/iconograph
This commit is contained in:
88
README.md
88
README.md
@@ -84,6 +84,55 @@ flags to build_image.py as long as the modules are compatible with each other.
|
|||||||
|
|
||||||
Stock modules:
|
Stock modules:
|
||||||
|
|
||||||
|
### autoimage.py
|
||||||
|
|
||||||
|
Build an image that will partition, mkfs, and install an image from a different
|
||||||
|
URL onto a target system. Used to create install USB drives, PXE boot, etc.
|
||||||
|
Use the build_image.py flag:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
--module="server/modules/autoimage.py --base-url=http://yourhost/ --ca-cert=/path/to/signing/cert.pem --device=/dev/sdx --persistent-percent=50"
|
||||||
|
```
|
||||||
|
|
||||||
|
`--device` specifies the device to partition and install to on the target
|
||||||
|
system.
|
||||||
|
|
||||||
|
Optional flags:
|
||||||
|
|
||||||
|
`--persistent-percent`, if non-zero, specifies the percent of the target
|
||||||
|
device to allocate to a LABEL=PERSISTENT filesystem. If the inner image uses
|
||||||
|
persistent.py, this filesystem will be automatically mounted.
|
||||||
|
|
||||||
|
`--https-ca-cert` specifies a local path to a PEM-encoded certificate to
|
||||||
|
validate the HTTPS image server cert against. This differs from `--ca-cert`,
|
||||||
|
which is used to validate the manifest.json signature.
|
||||||
|
|
||||||
|
`--https-client-cert` and `--https-client-key` are used together to specify
|
||||||
|
local paths to a PEM-encoded certificate and key pair that will be provided
|
||||||
|
to the server over HTTPS. This can be used to limit image availability.
|
||||||
|
|
||||||
|
### certclient.py
|
||||||
|
|
||||||
|
Use a local master key/cert pair to authenticate to a
|
||||||
|
[certserver](https://github.com/robot-tools/certserver) instance and retrieve
|
||||||
|
a system-specific key. Mainly intended to be used with autoimage.py and
|
||||||
|
systemid.py.
|
||||||
|
|
||||||
|
Use the build_image.py flag:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
--module="server/modules/certclient.py --server=https://certserver/ --ca-cert=/path/to/server/cert.pem --client-cert=/path/to/client/cert.pem --client-key=/path/to/client/key.pem --tag=www --subject='/C=US/ST=California/O=XXXX/OU=XXXX Test/CN=SYSTEMID'"
|
||||||
|
```
|
||||||
|
|
||||||
|
The new key and cert are saved to /systemid
|
||||||
|
|
||||||
|
`--tag` specifies a value added to the filename, so certclient.py can be
|
||||||
|
used more than once with different servers (e.g. once for an HTTPS client
|
||||||
|
key/cert pair, and once for an EAP-TLS key/cert pair).
|
||||||
|
|
||||||
|
`--subject` specifics the subject string passed to openssl. `SYSTEMID` is
|
||||||
|
replaced with the system hostname, possibly as set by systemid.py
|
||||||
|
|
||||||
### iconograph.py
|
### iconograph.py
|
||||||
|
|
||||||
Install icon inside the image. This allows the image to auto-update over HTTP.
|
Install icon inside the image. This allows the image to auto-update over HTTP.
|
||||||
@@ -95,6 +144,10 @@ Use the build_image.py flag:
|
|||||||
|
|
||||||
Optional flags:
|
Optional flags:
|
||||||
|
|
||||||
|
`--https-ca-cert` specifies a local path to a PEM-encoded certificate to
|
||||||
|
validate the HTTPS image server cert against. This differs from `--ca-cert`,
|
||||||
|
which is used to validate the manifest.json signature.
|
||||||
|
|
||||||
`--max-images` sets the number of recent images to keep. Older images are
|
`--max-images` sets the number of recent images to keep. Older images are
|
||||||
deleted. Defaults to 5. 0 means unlimited.
|
deleted. Defaults to 5. 0 means unlimited.
|
||||||
|
|
||||||
@@ -108,23 +161,24 @@ Use the build_image.py flag:
|
|||||||
--module="server/modules/persistent.py"
|
--module="server/modules/persistent.py"
|
||||||
```
|
```
|
||||||
|
|
||||||
### autoimage.py
|
See [imager/image.py](imager/image.py)'s or
|
||||||
|
[server/module/autoimage.py](autoimage.py)'s `--persistent-percent` flag to
|
||||||
|
create this partition.
|
||||||
|
|
||||||
Build an image that will partition, mkfs, and install an image from a different
|
### systemid.py
|
||||||
URL onto a target system. Used to create install USB drives, PXE boot, etc.
|
|
||||||
Use the build_image.py flag:
|
Mount a /systemid partition from a filesystem with LABEL=SYSTEMID. This is
|
||||||
|
intended to a be separate device (possibly a USB flash drive, SD card, etc.)
|
||||||
|
which contains data that persists across re-images and identifies the system,
|
||||||
|
including system-specific keys and certificates.
|
||||||
|
|
||||||
|
It also sets the hostname to the value found in the systemid config on the
|
||||||
|
device.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
--module="server/modules/autoimage.py --base-url=http://yourhost/ --ca-cert=/path/to/signing/cert.pem --device=/dev/sdx --persistent-percent=50"
|
--module="server/modules/systemid.py"
|
||||||
```
|
```
|
||||||
|
|
||||||
`--device` specifies the device to partition and install to on the target
|
|
||||||
system.
|
|
||||||
|
|
||||||
`--persistent-percent`, if non-zero, specifies the percent of the target
|
|
||||||
device to allocate to a LABEL=PERSISTENT filesystem. If the inner image uses
|
|
||||||
persistent.py, this filesystem will be automatically mounted.
|
|
||||||
|
|
||||||
## Module API
|
## Module API
|
||||||
|
|
||||||
Modules are passed the following long-style arguments:
|
Modules are passed the following long-style arguments:
|
||||||
@@ -184,3 +238,13 @@ or manually write them to a drive. To do so:
|
|||||||
# Needs sudo to partition and mkfs devices
|
# Needs sudo to partition and mkfs devices
|
||||||
sudo imager/image.py --base-url=http://yourhost/ --ca-cert=/path/to/signing/cert.pem --device=/dev/sdx --persistent-percent=50
|
sudo imager/image.py --base-url=http://yourhost/ --ca-cert=/path/to/signing/cert.pem --device=/dev/sdx --persistent-percent=50
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Optional flags:
|
||||||
|
|
||||||
|
`--https-ca-cert` specifies a local path to a PEM-encoded certificate to
|
||||||
|
validate the HTTPS image server cert against. This differs from `--ca-cert`,
|
||||||
|
which is used to validate the manifest.json signature.
|
||||||
|
|
||||||
|
`--https-client-cert` and `--https-client-key` are used together to specify
|
||||||
|
local paths to a PEM-encoded certificate and key pair that will be provided
|
||||||
|
to the server over HTTPS. This can be used to limit image availability.
|
||||||
|
|||||||
1
client/.gitignore
vendored
1
client/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
flags
|
|
||||||
@@ -1,15 +1,30 @@
|
|||||||
#!/bin/sh
|
#!/bin/bash
|
||||||
|
|
||||||
set -ex
|
set -ex
|
||||||
|
|
||||||
BASE=$(dirname $0)
|
BASE=$(dirname $0)
|
||||||
|
|
||||||
IMAGES=/isodevice/iconograph
|
IMAGES="/isodevice/iconograph"
|
||||||
mkdir -p "${IMAGES}"
|
mkdir -p "${IMAGES}"
|
||||||
|
|
||||||
BOOT=/isodevice
|
BOOT="/isodevice"
|
||||||
|
|
||||||
FLAGS=$(cat ${BASE}/flags)
|
FETCHER_FLAGS="$(cat /icon/config/fetcher.flags)"
|
||||||
|
if -f /icon/config/update_grub.flags; then
|
||||||
|
UPDATE_GRUB_FLAGS="$(cat /icon/config/update_grub.flags)"
|
||||||
|
fi
|
||||||
|
CA_CERT="/icon/config/ca.image.cert.pem"
|
||||||
|
|
||||||
${BASE}/fetcher.py --image-dir="${IMAGES}" --ca-cert=${BASE}/../config/ca.cert.pem ${FLAGS}
|
HTTPS_CLIENT_KEY="/systemid/$(hostname).www.key.pem"
|
||||||
${BASE}/update_grub.py --image-dir="${IMAGES}" --boot-dir="${BOOT}" > ${BOOT}/grub/grub.cfg.tmp && mv ${BOOT}/grub/grub.cfg.tmp ${BOOT}/grub/grub.cfg
|
HTTPS_CLIENT_CERT="/systemid/$(hostname).www.cert.pem"
|
||||||
|
HTTPS_CA_CERT="/icon/config/ca.www.cert.pem"
|
||||||
|
|
||||||
|
if test -e "${HTTPS_CLIENT_KEY}" -a -e "${HTTPS_CLIENT_CERT}"; then
|
||||||
|
HTTPS_CLIENT_FLAGS="--https-client-cert=${HTTPS_CLIENT_CERT} --https-client-key=${HTTPS_CLIENT_KEY}"
|
||||||
|
fi
|
||||||
|
if test -e "${HTTPS_CA_CERT}"; then
|
||||||
|
HTTPS_CA_FLAGS="--https-ca-cert=${HTTPS_CA_CERT}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
"${BASE}/fetcher.py" --image-dir="${IMAGES}" --ca-cert="${CA_CERT}" ${FETCHER_FLAGS} ${HTTPS_CLIENT_FLAGS} ${HTTPS_CA_FLAGS}
|
||||||
|
"${BASE}/update_grub.py" --image-dir="${IMAGES}" --boot-dir="${BOOT}" ${UPDATE_GRUB_FLAGS} > "${BOOT}/grub/grub.cfg.tmp" && mv "${BOOT}/grub/grub.cfg.tmp" "${BOOT}/grub/grub.cfg"
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ parser.add_argument(
|
|||||||
dest='image_dir',
|
dest='image_dir',
|
||||||
action='store',
|
action='store',
|
||||||
required=True)
|
required=True)
|
||||||
|
parser.add_argument(
|
||||||
|
'--kernel-arg',
|
||||||
|
dest='kernel_args',
|
||||||
|
action='append')
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--module',
|
'--module',
|
||||||
dest='modules',
|
dest='modules',
|
||||||
@@ -35,8 +39,13 @@ parser.add_argument(
|
|||||||
action='store',
|
action='store',
|
||||||
required=True)
|
required=True)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--shell',
|
'--post-module-shell',
|
||||||
dest='shell',
|
dest='post_module_shell',
|
||||||
|
action='store_true',
|
||||||
|
default=False)
|
||||||
|
parser.add_argument(
|
||||||
|
'--pre-module-shell',
|
||||||
|
dest='pre_module_shell',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
default=False)
|
default=False)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
@@ -55,7 +64,6 @@ class ImageBuilder(object):
|
|||||||
'iputils-ping',
|
'iputils-ping',
|
||||||
'linux-firmware',
|
'linux-firmware',
|
||||||
'linux-firmware-nonfree',
|
'linux-firmware-nonfree',
|
||||||
'openssh-server',
|
|
||||||
'ubuntu-minimal',
|
'ubuntu-minimal',
|
||||||
'ubuntu-standard',
|
'ubuntu-standard',
|
||||||
'user-setup',
|
'user-setup',
|
||||||
@@ -83,13 +91,14 @@ class ImageBuilder(object):
|
|||||||
'loopback.cfg': 'boot/grub/loopback.cfg',
|
'loopback.cfg': 'boot/grub/loopback.cfg',
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, source_iso, image_dir, archive, arch, release, modules):
|
def __init__(self, source_iso, image_dir, archive, arch, release, modules, kernel_args):
|
||||||
self._source_iso = source_iso
|
self._source_iso = source_iso
|
||||||
self._image_dir = image_dir
|
self._image_dir = image_dir
|
||||||
self._archive = archive
|
self._archive = archive
|
||||||
self._arch = arch
|
self._arch = arch
|
||||||
self._release = release
|
self._release = release
|
||||||
self._modules = modules or []
|
self._modules = modules or []
|
||||||
|
self._kernel_args = kernel_args or []
|
||||||
|
|
||||||
self._ico_server_path = os.path.dirname(sys.argv[0])
|
self._ico_server_path = os.path.dirname(sys.argv[0])
|
||||||
|
|
||||||
@@ -100,6 +109,27 @@ class ImageBuilder(object):
|
|||||||
def _ExecChroot(self, chroot_path, *args, **kwargs):
|
def _ExecChroot(self, chroot_path, *args, **kwargs):
|
||||||
self._Exec('chroot', chroot_path, *args, **kwargs)
|
self._Exec('chroot', chroot_path, *args, **kwargs)
|
||||||
|
|
||||||
|
def _CreateRoot(self, timestamp):
|
||||||
|
root = tempfile.mkdtemp()
|
||||||
|
self._rmtree.append(root)
|
||||||
|
self._Exec(
|
||||||
|
'mount',
|
||||||
|
'--types', 'tmpfs',
|
||||||
|
'none',
|
||||||
|
root)
|
||||||
|
self._umount.append(root)
|
||||||
|
return root
|
||||||
|
|
||||||
|
def _MountProc(self, chroot_path):
|
||||||
|
path = os.path.join(chroot_path, 'proc')
|
||||||
|
self._Exec(
|
||||||
|
'mount',
|
||||||
|
'--types', 'proc',
|
||||||
|
'none',
|
||||||
|
path)
|
||||||
|
self._umount.append(path)
|
||||||
|
return path
|
||||||
|
|
||||||
def _Debootstrap(self, root):
|
def _Debootstrap(self, root):
|
||||||
path = os.path.join(root, 'chroot')
|
path = os.path.join(root, 'chroot')
|
||||||
os.mkdir(path)
|
os.mkdir(path)
|
||||||
@@ -219,6 +249,12 @@ class ImageBuilder(object):
|
|||||||
source)
|
source)
|
||||||
os.unlink(os.path.join(chroot_path, 'usr', 'sbin', 'policy-rc.d'))
|
os.unlink(os.path.join(chroot_path, 'usr', 'sbin', 'policy-rc.d'))
|
||||||
|
|
||||||
|
def _Unmount(self, path):
|
||||||
|
self._Exec(
|
||||||
|
'umount',
|
||||||
|
path)
|
||||||
|
self._umount.remove(path)
|
||||||
|
|
||||||
def _Squash(self, chroot_path, union_path):
|
def _Squash(self, chroot_path, union_path):
|
||||||
self._Exec(
|
self._Exec(
|
||||||
'mksquashfs',
|
'mksquashfs',
|
||||||
@@ -228,9 +264,11 @@ class ImageBuilder(object):
|
|||||||
|
|
||||||
def _CopyISOFiles(self, union_path):
|
def _CopyISOFiles(self, union_path):
|
||||||
for source, dest in self._ISO_COPIES.items():
|
for source, dest in self._ISO_COPIES.items():
|
||||||
shutil.copyfile(
|
source_path = os.path.join(self._ico_server_path, 'iso_files', source)
|
||||||
os.path.join(self._ico_server_path, 'iso_files', source),
|
dest_path = os.path.join(union_path, dest)
|
||||||
os.path.join(union_path, dest))
|
with open(source_path, 'r') as source_fh, open(dest_path, 'w') as dest_fh:
|
||||||
|
for line in source_fh:
|
||||||
|
dest_fh.write(line.replace('$KERNEL_ARGS', ' '.join(self._kernel_args)))
|
||||||
|
|
||||||
def _CreateISO(self, union_path, timestamp):
|
def _CreateISO(self, union_path, timestamp):
|
||||||
dest_iso = os.path.join(self._image_dir, '%d.iso' % timestamp)
|
dest_iso = os.path.join(self._image_dir, '%d.iso' % timestamp)
|
||||||
@@ -241,31 +279,25 @@ class ImageBuilder(object):
|
|||||||
return dest_iso
|
return dest_iso
|
||||||
|
|
||||||
def _BuildImage(self):
|
def _BuildImage(self):
|
||||||
root = tempfile.mkdtemp()
|
|
||||||
self._rmtree.append(root)
|
|
||||||
|
|
||||||
timestamp = int(time.time())
|
timestamp = int(time.time())
|
||||||
|
root = self._CreateRoot(timestamp)
|
||||||
print('Building image in:', root)
|
print('Building image in:', root)
|
||||||
|
|
||||||
self._Exec(
|
|
||||||
'mount',
|
|
||||||
'--types', 'tmpfs',
|
|
||||||
'none',
|
|
||||||
root)
|
|
||||||
self._umount.append(root)
|
|
||||||
|
|
||||||
chroot_path = self._Debootstrap(root)
|
chroot_path = self._Debootstrap(root)
|
||||||
union_path = self._CreateUnion(root)
|
union_path = self._CreateUnion(root)
|
||||||
|
proc_path = self._MountProc(chroot_path)
|
||||||
self._FixSourcesList(chroot_path)
|
self._FixSourcesList(chroot_path)
|
||||||
self._AddDiversions(chroot_path)
|
self._AddDiversions(chroot_path)
|
||||||
self._InstallPackages(chroot_path)
|
self._InstallPackages(chroot_path)
|
||||||
self._WriteVersion(chroot_path, timestamp)
|
self._WriteVersion(chroot_path, timestamp)
|
||||||
|
if FLAGS.pre_module_shell:
|
||||||
|
self._Exec('bash', cwd=root)
|
||||||
self._RunModules(chroot_path)
|
self._RunModules(chroot_path)
|
||||||
self._CleanPackages(chroot_path)
|
self._CleanPackages(chroot_path)
|
||||||
self._RemoveDiversions(chroot_path)
|
self._RemoveDiversions(chroot_path)
|
||||||
if FLAGS.shell:
|
if FLAGS.post_module_shell:
|
||||||
self._Exec('bash', cwd=root)
|
self._Exec('bash', cwd=root)
|
||||||
|
self._Unmount(proc_path)
|
||||||
self._Squash(chroot_path, union_path)
|
self._Squash(chroot_path, union_path)
|
||||||
self._CopyISOFiles(union_path)
|
self._CopyISOFiles(union_path)
|
||||||
iso_path = self._CreateISO(union_path, timestamp)
|
iso_path = self._CreateISO(union_path, timestamp)
|
||||||
@@ -297,7 +329,8 @@ def main():
|
|||||||
FLAGS.archive,
|
FLAGS.archive,
|
||||||
FLAGS.arch,
|
FLAGS.arch,
|
||||||
FLAGS.release,
|
FLAGS.release,
|
||||||
FLAGS.modules)
|
FLAGS.modules,
|
||||||
|
FLAGS.kernel_args)
|
||||||
builder.BuildImage()
|
builder.BuildImage()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -43,14 +43,14 @@ class ManifestBuilder(object):
|
|||||||
self._image_dir = image_dir
|
self._image_dir = image_dir
|
||||||
self._old_manifest = old_manifest
|
self._old_manifest = old_manifest
|
||||||
|
|
||||||
def _Rollouts(self):
|
def _OldImages(self):
|
||||||
if not self._old_manifest:
|
if not self._old_manifest:
|
||||||
return {}
|
return {}
|
||||||
try:
|
try:
|
||||||
with open(self._old_manifest, 'r') as fh:
|
with open(self._old_manifest, 'r') as fh:
|
||||||
parsed = json.load(fh)
|
parsed = json.load(fh)
|
||||||
return dict(
|
return dict(
|
||||||
(image['timestamp'], image['rollout_‱'])
|
(image['timestamp'], image)
|
||||||
for image in parsed['images'])
|
for image in parsed['images'])
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
return {}
|
return {}
|
||||||
@@ -60,7 +60,7 @@ class ManifestBuilder(object):
|
|||||||
'timestamp': int(time.time()),
|
'timestamp': int(time.time()),
|
||||||
'images': [],
|
'images': [],
|
||||||
}
|
}
|
||||||
rollouts = self._Rollouts()
|
old_images = self._OldImages()
|
||||||
for filename in os.listdir(self._image_dir):
|
for filename in os.listdir(self._image_dir):
|
||||||
match = self._FILE_REGEX.match(filename)
|
match = self._FILE_REGEX.match(filename)
|
||||||
if not match:
|
if not match:
|
||||||
@@ -68,8 +68,15 @@ class ManifestBuilder(object):
|
|||||||
timestamp = int(match.group('timestamp'))
|
timestamp = int(match.group('timestamp'))
|
||||||
image = {
|
image = {
|
||||||
'timestamp': timestamp,
|
'timestamp': timestamp,
|
||||||
'rollout_‱': rollouts.get(timestamp, FLAGS.default_rollout),
|
'rollout_‱': FLAGS.default_rollout,
|
||||||
}
|
}
|
||||||
|
ret['images'].append(image)
|
||||||
|
|
||||||
|
old_image = old_images.get(timestamp)
|
||||||
|
if old_image:
|
||||||
|
image.update(old_image)
|
||||||
|
continue
|
||||||
|
|
||||||
with open(os.path.join(self._image_dir, filename), 'rb') as fh:
|
with open(os.path.join(self._image_dir, filename), 'rb') as fh:
|
||||||
hash_obj = hashlib.sha256()
|
hash_obj = hashlib.sha256()
|
||||||
while True:
|
while True:
|
||||||
@@ -78,7 +85,7 @@ class ManifestBuilder(object):
|
|||||||
break
|
break
|
||||||
hash_obj.update(data)
|
hash_obj.update(data)
|
||||||
image['hash'] = hash_obj.hexdigest()
|
image['hash'] = hash_obj.hexdigest()
|
||||||
ret['images'].append(image)
|
|
||||||
ret['images'].sort(key=lambda x: x['timestamp'], reverse=True)
|
ret['images'].sort(key=lambda x: x['timestamp'], reverse=True)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ set timeout=5
|
|||||||
terminal_output console
|
terminal_output console
|
||||||
|
|
||||||
menuentry "Ubuntu Server Live Image" {
|
menuentry "Ubuntu Server Live Image" {
|
||||||
linux /casper/vmlinuz.efi boot=casper root=LABEL=ISOIMAGE
|
linux /casper/vmlinuz.efi boot=casper root=LABEL=ISOIMAGE nomodeset $KERNEL_ARGS
|
||||||
initrd /casper/initrd.lz
|
initrd /casper/initrd.lz
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ set timeout=5
|
|||||||
terminal_output console
|
terminal_output console
|
||||||
|
|
||||||
menuentry "Ubuntu Server Live Image" {
|
menuentry "Ubuntu Server Live Image" {
|
||||||
linux /casper/vmlinuz.efi boot=casper iso-scan/filename=${iso_path}
|
linux /casper/vmlinuz.efi boot=casper iso-scan/filename=${iso_path} nomodeset $KERNEL_ARGS
|
||||||
initrd /casper/initrd.lz
|
initrd /casper/initrd.lz
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,18 @@ parser.add_argument(
|
|||||||
dest='device',
|
dest='device',
|
||||||
action='store',
|
action='store',
|
||||||
required=True)
|
required=True)
|
||||||
|
parser.add_argument(
|
||||||
|
'--https-ca-cert',
|
||||||
|
dest='https_ca_cert',
|
||||||
|
action='store')
|
||||||
|
parser.add_argument(
|
||||||
|
'--https-client-cert',
|
||||||
|
dest='https_client_cert',
|
||||||
|
action='store')
|
||||||
|
parser.add_argument(
|
||||||
|
'--https-client-key',
|
||||||
|
dest='https_client_key',
|
||||||
|
action='store')
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--persistent-percent',
|
'--persistent-percent',
|
||||||
dest='persistent_percent',
|
dest='persistent_percent',
|
||||||
@@ -51,18 +63,46 @@ def main():
|
|||||||
'apt-get',
|
'apt-get',
|
||||||
'install',
|
'install',
|
||||||
'--assume-yes',
|
'--assume-yes',
|
||||||
'git', 'grub-pc', 'python3-openssl')
|
'git', 'grub-pc', 'python3-openssl', 'python3-requests')
|
||||||
|
|
||||||
|
os.makedirs(os.path.join(FLAGS.chroot_path, 'icon', 'config'), exist_ok=True)
|
||||||
|
|
||||||
|
if not os.path.exists(os.path.join(FLAGS.chroot_path, 'icon', 'iconograph')):
|
||||||
ExecChroot(
|
ExecChroot(
|
||||||
'git',
|
'git',
|
||||||
'clone',
|
'clone',
|
||||||
'https://github.com/robot-tools/iconograph.git',
|
'https://github.com/robot-tools/iconograph.git',
|
||||||
'autoimage')
|
'icon/iconograph')
|
||||||
|
|
||||||
os.mkdir(os.path.join(FLAGS.chroot_path, 'autoimage', 'config'))
|
|
||||||
shutil.copyfile(
|
shutil.copyfile(
|
||||||
FLAGS.ca_cert,
|
FLAGS.ca_cert,
|
||||||
os.path.join(FLAGS.chroot_path, 'autoimage', 'config', 'ca.cert.pem'))
|
os.path.join(FLAGS.chroot_path, 'icon', 'config', 'ca.image.cert.pem'))
|
||||||
|
|
||||||
|
image_flags = []
|
||||||
|
|
||||||
|
if FLAGS.https_ca_cert:
|
||||||
|
https_ca_cert_path = os.path.join('icon', 'config', 'ca.www.cert.pem')
|
||||||
|
shutil.copyfile(
|
||||||
|
FLAGS.https_ca_cert,
|
||||||
|
os.path.join(FLAGS.chroot_path, https_ca_cert_path))
|
||||||
|
image_flags.extend([
|
||||||
|
'--https-ca-cert', os.path.join('/', https_ca_cert_path),
|
||||||
|
])
|
||||||
|
|
||||||
|
if FLAGS.https_client_cert and FLAGS.https_client_key:
|
||||||
|
https_client_cert_path = os.path.join('icon', 'config', 'client.www.cert.pem')
|
||||||
|
shutil.copyfile(
|
||||||
|
FLAGS.https_client_cert,
|
||||||
|
os.path.join(FLAGS.chroot_path, https_client_cert_path))
|
||||||
|
https_client_key_path = os.path.join('icon', 'config', 'client.www.key.pem')
|
||||||
|
shutil.copyfile(
|
||||||
|
FLAGS.https_client_key,
|
||||||
|
os.path.join(FLAGS.chroot_path, https_client_key_path))
|
||||||
|
os.chmod(os.path.join(FLAGS.chroot_path, https_client_key_path), 0o400)
|
||||||
|
image_flags.extend([
|
||||||
|
'--https-client-cert', os.path.join('/', https_client_cert_path),
|
||||||
|
'--https-client-key', os.path.join('/', https_client_key_path),
|
||||||
|
])
|
||||||
|
|
||||||
parsed = parse.urlparse(FLAGS.base_url)
|
parsed = parse.urlparse(FLAGS.base_url)
|
||||||
|
|
||||||
@@ -71,23 +111,22 @@ def main():
|
|||||||
fh.write("""
|
fh.write("""
|
||||||
description "AutoImage"
|
description "AutoImage"
|
||||||
|
|
||||||
start on stopped rc RUNLEVEL=[2345]
|
start on runlevel [2345]
|
||||||
|
|
||||||
stop on runlevel [!2345]
|
|
||||||
|
|
||||||
script
|
script
|
||||||
chvt 7
|
exec </dev/tty8 >/dev/tty8 2>&1
|
||||||
/autoimage/client/wait_for_service.py --host=%(host)s --service=%(service)s </dev/tty7 >/dev/tty7 2>&1
|
chvt 8
|
||||||
chvt 7
|
/icon/iconograph/client/wait_for_service.py --host=%(host)s --service=%(service)s
|
||||||
/autoimage/imager/image.py --device=%(device)s --persistent-percent=%(persistent_percent)d --ca-cert=/autoimage/config/ca.cert.pem --base-url=%(base_url)s </dev/tty7 >/dev/tty7 2>&1
|
chvt 8
|
||||||
chvt 7
|
/icon/iconograph/imager/image.py --device=%(device)s --persistent-percent=%(persistent_percent)d --ca-cert=/icon/config/ca.image.cert.pem --base-url=%(base_url)s %(image_flags)s
|
||||||
|
chvt 8
|
||||||
|
|
||||||
echo >/dev/tty7
|
echo
|
||||||
echo "==================" >/dev/tty7
|
echo "=================="
|
||||||
echo "autoimage complete" >/dev/tty7
|
echo "autoimage complete"
|
||||||
echo "==================" >/dev/tty7
|
echo "=================="
|
||||||
|
|
||||||
/autoimage/client/alert.py --type=happy </dev/tty7 >/dev/tty7
|
/icon/iconograph/client/alert.py --type=happy
|
||||||
end script
|
end script
|
||||||
""" % {
|
""" % {
|
||||||
'host': parsed.hostname,
|
'host': parsed.hostname,
|
||||||
@@ -95,6 +134,7 @@ end script
|
|||||||
'device': FLAGS.device,
|
'device': FLAGS.device,
|
||||||
'persistent_percent': FLAGS.persistent_percent,
|
'persistent_percent': FLAGS.persistent_percent,
|
||||||
'base_url': FLAGS.base_url,
|
'base_url': FLAGS.base_url,
|
||||||
|
'image_flags': ' '.join(image_flags),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
144
server/modules/certclient.py
Executable file
144
server/modules/certclient.py
Executable file
@@ -0,0 +1,144 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
from urllib import parse
|
||||||
|
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description='iconograph autoimage')
|
||||||
|
parser.add_argument(
|
||||||
|
'--chroot-path',
|
||||||
|
dest='chroot_path',
|
||||||
|
action='store',
|
||||||
|
required=True)
|
||||||
|
parser.add_argument(
|
||||||
|
'--ca-cert',
|
||||||
|
dest='ca_cert',
|
||||||
|
action='store',
|
||||||
|
required=True)
|
||||||
|
parser.add_argument(
|
||||||
|
'--client-cert',
|
||||||
|
dest='client_cert',
|
||||||
|
action='store',
|
||||||
|
required=True)
|
||||||
|
parser.add_argument(
|
||||||
|
'--client-key',
|
||||||
|
dest='client_key',
|
||||||
|
action='store',
|
||||||
|
required=True)
|
||||||
|
parser.add_argument(
|
||||||
|
'--subject',
|
||||||
|
dest='subject',
|
||||||
|
action='store',
|
||||||
|
required=True)
|
||||||
|
parser.add_argument(
|
||||||
|
'--tag',
|
||||||
|
dest='tag',
|
||||||
|
action='store',
|
||||||
|
required=True)
|
||||||
|
parser.add_argument(
|
||||||
|
'--server',
|
||||||
|
dest='server',
|
||||||
|
action='store',
|
||||||
|
required=True)
|
||||||
|
FLAGS = parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def Exec(*args, **kwargs):
|
||||||
|
print('+', args)
|
||||||
|
subprocess.check_call(args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def ExecChroot(*args, **kwargs):
|
||||||
|
Exec('chroot', FLAGS.chroot_path, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ExecChroot(
|
||||||
|
'apt-get',
|
||||||
|
'install',
|
||||||
|
'--assume-yes',
|
||||||
|
'git', 'python3-requests', 'openssl')
|
||||||
|
|
||||||
|
os.makedirs(os.path.join(FLAGS.chroot_path, 'icon', 'config'), exist_ok=True)
|
||||||
|
|
||||||
|
if not os.path.exists(os.path.join(FLAGS.chroot_path, 'icon', 'iconograph')):
|
||||||
|
ExecChroot(
|
||||||
|
'git',
|
||||||
|
'clone',
|
||||||
|
'https://github.com/robot-tools/iconograph.git',
|
||||||
|
'icon/iconograph')
|
||||||
|
|
||||||
|
if not os.path.exists(os.path.join(FLAGS.chroot_path, 'icon', 'certserver')):
|
||||||
|
ExecChroot(
|
||||||
|
'git',
|
||||||
|
'clone',
|
||||||
|
'https://github.com/robot-tools/certserver.git',
|
||||||
|
'icon/certserver')
|
||||||
|
|
||||||
|
ca_cert_path = os.path.join('icon', 'config', 'ca.%s.certserver.cert.pem' % FLAGS.tag)
|
||||||
|
shutil.copyfile(
|
||||||
|
FLAGS.ca_cert,
|
||||||
|
os.path.join(FLAGS.chroot_path, ca_cert_path))
|
||||||
|
|
||||||
|
client_cert_path = os.path.join('icon', 'config', 'client.%s.certserver.cert.pem' % FLAGS.tag)
|
||||||
|
shutil.copyfile(
|
||||||
|
FLAGS.client_cert,
|
||||||
|
os.path.join(FLAGS.chroot_path, client_cert_path))
|
||||||
|
client_key_path = os.path.join('icon', 'config', 'client.%s.certserver.key.pem' % FLAGS.tag)
|
||||||
|
shutil.copyfile(
|
||||||
|
FLAGS.client_key,
|
||||||
|
os.path.join(FLAGS.chroot_path, client_key_path))
|
||||||
|
os.chmod(os.path.join(FLAGS.chroot_path, client_key_path), 0o400)
|
||||||
|
|
||||||
|
parsed = parse.urlparse(FLAGS.server)
|
||||||
|
|
||||||
|
init = os.path.join(FLAGS.chroot_path, 'etc', 'init', 'certclient.%s.conf' % FLAGS.tag)
|
||||||
|
with open(init, 'w') as fh:
|
||||||
|
fh.write("""
|
||||||
|
description "CertClient %(tag)s"
|
||||||
|
|
||||||
|
start on systemid-ready
|
||||||
|
|
||||||
|
script
|
||||||
|
exec </dev/tty9 >/dev/tty9 2>&1
|
||||||
|
chvt 9
|
||||||
|
|
||||||
|
KEY="/systemid/$(hostname).%(tag)s.key.pem"
|
||||||
|
CERT="/systemid/$(hostname).%(tag)s.cert.pem"
|
||||||
|
SUBJECT="$(echo '%(subject)s' | sed s/SYSTEMID/$(hostname)/g)"
|
||||||
|
|
||||||
|
if test ! -s "${KEY}"; then
|
||||||
|
openssl ecparam -name secp384r1 -genkey | openssl ec -out "${KEY}"
|
||||||
|
chmod 0400 "${KEY}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
chvt 9
|
||||||
|
/icon/iconograph/client/wait_for_service.py --host=%(host)s --service=%(service)s
|
||||||
|
chvt 9
|
||||||
|
|
||||||
|
if test ! -s "${CERT}"; then
|
||||||
|
openssl req -new -key "${KEY}" -subj "${SUBJECT}" | /icon/certserver/certclient.py --ca-cert=/icon/config/ca.%(tag)s.certserver.cert.pem --client-cert=/icon/config/client.%(tag)s.certserver.cert.pem --client-key=/icon/config/client.%(tag)s.certserver.key.pem --server=%(server)s > "${CERT}"
|
||||||
|
chmod 0444 "${CERT}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
chvt 9
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "=================="
|
||||||
|
echo "certclient %(tag)s complete"
|
||||||
|
echo "=================="
|
||||||
|
end script
|
||||||
|
""" % {
|
||||||
|
'host': parsed.hostname,
|
||||||
|
'service': parsed.port or parsed.scheme,
|
||||||
|
'subject': FLAGS.subject,
|
||||||
|
'tag': FLAGS.tag,
|
||||||
|
'server': FLAGS.server,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -22,6 +22,10 @@ parser.add_argument(
|
|||||||
dest='chroot_path',
|
dest='chroot_path',
|
||||||
action='store',
|
action='store',
|
||||||
required=True)
|
required=True)
|
||||||
|
parser.add_argument(
|
||||||
|
'--https-ca-cert',
|
||||||
|
dest='https_ca_cert',
|
||||||
|
action='store')
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--max-images',
|
'--max-images',
|
||||||
dest='max_images',
|
dest='max_images',
|
||||||
@@ -45,19 +49,28 @@ def main():
|
|||||||
'apt-get',
|
'apt-get',
|
||||||
'install',
|
'install',
|
||||||
'--assume-yes',
|
'--assume-yes',
|
||||||
'daemontools-run', 'git', 'python3-openssl')
|
'daemontools-run', 'git', 'python3-openssl', 'python3-requests')
|
||||||
|
|
||||||
|
os.makedirs(os.path.join(FLAGS.chroot_path, 'icon', 'config'), exist_ok=True)
|
||||||
|
|
||||||
|
if not os.path.exists(os.path.join(FLAGS.chroot_path, 'icon', 'iconograph')):
|
||||||
ExecChroot(
|
ExecChroot(
|
||||||
'git',
|
'git',
|
||||||
'clone',
|
'clone',
|
||||||
'https://github.com/robot-tools/iconograph.git')
|
'https://github.com/robot-tools/iconograph.git',
|
||||||
|
'icon/iconograph')
|
||||||
|
|
||||||
os.mkdir(os.path.join(FLAGS.chroot_path, 'iconograph', 'config'))
|
|
||||||
shutil.copyfile(
|
shutil.copyfile(
|
||||||
FLAGS.ca_cert,
|
FLAGS.ca_cert,
|
||||||
os.path.join(FLAGS.chroot_path, 'iconograph', 'config', 'ca.cert.pem'))
|
os.path.join(FLAGS.chroot_path, 'icon', 'config', 'ca.image.cert.pem'))
|
||||||
|
|
||||||
path = os.path.join(FLAGS.chroot_path, 'iconograph', 'client', 'flags')
|
if FLAGS.https_ca_cert:
|
||||||
|
shutil.copyfile(
|
||||||
|
FLAGS.https_ca_cert,
|
||||||
|
os.path.join(FLAGS.chroot_path, 'icon', 'config', 'ca.www.cert.pem'))
|
||||||
|
|
||||||
|
|
||||||
|
path = os.path.join(FLAGS.chroot_path, 'icon', 'config', 'fetcher.flags')
|
||||||
with open(path, 'w') as fh:
|
with open(path, 'w') as fh:
|
||||||
fh.write('--base-url=%(base_url)s --max-images=%(max_images)d\n' % {
|
fh.write('--base-url=%(base_url)s --max-images=%(max_images)d\n' % {
|
||||||
'base_url': FLAGS.base_url,
|
'base_url': FLAGS.base_url,
|
||||||
@@ -65,8 +78,8 @@ def main():
|
|||||||
})
|
})
|
||||||
|
|
||||||
os.symlink(
|
os.symlink(
|
||||||
'/iconograph/client',
|
'/icon/iconograph/client',
|
||||||
os.path.join(FLAGS.chroot_path, 'etc', 'service', 'iconograph'))
|
os.path.join(FLAGS.chroot_path, 'etc', 'service', 'iconograph-client'))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
43
server/modules/openssh.py
Executable file
43
server/modules/openssh.py
Executable file
@@ -0,0 +1,43 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import glob
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description='iconograph openssh')
|
||||||
|
parser.add_argument(
|
||||||
|
'--chroot-path',
|
||||||
|
dest='chroot_path',
|
||||||
|
action='store',
|
||||||
|
required=True)
|
||||||
|
FLAGS = parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def Exec(*args, **kwargs):
|
||||||
|
print('+', args)
|
||||||
|
subprocess.check_call(args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def ExecChroot(*args, **kwargs):
|
||||||
|
Exec('chroot', FLAGS.chroot_path, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ExecChroot(
|
||||||
|
'apt-get',
|
||||||
|
'install',
|
||||||
|
'--assume-yes',
|
||||||
|
'openssh-server')
|
||||||
|
|
||||||
|
for path in glob.glob(os.path.join(FLAGS.chroot_path, 'etc', 'ssh', 'ssh_host_*')):
|
||||||
|
os.unlink(path)
|
||||||
|
|
||||||
|
os.symlink(
|
||||||
|
'/systemid/ssh_host_ed25519_key',
|
||||||
|
os.path.join(FLAGS.chroot_path, 'etc', 'ssh', 'ssh_host_ed25519_key'))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -22,9 +22,13 @@ def main():
|
|||||||
description "Mount /persistent"
|
description "Mount /persistent"
|
||||||
|
|
||||||
start on filesystem
|
start on filesystem
|
||||||
|
task
|
||||||
|
|
||||||
|
emits persistent-ready
|
||||||
|
|
||||||
script
|
script
|
||||||
mount LABEL=PERSISTENT /persistent
|
mount LABEL=PERSISTENT /persistent
|
||||||
|
initctl emit --no-wait persistent-ready
|
||||||
end script
|
end script
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ def main():
|
|||||||
description "Mount /systemid"
|
description "Mount /systemid"
|
||||||
|
|
||||||
start on filesystem
|
start on filesystem
|
||||||
|
task
|
||||||
|
|
||||||
|
emits systemid-ready
|
||||||
|
|
||||||
script
|
script
|
||||||
mount LABEL=SYSTEMID /systemid
|
mount LABEL=SYSTEMID /systemid
|
||||||
@@ -29,6 +32,7 @@ script
|
|||||||
echo ${SYSTEMID} > /etc/hostname
|
echo ${SYSTEMID} > /etc/hostname
|
||||||
hostname --file /etc/hostname
|
hostname --file /etc/hostname
|
||||||
grep ${SYSTEMID} /etc/hosts >/dev/null || echo "127.0.2.1 ${SYSTEMID}" >> /etc/hosts
|
grep ${SYSTEMID} /etc/hosts >/dev/null || echo "127.0.2.1 ${SYSTEMID}" >> /etc/hosts
|
||||||
|
initctl emit --no-wait systemid-ready
|
||||||
end script
|
end script
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
|||||||
@@ -121,6 +121,14 @@ SYSTEMID=%(system_id)s
|
|||||||
'system_id': new_id,
|
'system_id': new_id,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def _GenerateSSHKey(self, root):
|
||||||
|
self._Exec(
|
||||||
|
'ssh-keygen',
|
||||||
|
'-f', os.path.join(root, 'ssh_host_ed25519_key'),
|
||||||
|
'-N', '',
|
||||||
|
'-t', 'ed25519',
|
||||||
|
)
|
||||||
|
|
||||||
def _Image(self):
|
def _Image(self):
|
||||||
self._PartitionAndMkFS()
|
self._PartitionAndMkFS()
|
||||||
root = self._Mount()
|
root = self._Mount()
|
||||||
@@ -136,6 +144,7 @@ New ID: \033[91m%s\033[00m
|
|||||||
==============
|
==============
|
||||||
|
|
||||||
""" % new_id)
|
""" % new_id)
|
||||||
|
self._GenerateSSHKey(root)
|
||||||
|
|
||||||
def Image(self):
|
def Image(self):
|
||||||
self._umount = []
|
self._umount = []
|
||||||
|
|||||||
70
util/fetch_archive.py
Executable file
70
util/fetch_archive.py
Executable file
@@ -0,0 +1,70 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description='iconograph fetch_archive')
|
||||||
|
parser.add_argument(
|
||||||
|
'--dest-dir',
|
||||||
|
dest='dest_dir',
|
||||||
|
action='store',
|
||||||
|
default='.')
|
||||||
|
parser.add_argument(
|
||||||
|
'--https-ca-cert',
|
||||||
|
dest='https_ca_cert',
|
||||||
|
action='store')
|
||||||
|
parser.add_argument(
|
||||||
|
'--https-client-cert',
|
||||||
|
dest='https_client_cert',
|
||||||
|
action='store')
|
||||||
|
parser.add_argument(
|
||||||
|
'--https-client-key',
|
||||||
|
dest='https_client_key',
|
||||||
|
action='store')
|
||||||
|
parser.add_argument(
|
||||||
|
'--url',
|
||||||
|
dest='url',
|
||||||
|
action='store',
|
||||||
|
required=True)
|
||||||
|
FLAGS = parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
class ArchiveFetcher(object):
|
||||||
|
|
||||||
|
_BUF_SIZE = 2 ** 16
|
||||||
|
|
||||||
|
def __init__(self, https_ca_cert, https_client_cert, https_client_key):
|
||||||
|
self._session = requests.Session()
|
||||||
|
if https_ca_cert:
|
||||||
|
self._session.verify = https_ca_cert
|
||||||
|
if https_client_cert and https_client_key:
|
||||||
|
self._session.cert = (https_client_cert, https_client_key)
|
||||||
|
|
||||||
|
def Fetch(self, url, dest_dir='.'):
|
||||||
|
resp = self._session.get(url, stream=True)
|
||||||
|
|
||||||
|
tar = subprocess.Popen(
|
||||||
|
['tar', '--extract'],
|
||||||
|
stdin=subprocess.PIPE,
|
||||||
|
cwd=dest_dir)
|
||||||
|
for data in resp.iter_content(self._BUF_SIZE):
|
||||||
|
tar.stdin.write(data)
|
||||||
|
tar.wait()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
fetcher = ArchiveFetcher(
|
||||||
|
FLAGS.https_ca_cert,
|
||||||
|
FLAGS.https_client_cert,
|
||||||
|
FLAGS.https_client_key)
|
||||||
|
fetcher.Fetch(
|
||||||
|
FLAGS.url,
|
||||||
|
FLAGS.dest_dir)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
BIN
www/code-39.woff
Normal file
BIN
www/code-39.woff
Normal file
Binary file not shown.
101
www/systemid.html
Normal file
101
www/systemid.html
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>SystemID</title>
|
||||||
|
<script src="https://use.typekit.net/hsz4ulo.js"></script>
|
||||||
|
<script>try{Typekit.load({ async: true });}catch(e){}</script>
|
||||||
|
<style>
|
||||||
|
@font-face {
|
||||||
|
font-family: 'code-39';
|
||||||
|
src: url('code-39.woff') format('woff');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.barcode {
|
||||||
|
font-family: 'code-39';
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
[contenteditable=true]:empty::before{
|
||||||
|
content: attr(placeholder);
|
||||||
|
color: grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen {
|
||||||
|
.sysid {
|
||||||
|
border: 1px dashed #AAA;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sysid {
|
||||||
|
font-family: 'droid-sans-mono';
|
||||||
|
font-size: 140px;
|
||||||
|
text-align: center;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sysid:focus {
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block {
|
||||||
|
width: 600px;
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="block">
|
||||||
|
<div id="barcode0.0" class="top barcode"></div>
|
||||||
|
<div id="sysid0" class="sysid" contenteditable="true" placeholder="SYSID#"></div>
|
||||||
|
<div id="barcode0.1" class="bottom barcode"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="block">
|
||||||
|
<div id="barcode1.0" class="top barcode"></div>
|
||||||
|
<div id="sysid1" class="sysid" contenteditable="true" placeholder="SYSID#"></div>
|
||||||
|
<div id="barcode1.1" class="bottom barcode"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="block">
|
||||||
|
<div id="barcode2.0" class="top barcode"></div>
|
||||||
|
<div id="sysid2" class="sysid" contenteditable="true" placeholder="SYSID#"></div>
|
||||||
|
<div id="barcode2.1" class="bottom barcode"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
"use strict";
|
||||||
|
document.addEventListener('DOMContentLoaded', (e) => {
|
||||||
|
for (let i = 0; true; i++) {
|
||||||
|
let sysid = document.getElementById('sysid' + i);
|
||||||
|
if (!sysid) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let barcodes = [];
|
||||||
|
for (let j = 0; true; j++) {
|
||||||
|
let barcode = document.getElementById('barcode' + i + '.' + j);
|
||||||
|
if (!barcode) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
barcodes.push(barcode);
|
||||||
|
barcode.innerText = '**';
|
||||||
|
}
|
||||||
|
sysid.addEventListener('input', (e) => {
|
||||||
|
let barcodeText = '*' + sysid.innerText.toUpperCase() + '*';
|
||||||
|
barcodes.forEach((barcode) => {
|
||||||
|
barcode.innerText = barcodeText;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user