chenhg5/cc-connect · error

chmod unit file

Error message

chmod unit file: %w

What it means

Install() wraps os.Chmod failure when hardening the unit file to 0600 after writing it. WriteFile only applies permissions on create, so Chmod is required to tighten pre-existing 0644 units from older cc-connect versions; if Chmod fails the unit would be left world-readable despite containing secrets.

Solutions

  1. Run as the same user (or root) that owns the existing unit file.
  2. Remove the stale unit first: `sudo rm /etc/systemd/system/cc-connect.service`, then reinstall.
  3. Check ownership: `ls -l <unitPath>` and `sudo chown` if needed.
  4. Verify the filesystem supports permission changes.

Example fix

// before
mgr.Install(cfg) // chmod unit file: operation not permitted (root-owned unit)
// after
exec.Command("sudo", "rm", "/etc/systemd/system/cc-connect.service").Run()
mgr.Install(cfg)
Defensive patterns

Strategy: validation

Validate before calling

unitPath := mgr.unitPath()
if fi, err := os.Stat(unitPath); err == nil {
    if st, err := os.Stat(unitPath); err == nil && st.Uid() != uint32(os.Getuid()) && os.Getuid() != 0 {
        return fmt.Errorf("existing unit owned by uid %d; rerun as root", st.Uid())
    }
}

Type guard

func ownsFile(path string) bool {
    fi, err := os.Stat(path)
    if err != nil { return true }
    return fi.Sys().(*syscall.Stat_t).Uid == uint32(os.Getuid())
}

Try / catch

if err := mgr.Install(cfg); err != nil {
    if strings.Contains(err.Error(), "chmod unit file") {
        _ = os.Remove(mgr.unitPath()) // drop stale unit and retry as correct user
        return mgr.Install(cfg)
    }
    return err
}

Prevention

When it happens

Trigger: Calling Install over an existing unit file whose ownership prevents Chmod (file owned by another user), or on a filesystem that does not support chmod (some network/Windows mounts).

Common situations: Previous unit installed as root, now reinstalling as a different user; reinstalling on a WSL mount or FAT/NTFS volume; stale root-owned unit from an old version.

Understand the failure class

Background: "Permission denied" / "Failed to write" file errors: why a library can't write its files to disk (EACCES, EPERM, ENOSPC) and how to fix them — this error's family across 43 libraries.

Related errors


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/cffe2805c00006ae. Report an issue: GitHub.

Appendix: source

Thrown at daemon/systemd.go:73

		return fmt.Errorf("create systemd dir: %w", err)
	}
	if err := os.MkdirAll(filepath.Dir(cfg.LogFile), 0755); err != nil {
		return fmt.Errorf("create log dir: %w", err)
	}

	unit := m.buildUnit(cfg)
	// 0600: unit file may contain captured secret values (config.toml ${ENV}
	// placeholders and any EnvDiscoverer extension output). For system-level
	// units (/etc/systemd/system/) the file is owned by root and remains
	// readable by root only; for user-level units under
	// ~/.config/systemd/user it remains owner-only. WriteFile only applies
	// perm on create, so Chmod afterwards is required to harden reinstalls
	// of pre-existing 0644 units from earlier cc-connect versions.
	if err := os.WriteFile(unitPath, []byte(unit), 0600); err != nil {
		return fmt.Errorf("write unit file: %w", err)
	}
	if err := os.Chmod(unitPath, 0600); err != nil {
		return fmt.Errorf("chmod unit file: %w", err)
	}

	for _, cmdArgs := range [][]string{
		m.sysArgs("daemon-reload"),
		m.sysArgs("enable", systemdServiceName),
		m.sysArgs("restart", systemdServiceName),
	} {
		if out, err := runSystemctl(cmdArgs...); err != nil {
			return fmt.Errorf("systemctl %s: %s (%w)", strings.Join(cmdArgs, " "), out, err)
		}
	}

	return nil
}

func (m *systemdManager) Uninstall() error {
	if _, err := runSystemctl(m.sysArgs("disable", "--now", systemdServiceName)...); err != nil {
		slog.Warn("systemd: disable failed", "error", err)

View on GitHub (pinned to 4000b2338a)