Add visual smoothing for snake game and systemd deployment

CSS animations for smoother 60fps feel despite 7Hz game ticks:
- 130ms transitions on cell background/box-shadow
- Head pop-in animation on direction changes
- Food pulse animation
- Smooth death state fade with grayscale

Per-snake colored glow on head cells.

Make server port configurable via PORT env var (default 8080).

Add deploy/ with systemd service and scripts:
- setup.sh: create games user, /opt/c4, install unit
- deploy.sh: build and install binary, restart service
- package.sh: cross-compile, tarball, base64 split for transfer
- reassemble.sh: decode and extract on target server
This commit is contained in:
Ryan Hamamura
2026-02-04 06:50:18 -10:00
parent 038c4b3f22
commit 2dc75107d1
9 changed files with 307 additions and 1770 deletions

24
deploy/c4.service Normal file
View File

@@ -0,0 +1,24 @@
[Unit]
Description=C4 Game Lobby
After=network.target
[Service]
Type=simple
User=games
Group=games
WorkingDirectory=/opt/c4
ExecStart=/opt/c4/c4
Restart=on-failure
RestartSec=5
Environment=PORT=8080
# Hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/c4
PrivateTmp=true
[Install]
WantedBy=multi-user.target

31
deploy/deploy.sh Executable file
View File

@@ -0,0 +1,31 @@
#!/usr/bin/env bash
# Deploy the c4 binary to /opt/c4, then restart the service.
# Works from the repo (builds first) or from an extracted tarball (pre-built binary).
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
INSTALL_DIR="/opt/c4"
BINARY="$ROOT_DIR/c4"
# If Go is available and we have source, build fresh
if [[ -f "$ROOT_DIR/go.mod" ]] && command -v go &>/dev/null; then
echo "Building CSS..."
(cd "$ROOT_DIR" && go tool gotailwind -i assets/css/input.css -o assets/css/output.css --minify)
echo "Building binary..."
(cd "$ROOT_DIR" && CGO_ENABLED=0 go build -o c4 .)
fi
if [[ ! -f "$BINARY" ]]; then
echo "ERROR: Binary not found at $BINARY" >&2
exit 1
fi
echo "Installing to $INSTALL_DIR..."
install -o games -g games -m 755 "$BINARY" "$INSTALL_DIR/c4"
echo "Restarting service..."
systemctl restart c4.service
echo "Done. Status:"
systemctl status c4.service --no-pager

87
deploy/package.sh Executable file
View File

@@ -0,0 +1,87 @@
#!/usr/bin/env bash
# Build the c4 binary, bundle it with deploy files into a tarball,
# base64-encode it, and split into 25MB chunks for transfer.
set -euo pipefail
REPO_DIR="$(cd "$(dirname "$0")/.." && pwd)"
cd "$REPO_DIR"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
TARBALL="c4-deploy-${TIMESTAMP}.tar.gz"
BASE64_FILE="c4-deploy-${TIMESTAMP}_b64.txt"
#==============================================================================
# Clean previous artifacts
#==============================================================================
echo "--- Cleaning old artifacts ---"
rm -f ./c4 c4-deploy-*.tar.gz c4-deploy-*_b64*.txt
#==============================================================================
# Build
#==============================================================================
echo "--- Building CSS ---"
go tool gotailwind -i assets/css/input.css -o assets/css/output.css --minify
echo "--- Building binary (linux/amd64) ---"
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o c4 .
#==============================================================================
# Verify required files
#==============================================================================
echo "--- Verifying files ---"
REQUIRED_FILES=(
c4
deploy/setup.sh
deploy/deploy.sh
deploy/reassemble.sh
deploy/c4.service
)
for f in "${REQUIRED_FILES[@]}"; do
if [[ ! -f "$f" ]]; then
echo "ERROR: Missing required file: $f" >&2
exit 1
fi
echo " OK $f"
done
#==============================================================================
# Create tarball
#==============================================================================
echo "--- Creating tarball ---"
tar -czf "/tmp/${TARBALL}" --transform 's,^,c4/,' \
c4 \
deploy/setup.sh \
deploy/deploy.sh \
deploy/reassemble.sh \
deploy/c4.service
mv "/tmp/${TARBALL}" .
echo " -> ${TARBALL} ($(du -h "${TARBALL}" | cut -f1))"
#==============================================================================
# Base64 encode and split
#==============================================================================
echo "--- Base64 encoding ---"
base64 "${TARBALL}" > "${BASE64_FILE}"
echo " -> ${BASE64_FILE} ($(du -h "${BASE64_FILE}" | cut -f1))"
echo "--- Splitting into 25MB chunks ---"
split -b 25M -d --additional-suffix=.txt "${BASE64_FILE}" "c4-deploy-${TIMESTAMP}_b64_part"
rm -f "${BASE64_FILE}"
CHUNKS=(c4-deploy-${TIMESTAMP}_b64_part*.txt)
echo " -> ${#CHUNKS[@]} chunk(s):"
for chunk in "${CHUNKS[@]}"; do
echo " $chunk ($(du -h "$chunk" | cut -f1))"
done
#==============================================================================
# Done
#==============================================================================
echo ""
echo "=== Package Complete ==="
echo ""
echo "Transfer the chunk files to the target server, then run:"
echo " ./reassemble.sh"
echo " cd ~/c4 && sudo ./deploy/setup.sh # first time only"
echo " cd ~/c4 && sudo ./deploy/deploy.sh"

96
deploy/reassemble.sh Executable file
View File

@@ -0,0 +1,96 @@
#!/usr/bin/env bash
# Reassembles base64 chunks and extracts the c4 deployment tarball.
# Expects chunk files in the current directory.
set -euo pipefail
cd "$HOME"
echo "=== C4 Deployment Reassembler ==="
echo "Working directory: $HOME"
echo ""
#==============================================================================
# Find and validate chunk files
#==============================================================================
echo "--- Finding chunk files ---"
CHUNKS=($(ls -1 c4-deploy-*_b64_part*.txt 2>/dev/null | sort))
if [[ ${#CHUNKS[@]} -eq 0 ]]; then
echo "ERROR: No chunk files found matching c4-deploy-*_b64_part*.txt"
exit 1
fi
echo "Found ${#CHUNKS[@]} chunks:"
for chunk in "${CHUNKS[@]}"; do
echo " - $chunk ($(du -h "$chunk" | cut -f1))"
done
#==============================================================================
# Reassemble and decode
#==============================================================================
echo ""
echo "--- Reassembling chunks ---"
TIMESTAMP=$(echo "${CHUNKS[0]}" | sed -E 's/c4-deploy-([0-9]+-[0-9]+)_b64_part.*/\1/')
TARBALL="c4-deploy-${TIMESTAMP}.tar.gz"
COMBINED="combined_b64.txt"
echo "Concatenating chunks..."
cat "${CHUNKS[@]}" > "$COMBINED"
echo " -> Created $COMBINED ($(du -h "$COMBINED" | cut -f1))"
echo "Decoding base64..."
base64 -d "$COMBINED" > "$TARBALL"
echo " -> Created $TARBALL ($(du -h "$TARBALL" | cut -f1))"
echo "Verifying tarball..."
if tar -tzf "$TARBALL" > /dev/null 2>&1; then
echo " -> Tarball is valid"
else
echo "ERROR: Tarball verification failed"
exit 1
fi
#==============================================================================
# Archive existing source
#==============================================================================
echo ""
echo "--- Archiving existing source ---"
if [[ -d c4 ]]; then
rm -rf c4.bak
mv c4 c4.bak
echo " -> Moved c4 -> c4.bak"
else
echo " -> No existing c4 directory"
fi
#==============================================================================
# Extract
#==============================================================================
echo ""
echo "--- Extracting tarball ---"
tar -xzf "$TARBALL"
echo " -> Extracted to ~/c4"
#==============================================================================
# Cleanup
#==============================================================================
echo ""
echo "--- Cleaning up ---"
rm -f "$COMBINED" "$TARBALL" "${CHUNKS[@]}"
echo " -> Removed chunks, combined base64, and tarball"
#==============================================================================
# Next steps
#==============================================================================
echo ""
echo "=== Reassembly Complete ==="
echo ""
echo "Next steps:"
echo " cd ~/c4"
echo " sudo ./deploy/setup.sh # first time only"
echo " sudo ./deploy/deploy.sh"

29
deploy/setup.sh Executable file
View File

@@ -0,0 +1,29 @@
#!/usr/bin/env bash
# One-time setup: create the games user, install directory, and systemd unit.
# Run as root (or with sudo).
set -euo pipefail
if [[ $EUID -ne 0 ]]; then
echo "Run as root: sudo $0" >&2
exit 1
fi
# Create system user if it doesn't exist
if ! id -u games &>/dev/null; then
useradd --system --shell /usr/sbin/nologin --home-dir /opt/c4 --create-home games
echo "Created system user: games"
else
echo "User 'games' already exists"
fi
# Ensure install directory exists with correct ownership
install -d -o games -g games -m 755 /opt/c4
install -d -o games -g games -m 755 /opt/c4/data
# Install systemd unit
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cp "$SCRIPT_DIR/c4.service" /etc/systemd/system/c4.service
systemctl daemon-reload
systemctl enable c4.service
echo "Setup complete. Run deploy.sh to build and start the service."