04 December 2025

Opt-In Directory Browsing in Caddy Using a Hidden Sentinel File

Sometimes you want Caddy to behave like a normal Single Page Application (SPA) host, but still have the option to enable directory listings only in specific folders without turning browsing on globally.

In my case, I wanted:

  • No directory listing at the site root (/)
  • No browsing by default in any directory
  • Opt-in browsing in a directory only if a special hidden file exists
  • Real 404s for missing static files (e.g. .css, .txt)
  • SPA fallback for “virtual” routes (no file extension)

The solution: use a hidden sentinel file (I chose .browse) and Caddy’s matchers to decide:

  • When to show a directory listing
  • When to serve static files or return 404
  • When to fall back to /index.html for SPA routing

The Final Caddyfile Snippet

.net:443 {
    root * /srv/static

    encode zstd gzip

    # Browsable directories: any non-root path whose directory
    # contains a file named `.browse`
    @browsable_dir {
        not path /      # never browse the site root
        file {
            # Handle both `/dir` and `/dir/` request forms:
            try_files {path}/.browse {path}.browse
        }
    }

    # Requests that look like "static files" (have an extension),
    # e.g. /foo.css, /img/logo.png, /docs/file.pdf
    @static_with_ext path_regexp static_ext \.[^/]+$

    route {
        # 1) Browsable directories (sentinel present)
        handle @browsable_dir {
            file_server browse {
                hide .browse
            }
        }

        # 2) Static-looking requests (with an extension):
        #    serve file if it exists, otherwise 404
        handle @static_with_ext {
            try_files {path} =404

            file_server {
                hide .browse
            }
        }

        # 3) Everything else → SPA fallback
        #    - try to serve real files/dirs
        #    - if none match, fall back to /index.html
        handle {
            try_files {path} {path}/ /index.html
            file_server {
                hide .browse
            }
        }
    }

    # Security headers
    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
        X-Content-Type-Options "nosniff"
        Referrer-Policy "strict-origin-when-cross-origin"
        Permissions-Policy "camera=(), microphone=(), geolocation=()"
        X-Frame-Options "SAMEORIGIN"
    }

    # Simple text error responses (e.g., "404 Not Found")
    handle_errors {
        respond "{err.status_code} {err.status_text}"
    }
}

How the Matchers Work

1. Browsable directories (@browsable_dir)

@browsable_dir {
    not path /
    file {
        try_files {path}/.browse {path}.browse
    }
}

This does two key things:

  1. not path /
    Ensures the matcher never triggers on the site root. The root path / will never show a directory listing, even if a .browse file exists there.
  2. file { try_files {path}/.browse {path}.browse }
    Checks for a hidden sentinel file named .browse in the directory corresponding to the request path. It works with both /dir and /dir/ request forms.

    For example:
    • Request: /bar/
    • Checks: /srv/static/bar/.browse
    • If that file exists, @browsable_dir matches and browsing is enabled.

2. Static files with extensions (@static_with_ext)

@static_with_ext path_regexp static_ext \.[^/]+$

This matcher catches paths that look like real files because they end with an extension:

  • /foo.css
  • /img/logo.png
  • /docs/file.pdf
  • /foo/thing1.txt

Those requests are handled here:

handle @static_with_ext {
    try_files {path} =404

    file_server {
        hide .browse
    }
}

If the file exists, it’s served normally. If it does not exist, the =404 causes Caddy to emit a real 404, which then flows through handle_errors and becomes a simple text response like:

404 Not Found

Because we use hide .browse in file_server, any direct request to a .browse file will also result in a 404, even if the file exists. The sentinel is never exposed directly, which is exactly what we want.

3. SPA fallback for everything else

handle {
    try_files {path} {path}/ /index.html
    file_server {
        hide .browse
    }
}

Any request that is not:

  • a “browsable directory” (@browsable_dir), and
  • not a “static file with extension” (@static_with_ext)

falls into this final handler. The logic is:

  • Try the request as a file: {path}
  • Then as a directory: {path}/
  • If neither exists, rewrite to /index.html (SPA entrypoint)

That means “virtual” SPA routes like /app/settings or /docs/getting-started will still end up at /index.html, and your JS router can take over from there.


Error Handling

For errors, this simple handler is used:

handle_errors {
    respond "{err.status_code} {err.status_text}"
}

So a missing static file (like /foo/missing.txt) results in a plain-text:

404 Not Found

This keeps error responses easy to see and debug.


Example Directory Structure and Request Behavior

Assume the following directory layout under /srv/static (plus a normal SPA entrypoint at /srv/static/index.html):

/
├── .browse
├── index.html
├── foo
│   └── thing1.txt
└── bar
    ├── .browse
    ├── thing2.txt
    ├── a
    │   └── thing3.txt
    └── b
        ├── .browse
        └── thing4.txt

Here’s how various requests behave with this configuration:

Request URL Matched Handler Result
/ Final handle (SPA) Serves /index.html (SPA entrypoint).
Root never shows a directory listing, even though .browse exists at the root, because of not path / in @browsable_dir.
/.browse @static_with_extfile_server with hide .browse The file exists at /srv/static/.browse, but it is hidden by hide .browse.
Result: 404 Not Found — the sentinel is not directly accessible.
/foo/ Final handle (SPA branch) /foo/ exists as a directory, but there is no .browse inside it and no index.html in /foo/.
Result: 404 Not Found (directory listing is disabled, and there's no index file).
/foo/thing1.txt @static_with_ext Looks like a static file (has .txt extension).
File exists at /srv/static/foo/thing1.txt.
Result: serves thing1.txt (200 OK).
/bar/ @browsable_dir /bar/.browse exists, so @browsable_dir matches.
Result: directory listing for /bar/ via file_server browse, with .browse hidden.
You’ll see entries like thing2.txt, a/, and b/.
/bar/thing2.txt @static_with_ext Static file with extension, exists on disk.
Result: serves thing2.txt (200 OK).
/bar/a/ Final handle /bar/a/ is a real directory, but:
- It does not contain .browse → no directory listing.
- It has no index.html.
Result: 404 Not Found.
/bar/a/thing3.txt @static_with_ext Static file with extension, exists at /srv/static/bar/a/thing3.txt.
Result: serves thing3.txt (200 OK).
/bar/b/ @browsable_dir /bar/b/.browse exists, so this directory is explicitly marked as browsable.
Result: directory listing for /bar/b/, showing thing4.txt (with .browse hidden).
/bar/b/thing4.txt @static_with_ext Normal static file.
Result: serves thing4.txt (200 OK).
/bar/.browse @static_with_extfile_server with hide .browse Sentinel file exists at /srv/static/bar/.browse, but hide .browse makes it behave like a hidden file.
Result: 404 Not Found — again, the sentinel is not directly accessible.
/foo/missing.txt (not in tree) @static_with_ext Matches the static-file matcher, but the file does not exist.
try_files {path} =404 falls through to =404.
Result: 404 Not Found (plain text via handle_errors).

Why Use a Hidden Sentinel File?

This pattern has a few nice properties:

  • Safe by default: Browsing is off everywhere unless you explicitly opt a directory in with .browse.
  • Per-directory control: You can toggle listing for any directory by simply adding or removing a tiny hidden file.
  • Works with SPAs: Non-extension routes still fall back to /index.html, so your front-end router behaves as expected.
  • Real 404s for static files: Missing .css, .js, .txt, etc. return a genuine 404 Not Found.
  • Sentinel stays private: .browse is hidden from directory listings and returns 404 when requested directly.

If you’re hosting a SPA with Caddy and want fine-grained, opt-in directory listings plus correct 404 behavior for static assets, this hidden .browse sentinel pattern is a clean and flexible way to do it.

21 November 2025

Wireshark Capture the Flag (CTF)

This post contains a complete Wireshark-based Capture the Flag (CTF) activity suitable for high school students who are new to cybersecurity. It includes:

  • Teacher Guide – detailed, step-by-step instructions to generate the PCAP and run the activity.
  • Student Handout – ready to print or share digitally.

🧑‍🏫 TEACHER GUIDE – Wireshark CTF in One PCAP

1. Purpose and Overview

This activity is a guided Capture The Flag (CTF) using a single Wireshark capture file that contains:

  • Realistic background network traffic (“noise”)
  • Seven embedded challenges, each hiding a FLAG{...} string

Students will:

  • Learn basic Wireshark navigation and filters
  • Inspect ICMP, DNS, HTTP, FTP, ARP/DHCP
  • Extract files from network captures
  • Understand why cleartext protocols (FTP, HTTP, basic auth) are insecure

This guide explains:

  1. How to prepare the environment
  2. Step-by-step commands to generate the PCAP
  3. How to verify each challenge is present
  4. How to run the activity with students
  5. Full answer key

2. Technical Prerequisites

You’ll need:

  1. One Linux machine or VM to generate the PCAP:
    • Ubuntu / Debian works great
    • Needs internet access (optional but helpful for realistic DNS/web noise)
  2. Installed software on that machine:
    • tcpdump (for capturing)
    • python3 & pip
    • Wireshark (for verification)
    • Optional but recommended: curl, dig (DNS client), ftp, hostnamectl
  3. For the classroom:
    • Each student or team has Wireshark installed
    • A copy of the final final_ctf.pcapng
    • Projector/whiteboard to explain protocol concepts

Note: All traffic you generate is synthetic and local. You’re not hacking anything; you’re simulating traffic to be analyzed offline later.

3. High-Level Build Plan

You will:

  1. Start a single long-running capture: final_ctf.pcapng
  2. Generate:
    • Background noise (pings, normal HTTP/HTTPS, DNS)
    • Seven challenge sequences:
      • ICMP flag
      • HTTP POST with flag in password
      • ARP/DHCP network mapping
      • DNS exfil-style queries
      • HTTP image download
      • FTP private key transfer
      • HTTP Basic Auth credential leak
    • More background noise interspersed
  3. Stop the capture
  4. Verify each challenge in Wireshark
  5. Distribute the final_ctf.pcapng plus the Student Handout
  6. Use the answer key (at the end of this guide) for grading and hints

4. Environment Setup (Once)

On your Linux CTF-builder machine, run:

sudo apt update
sudo apt install -y tcpdump curl dnsutils ftp python3 python3-pip

Install Python libraries:

pip install flask pyftpdlib

Confirm Wireshark is installed (or install via GUI / package manager).

5. Start the Master Capture

  1. Identify your main network interface:
    ip addr
    

    Look for something like eth0, ens33, or wlan0.
    In this guide, we’ll use eth0. Replace it as needed.

  2. Start the capture:
    sudo tcpdump -i eth0 -w final_ctf.pcapng
    

    Leave this terminal open and running. Everything you do now will be recorded.

6. Generate Realistic Background Noise

You can sprinkle these before, between, and after challenges.

6.1 ICMP Noise (Pings)

ping -c 10 8.8.8.8 &
ping -c 10 1.1.1.1 &

6.2 HTTP & HTTPS Noise

curl http://example.com > /dev/null 2>&1
curl http://neverssl.com > /dev/null 2>&1
curl https://www.debian.org > /dev/null 2>&1
curl https://www.python.org > /dev/null 2>&1

6.3 DNS Noise

dig google.com
dig wikipedia.org
dig schooldistrict.local

You can repeat noise periodically between challenges.

7. Generate Each Challenge (Step-by-Step)

You can do these in any order, but following this sequence makes your answer key easier to manage.

Challenge 1 – ICMP “Curious Ping”

Goal for students: Find a flag hidden in the payload of an ICMP echo request.

Flag: FLAG{CURIOUS_PING}

Command:

ping -c 1 -p 464C41477B435552494F55535F50494E477D 8.8.8.8

The hex payload decodes to ASCII FLAG{CURIOUS_PING}.

Verify later:

  1. Open final_ctf.pcapng in Wireshark.
  2. Filter: icmp.
  3. Click the ICMP Echo Request to 8.8.8.8.
  4. Expand ICMP → Data and confirm the hex matches the flag.

Challenge 2 – HTTP POST “Forgotten Login”

Goal: Use “Follow TCP Stream” to find a flag hidden in HTTP POST parameters.

Flag: FLAG{FOLLOW_THE_STREAM}

7.2.1 Run a Simple HTTP Server
mkdir -p /tmp/ctf_http && cd /tmp/ctf_http
python3 -m http.server 8080 &
7.2.2 Generate the POST Request
curl -X POST \
  -d "username=student&password=FLAG{FOLLOW_THE_STREAM}" \
  http://localhost:8080/login

Verify later:

  1. Filter: http && tcp.port == 8080.
  2. Find the HTTP POST /login packet.
  3. Right-click → FollowTCP Stream.
  4. Look for: username=student&password=FLAG{FOLLOW_THE_STREAM}.

Challenge 3 – ARP/DHCP “Who’s in the Network?”

Goal: Observe ARP and DHCP to see devices and recognize a suspicious hostname.

Flag (conceptual, from hostname/clue): FLAG{EVIL_HAXOR}

7.3.1 Create ARP/DHCP Traffic
sudo dhclient -r
sudo dhclient

This generates DHCP/BOOTP and ARP traffic as it requests an IP.
If you have multiple machines, repeat similar operations on them to create a richer ARP table.

7.3.2 (Optional) Set Suspicious Hostname
sudo hostnamectl set-hostname EVIL-HAXOR-666

Then generate a bit more traffic (pings, web requests).

Verify later:

  1. Filter: bootp || dhcp to see DHCP traffic.
  2. Filter: arp to see IP/MAC mapping.
  3. If your DHCP implementation reveals hostname, look for EVIL-HAXOR-666 in DHCP options.
    Otherwise, treat it as a narrative clue: the rogue host is named EVIL-HAXOR-666, so the flag is FLAG{EVIL_HAXOR}.

Challenge 4 – DNS “Conspiracy Queries”

Goal: Spot suspicious DNS queries and reconstruct a fragmented flag.

Flag: FLAG{THIS_DNS_CONSPIRACY}

Split into three queries:

dig FLAG{THIS_.example.com
dig DNS_CONS.example.com
dig PIRACY}.example.com

Verify later:

  1. Filter: dns.
  2. Look at the Query Name column.
  3. You should see:
    • FLAG{THIS_.example.com
    • DNS_CONS.example.com
    • PIRACY}.example.com
  4. Students reconstruct the full flag: FLAG{THIS_DNS_CONSPIRACY}.

Challenge 5 – HTTP File “Lost Photo”

Goal: Export HTTP objects and recover a file (image) that contains the flag.

Flag inside the image: FLAG{HTTP_OBJECT_EXTRACT}

7.5.1 Prepare an Image
  1. Pick any JPEG (e.g., school logo).
  2. Add visible text: FLAG{HTTP_OBJECT_EXTRACT}.
  3. Save it as mascot.jpg.

Serve it:

mkdir -p /tmp/ctf_http2
cp mascot.jpg /tmp/ctf_http2/
cd /tmp/ctf_http2
python3 -m http.server 8081 &
7.5.2 Generate the Download
curl http://localhost:8081/mascot.jpg > /dev/null

Verify later:

  1. Filter: http && tcp.port == 8081.
  2. Go to File → Export Objects → HTTP.
  3. Find and save mascot.jpg.
  4. Open it and confirm the flag text is visible.

Challenge 6 – FTP “Leaky Key”

Goal: See how FTP transfers a private key in cleartext and extract the flag from that file.

Flag in file: FLAG{FTP_LEAKED_PRIVATE_KEY}

7.6.1 Set Up FTP Server
mkdir -p /tmp/ftp_home
cat > /tmp/ftp_home/id_rsa << 'EOF'
-----BEGIN OPENSSH PRIVATE KEY-----
MIIBVgIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEA...
... (any dummy key-like text is fine) ...
-----END OPENSSH PRIVATE KEY-----
# FLAG{FTP_LEAKED_PRIVATE_KEY}
EOF

Create ftp_server.py:

from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer

authorizer = DummyAuthorizer()
authorizer.add_user("testuser", "p@ssw0rd", "/tmp/ftp_home", perm="elradfmw")

handler = FTPHandler
handler.authorizer = authorizer

server = FTPServer(("0.0.0.0", 2121), handler)
server.serve_forever()

Run it:

python3 ftp_server.py &
7.6.2 Perform FTP Transfer
ftp localhost 2121 <<EOF
user testuser p@ssw0rd
binary
get id_rsa
bye
EOF

Verify later:

  1. Filter: ftp || ftp-data.
  2. Go to File → Export Objects → FTP.
  3. Save id_rsa.
  4. Open it and confirm the last line contains the flag.

Challenge 7 – HTTP Basic Auth “Secret Password”

Goal: Recognize HTTP Basic Auth, decode credentials, and see the flag in the password.

Flag (as password): FLAG{PASSWORD_IN_BASIC_AUTH}

7.7.1 Create Basic Auth Web Server

Create basic_auth_server.py:

from flask import Flask, Response, request

app = Flask(__name__)

@app.route("/secret")
def secret():
    auth = request.authorization
    if not auth:
        return Response("Auth required", status=401)
    return f"Welcome {auth.username}"

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5001)

Run it:

python3 basic_auth_server.py &
7.7.2 Send Request with Flag as Password
curl -u admin:FLAG{PASSWORD_IN_BASIC_AUTH} http://localhost:5001/secret

Verify later:

  1. Filter: http && tcp.port == 5001.
  2. Find the HTTP GET /secret.
  3. Inspect the HTTP headers for Authorization: Basic ....
  4. Wireshark may show the decoded credentials directly (or students can base64-decode). The password will be FLAG{PASSWORD_IN_BASIC_AUTH}.

8. Stop Capture and Save

When all challenges and noise are done, return to the tcpdump terminal and press:

Ctrl + C

You should now have final_ctf.pcapng in your working directory. Make a backup copy somewhere safe.

9. Distribution to Students

  • Copy final_ctf.pcapng to each lab machine (USB, shared drive, LMS, etc.).
  • Provide them with the Student Handout (below).
  • Optionally, tell them: “There are 7 flags hidden in this file. Each flag looks like FLAG{SOME_TEXT_HERE}.”

10. Running the Activity

Suggested structure for a 50–90 minute session:

  1. Intro (10–15 minutes)
    • What is Wireshark?
    • What is a “flag”?
    • Show basic Wireshark navigation:
      • Open a PCAP
      • Use the filter bar (e.g., icmp, http)
      • Understand packet list, details, and bytes panes
  2. Guided first challenge (10 minutes) – Solve the ICMP challenge together as a class.
  3. Independent/Team work (30–50 minutes) – Let students hunt for the remaining 6 flags. Offer hints over time.
  4. Debrief (10–15 minutes) – Discuss why unencrypted protocols are dangerous and what secure alternatives exist.

11. Suggested Hints (Optional)

  • Hint 1: Some flags are in ICMP and DNS. Look for unusual values.
  • Hint 2: Some flags hide in web traffic. Watch for HTTP POST/GET requests.
  • Hint 3: One file contains a private key.
  • Hint 4: One password itself is the flag. Look for “Authorization: Basic”.

12. Scoring (Suggested)

  • Challenge 1 (ICMP): 10 points
  • Challenge 2 (HTTP POST): 15 points
  • Challenge 3 (ARP/DHCP): 15 points
  • Challenge 4 (DNS): 20 points
  • Challenge 5 (HTTP image): 15 points
  • Challenge 6 (FTP key): 15 points
  • Challenge 7 (Basic Auth): 10 points

Total: 100 points

13. Answer Key (For Teacher Only)

  1. ICMP Curious Ping
    Filter: icmp
    Location: ICMP Echo Request data
    Flag: FLAG{CURIOUS_PING}
  2. HTTP POST – Forgotten Login
    Filter: http && tcp.port == 8080
    Action: Follow TCP Stream of POST /login
    Flag: FLAG{FOLLOW_THE_STREAM}
  3. ARP/DHCP – Who’s in the Network?
    Filters: arp, bootp || dhcp
    Concept: suspicious hostname EVIL-HAXOR-666
    Flag: FLAG{EVIL_HAXOR}
  4. DNS – Conspiracy
    Filter: dns
    Reassemble from query names:
    FLAG{THIS_ + DNS_CONS + PIRACY}
    Flag: FLAG{THIS_DNS_CONSPIRACY}
  5. HTTP Image – Lost Photo
    Filter: http && tcp.port == 8081
    Export Objects → HTTP → mascot.jpg
    Flag text in image: FLAG{HTTP_OBJECT_EXTRACT}
  6. FTP – Leaky Key
    Filter: ftp || ftp-data
    Export Objects → FTP → id_rsa
    Flag in last line: FLAG{FTP_LEAKED_PRIVATE_KEY}
  7. HTTP Basic Auth – Secret Password
    Filter: http && tcp.port == 5001
    Check Authorization header / decoded creds:
    admin:FLAG{PASSWORD_IN_BASIC_AUTH}
    Flag: FLAG{PASSWORD_IN_BASIC_AUTH}

🧑‍🎓 STUDENT HANDOUT – Wireshark Capture The Flag

Title: “Mystery on the Network: A Wireshark CTF”

1. Scenario

Your school’s network has captured a mysterious packet trace. Inside this captured traffic are seven hidden flags.

Each flag looks like this:

FLAG{SOME_TEXT_HERE}

Your mission is to find as many flags as you can using Wireshark.

2. What You Need

  • Wireshark installed on your computer
  • The capture file: final_ctf.pcapng
  • A teammate or two (optional but encouraged)
  • This handout

3. Getting Started

  1. Open Wireshark.
  2. Go to File → Open… and select final_ctf.pcapng.
  3. Notice the three main parts:
    • Top pane: list of packets
    • Middle pane: details of the selected packet
    • Bottom pane: raw bytes (hex and ASCII)

4. Helpful Wireshark Tips

4.1 Display Filter Bar

At the top of Wireshark is a filter bar. You can type conditions there to show only certain types of traffic. Examples:

  • icmp – only pings
  • dns – only DNS traffic
  • http – only HTTP traffic
  • ftp || ftp-data – both FTP control and data
  • arp – ARP traffic
  • bootp || dhcp – DHCP traffic

After typing a filter, press Enter to apply it.

4.2 Following Streams

Sometimes a flag is hidden in a conversation, not just one packet.

  1. Right-click a packet (e.g., an HTTP or FTP packet).
  2. Choose “Follow → TCP Stream”.
  3. A new window will show the reconstructed conversation.
  4. Look for usernames, passwords, or FLAG{...} strings.

4.3 Exporting Objects (Files)

Some flags are hidden in files transmitted over the network.

  1. In Wireshark, go to File → Export Objects.
  2. Choose the appropriate protocol:
    • HTTP for web files (like images)
    • FTP for files transferred over FTP
  3. Select a file from the list (e.g., mascot.jpg or id_rsa).
  4. Click “Save”, then open the file on your computer.
  5. Look inside it for a FLAG{...}.

5. Your Objectives

There are seven flags total. They are hidden across different protocols and methods, including:

  • ICMP (ping) traffic
  • HTTP web traffic (including forms and images)
  • FTP file transfer
  • DNS queries
  • ARP/DHCP network discovery traffic
  • HTTP Basic Authentication headers

You do not need to guess or brute force anything; every flag is plainly visible somewhere in the data if you look in the right place.

6. Suggested Hunt Order (Optional)

If you’re new to Wireshark, try this sequence:

  1. ICMP Flag
    Filter: icmp
    Look at the contents/payload of the ping packets.
  2. HTTP Login Flag
    Filter: http
    Look for POST requests with form data (e.g., username=...&password=...).
  3. DNS Flag
    Filter: dns
    Look for suspicious or unusual domain names that look like parts of a message.
  4. HTTP File (Image) Flag
    Filter: http
    Use File → Export Objects → HTTP and open any saved files (especially images).
  5. FTP Key Flag
    Filter: ftp || ftp-data
    Use File → Export Objects → FTP to save files and inspect them.
  6. HTTP Basic Auth Flag
    Filter: http
    Look at headers for Authorization: Basic ... and see whether Wireshark shows decoded usernames/passwords.
  7. Network Mapping / ARP/DHCP Flag
    Filters: arp and bootp || dhcp
    Think about how many devices are on the network and whether any hostnames look suspicious.

7. Recording Your Answers

Use a table like this to track your discoveries:

# Protocol / Clue Filter Used Where You Found It (packet / stream / file) Flag
1
2
3
4
5
6
7

Make sure to write the flags exactly as they appear (capitalization, braces, underscores).

8. Rules & Expectations

  • Do not modify, attack, or scan any real systems. This is a closed, offline exercise.
  • Work individually or in small teams as allowed by your teacher.
  • Ask for hints if you get stuck, but try exploring filters and menus first.
  • Be prepared to share which protocol the flag was in and how you discovered it.

9. Reflection Questions

After the activity, be ready to discuss:

  1. Which protocol made it easiest to find sensitive information?
  2. What surprised you most about how much you can see in plain text?
  3. How do encryption and secure protocols (like HTTPS instead of HTTP, SFTP instead of FTP) change this picture?
  4. If you were defending a network, what would you do differently after seeing this capture?

Good luck, and happy hunting!

Maytag washer: Sense to Done without Washing

Maytag MVW6200 F8E1: Why Your Washer Fills in Test Mode but Not in a Normal Cycle

This guide walks through a very specific and frustrating failure mode on the Maytag MVW6200 (and similar Whirlpool/Maytag VMW platform washers):

  • The washer will not fill during a Normal cycle.
  • The cycle jumps from Sense straight to Done.
  • Error code F8E1 appears.
  • The washer does fill correctly in Automatic Test Mode.
  • A calibration/reset makes it work briefly, then the failure returns.
TL;DR: If valves work in Test Mode, hoses/screens are clean, and calibration only helps temporarily, the problem is almost always a failing pressure sensor integrated into the control board.

Technical reference: Tech Sheet W11416398B. (Diagnostic logic, LED groups, and service modes) https://www.maytag.com/content/dam/global/documents/202005/tech-sheet-w11416398-revb.pdf

Optional Video Reference

The calibration/reset procedure shown later matches this video, but the written instructions below are fully standalone.

▶ Optional: Reset & Calibration Demonstration https://youtu.be/7RPjWTkbjOU


Control Panel Overview: Keys and LED Groups

The washer uses six keys and six LED groups. In diagnostic modes, these groups display binary output for Fault (F) and Error (E) codes.

Key/LED layout for the MVW6200 console.

Step 1 — Enter Diagnostic (Service) Mode

  1. Ensure washer is in Standby (plugged in, no lights).
  2. Within 8 seconds press:
    Key 1 → Key 2 → Key 3, three times in a row.
  3. All LEDs flash twice: Service Mode active.
  4. Stored Fault/Error codes appear automatically using LED Groups 5 and 6.
If LEDs do not flash twice, you missed the timing. Try again from Standby.

Step 2 — View Stored Fault/Error Codes

Each diagnostic entry consists of:

  • Fault (F#) — category
  • Error (E#) — detail within the category
Lock icon OFF = Fault (F)
Lock icon ON = Error (E)

Use Key 3 to step through fault history.


Understanding How to Decode Binary Fault (F) and Error (E) Codes

The washer displays its F and E values in binary using LED Groups 5 and 6.

1. Determine F or E via lock indicator

Lock OFF → Fault
Lock ON → Error

2. LED Groups

  • Group 5 = high nibble (first hex digit)
  • Group 6 = low nibble (second hex digit)

3. Binary → Decimal → Hex table

BinaryDecHex
000000
000111
001022
001133
010044
010155
011066
011177
100088
100199
101010A
101111B
110012C
110113D
111014E
111115F

4. How to decode any code

  1. Check lock icon (OFF = F, ON = E).
  2. Read Group 5 binary → convert to hex.
  3. Read Group 6 binary → convert to hex.
  4. Combine: Fxy or Exy.
Once learned, you can decode any Whirlpool/Maytag VMW code.

Example: Decoding F8E1

Fault (lock OFF)

Group 5 = 10
Group 6 = 00
→ F8

Error (lock ON)

Group 5 = 00
Group 6 = 01
→ E1
F8E1 = Long Fill + No Water Level Change

Step 3 — Verify Water Supply, Hoses, and Screens BEFORE Test Mode

These eliminate simple mechanical causes of F8E1.

Check supply

  • Hot/cold valves fully open
  • Normal household pressure

Check hoses

  • No kinks, crushing, or cross-connection

Clean inlet screens

  1. Turn off water
  2. Remove hoses
  3. Clean screens

Flush hoses

  1. Open valves briefly with hoses aimed into a bucket
  2. Restore connections
If water flow is strong and screens are clear, you can rule out mechanical fill restrictions.

Step 4 — Check the Pressure Sensor Tube (Airflow Test)

The pressure tube must be unobstructed for the board to sense water-level changes.

Procedure

  1. Unplug washer
  2. Remove back panel
  3. Locate the thin pressure hose
  4. Disconnect it from board
  5. Blow gently toward tub:
    • Air should pass freely
    • No water sloshing or resistance
  6. Inspect tube for kinks, moisture, or cracks
If airflow is free, the hose is not the cause. Pressure sensing errors now point to the board’s internal sensor.

Step 5 — Use Automatic Test Mode

This confirms the solenoids and plumbing physically work.

  1. Enter Service Mode
  2. Press Key 2, then Key 5
  3. Washer cycles through:
    • Hot valve
    • Cold valve
    • Drain pump
    • Shifter
    • Spin
If valves flow properly here but not in a Normal cycle, the control-board logic (not plumbing) is failing.

Step 6 — What F8E1 Means After All Tests Pass

If:

  • Valves open in Test Mode
  • Hoses/screens are clear
  • Pressure tube flows freely
  • Calibration temporarily restores operation

You are dealing with a failing solid-state pressure sensor embedded in the control board.


Step 7 — Full Factory Reset & Calibration (Correct Procedure)

Standalone written instructions (no video required):

Preparation

  • Standby mode
  • Empty tub
  • Lid closed
  • Water on

Steps

  1. Key 1 once → Key 2 twice
  2. Lid locks, all LEDs on
  3. Turn cycle knob one click clockwise
  4. Press Keys 1 → 2 → 3 → 4 → 5 → 6 in sequence
  5. Wait for LEDs to blink and lid to unlock
  6. Open/close lid immediately
  7. Calibration begins (motor, valves, shifter activity)
  8. Wait 4 minutes
  9. Washer returns to Standby

Post-check

Run an empty Normal cycle to confirm proper fill.

If calibration restores operation briefly then F8E1 returns, sensor drift is confirmed.

Step 8 — When Calibration Won’t Hold: Replace the Control Board

The pressure sensor is built into the main board. It is not replaceable separately.

  • Correct part (verify): W11419051
  • Install board
  • Run calibration again
Continuing to recalibrate without replacing the failing sensor will not resolve F8E1 permanently.

Summary

  • F8E1 = Long Fill + No Water Level Change
  • Water supply, screens, hoses must be verified
  • Binary decoding via LED Groups 5/6 identifies exact F/E
  • Pressure tube must pass airflow
  • Valves passing Test Mode rules out mechanical issues
  • Calibration restoring then failing → board sensor is drifting
  • Board replacement is the long-term fix

22 August 2025

Stable USB Names for RTL-SDR (and Passing the Right Stick into Docker)

When you run multiple RTL-SDR sticks (ADS-B, airband, weather, etc.), you want each container to receive the correct dongle every time—regardless of USB bus renumbering or reboots. This post shows a robust udev rule that creates a persistent, human-readable symlink per stick (based on its serial), plus a deep dive on security choices (uaccess vs plugdev). We’ll also cover setting your own serials with rtl_eeprom.


The Finished udev Rule

Create /etc/udev/rules.d/99-rtlsdr.rules with a single line:

SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTR{idVendor}=="0bda", ATTR{idProduct}=="2838", IMPORT{builtin}="usb_id", ENV{DEVNAME}!="" , ENV{ID_SERIAL_SHORT}!="" , TAG+="uaccess", MODE="0666", SYMLINK+="sdr-%E{ID_SERIAL_SHORT}"

Reload rules and cold-trigger the device (or replug):

sudo udevadm control --reload
sudo udevadm trigger --subsystem-match=usb --attr-match=idVendor=0bda --attr-match=idProduct=2838

You should see a stable link like:

/dev/sdr-80868086 -> /dev/bus/usb/001/007
Why one physical line?

Backslashes / line continuations and smart quotes sometimes sneak in during copy/paste and break parsing. Keep each rule on a single line with straight quotes.


Explaining Every Component

SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device"

Targets the USB device node (not an interface). Only the device node has a real DEVNAME (e.g., /dev/bus/usb/<bus>/<dev>) that a symlink can point to.

ATTR{idVendor}=="0bda", ATTR{idProduct}=="2838"

Matches Realtek RTL2832U family sticks (0bda:2838). Prevents touching unrelated USB gear.

IMPORT{builtin}="usb_id"

Runs udev’s builtin parser so we get ID_SERIAL_SHORT from the descriptors.

ENV{DEVNAME}!="" , ENV{ID_SERIAL_SHORT}!=""

Race-proofing: require that the kernel node already exists and the serial string is present. This avoids creating a dangling name like /dev/sdr- or failing due to timing.

TAG+="uaccess"

Grants access via ACLs to the active desktop/console user (through systemd-logind). Safer than world-writable; details in the security section.

MODE="0666"

Optional. Allows any user to open the device (handy in labs/headless setups). Remove it if you want to rely solely on uaccess ACLs.

SYMLINK+="sdr-%E{ID_SERIAL_SHORT}"

Creates a persistent, friendly name per stick, e.g. /dev/sdr-ADS-B01. This stays stable across reboots and bus renumbering—perfect for Docker bindings.

Fallback for sticks without a serial

Add this rule after the main one to name by physical port:

SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTR{idVendor}=="0bda", ATTR{idProduct}=="2838", IMPORT{builtin}="path_id", ENV{DEVNAME}!="" , SYMLINK+="sdr-port-%E{ID_PATH}"

This produces stable, port-based names (e.g., /dev/sdr-port-pci-0000_00_14_0-usb-0_3_1_0).


Security: uaccess vs plugdev vs 0666

TAG+="uaccess" (modern, desktop-friendly)

  • How: udev tags the device; systemd-logind applies ACLs to the active seat user.
  • Pros: Least privilege, no group management, follows the logged-in user.
  • Cons: Requires systemd-logind; on headless servers there’s no “active seat”.

Tight variant (no world write):

SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTR{idVendor}=="0bda", ATTR{idProduct}=="2838", IMPORT{builtin}="usb_id", ENV{DEVNAME}!="" , ENV{ID_SERIAL_SHORT}!="" , TAG+="uaccess", SYMLINK+="sdr-%E{ID_SERIAL_SHORT}"

GROUP="plugdev", MODE="0660" (classic, headless/server-friendly)

  • How: set device group to plugdev; any member can access it.
  • Pros: Works without systemd; simple mental model.
  • Cons: Anyone in the group can access all matching devices; manage GIDs for Docker.

Rule variant:

SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTR{idVendor}=="0bda", ATTR{idProduct}=="2838",
  IMPORT{builtin}="usb_id", ENV{DEVNAME}!="" , ENV{ID_SERIAL_SHORT}!="" ,
  GROUP="plugdev", MODE="0660", SYMLINK+="sdr-%E{ID_SERIAL_SHORT}"

Create/add group:

sudo groupadd plugdev 2>/dev/null || true
sudo usermod -aG plugdev $USER
# Re-login or: newgrp plugdev

MODE="0666" (open lab)

  • How: Anyone on the box can open the device.
  • Pros: Zero friction; containers “just work”.
  • Cons: Least restrictive; only use on trusted machines.

Give Sticks Human-Friendly Serials with rtl_eeprom

Many RTL-SDR dongles store their USB serial in EEPROM. You can set your own (e.g., ADS-B01, AIRBAND1) so your symlinks are readable and obvious.

# Install the tools (Debian/Ubuntu):
sudo apt install rtl-sdr

# Inspect current EEPROM (non-destructive):
rtl_eeprom -d 0

# Set a new serial (example: ADS-B01):
sudo rtl_eeprom -d 0 -s ADS-B01

# Unplug/replug the dongle for changes to take effect.

Notes:

  • -d 0 selects the first detected device (increment for additional sticks).
  • -s sets the serial string (8 characters or fewer is widely compatible).
  • Warning: only change the serial unless you know what you’re doing; other fields affect calibration.

After setting serials, the udev link names become self-documenting:

/dev/sdr-ADS-B01
/dev/sdr-AIRBAND1
/dev/sdr-WEATHER

Using Stable Names with Docker

docker run

docker run --rm \
  --name adsb \
  --device /dev/sdr-ADS-B01 \
  ghcr.io/<your-org>/readsb:latest

docker-compose

services:
  adsb:
    image: ghcr.io/<your-org>/readsb:latest
    devices:
      - /dev/sdr-ADS-B01:/dev/sdr-adsb
    # If you rely on uaccess or plugdev, run as your user to inherit host permissions:
    user: "1000:1000"

If running containers as non-root, ensure the container user’s UID/GID matches your host user (for uaccess) or is a member of the host’s plugdev GID (for group-based access).


Reliability Checklist

  • Use a late filename like 99-rtlsdr.rules so the kernel node exists when your rule runs.
  • Keep each rule on one physical line with straight quotes.
  • Strip Windows line endings if you edited on Windows:
    sudo sed -i 's/\r$//' /etc/udev/rules.d/99-rtlsdr.rules
    
  • Ensure coldplug runs at boot so present devices get rules applied:
    systemctl is-enabled systemd-udev-trigger.service
    sudo systemctl enable --now systemd-udev-trigger.service
    

Debugging Like a Pro

See the exact sysfs object and attributes

# Replace /dev/ttyUSB0 with any node you have (or locate by lsusb)
udevadm info -q path -n /dev/ttyUSB0
udevadm info -a -p /sys/bus/usb/devices/1-1

Dry-run rules (no changes, very verbose)

sudo udevadm test --action=add /sys/bus/usb/devices/1-1

Watch real events and environment while (un)plugging

udevadm monitor --udev --environment

Live udev logs

sudo udevadm control --log-level=debug
journalctl -fu systemd-udevd

TL;DR

  • Use udev to create stable per-stick symlinks: /dev/sdr-<serial>.
  • Prefer TAG+="uaccess" on desktops (least privilege); use GROUP="plugdev", MODE="0660" on headless servers; 0666 only for trusted labs.
  • Set your own serials with rtl_eeprom for human-readable names.
  • Bind the symlink into Docker with --device so each service gets the right stick.

12 November 2020

PFAFF 7570/1475CD Serial Cable for the 21st century.

The victim resting in its natural habitat.

My mother wanted to be able to update some of the stitches on her PFAFF 1475CD using her PC. As the process of programming on the machine itself is tedious and does not lend it self to corrections. I had two problems to solve off the bat. First the cable is a D-Sub 25pin Serial RS232. No computer in the last 20 years has been made with this connector.

"Made for PFAFF in West Germany"
 
I had a DB25 to DB9 adapter, but modern computers, laptops even more so, don't have 9 pin serial ports... So i grab a USB to serial adapter. Unfortunately the manufacture, Prolific, intentionally didn't support Windows 7 or 10. bummer. 

DB25/DB9 adapter

I found in an old "Random Electronics Junk Box" a Keyspan serial adapter. Great! the Drivers work with Windows 10, ComPort33. OK but what about the software?

Keyspan serial USB adapter.

The software is from 1990. Meaning MSDOS. Well i happen to be a vintage video game enthusiast and run many old dos games using the wonderful DOSBOX program. But could it handle a physical serial port? The manual, you guys/gals read the manual don't you? The manual says YES!

Cool! so following some configuration and more near swearing than I'd like to admit... 

I was able to read to contents of the machine's memory using the PFAFF Creative Designer software from within DOSBOX using the many adapters to talk to the machine. The solutions was ugly and unwieldy. It risked being broken. 

Big ugly complex fragile cable.
So I searched for a better solution. Enter Limor "Ladyada" Fried and her crew at Adafruit Industries. They make a cable that will work perfectly for this application and it's low cost. USB to TTL Serial Cable.

All I need now is a connector for the PFAFF 1475CD machine, a 7pin DIN. Thanks Amazon! http://amzn.com/B07XCSX3JP

With a two part count and my soldering iron I'm ready to improve the situation!

A clean a elegant solution. Isn't that neater?

PC-Designer Software V1.3 (c) 1991 running in DOSBOX
 
Now for a bit more background and the technical details of how I pulled it off:

The original PFAFF cable (made in West Germany by the way) was a DB25 to 7 pin DIN connector. Inside the D shaped 25pin connector (DB25) is a circuit, depicted far below, that converts and inverts RS232, 25 Volt Direct Current differential signal to 5vdc TTL signal. PFAFF used a 5vdc supply from the machine to power and reference this circuit. We won't need the circuit as the TTL-USB adapter makes the circuit unnecessary. 

We won't be using PFAFF +5v supply and will leave the pin unconnected. The TTL-USB adapter has a +5v supply as well. I used heat shrink to cap it off safely as we won't be using it either. 

Red +5vdc -/- no connection
PFAFF +5vdc DIN7 Pin 3 -/- no connection

Now we connect a common signal reference and grounding wire (GND, black, to pin 4 on the DIN7 connector).
Black gnd -- DIN pin 4

Connect the TTL-USB Transmit to PFAFF receive and vise versa:
Green tx -> DIN pin 1
White rx <- DIN pin 7

Adafruit TTL-USB -- DIN7 connector summary:
Green tx -> DIN pin 1
White rx <- DIN pin 7
Black gnd -- DIN pin 4
Red +5vdc -/- no connection

My USB-TTL to DIN7 pin out


Ok, I know my soldering isn't pretty in these images. I did it quick. After testing and confirming everything works, I redid this and used matching color heat shrink.

TTL adapter soldered to DIN7 connector

TTL adapter soldered to DIN7 connector

Inside the adafruit USB-TTL adaptor


Thanks to Rudolf of achatina.de . They depict the circuit in the DB25 (the black rectangular connector).
Rudolf's pinout diagram of the PFAFF serial cable.

Original: Standard 232 (DB9/DB25) Pin -- PFAFF 7pin DIN connector
PC TX serial pin 3 -> brown PFAFF RX (pin 1)
PC RX serial pin 2 <- yellow PFAFF TX (pin 7)
Ground serial pin 7 -- green gnd (pin 4)
No connection* -/- white vcc (pin 3)

*while the PFAFF white (DIN7 pin3) does not have a pinout on the DB25, it does supply power and a voltage reference to the circuit housed in the DB25 connector.
 
This circuit converts and inverts RS232 signal to TTL signal.
Rudolf's circuit diagram of the original PFAFF serial cable.
 
DOSBOX conf (windows, you may have to adjust the com-port number):
serial1=directserial realport:COM33
 
DOSBOX conf (linux/mac, you may have to adjust the USB tty number):
serial1=directserial realport:ttyUSB
 
DOSBOX conf (all systems)
[autoexec]
#mount c ~/dos/PFAFF
mount c c:\users\USER\PFAFF\
C:
PCD.EXE
exit

for the software create a PFAFF directory (case sensitive) and copy the PCD program files to it. adjust the [autoexec] section of dosbox.conf to mount the PFAFF directory. upon running dosbox the PCD program should automatically launch.

Parts: 

Software:

Thanks to: 

24 July 2017

Debian Fluxbox Setup (November 2020 update)

Biggest change from 2017 and 2019 updates is WICD is now really dead. I've switched to network-manager and network-manager-gnome (for the applet) there is a fair amount of package and space overhead for this but there isn't really a better alternative. Also network-manger is likely to be the first to have WPA3 support, which I plan to implement in my networks as soon as they are available. From here out I plan to keep a change log of this post.


autologin w/ systemd:

/etc/systemd/system/getty@tty1.service.d/autologin.conf:
[Service]
ExecStart=
ExecStart=-/sbin/agetty --autologin $USERNAME %I


Wifi:
$lspci
...
00:14.3 Network controller: Intel Corporation Comet Lake PCH CNVi WiFi
...

$sudo apt-get install firmware-iwlwifi

$ip addr show
1: lo ...
2: enp1s0: ...
    link/ether XX:XX:XX:XX:XX:XX...
3: wlp2s0: ...
    link/ether XX:XX:XX:XX:XX:XX...

As we are using network manager now we have tow choices:
   1: Remove interfaces from /etc/network/interfaces so NM will automatically manage them -or-
   2: Force NM to namage=true in /etc/NetworkManager/NetworkManager.conf
I have chosen to use option 1.
for more information see https://wiki.debian.org/NetworkManager

/etc/network/interfaces:
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).
source /etc/network/interfaces.d/*
# The loopback network interface
auto lo
iface lo inet loopback
# The primary network interface
#allow-hotplug enp1s0
#iface enp1s0 inet dhcp
# wifi
#allow-hotplug wlp2s0
#iface wlp2s0 inet dhcp

Sound:
apt-get install alsa-utils pulsemixer pulseaudio-utils pulseaudio-module-bluetooth


Backlight:
#Screen Brightness.  Will lower or raise brightness by 5%.
# had to install xbacklight and add the following to /etc/X11/xorg.conf:
Section "Device"
        Identifier  "Card0"
        Driver      "intel"
        Option      "Backlight"  "/sys/devices/pci0000:00/0000:00:02.0/drm/card0/card0-LVDS-1/intel_backlight/"
EndSection


Thunar:
apt-get install thunar thunar-volman
remove "Set as wallpaper" context menu item:
https://forums.bunsenlabs.org/viewtopic.php?id=1533
"The Thunar wallpaper plugin is compiled into the Debian standard Thunar (EDIT: libthunarx-2-0 in fact), and we don't want to have to compile our own version, just for a little thing like this. There is a fix though. Rename /usr/lib/{i386-linux-gnu,x86_64-linux-gnu}/thunarx-2/thunar-wallpaper-plugin.so and restart Thunar."


~/.fluxobx/keys:
Mod1 Tab :NextWindow (workspace=[current]) !! FBCV13 !!
Mod1 Shift Tab :PrevWindow (workspace=[current]) !! FBCV13 !!
#
#Volume.  Will lower or raise volume by 5%.  The third key binding will toggle mute.
XF86AudioLowerVolume : Exec amixer sset Master,0 5%-
XF86AudioRaiseVolume : Exec amixer sset Master,0 5%+
XF86AudioMute : Exec amixer sset Master,0 toggle
#
XF86MonBrightnessUp : exec /usr/bin/xbacklight -inc 5
XF86MonBrightnessDown : exec /usr/bin/xbacklight -dec 5
#
Mod1 F4 :Close
Mod1 z :RootMenu
Mod1 Shift z :HideMenus
Mod1 x :exec /usr/bin/xterm
Control Shift n : exec /usr/bin/google-chrome --incognito


~/.fluxbox/startup:
# fluxbox startup-script:
#
# Lines starting with a '#' are ignored.
#
# You can set your favourite wallpaper here if you don't want
# to do it from your style.
#
# This sets a black background
# /usr/bin/fbsetroot -solid black
# /usr/bin/fbsetbg -l
# /usr/bin/fbsetbg -f -r ~/.fluxbox/backgrounds/
# Now set by .fluxbox/init rootCommand
#
# This shows the fluxbox-splash-screen
# fbsetbg -C /usr/share/fluxbox/splash.jpg
#
# Other examples. Check man xset for details.
#
# Turn off beeps:
xset -b
#
# Increase the keyboard repeat-rate:
# xset r rate 195 35
#
# Your own fonts-dir:
# xset +fp "~/.fonts"
#
# Your favourite mouse cursor:
# xsetroot -cursor_name right_ptr
#
# Change your keymap:
# xmodmap "~/.Xmodmap"
#
#
#
# Applications you want to run with fluxbox.
# MAKE SURE THAT APPS THAT KEEP RUNNING HAVE AN ''&'' AT THE END.
#
# unclutter -idle 2 &
# wmnd &
# wmsmixer -w &
# idesk &
#/usr/bin/xscreensaver -no-splash &
#/usr/bin/wicd-client -t &
/usr/bin/numlockx on &
/usr/bin/nm-applet &
/usr/bin/blueman-applet &
#/usr/bin/volumeicon &
/usr/bin/pasystray &
~/sbin/cbatticon -i symbolic &
#/usr/local/bin/wmbattery &
#/usr/bin/wmnd -i wlan0 -I wlan0,eth0 &
#/usr/bin/unclutter -idle 2 &
#/usr/bin/chromium --no-startup-window &
/usr/bin/xterm -geometry 120x55+0+0 &
#/usr/bin/xterm -geometry 80x55+725+0 &
#/usr/bin/xterm -geometry 120x55+485+0 &
#/usr/bin/xterm -geometry 80x24+0+403 &
#/usr/bin/xterm -geometry 80x29+0+0 &
#/usr/bin/dropbox start &
#/usr/bin/xtrlock -f -b
/usr/bin/fbsetbg -l &
~/.screenlayout/dual.sh &
#
# And last but not least we start fluxbox.
# Because it is the last app you have to run it with ''exec'' before it.
#
exec /usr/bin/fluxbox
# or if you want to keep a log:
# exec /usr/bin/fluxbox -log "~/.fluxbox/log"


~/.fluxbox/keys (my additions not the entire file):
XF86MonBrightnessUp : exec /usr/bin/xbacklight -inc 5 XF86MonBrightnessDown : exec /usr/bin/xbacklight -dec 5 XF86Calculator : exec ~/sbin/touchpadtoggle.sh Mod1 F4 :Close Mod1 z :RootMenu Mod1 Shift z :HideMenus Mod1 x :exec /usr/bin/xterm Control Shift n : exec /usr/bin/google-chromqe --incognito Mod4 l :xtrlock -b #Mod4 T :ToggleDecor

~/sbin/touchpadtoggle.sh:
#!/bin/dash device="CUST0001:00 04F3:3147 Touchpad" #find the device you wish to toggle using /usr/bin/xinput state=$(xinput list-props "$device" | grep "Device Enabled" | grep -o "[01]$") if [ $state = '1' ]; then /usr/bin/xinput --disable "$device" else /usr/bin/xinput --enable "$device" fi

Other software installed:
sigil
eye of mate (eom)
simple-scan
dropbox
chrome
network-manager
network-manager-gnome
calibre
evince
libreoffice
moneydance
xfburn
xtrlock
arandr
rhythmbox
soundjuicer
vlc
thunar

01 March 2015

IMG 2 SVG

IMG 2 SVG: Converting raster images to scalable vector graphics using the GNU Image Manipulation Program (GIMP) and Inkscape

Note: This tutorial assumes the user has a basic knowledge of computers. Including but not limited to image processing software (Photoshop, GIMP, etc.), the internet, downloading and handling files, installing software, and using a mouse. It is also assumed that GIMP, Inkscape and a web browser are already installed. A functional internet connection will be useful. Versions used include GIMP 2.8.14 and Inkscape 0.48.5 r10040 both from the Debian Linux testing repository, pulled on 01 March 2015. Ubuntu, Fedora, Centos, OpenSUSE, Redhat, OSX, and Windows all have packages/installers for GIMP and Inkscape. Your mileage may vary.
In this example I used a PNG from the wikipedia article on the Futurama character Bender, http://en.wikipedia.org/wiki/Bender_(Futurama)

Launch GIMP:
  1. File > Open [Figure 1]
  2. Colors > Desaturate (I typically use lightness, but use what works best for you)
    Note: GIMP only operates on the selected area. Using a selection area may be useful for getting different thresholds in various places in the image
  3. Colors > Threshold (you’ll have to play around to find the best values but auto does a good job of getting close most of the time) [Figure 2, Figure3]
  4. Image > Mode > Indexed (select “Black and White 1-bit palette” ) and “Remove unused colors” (default) [Figure 4]
  5. Remove any signature and unwanted artifacts (speckles, etc.). SVG conversion is difficult with such things or speckles in the image. You will see I have left some under Bender's cigar because they are intentional. [Figure 5, Figure 6]
  6. Ensure you have permission or legal right to remove signatures/watermarks or ensure they get placed in the final image after conversion. You may wish to cut and paste the signature into a new image for use later.
  7. Image > Zealous Crop ( I forgot this when making the walkthrough so my canvas sizes are a bit bigger, with more white space)
  8. Save in gimp xcf format (for easy changes later) and export to png format (File > Export As). I change the name so as not to overwrite the original “bender.png” becomes bender.xcf and “bender_clean.png”. When exporting there is no need to save background color, creation time, or transparent pixel color values. [Figure 7]
Launch InkScape:
  1. Open your file (File > Open > “Bender_clean.png”)
  2. Using the arrow tool select your object
  3. Path > Trace Bitmap
  4. On the Options tab [Figure 8]
    1. Disable suppress speckles
    2. Enable smooth corners @ 1.00
    3. Enable optimize paths @ 5.00
  5. On the Mode Tab [Figure 9]
    1. Select the colors radio button
    2. Set scans to 2
    3. Disable Smooth
    4. Enable Stack Scans
    5. Disable remove background
    6. Press Update on the Mode Tab
  6. Press OK. You now have a Vector Object on top of a Raster Object.
  7. Move the Vector Object aside (use the select tool if needed) [Figure 10] 
  8. Delete the Raster Object [Figure 11]
  9. Select the Vector Object
  10. Move the Vector Object to the home position using the X,Y coordinates in the toolbar [Figure 12]
  11. Save as an SVG (I typically save as a “Plane SVG (.SVG)” rather than an InkScape SVG if I’m going to be using this image anywhere other than in Inkscape (e.g. webpages, Adobe Illustrator, etc.)
  12. You can cut and paste the signature raster object from gimp directly into Inkscape if you need to. I copied the signature after desaturating but before converting from RGB to Black and White. [Figure 13]
  13. Don’t forget to save again if needed.
  14. ADVANCED TOPIC: ::INCOMPLETE:: If your going to color the image after word, The Inkscape fill bucket works similar to that of raster image programs but may leave a white ring between your outline and colors. The easiest (read maybe not the best) way to fix this is to break apart the vector paths.
    1. Select the Edit Node Tool
    2. Click on the outline vector object
    3. Path > Break Apart
    4. Select fill bucket tool
    5. Select fill color
    6. Click on empty area to fill

image/svg+xml
Figure 2 Figure 1 Figure 3 Figure 4 Figure 5 Figure 7 Figure 9 Figure 10 Figure 11 Figure 12 Figure 13