This repository showcases how to:
- Store a Cloud-Init configuration (
cloud-init.yaml
) in version control. - Create an Incus (LXD) profile for devices (network, storage, etc.) without hardcoding Cloud-Init directly in the profile.
- Inject the Cloud-Init file into the profile at runtime.
- Lock down user access so that only a
gameshell
user can log in. - Run GameShell with optional language flags.
- Incus (or LXD) installed and initialized on your host.
- Refer to the Incus documentation or your distribution’s package manager for installation instructions.
- A storage pool named
default
(adjust names if you use something else). - A bridged network interface such as
incusbr0
(orlxdbr0
if you use the default LXD bridge).
-
cloud-init.yaml
This file contains our Cloud-Init configuration:- Sets up the
gameshell
user, locks out root, disables SSH password auth. - Creates a wrapper script that launches the GameShell script with a specific language flag.
- Installs minimal packages required (e.g.,
wget
,curl
,tar
). - Downloads the GameShell script from GitHub.
- Sets up the
-
gameshell-profile.yml
An example Incus profile definition (devices only). You may or may not store this in Git—some prefer to create/edit profiles on-the-fly. -
README.md
This documentation.
Follow these steps to replicate the environment on your machine.
git clone https://github.com/tsaquet/gameshell-incus.git
cd gameshell-incus
Create a persistent storage volume for game data. This volume will be mounted to /home/gameshell/game_data
inside the container. This way, data persists even if the container is destroyed or recreated.
incus storage volume create default gameshell-data
You can create a new profile named gameshell-profile
that only includes device definitions (network, storage) — no embedded Cloud-Init. Various options to do this:
# Create (or overwrite) the profile using `gameshell-profile.yml`:
incus profile create gameshell-profile
incus profile edit gameshell-profile < gameshell-profile.yml
# Create an empty profile
incus profile create gameshell-profile --empty
# Add a root disk device (pointing to your default pool)
incus profile device add gameshell-profile root disk path=/ pool=default
# Attach the named storage volume to /home/gameshell/game_data
incus profile device add gameshell-profile gameshell-data disk \
path=/home/gameshell/game_data \
pool=default \
source=gameshell-data
# Add a bridged network interface
incus profile device add gameshell-profile eth0 nic \
nictype=bridged \
parent=incusbr0
If you prefer manual editing:
incus profile create gameshell-profile
incus profile edit gameshell-profile
Paste in something like:
config: {}
description: "Profile devices only; cloud-init is applied separately."
devices:
root:
type: disk
path: /
pool: default
gameshell-data:
type: disk
path: /home/gameshell/game_data
pool: default
source: gameshell-data
eth0:
type: nic
nictype: bridged
parent: incusbr0
name: gameshell-profile
Then save and exit the editor.
Inside this repository, you will find cloud-init.yaml
. This includes:
- User creation (
gameshell
). - Locking the
root
account, disabling SSH password logins. - Locale/region setup for French (
fr_FR
) and English (en_US
). - Download of the GameShell script.
- A wrapper script that calls
gameshell.sh
with a language flag (e.g.,-L fr
).
To load that configuration into your profile:
incus profile set gameshell-profile user.user-data - < cloud-init.yaml
Note:
- The
-
indicates to read from standard input. < cloud-init.yaml
feeds the file intoincus profile set
.
This command overwrites (or sets) only the user.user-data
key in the profile.
Pick a cloud-init-capable image such as ubuntu:22.04
:
incus launch images:ubuntu/24.04/cloud gameshell-container -p gameshell-profile
Incus creates the container and boots it. Cloud-Init runs at first boot:
- The
gameshell
user is created, - The
gameshell.sh
script is downloaded to/home/gameshell/game_data/
, - A wrapper script sets the default language option,
root
is locked.
You can see Cloud-init progress with this command to be sure it has finished :
incus exec gameshell-container -- cloud-init status --wait
After the container is up, verify:
incus list
incus exec gameshell-container -- su - gameshell
You will be logged in as the gameshell
user, and it should launch the wrapper script. If the script calls GameShell with -L fr
, you’ll see the French version.
If you want to switch to English, you can either:
- Modify
cloud-init.yaml
and re-inject it (only applies for new containers or if you reset the existing one). - Set an environment variable in the container or profile. For example:
This is used by the wrapper script:
incus profile set gameshell-profile environment.GAMESHELL_LANG en incus restart gameshell-container
(See repository code comments for more info.)exec /home/gameshell/game_data/gameshell.sh -L "${GAMESHELL_LANG:-fr}"
By default, the root
account has no password, and ssh_pwauth
is false. This means only the gameshell
user can log in with an interactive shell. The host’s Incus admin can still run commands like incus exec gameshell-container -- bash
, but that’s normal for container management.
-
If you want to edit your Cloud-Init config, simply update
cloud-init.yaml
and run:incus profile set gameshell-profile user.user-data - < cloud-init.yaml
Note: Cloud-Init usually runs once on first boot. To re-run it on an existing container, you might need to reset Cloud-Init’s state manually or recreate the container.
-
If you want to modify devices (e.g., attach different volumes or networks), edit the profile devices using either the CLI or
incus profile edit gameshell-profile
. -
Game data is stored on the named volume
gameshell-data
. If you destroy the container, the data remains in that volume until you remove the volume itself.
Feel free to open issues or PRs if you notice any improvements or bugs. The GameShell project is maintained separately; this repo just provides an Incus-based deployment example.
- Phyver’s GameShell for the actual GameShell script.
- Incus (LXD) Documentation for container virtualization tooling.
Enjoy your GameShell containerized environment!