Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add gvproxy to windows packages #16807

Merged
merged 2 commits into from
Jan 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@ $(MANPAGES): %: %.md .install.md2man docdir
-e 's/\[\([^]]*\)](http[^)]\+)/\1/g' \
-e 's;<\(/\)\?\(a\|a\s\+[^>]*\|sup\)>;;g' \
-e 's/\\$$/ /g' $< |\
$(GOMD2MAN) -in /dev/stdin -out $(subst source/markdown,build/man,$@)
$(GOMD2MAN) -out $(subst source/markdown,build/man,$@)

.PHONY: docdir
docdir:
Expand Down Expand Up @@ -731,7 +731,7 @@ podman-remote-release-%.zip: test/version/version ## Build podman-remote for %=$
$(MAKE) $(GOPLAT) podman-remote; \
fi
if [[ "$(GOOS)" == "windows" ]]; then \
$(MAKE) $(GOPLAT) TMPDIR="" win-sshproxy; \
$(MAKE) $(GOPLAT) TMPDIR="" win-gvproxy; \
fi
if [[ "$(GOOS)" == "darwin" ]]; then \
$(MAKE) $(GOPLAT) podman-mac-helper;\
Expand All @@ -751,7 +751,7 @@ podman.msi: test/version/version ## Build podman-remote, package for installati
podman-v%.msi: test/version/version
# Passing explicitly OS and ARCH, because ARM is not supported by wixl https://gitlab.gnome.org/GNOME/msitools/-/blob/master/tools/wixl/builder.vala#L3
$(MAKE) GOOS=windows GOARCH=amd64 podman-remote-windows-docs
$(MAKE) GOOS=windows GOARCH=amd64 clean-binaries podman-remote podman-winpath win-sshproxy
$(MAKE) GOOS=windows GOARCH=amd64 clean-binaries podman-remote podman-winpath win-gvproxy
$(eval DOCFILE := docs/build/remote/windows)
find $(DOCFILE) -print | \
wixl-heat --var var.ManSourceDir --component-group ManFiles \
Expand All @@ -761,16 +761,17 @@ podman-v%.msi: test/version/version
-o $@ contrib/msi/podman.wxs $(DOCFILE)/pages.wsx --arch x64

# Checks out and builds win-sshproxy helper. See comment on GV_GITURL declaration
.PHONY: win-sshproxy
win-sshproxy: test/version/version
.PHONY: win-gvproxy
win-gvproxy: test/version/version
rm -rf tmp-gv; mkdir tmp-gv
(cd tmp-gv; \
git init; \
git remote add origin $(GV_GITURL); \
git fetch --depth 1 origin $(GV_SHA); \
git checkout FETCH_HEAD; make win-sshproxy)
git checkout FETCH_HEAD; make win-gvproxy win-sshproxy)
mkdir -p bin/windows/
cp tmp-gv/bin/win-sshproxy.exe bin/windows/
cp tmp-gv/bin/gvproxy.exe bin/windows/
rm -rf tmp-gv

.PHONY: package
Expand Down Expand Up @@ -804,6 +805,8 @@ install.remote:
$(DESTDIR)$(BINDIR)/podman$(BINSFX)
test "${GOOS}" != "windows" || \
install -m 755 $(SRCBINDIR)/win-sshproxy.exe $(DESTDIR)$(BINDIR)
test "${GOOS}" != "windows" || \
install -m 755 $(SRCBINDIR)/gvproxy.exe $(DESTDIR)$(BINDIR)
test "${GOOS}" != "darwin" || \
install -m 755 $(SRCBINDIR)/podman-mac-helper $(DESTDIR)$(BINDIR)
test -z "${SELINUXOPT}" || \
Expand Down
4 changes: 4 additions & 0 deletions contrib/msi/podman.wxs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@
<Component Id="WinSshProxyExecutable" Guid="0DA730AB-2F97-40E8-A8FC-356E88EAA4D2" Win64="Yes">
<File Id="4A2AD125-34E7-4BD8-BE28-B2A9A5EDBEB5" Name="win-sshproxy.exe" Source="bin/windows/win-sshproxy.exe" KeyPath="yes"/>
</Component>
<Component Id="GvProxyExecutable" Guid="1A4A2975-AD2D-44AA-974B-9B343C098333" Win64="Yes">
<File Id="0C9BDFB8-1DBD-4E51-BE7B-3F55478DACB7" Name="gvproxy.exe" Source="bin/windows/gvproxy.exe" KeyPath="yes"/>
</Component>
</Directory>
</Directory>
</Directory>
Expand All @@ -47,6 +50,7 @@
<ComponentRef Id="MainExecutable"/>
<ComponentRef Id="WinPathExecutable"/>
<ComponentRef Id="WinSshProxyExecutable"/>
<ComponentRef Id="GvProxyExecutable"/>
<ComponentGroupRef Id="ManFiles"/>
</Feature>

Expand Down
6 changes: 6 additions & 0 deletions contrib/win-installer/build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,12 @@ SignItem @("artifacts/win-sshproxy.exe",
"artifacts/podman.exe",
"artifacts/podman-msihooks.dll",
"artifacts/podman-wslkerninst.exe")
$gvExists = Test-Path "artifacts/gvproxy.exe"
if ($gvExists) {
SignItem @("artifacts/gvproxy.exe")
} else {
$env:UseGVProxy = "Skip"
}

.\build-msi.bat $ENV:INSTVER; ExitOnError
SignItem @("podman.msi")
Expand Down
14 changes: 14 additions & 0 deletions contrib/win-installer/podman.wxs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
<?error VERSION must be defined via command line argument?>
<?endif?>

<?ifdef env.UseGVProxy?>
<?define UseGVProxy = "$(env.UseGVProxy)"?>
<?else?>
<?define UseGVProxy = ""?>
<?endif?>

<Product Name="Podman $(var.VERSION)" Id="*" UpgradeCode="696BAB5D-CA1F-4B05-B123-320F245B8D6D" Version="$(var.VERSION)" Language="1033" Manufacturer="Red Hat Inc.">

<Package Id="*" Platform="x64" Keywords="Installer" Description="Red Hat's Podman $(var.VERSION) Installer" Comments="Apache 2.0 License" Manufacturer="Red Hat Inc." InstallScope="perMachine" InstallerVersion="200" Compressed="yes"/>
Expand All @@ -30,6 +36,11 @@
<Component Id="WinSshProxyExecutable" Guid="0DA730AB-2F97-40E8-A8FC-356E88EAA4D2" Win64="yes">
<File Id="WinSshProxyExecutableFile" Name="win-sshproxy.exe" Source="artifacts/win-sshproxy.exe" KeyPath="yes"/>
</Component>
<?if $(var.UseGVProxy) != Skip?>
<Component Id="GvProxyExecutable" Guid="1A4A2975-AD2D-44AA-974B-9B343C098333" Win64="yes">
<File Id="GvProxyExecutableFile" Name="gvproxy.exe" Source="artifacts/gvproxy.exe" KeyPath="yes"/>
</Component>
<?endif?>
<Component Id="GuideHTMLComponent" Guid="8B23C76B-F7D4-4030-8C46-1B5729E616B5" Win64="yes">
<File Id="GuideHTMLFile" Name="welcome-podman.html" Source="docs/podman-for-windows.html" KeyPath="yes"/>
</Component>
Expand Down Expand Up @@ -60,6 +71,9 @@
<ComponentRef Id="EnvEntriesComponent"/>
<ComponentRef Id="MainExecutable"/>
<ComponentRef Id="WinSshProxyExecutable"/>
<?if $(var.UseGVProxy) != Skip?>
<ComponentRef Id="GvProxyExecutable"/>
<?endif?>
<ComponentRef Id="GuideHTMLComponent"/>
<ComponentGroupRef Id="ManFiles"/>
<ComponentGroupRef Id="WSLFeature"/>
Expand Down
7 changes: 7 additions & 0 deletions contrib/win-installer/process-release.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,13 @@ try {
Copy-Artifact($fileName)
}

$loc = Get-ChildItem -Recurse -Path . -Name gvproxy.exe
if (!$loc) {
Write-Host "Skipping gvproxy.exe artifact"
} else {
Copy-Artifact("gvproxy.exe")
}

$docsloc = Get-ChildItem -Path . -Name docs -Recurse
$loc = Get-ChildItem -Recurse -Path . -Name podman-for-windows.html
if (!$loc) {
Expand Down
2 changes: 1 addition & 1 deletion docs/remote-docs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ SOURCES=${@:3} ## directories to find markdown files
# invoked in a cross-compilation environment, so even if PLATFORM=windows
# we need an actual executable that we can invoke).
if [[ -z "$PODMAN" ]]; then
DETECTED_OS=$(env -i HOME="$HOME" PATH="$PATH" go env GOOS)
DETECTED_OS=$(env -i HOME="$HOME" PATH="$PATH" GOROOT="$GOROOT" go env GOOS)
case $DETECTED_OS in
windows)
PODMAN=bin/windows/podman.exe ;;
Expand Down
132 changes: 132 additions & 0 deletions docs/tutorials/qemu-remote-tutorial.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# Podman-remote client for Windows with QEMU VM

***
**_NOTE:_** For running Podman on Windows, refer to the [Podman for Windows](podman-for-windows.md) guide, which uses the recommended approach of a Podman-managed Linux backend. For Mac, see the [Podman installation instructions](https://podman.io/getting-started/installation). This guide covers the advanced usage of Podman with a custom Linux VM.
***

## Introduction

This is an experimental setup using QEMU VM for running Podman for the already supported [Podman-remote](https://docs.podman.io/en/latest/markdown/podman-remote.1.html) client on Windows.
The officially supported and recommended way of running Podman on Windows is using [Podman machine](https://docs.podman.io/en/latest/markdown/podman-machine.1.html).

## Prerequisites

* Windows 10 Build 18362 or later (Build 19044/Version 21H2 or later recommended)
* SSH client feature installed on the machine
* Hyper-V acceleration should be operational on the machine
* Direcroty `C:\qemu-remote\` will be used for storing needed assets
* Port `57561` is free to use for ssh over a loopback interface

## Obtaining and installing

### QEMU

Download QEMU (7.2.0 minimal) from https://qemu.weilnetz.de/w64/

Then download the Fedora CoreOS (FCOS) image for QEMU from https://getfedora.org/coreos/download?tab=metal_virtualized&stream=testing&arch=x86_64

One will need `.xz` format extraction tool like xz itself or 7-zip. Use it to extract the `.qcow2` image to C:\qemu-remote\fedora-coreos-37.20221127.2.0-qemu.x86_64.qcow2

With xz the command line (when run from the same directory) will be
```
xz -d fedora-coreos-37.20221127.2.0-qemu.x86_64.qcow2.xz
```

### Podman

Download and install the latest release of Podman for Windows. Podman releases can be obtained from the official Podman GitHub release page: https://github.com/containers/podman/releases

#### Older Podman releases

When using older Podman releases (4.3.x and earlier), where `gvproxy.exe` is missing from the installation directory,
it could be obtained from the official releases https://github.com/containers/gvisor-tap-vsock/releases
One would need version `0.5.0` or a more recent release. Download `gvproxy-windows.exe` and copy it to
the Podman installation directory (or any other location, which is added to the PATH environment variable)
renaming the binary to `gvproxy.exe`.

### SSH

Generate ssh keys with an empty passphrase

ssh-keygen -t ed25519 -f C:\qemu-remote\remote

### Ingition for FCOS

Create ignition file C:\qemu-remote\remote.ign with the content of
```
{"ignition":{"config":{"replace":{"verification":{}}},"proxy":{},"security":{"tls":{}},"timeouts":{},"version":"3.2.0"},"passwd":{"users":[{"name":"core","sshAuthorizedKeys":["YOURSSHKEYHERE"],"uid":501},{"name":"root","sshAuthorizedKeys":["YOURSSHKEYHERE"]}]},"storage":{"directories":[{"group":{"name":"core"},"path":"/home/core/.config","user":{"name":"core"},"mode":493},{"group":{"name":"core"},"path":"/home/core/.config/containers","user":{"name":"core"},"mode":493},{"group":{"name":"core"},"path":"/home/core/.config/systemd","user":{"name":"core"},"mode":493},{"group":{"name":"core"},"path":"/home/core/.config/systemd/user","user":{"name":"core"},"mode":493},{"group":{"name":"core"},"path":"/home/core/.config/systemd/user/default.target.wants","user":{"name":"core"},"mode":493},{"group":{"name":"root"},"path":"/etc/containers/registries.conf.d","user":{"name":"root"},"mode":493},{"group":{"name":"root"},"path":"/etc/systemd/system.conf.d","user":{"name":"root"},"mode":493},{"group":{"name":"root"},"path":"/etc/environment.d","user":{"name":"root"},"mode":493}],"files":[{"group":{"name":"core"},"path":"/home/core/.config/systemd/user/linger-example.service","user":{"name":"core"},"contents":{"source":"data:,%5BUnit%5D%0ADescription=A%20systemd%20user%20unit%20demo%0AAfter=network-online.target%0AWants=network-online.target%20podman.socket%0A%5BService%5D%0AExecStart=%2Fusr%2Fbin%2Fsleep%20infinity%0A","verification":{}},"mode":484},{"group":{"name":"core"},"path":"/home/core/.config/containers/containers.conf","user":{"name":"core"},"contents":{"source":"data:,%5Bcontainers%5D%0Anetns=%22bridge%22%0A","verification":{}},"mode":484},{"group":{"name":"root"},"overwrite":true,"path":"/etc/subuid","user":{"name":"root"},"contents":{"source":"data:,core:100000:1000000","verification":{}},"mode":484},{"group":{"name":"root"},"overwrite":true,"path":"/etc/subgid","user":{"name":"root"},"contents":{"source":"data:,core:100000:1000000","verification":{}},"mode":484},{"group":{"name":"root"},"path":"/etc/systemd/system/[email protected]/delegate.conf","user":{"name":"root"},"contents":{"source":"data:,%5BService%5D%0ADelegate=memory%20pids%20cpu%20io%0A","verification":{}},"mode":420},{"group":{"name":"core"},"path":"/var/lib/systemd/linger/core","user":{"name":"core"},"contents":{"verification":{}},"mode":420},{"group":{"name":"root"},"path":"/etc/containers/containers.conf","user":{"name":"root"},"contents":{"source":"data:,%5Bengine%5D%0Amachine_enabled=true%0A","verification":{}},"mode":420},{"group":{"name":"root"},"path":"/etc/containers/podman-machine","user":{"name":"root"},"contents":{"source":"data:,qemu%0A","verification":{}},"mode":420},{"group":{"name":"root"},"path":"/etc/containers/registries.conf.d/999-podman-machine.conf","user":{"name":"root"},"contents":{"source":"data:,unqualified-search-registries=%5B%22docker.io%22%5D%0A","verification":{}},"mode":420},{"group":{},"path":"/etc/tmpfiles.d/podman-docker.conf","user":{},"contents":{"source":"data:,L+%20%20%2Frun%2Fdocker.sock%20%20%20-%20%20%20%20-%20%20%20%20-%20%20%20%20%20-%20%20%20%2Frun%2Fpodman%2Fpodman.sock%0A","verification":{}},"mode":420},{"group":{"name":"root"},"path":"/etc/profile.d/docker-host.sh","user":{"name":"root"},"contents":{"source":"data:,export%20DOCKER_HOST=%22unix:%2F%2F$%28podman%20info%20-f%20%22%7B%7B.Host.RemoteSocket.Path%7D%7D%22%29%22%0A","verification":{}},"mode":420}],"links":[{"group":{"name":"core"},"path":"/home/core/.config/systemd/user/default.target.wants/linger-example.service","user":{"name":"core"},"hard":false,"target":"/home/core/.config/systemd/user/linger-example.service"},{"group":{"name":"root"},"overwrite":true,"path":"/usr/local/bin/docker","user":{"name":"root"},"hard":false,"target":"/usr/bin/podman"},{"group":{"name":"root"},"overwrite":false,"path":"/etc/localtime","user":{"name":"root"},"hard":false,"target":"\\usr\\share\\zoneinfo"}]},"systemd":{"units":[{"enabled":true,"name":"podman.socket"},{"contents":"[Unit]\nRequires=dev-virtio\\\\x2dports-vport1p1.device\nAfter=remove-moby.service sshd.socket sshd.service\nOnFailure=emergency.target\nOnFailureJobMode=isolate\n[Service]\nType=oneshot\nRemainAfterExit=yes\nExecStart=/bin/sh -c '/usr/bin/echo Ready \u003e/dev/vport1p1'\n[Install]\nRequiredBy=default.target\n","enabled":true,"name":"ready.service"},{"enabled":false,"mask":true,"name":"docker.service"},{"enabled":false,"mask":true,"name":"docker.socket"},{"contents":"[Unit]\nDescription=Remove moby-engine\n# Run once for the machine\nAfter=systemd-machine-id-commit.service\nBefore=zincati.service\nConditionPathExists=!/var/lib/%N.stamp\n\n[Service]\nType=oneshot\nRemainAfterExit=yes\nExecStart=/usr/bin/rpm-ostree override remove moby-engine\nExecStart=/usr/bin/rpm-ostree ex apply-live --allow-replacement\nExecStartPost=/bin/touch /var/lib/%N.stamp\n\n[Install]\nWantedBy=default.target\n","enabled":true,"name":"remove-moby.service"},{"contents":"[Unit]\nDescription=Environment setter from QEMU FW_CFG\n[Service]\nType=oneshot\nRemainAfterExit=yes\nEnvironment=FWCFGRAW=/sys/firmware/qemu_fw_cfg/by_name/opt/com.coreos/environment/raw\nEnvironment=SYSTEMD_CONF=/etc/systemd/system.conf.d/default-env.conf\nEnvironment=ENVD_CONF=/etc/environment.d/default-env.conf\nEnvironment=PROFILE_CONF=/etc/profile.d/default-env.sh\nExecStart=/usr/bin/bash -c '/usr/bin/test -f ${FWCFGRAW} \u0026\u0026\\\n\techo \"[Manager]\\n#Got from QEMU FW_CFG\\nDefaultEnvironment=$(/usr/bin/base64 -d ${FWCFGRAW} | sed -e \"s+|+ +g\")\\n\" \u003e ${SYSTEMD_CONF} ||\\\n\techo \"[Manager]\\n#Got nothing from QEMU FW_CFG\\n#DefaultEnvironment=\\n\" \u003e ${SYSTEMD_CONF}'\nExecStart=/usr/bin/bash -c '/usr/bin/test -f ${FWCFGRAW} \u0026\u0026 (\\\n\techo \"#Got from QEMU FW_CFG\"\u003e ${ENVD_CONF};\\\n\tIFS=\"|\";\\\n\tfor iprxy in $(/usr/bin/base64 -d ${FWCFGRAW}); do\\\n\t\techo \"$iprxy\" \u003e\u003e ${ENVD_CONF}; done ) || \\\n\techo \"#Got nothing from QEMU FW_CFG\"\u003e ${ENVD_CONF}'\nExecStart=/usr/bin/bash -c '/usr/bin/test -f ${FWCFGRAW} \u0026\u0026 (\\\n\techo \"#Got from QEMU FW_CFG\"\u003e ${PROFILE_CONF};\\\n\tIFS=\"|\";\\\n\tfor iprxy in $(/usr/bin/base64 -d ${FWCFGRAW}); do\\\n\t\techo \"export $iprxy\" \u003e\u003e ${PROFILE_CONF}; done ) || \\\n\techo \"#Got nothing from QEMU FW_CFG\"\u003e ${PROFILE_CONF}'\nExecStartPost=/usr/bin/systemctl daemon-reload\n[Install]\nWantedBy=sysinit.target\n","enabled":true,"name":"envset-fwcfg.service"}]}}
```

Replace "YOURSSHKEYHERE" with the actual pub keys you generated.

## Launching

### gvproxy

One needs to run gvproxy first to make it ready for the QEMU VM launched afterward. Run it with the command below:
```
gvproxy.exe -listen-qemu unix://C:/qemu-remote/vlan_remote.sock -pid-file C:\qemu-remote\proxy.pid -ssh-port 57561 -forward-sock C:\qemu-remote\podman.sock -forward-dest /run/user/501/podman/podman.sock -forward-user core -forward-identity C:\qemu-remote\remote
```

### QEMU

Launch QEMU with the following command (the following configures it to use 4 CPUs and 8 GB RAM, but it could be adjusted for less):

```
qemu-system-x86_64w.exe -m 8192 -smp 4 -fw_cfg name=opt/com.coreos/config,file=C:\qemu-remote\remote.ign -netdev stream,id=vlan,server=off,addr.type=unix,addr.path=C:\qemu-remote\vlan_remote.sock -device virtio-net-pci,netdev=vlan,mac=5a:94:ef:e4:0c:ee -device virtio-serial -chardev socket,path=C:\qemu-remote\ready.sock,server=on,wait=off,id=apodman-machine-default_ready -device virtserialport,chardev=apodman-machine-default_ready,name=org.fedoraproject.port.0 -pidfile C:\qemu-remote\vm.pid -machine q35,accel=whpx:tcg -cpu max,vmx=off,monitor=off -drive if=virtio,file=C:\qemu-remote\fedora-coreos-37.20221127.2.0-qemu.x86_64.qcow2
```

### First time launch extras

Observe QEMU loading and wait for the message of SSH keys being provisioned to the machine. Next, before making the first ssh connection, one would need to add it to known hosts.
We are using `127.0.0.1` instead of `localhost` to force IPv4.

```
ssh-keyscan -p 57561 127.0.0.1 >> %USERPROFILE%\.ssh\known_hosts
```

### Add new connection to Podman

Create a connection named "qemuremote"

```
podman system connection add --identity C:\qemu-remote\remote -p 57561 qemuremote ssh://[email protected]
```

#### Optional

Make it default for simplicity of operation/testing

```
podman system connection default qemuremote
```

## Using Podman

Choose the active connection to be "qemuremote" (not needed if one made it default).

Run some basic network enabled workload:

```
podman run -d --rm -p 8080:80 nginx
```

Test it with

```
curl http -v http://localhost:8080
```

## Shutting down the machine

The built-in machinery of Podman machine will not work for a custom machine. One needs to gracefully shut it down by connecting via SSH:

```
ssh -i C:\qemu-remote\remote -p 57561 [email protected]
```

And then executing

```
sudo poweroff
```
5 changes: 3 additions & 2 deletions hack/markdown-preprocess
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class Preprocessor():
outfile = os.path.splitext(infile)[0]
outfile_tmp = outfile + '.tmp.' + str(os.getpid())

with open(infile, 'r', encoding='utf-8') as fh_in, open(outfile_tmp, 'w', encoding='utf-8') as fh_out:
with open(infile, 'r', encoding='utf-8') as fh_in, open(outfile_tmp, 'w', encoding='utf-8', newline='\n') as fh_out:
for line in fh_in:
# '@@option foo' -> include file options/foo.md
if line.startswith('@@option '):
Expand Down Expand Up @@ -71,7 +71,7 @@ class Preprocessor():
"""
for optionfile in self.used_by:
tmpfile = optionfile + '.tmp'
with open(optionfile, 'r', encoding='utf-8') as fh_in, open(tmpfile, 'w', encoding='utf-8') as fh_out:
with open(optionfile, 'r', encoding='utf-8') as fh_in, open(tmpfile, 'w', encoding='utf-8', newline='\n') as fh_out:
fh_out.write("####> This option file is used in:\n")
used_by = ', '.join(x for x in self.used_by[optionfile])
fh_out.write(f"####> podman {used_by}\n")
Expand All @@ -82,6 +82,7 @@ class Preprocessor():
fh_out.write(line)
# Compare files; only rewrite if the new one differs
if not filecmp.cmp(optionfile, tmpfile):
os.unlink(optionfile)
os.rename(tmpfile, optionfile)
else:
os.unlink(tmpfile)
Expand Down